diff --git a/.gitignore b/.gitignore index 27eeae252..c5fc1f375 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ src/lexer.[ch] src/augmatch src/augtool src/augparse +src/augprint src/callgrind.* tests/fatest tests/leak diff --git a/man/Makefile.am b/man/Makefile.am index d0c56e6d9..9fdd77378 100644 --- a/man/Makefile.am +++ b/man/Makefile.am @@ -1,7 +1,7 @@ EXTRA_DIST=$(wildcard *.pod) $(wildcard *.[0-9]) -man1_MANS=augtool.1 augparse.1 augmatch.1 +man1_MANS=augtool.1 augparse.1 augmatch.1 augprint.1 %.1: %.pod pod2man -c "Augeas" -r "Augeas $(VERSION)" $< > $@ diff --git a/man/augmatch.pod b/man/augmatch.pod index 076bd148e..95522dd99 100644 --- a/man/augmatch.pod +++ b/man/augmatch.pod @@ -126,3 +126,7 @@ License (LGPL) =head1 SEE ALSO B project homepage L + +B path expressions L + +L diff --git a/man/augprint.pod b/man/augprint.pod new file mode 100644 index 000000000..4dd0d1a96 --- /dev/null +++ b/man/augprint.pod @@ -0,0 +1,296 @@ +# vim: expandtab +=head1 NAME + +augprint - create an idempotent augtool script for a given file + +=head1 SYNOPSIS + +augprint [--pretty|-p] [--regexp[=n]|-r[n]] [--noseq|-s] [--verbose|-v] [--lens name|-l name] [--target /target|-t /target] FILE + +=head1 DESCRIPTION + +B creates an augtool script for a given B +consisting primarily of C commands. + +The resulting augtool script is designed to be idempotent, and +will not result in any changes when applied to the original file. + +B replaces each numbered location in the tree with +a path-expression that uniquely identifies the position using the values +I that position. + +This makes the path-expression independant of the position-number, +and thereby applicable to files which in which the same data may exist at +an alternate position-number + +See "Examples" for sample output + +=head2 Regexp output + +By default B produces path-expressions made up of simple equality C<=> comparisions + + set /files/etc/hosts/seq::*[ipaddr='127.0.0.1']/ipaddr '127.0.0.1' + +The option B<--regexp> changes the output to produce regular expression comparisions + + set /files/etc/hosts/seq::*[ipaddr=~regexp('127\\.0\\..*')]/ipaddr '127.0.0.1' + +The minimum length I of the regular expression can be specified using C<--regexp=N> + +B will choose a longer regular expression than I if multiple values +would match using the I character regular expression. + +=head2 Limitations + +=head3 Append-only + +The output is based primarily on set operations. +The set operation can only: + +a) change an existing value in-situ + +b) append a new value after the last position in the group + +This means that when an entry is re-created, it may not be in the same position as originally intended. +ie if the entry for C<192.0.2.3> does not already exist, it will be created as the I entry in F + +Often, such out-of-sequence entries will not matter to the resulting configuration file. +If it does matter, further manual editing of the C script will be required. + +=head3 Repeated Values + +B is not always successful in finding a path-expression which is unique to a position. +In this case B appends a position to an expression which is not unique + +This occurs in particular if there are repeated values within the file. + +For an F file of + + #------ + 192.0.2.3 defaultdns + #------ + +B would produce the output + + set /files/etc/hosts/#comment[.='--------'][1] '--------' + set /files/etc/hosts/seq::*[ipaddr='192.0.2.3']/ipaddr '192.0.2.3' + set /files/etc/hosts/seq::*[ipaddr='192.0.2.3']/canonical 'defaultdns' + set /files/etc/hosts/#comment[.='--------'][2] '--------' + +Notice how C<#comment> paths have C<[1]> and C<[2]> appended respectively to the C<[expr]> + +Other paths which do have unique path-expressions are not directly affected + + +=head1 OPTIONS + +=over 4 + +=item B<-v>, B<--verbose> + +Include the original numbered paths as comments in the output + +=item B<-p>, B<--pretty> + +Create more readable output by adding spaces and empty lines + +=item B<-r>, B<-r>I, B<--regexp>, B<--regexp>=I + +Generate regular expressions to match values, +using a minumum length of I characters from the value + +I can be omitted and defaults to 8 + +=item B<-l>, B<--lens>=I + +Use I for the given file; without this option, B uses the +default lens for the file + +=item B<-t> I, B<--target>=I + +Generate the script for the I specified as if its path was really I + +This will apply the lens corresponding to I to I +and modifying the resulting path-expressions of I to correspond to I + +I must be the full path name, starting with a '/' + +See "Examples" for how B<--target> can be used in practice + +=item B<-s>, B<--noseq> + +Do not use C in the output, use C<*> instead. +For example + + set /files/etc/hosts/*[ipaddr='127.0.0.1']/ipaddr '127.0.0.1' + +IMPORTANT: The resulting output will no longer I a new entry +for C<127.0.0.1> if none already exists. The C<--noseq> option exists so +that the resulting paths can be used with augeas versions prior to 1.13.0 +(subject to this limitation) + +=back + +=head1 EXAMPLES + +These examples use the following F file as the I + + 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 + 192.0.2.3 dns-a + 192.0.2.4 dns-b + +The output from C would be + + /files/etc/hosts + /files/etc/hosts/1 + /files/etc/hosts/1/ipaddr = "127.0.0.1" + /files/etc/hosts/1/canonical = "localhost" + /files/etc/hosts/1/alias[1] = "localhost.localdomain" + /files/etc/hosts/1/alias[2] = "localhost4" + /files/etc/hosts/1/alias[3] = "localhost4.localdomain4" + /files/etc/hosts/2 + /files/etc/hosts/2/ipaddr = "192.0.2.3" + /files/etc/hosts/2/canonical = "dns-a" + /files/etc/hosts/3 + /files/etc/hosts/3/ipaddr = "192.0.2.4" + /files/etc/hosts/3/canonical = "dns-b" + +=head2 Default output + +C + + set /files/etc/hosts/seq::*[ipaddr='127.0.0.1']/ipaddr '127.0.0.1' + set /files/etc/hosts/seq::*[ipaddr='127.0.0.1']/canonical 'localhost' + set /files/etc/hosts/seq::*[ipaddr='127.0.0.1']/alias[.='localhost.localdomain'] 'localhost.localdomain' + set /files/etc/hosts/seq::*[ipaddr='127.0.0.1']/alias[.='localhost4'] 'localhost4' + set /files/etc/hosts/seq::*[ipaddr='127.0.0.1']/alias[.='localhost4.localdomain4'] 'localhost4.localdomain4' + set /files/etc/hosts/seq::*[ipaddr='192.0.2.3']/ipaddr '192.0.2.3' + set /files/etc/hosts/seq::*[ipaddr='192.0.2.3']/canonical 'dns-a' + set /files/etc/hosts/seq::*[ipaddr='192.0.2.4']/ipaddr '192.0.2.4' + set /files/etc/hosts/seq::*[ipaddr='192.0.2.4']/canonical 'dns-b' + +=head2 Verbose output + +C + + # /files/etc/hosts + # /files/etc/hosts/1 + # /files/etc/hosts/1/ipaddr '127.0.0.1' + set /files/etc/hosts/seq::*[ipaddr='127.0.0.1']/ipaddr '127.0.0.1' + # /files/etc/hosts/1/canonical 'localhost' + set /files/etc/hosts/seq::*[ipaddr='127.0.0.1']/canonical 'localhost' + # /files/etc/hosts/1/alias[1] 'localhost.localdomain' + set /files/etc/hosts/seq::*[ipaddr='127.0.0.1']/alias[.='localhost.localdomain'] 'localhost.localdomain' + ... + +=head2 Rexexp output + +C + + set /files/etc/hosts/seq::*[ipaddr=~regexp('127\\..*')]/ipaddr '127.0.0.1' + set /files/etc/hosts/seq::*[ipaddr=~regexp('127\\..*')]/canonical 'localhost' + set /files/etc/hosts/seq::*[ipaddr=~regexp('127\\..*')]/alias[.=~regexp('localhost\\..*')] 'localhost.localdomain' + set /files/etc/hosts/seq::*[ipaddr=~regexp('127\\..*')]/alias[.=~regexp('localhost4')] 'localhost4' + set /files/etc/hosts/seq::*[ipaddr=~regexp('127\\..*')]/alias[.=~regexp('localhost4\\..*')] 'localhost4.localdomain4' + set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.3')]/ipaddr '192.0.2.3' + set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.3')]/canonical 'dns-a' + set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.4')]/ipaddr '192.0.2.4' + set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.4')]/canonical 'dns-b' + +Note that although a I length of 4 has been specified, B will choose longer regular expressions +as needed to ensure a unique match. + +=head2 Using --lens + +If a file is not assocatiated with a lens by default, I<--lens lensname> can be used to specify a lens. + +When I<--lens> is specified, the output is prefixed with suitable C and C statements, +as required to complete the augtool script, and a I statement to exclude other autoloaded lenses. + +C + + setm /augeas/load/*[incl='/etc/skel/.bashrc' and label() != 'shellvars']/excl '/etc/skel/.bashrc' + transform shellvars incl /etc/skel/.bashrc + load-file /etc/skel/.bashrc + set /files/etc/skel/.bashrc/#comment[.='.bashrc'] '.bashrc' + set /files/etc/skel/.bashrc/#comment[.='Source global definitions'] 'Source global definitions' + set /files/etc/skel/.bashrc/@if[.='[ -f /etc/bashrc ]'] '[ -f /etc/bashrc ]' + set /files/etc/skel/.bashrc/@if[.='[ -f /etc/bashrc ]']/.source '/etc/bashrc' + set /files/etc/skel/.bashrc/#comment[.='User specific environment'] 'User specific environment' + ... + +The lenses C C are most commonly useful as lenses for files that do not have +a specific lens + +=head2 Using --target + +In order to prepare an augtool script intended for a given file, it may be desired to +copy the file to another location, rather than editting the original file. + +The option I<--target> simplifies this process. + +a) copy F to a new location + + cp /etc/hosts ~ + +b) edit F<~/hosts> to suit + + echo '192.0.2.7 defaultdns' >> ~/hosts + +c) Run C as follows + + augprint --target /etc/hosts ~/hosts + +d) Copy the relevant part of the output to an augtool script or other Augeas client + + set /files/etc/hosts/seq::*[ipaddr='192.0.2.7']/ipaddr '192.0.2.7' + set /files/etc/hosts/seq::*[ipaddr='192.0.2.7']/canonical 'defaultdns' + +Notice that C has generated paths corresponding to I<--target> (/etc/hosts) instead of the I argument (~/hosts) + + + +=head1 ENVIRONMENT VARIABLES + +=over 4 + +=item B + +The effective file system root, defaults to '/'. + +=item B + +Colon separated list of directories with lenses. Directories specified here +are searched before the default directories F and +F + +=back + +=head1 EXIT STATUS + +The exit status is 0 when the command was successful +and 1 if any error occurred. + +=head1 FILES + +Lenses and schema definitions in F and +F + +=head1 AUTHOR + +George Hansper + +=head1 COPYRIGHT AND LICENSE + +Copyright 2022 George Hansper + +Augeas (and augprint) are distributed under the GNU Lesser General Public +License (LGPL), version 2.1 + +=head1 SEE ALSO + +augtool(1) + +B project homepage L + +B path expressions L diff --git a/man/augtool.1 b/man/augtool.1 index 7279654cb..519b31b28 100644 --- a/man/augtool.1 +++ b/man/augtool.1 @@ -1,4 +1,4 @@ -.\" Automatically generated by Pod::Man 4.09 (Pod::Simple 3.35) +.\" Automatically generated by Pod::Man 4.14 (Pod::Simple 3.43) .\" .\" Standard preamble: .\" ======================================================================== @@ -54,16 +54,20 @@ .\" Avoid warning from groff about undefined register 'F'. .de IX .. -.if !\nF .nr F 0 -.if \nF>0 \{\ -. de IX -. tm Index:\\$1\t\\n%\t"\\$2" +.nr rF 0 +.if \n(.g .if rF .nr rF 1 +.if (\n(rF:(\n(.g==0)) \{\ +. if \nF \{\ +. de IX +. tm Index:\\$1\t\\n%\t"\\$2" .. -. if !\nF==2 \{\ -. nr % 0 -. nr F 2 +. if !\nF==2 \{\ +. nr % 0 +. nr F 2 +. \} . \} .\} +.rr rF .\" .\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). .\" Fear. Run. Save yourself. No user-serviceable parts. @@ -129,7 +133,7 @@ .\" ======================================================================== .\" .IX Title "AUGTOOL 1" -.TH AUGTOOL 1 "2021-05-26" "Augeas 1.13.0" "Augeas" +.TH AUGTOOL 1 "2022-10-24" "Augeas 1.13.0" "Augeas" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l @@ -441,4 +445,8 @@ License (\s-1LGPL\s0) .IX Header "SEE ALSO" \&\fBAugeas\fR project homepage .PP +\&\fBAugeas\fR path expressions +.PP augparse +.PP +augprint diff --git a/man/augtool.pod b/man/augtool.pod index 4afbd27d7..ab52cf5e2 100644 --- a/man/augtool.pod +++ b/man/augtool.pod @@ -390,4 +390,8 @@ License (LGPL) B project homepage L +B path expressions L + L + +L diff --git a/src/Makefile.am b/src/Makefile.am index d14314dda..60ee48db6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -14,7 +14,7 @@ DISTCLEANFILES = datadir.h lib_LTLIBRARIES = libfa.la libaugeas.la noinst_LTLIBRARIES = liblexer.la -bin_PROGRAMS = augtool augparse augmatch +bin_PROGRAMS = augtool augparse augmatch augprint include_HEADERS = augeas.h fa.h @@ -46,6 +46,9 @@ augparse_LDADD = libaugeas.la $(LIBXML_LIBS) $(GNULIB) augmatch_SOURCES = augmatch.c augmatch_LDADD = libaugeas.la $(LIBXML_LIBS) $(GNULIB) +augprint_SOURCES = augprint.c +augprint_LDADD = libaugeas.la $(GNULIB) + libfa_la_SOURCES = fa.c fa.h hash.c hash.h memory.c memory.h ref.h ref.c libfa_la_LIBADD = $(LIB_SELINUX) $(GNULIB) libfa_la_LDFLAGS = $(FA_VERSION_SCRIPT) -version-info $(LIBFA_VERSION_INFO) diff --git a/src/augprint.c b/src/augprint.c new file mode 100644 index 000000000..dc49f003a --- /dev/null +++ b/src/augprint.c @@ -0,0 +1,1708 @@ +/* vim: expandtab:softtabstop=2:tabstop=2:shiftwidth=2 + * + * Copyright (C) 2022 George Hansper + * ----------------------------------------------------------------------- + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + * + * Author: George Hansper + * ----------------------------------------------------------------------- + * This tool produces output similar to the augeas 'print' statement + * or aug_print() API function + * + * Where augeas print may produce a set of paths and values like this: + * + * /files/some/path/label[1]/tail_a value_1a + * /files/some/path/label[1]/tail_b value_1b + * /files/some/path/label[2]/tail_a value_2a + * /files/some/path/label[2]/tail_b value_2b + * + * or + * + * /files/some/path/1/tail_a value_1a + * /files/some/path/1/tail_b value_1b + * /files/some/path/2/tail_a value_2a + * /files/some/path/2/tail_b value_2b + * + * + * This tool replaces the abosolute 'position' (1, 2, 3,..) with a path-expression that matches the position + * where the values are used to identify the position + * + * /files/some/path/label[tail_a = value_1a]/tail_a value_1a + * /files/some/path/label[tail_a = value_1a]/tail_b value_1b + * /files/some/path/label[tail_a = value_2a]/tail_a value_2a + * /files/some/path/label[tail_a = value_2a]/tail_b value_2b + * + * Terms used within: + * + * /files/some/path/label[1]/tail_a value_1a + * `--------------------' \ `-----' `------' + * `--- head \ \ `--- value + * \ `--- tail + * `-- position + * + * For more complex paths + * /files/some/path/1/segement/label[1]/tail_a value_1a + * `----------------------' + * | + * v + * /segement/label/tail_a + * `--------------------' + * `--- simple_tail + */ + +#define _GNU_SOURCE +#include +#include +#include /* for exit, strtoul */ +#include +#include +#include +#include +#include +#include /* for MIN() MAX() */ +#include +#include "augprint.h" + +#define CHECK_OOM(condition, action, arg) \ + do { \ + if (condition) { \ + out_of_memory|=1; \ + action(arg); \ + } \ + } while (0) + +#define MAX_PRETTY_WIDTH 30 + +static augeas *aug = NULL; +static unsigned int flags = AUG_NONE; +static unsigned int num_groups = 0; +static struct group **all_groups=NULL; +static char **all_matches; +static int num_matched; +static struct augeas_path_value **all_augeas_paths; /* array of pointers */ + +static int out_of_memory=0; +static int verbose=0; +static int debug=0; +static int pretty=0; +static int noseq=0; +static int help=0; +static int print_version=0; +static int use_regexp=0; +static char *lens = NULL; +static char *loadpath = NULL; + +static char *str_next_pos(char *start, char **head_end, unsigned int *pos); +static char *str_simplified_tail(char *tail_orig); +static void add_segment_to_group(struct path_segment *segment, struct augeas_path_value *); +static char *quote_value(char *); +static char *regexp_value(char *, int); + + +static void exit_oom(const char *msg) { + fprintf(stderr, "Out of memory"); + if( msg ) { + fprintf(stderr, " %s\n", msg); + } else { + fprintf(stderr, "\n"); + } + exit(1); +} + +/* Remove /./ and /../ components from path + * because they just don't work with augeas + */ +static void cleanup_filepath(char *path) { + char *to=path, *from=path; + while(*from) { + if(*from == '/' ) { + if( *(from+1) == '/' ) { + /* // skip over 2nd / */ + from++; + continue; + } else if( *(from+1) == '.' ) { + if( *(from+2) == '/' ) { + /* /./ skip 2 spaces */ + from+=2; + continue; + } else if ( *(from+2) == '.' && *(from+3) == '/' ) { + /* /../ rewind to previous / */ + from+=3; + while( to > path && *(--to) != '/' ) + ; + continue; + } + } + } + *to++ = *from++; + } + *to='\0'; +} + +static char *find_lens_for_path(char *filename) { + char *aug_load_path = NULL; + char **matching_lenses; + int num_lenses, result, ndx; + char *filename_tail; + filename_tail = filename; + for (char *s1 = filename; *s1; s1++ ) { + if ( *s1 == '/' ) + filename_tail = s1+1; + } + result = asprintf(&aug_load_path, "/augeas/load/*['%s' =~ glob(incl)]['%s' !~ glob(excl)]['%s' !~ glob(excl)]", filename, filename, filename_tail); + CHECK_OOM( result < 0, exit_oom, NULL); + + if(debug) { + fprintf(stderr,"path expr: %s\n",aug_load_path); + aug_print(aug, stderr, aug_load_path); + } + num_lenses = aug_match( aug, aug_load_path, &matching_lenses); + if ( num_lenses == 0 ) { + fprintf(stderr, "Aborting - no lens applies for target: %s\n", filename); + exit(1); + } + lens = matching_lenses[0] + 13; /* skip over /augeas/load */ + + if ( num_lenses > 1 ) { + /* Should never happen */ + for( ndx=0; ndxpath; + struct path_segment *first_segment = NULL; + struct path_segment *this_segment = NULL; + struct path_segment **next_segment = &first_segment; + unsigned int position; + char *head_end; + char *path_seg_start=path; + char *path_seg_end; + + while(*path_seg_start) { + this_segment = malloc(sizeof(struct path_segment)); + CHECK_OOM(! this_segment, exit_oom, "split_path() allocating struct path_segment"); + + *next_segment = this_segment; + path_seg_end = str_next_pos(path_seg_start, &head_end, &position); + this_segment->head = strndup(path, (head_end-path)); + this_segment->segment = (this_segment->head) + (path_seg_start-path); + this_segment->position = position; + this_segment->simplified_tail = str_simplified_tail(path_seg_end); + path_seg_start = path_seg_end; + this_segment->next = NULL; + next_segment = &(this_segment->next); + if ( position != UINT_MAX ) { + add_segment_to_group(this_segment, path_value); + } else { + this_segment->group = NULL; + } + } + return(first_segment); +} + +/* + * str_next_pos() scans a string from (char *)start, and finds the next occurance + * of the substring '[123]' or '/123/' where 123 is a decimal number + * (int *)pos is set to the value of 123 + * if [123] is found, + * - head_end points to the character '[' + * - returns a pointer to the character after the ']' + * if /123/ or /123\0 is found + * - head_end points to character after the first '/' + * - returns a pointer to the second '/' or '\0' + * if none of the above are found + * - head_end points to the terminating '\0' + * - returns a pointer to the terminating '\0' (same as head_end) + * 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 +*/ +static char *str_next_pos(char *start, char **head_end, unsigned int *pos) { + char *endptr=NULL; + char *s=start; + unsigned long lpos; + *pos=UINT_MAX; + while(*s) { + if( *s=='[' && *(s+1) >= '0' && *(s+1) <= '9' ) { + lpos = strtoul(s+1, &endptr, 10); + *pos = MIN(lpos, UINT_MAX); + if ( *endptr == ']' ) { + /* success */ + *head_end = s; + return(endptr+1); + } + } else if ( *s == '/' && *(s+1) >= '0' && *(s+1) <= '9' ) { + lpos = strtoul(s+1, &endptr, 10); + *pos = MIN(lpos, UINT_MAX); + if ( *endptr == '\0' || *endptr == '/' ) { + /* success */ + *head_end = s+1; + return(endptr); + } + } + s++; + } + *head_end=s; + return(s); +} + +static char *str_simplified_tail(char *tail_orig) { + int tail_len=0; + char *tail; + char *from, *to, *scan; + char *simple; + /* first work out how much space we will need to allocate */ + tail=tail_orig; + while(*tail) { + if( *tail == '[' && *(tail+1) >= '0' && *(tail+1) <= '9' ) { + /* Look for matching ']' */ + scan = tail; + scan++; + while (*scan >= '0' && *scan <= '9') + scan++; + if(*scan == ']') { + tail=scan+1; + continue; + } + } else if ( *tail == '/' && *(tail+1) >= '0' && *(tail+1) <= '9' ) { + /* Look for next '/' or '\0' */ + scan = tail; + scan++; + while (*scan >= '0' && *scan <= '9') + scan++; + if(*scan == '/' || *scan == '\0' ) { + tail=scan; + tail_len += 7; /* allow for /seq::* */ + continue; + } + } + tail_len++; + tail++; + } + simple = (char *) malloc( sizeof(char) * (tail_len+1)); + CHECK_OOM( ! simple, exit_oom, "allocating simple_tail in str_simplified_tail()"); + + from=tail_orig; + to=simple; + while(*from) { + if( *from == '[' && *(from+1) >= '0' && *(from+1) <= '9' ) { + /* skip over [123] */ + scan = from; + scan++; + while (*scan >= '0' && *scan <= '9') + scan++; + if(*scan == ']') { + from=scan+1; + continue; + } + } else if ( *from == '/' && *(from+1) >= '0' && *(from+1) <= '9' ) { + /* replace /123 with /seq::* */ + scan = from; + scan++; + while (*scan >= '0' && *scan <= '9') + scan++; + if(*scan == '/' || *scan == '\0' ) { + from=scan; + if ( noseq ) { + strcpy(to,"/*"); + to += 2; + } else { + strcpy(to,"/seq::*"); + to += 7; /* len("/seq::*") */ + } + continue; + } + } + *to++ = *from++; /* copy */ + } + *to='\0'; + return(simple); +} + +/* Compare two values (char *), subject to use_regexp + * If both pointers are NULL, return 1 (true) + * If only one pointer is NULL, return 0 (false) + * set *matched to the number of characters in common + * return 1 (true) if the strings match, otherwise 0 (false) + */ +static int value_cmp(char *v1, char *v2, unsigned int *matched) { + char *s1, *s2; + if( v1 == NULL && v2 == NULL ) { + *matched = 0; + return(1); + } + if( v1 == NULL || v2 == NULL ) { + *matched = 0; + return(0); + } + s1 = v1; + s2 = v2; + *matched = 0; + if( use_regexp ) { + /* Compare values, allowing for the fact that ']' is replaced with '.' */ + while( *s1 || *s2 ) { + if( *s1 != *s2 ) { + if( *s1 =='\0' || *s2 == '\0') + return(0); + if( *s1 != ']' && *s2 != ']' ) + return(0); + } + s1++; s2++; (*matched)++; + } + return(1); + } else { + while( *s1 == *s2 ) { + if( *s1 == '\0' ) { + return(1); + } + s1++; s2++; (*matched)++; + } + return(0); + } + return(1); /* unreachable */ +} + +/* Find an existing group with the same 'head' + * If no such group exists, create a new one + * Update the size of all_groups array if required + */ +static struct group *find_or_create_group(char *head) { + unsigned long ndx; + struct group **all_groups_realloc; + unsigned int num_groups_newsize; + struct group *group = NULL; + /* Look for an existing group with group->head matching path_seg->head */ + for(ndx=0; ndx < num_groups; ndx++) { + if( strcmp(head, all_groups[ndx]->head) == 0 ) { + group = all_groups[ndx]; + return(group); + } + } + /* Group not found - create a new one */ + /* First, grow all_groups[] array if required */ + if ( num_groups % 32 == 0 ) { + num_groups_newsize = (num_groups)/32*32+32; + all_groups_realloc = reallocarray(all_groups, sizeof(struct group *), num_groups_newsize); + CHECK_OOM( ! all_groups_realloc, exit_oom, "in find_or_create_group()"); + + all_groups=all_groups_realloc; + } + /* Create new group */ + group = malloc(sizeof(struct group)); + CHECK_OOM( ! group, exit_oom, "allocating struct group in find_or_create_group()"); + + all_groups[num_groups++] = group; + group->head = head; + group->all_tails = NULL; + group->position_array_size = 0; + group->tails_at_position = NULL; + group->chosen_tail = NULL; + group->chosen_tail_state = NULL; + group->first_tail = NULL; + group->position_array_size = 0; + group->max_position = 0; + group->subgroups = NULL; /* subgroups are only created if we need to use our 3rd preference */ + group->subgroup_position = NULL; + /* for --pretty */ + group->pretty_width_ct = NULL; + /* for --regexp */ + group->re_width_ct = NULL; + group->re_width_ft = NULL; + + return(group); +} + +/* Find a matching tail+value within group->all_tails linked list + * If no such tail exists, append a new (struct tail) list item + * Return the tail found, or the new tail + */ +static struct tail *find_or_create_tail(struct group *group, struct path_segment *path_seg, struct augeas_path_value *path_value) { + /* Scan for a matching simplified tail+value in group->all_tails */ + struct tail *tail; + struct tail *found_tail_value=NULL; + struct tail *found_tail=NULL; + struct tail **all_tails_end; + unsigned int tail_found_this_pos=1; + unsigned int match_length; + all_tails_end =&(group->all_tails); + found_tail_value=NULL; + for( tail = group->all_tails; tail != NULL; tail=tail->next ) { + if( strcmp(path_seg->simplified_tail, tail->simple_tail) == 0 ) { + /* found matching simple_tail - increment counters */ + tail->tail_found_map[path_seg->position]++; + tail_found_this_pos = tail->tail_found_map[path_seg->position]; + if ( value_cmp(tail->value, path_value->value, &match_length ) ) { + /* matching tail+value found, increment tail_value_found */ + tail->tail_value_found_map[path_seg->position]++; + tail->tail_value_found++; + found_tail_value=tail; + } + found_tail=tail; + } + all_tails_end=&tail->next; + } + if ( found_tail_value == NULL ) { + /* matching tail+value not found, create a new one */ + tail = malloc(sizeof(struct tail)); + CHECK_OOM( ! tail, exit_oom, "in find_or_create_tail()"); + + tail->tail_found_map = reallocarray(NULL, sizeof(unsigned int), group->position_array_size); + CHECK_OOM( ! tail->tail_found_map, exit_oom, "in find_or_create_tail()"); + + tail->tail_value_found_map = reallocarray(NULL, sizeof(unsigned int), group->position_array_size); + CHECK_OOM( ! tail->tail_value_found_map, exit_oom, "in find_or_create_tail()"); + + + for(unsigned int i=0; iposition_array_size; i++) { + tail->tail_found_map[i]=0; + tail->tail_value_found_map[i]=0; + } + + if ( found_tail ) { + for( unsigned int ndx=0; ndx<=group->max_position; ndx++ ) { + tail->tail_found_map[ndx] = found_tail->tail_found_map[ndx]; + } + } + tail->tail_found_map[path_seg->position]=tail_found_this_pos; + tail->tail_value_found_map[path_seg->position]=1; + tail->tail_value_found = 1; + tail->simple_tail = path_seg->simplified_tail; + tail->value = path_value->value; + tail->value_qq = path_value->value_qq; + tail->next = NULL; + *all_tails_end = tail; + return(tail); + } else { + return(found_tail_value); + } +} + +/* Append a (struct tail_stub) to the linked list group->tails_at_position[position] */ +static void append_tail_stub(struct group *group, struct tail *tail, unsigned int position) { + struct tail_stub **tail_stub_pp; + + for( tail_stub_pp=&(group->tails_at_position[position]); *tail_stub_pp != NULL; tail_stub_pp=&(*tail_stub_pp)->next ) { + } + *tail_stub_pp = malloc(sizeof(struct tail_stub)); + CHECK_OOM( ! *tail_stub_pp, exit_oom, "in append_tail_stub()"); + + (*tail_stub_pp)->tail = tail; + (*tail_stub_pp)->next = NULL; +} + +/* Grow memory structures within the group record and associated tail records + * to accommodate additional positions + */ +static void grow_position_arrays(struct group *group, unsigned int new_max_position) { + struct tail_stub **tails_at_position_realloc; + struct tail **chosen_tail_realloc; + struct tail_stub **first_tail_realloc; + unsigned int *chosen_tail_state_realloc; + unsigned int *pretty_width_ct_realloc; + unsigned int *re_width_ct_realloc; + unsigned int *re_width_ft_realloc; + unsigned int ndx; + if( new_max_position != UINT_MAX && new_max_position >= group->position_array_size ) { + unsigned int old_size = group->position_array_size; + unsigned int new_size = (new_max_position+1) / 8 * 8 + 8; + + /* Grow arrays within struct group */ + tails_at_position_realloc = reallocarray(group->tails_at_position, sizeof(struct tail_stub *), new_size); + chosen_tail_realloc = reallocarray(group->chosen_tail, sizeof(struct tail *), new_size); + first_tail_realloc = reallocarray(group->first_tail, sizeof(struct tail_stub *), new_size); + chosen_tail_state_realloc = reallocarray(group->chosen_tail_state, sizeof(chosen_tail_state_t), new_size); + pretty_width_ct_realloc = reallocarray(group->pretty_width_ct, sizeof(unsigned int), new_size); + re_width_ct_realloc = reallocarray(group->re_width_ct, sizeof(unsigned int), new_size); + re_width_ft_realloc = reallocarray(group->re_width_ft, sizeof(unsigned int), new_size); + CHECK_OOM( ! tails_at_position_realloc || ! chosen_tail_realloc || ! chosen_tail_state_realloc || + ! pretty_width_ct_realloc || ! re_width_ct_realloc || ! re_width_ft_realloc || + ! first_tail_realloc, exit_oom, "in grow_position_arrays()"); + + /* initialize array entries between old size to new_size */ + for( ndx=old_size; ndx < new_size; ndx++) { + tails_at_position_realloc[ndx]=NULL; + chosen_tail_realloc[ndx]=NULL; + first_tail_realloc[ndx]=NULL; + chosen_tail_state_realloc[ndx] = NOT_DONE; + pretty_width_ct_realloc[ndx] = 0; + re_width_ct_realloc[ndx] = 0; + re_width_ft_realloc[ndx] = 0; + } + group->tails_at_position = tails_at_position_realloc; + group->chosen_tail = chosen_tail_realloc; + group->first_tail = first_tail_realloc; + group->chosen_tail_state = chosen_tail_state_realloc; + group->pretty_width_ct = pretty_width_ct_realloc; + group->re_width_ct = re_width_ct_realloc; + group->re_width_ft = re_width_ft_realloc; + /* Grow arrays in all_tails */ + struct tail *tail; + for( tail = group->all_tails; tail != NULL; tail=tail->next ) { + unsigned int *tail_found_map_realloc; + unsigned int *tail_value_found_map_realloc; + tail_found_map_realloc = reallocarray(tail->tail_found_map, sizeof(unsigned int), new_size); + tail_value_found_map_realloc = reallocarray(tail->tail_value_found_map, sizeof(unsigned int), new_size); + CHECK_OOM( ! tail_found_map_realloc || ! tail_value_found_map_realloc, exit_oom, "in grow_position_arrays()"); + + /* initialize array entries between old size to new_size */ + for( ndx=old_size; ndx < new_size; ndx++) { + tail_found_map_realloc[ndx]=0; + tail_value_found_map_realloc[ndx]=0; + } + tail->tail_found_map = tail_found_map_realloc; + tail->tail_value_found_map = tail_value_found_map_realloc; + } + group->position_array_size = new_size; + } +} + +static void add_segment_to_group(struct path_segment *path_seg, struct augeas_path_value *path_value) { + struct group *group = NULL; + struct tail *tail; + group = find_or_create_group(path_seg->head); + + /* group is our new or matching group for this segment->head */ + path_seg->group = group; + if( path_seg->position != UINT_MAX && path_seg->position > group->max_position ) { + group->max_position = path_seg->position; + if( group->max_position >= group->position_array_size ) { + /* grow arrays in group */ + grow_position_arrays(group, group->max_position); + } + } + tail = find_or_create_tail(group, path_seg, path_value); + + /* Append a tail_stub record to the linked list @ group->tails_at_position[position] */ + append_tail_stub(group, tail, path_seg->position); +} + +/* find_or_create_subgroup() + * This is called from choose_tail(), and is only used if we need to go to our 3rd Preference + */ +static struct subgroup *find_or_create_subgroup(struct group *group, struct tail *first_tail) { + struct subgroup *subgroup_ptr; + struct subgroup **sg_pp; + for( sg_pp=&(group->subgroups); *sg_pp != NULL; sg_pp=&(*sg_pp)->next) { + if( (*sg_pp)->first_tail == first_tail ) { + return(*sg_pp); + } + } + /* Create and populate subgroup */ + subgroup_ptr = (struct subgroup *) malloc( sizeof(struct subgroup)); + CHECK_OOM( ! subgroup_ptr, exit_oom, "in find_or_create_subgroup()"); + + subgroup_ptr->next=NULL; + subgroup_ptr->first_tail=first_tail; + /* positions are 1..max_position, +1 for the terminating 0=end-of-list */ + subgroup_ptr->matching_positions = malloc( (group->max_position+1) * sizeof( unsigned int )); + CHECK_OOM( ! subgroup_ptr->matching_positions, exit_oom, "in find_or_create_subgroup()"); + + /* malloc group->subgroup_position if not already done */ + if ( ! group->subgroup_position ) { + group->subgroup_position = malloc( (group->max_position+1) * sizeof( unsigned int )); + CHECK_OOM( ! group->subgroup_position, exit_oom, "in find_or_create_subgroup()"); + + } + *sg_pp = subgroup_ptr; /* Append new subgroup record to list */ + /* populate matching_positions */ + unsigned int pos_ndx; + unsigned int ndx = 0; + for(pos_ndx=1; pos_ndx <= group->max_position; pos_ndx++ ){ + /* 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 */ + struct tail_stub *tail_stub_ptr; + for( tail_stub_ptr = group->tails_at_position[pos_ndx]; tail_stub_ptr != NULL; tail_stub_ptr=tail_stub_ptr->next ) { + if( tail_stub_ptr->tail == first_tail ) { + subgroup_ptr->matching_positions[ndx++] = pos_ndx; + if( first_tail == group->first_tail[pos_ndx]->tail ) { + /* If first_fail is also the first_tail for this position, update subgroup_position[] */ + 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 */ + } + break; + } + } + } + subgroup_ptr->matching_positions[ndx] = 0; /* 0 = end of list */ + return(subgroup_ptr); +} + +/* str_ischild() + * compare 2 strings which are of the form simple_tail + * return true(1) if parent == /path and child == /path/tail + * return false(0) if child == /pathother or child == /pat or anything else + */ +static int str_ischild(char *parent, char *child) { + while( *parent ) { + if( *parent != *child ) { + return(0); + } + parent++; + child++; + } + if( *child == '/' ) { + return(1); + } else { + return(0); + } +} + +/* Find the first tail in the linked-list that is not NULL, or has no child nodes + * eg for paths starting with /head/123/... ignore the entry: + * /head/123 (null) + * and any further paths like this + * head/123/tail (null) + * head/123/tail/child (null) + * stop when we encounter a value, or find a tail that has no child nodes, + * if the next tail is eg + * head/123/tail2 + * then head/123/tail/child is significant, and that becomes the first_tail + */ +static struct tail_stub *find_first_tail(struct tail_stub *tail_stub_ptr) { + if( tail_stub_ptr == NULL ) + return(NULL); + for( ; tail_stub_ptr->next != NULL; tail_stub_ptr=tail_stub_ptr->next ) { + if ( tail_stub_ptr->tail->value != NULL && tail_stub_ptr->tail->value[0] != '\0' ) { + break; + } + if( ! str_ischild( tail_stub_ptr->tail->simple_tail, tail_stub_ptr->next->tail->simple_tail) ) { + /* the next tail is not a child-node of this tail */ + break; + } + } + return(tail_stub_ptr); +} + +static struct tail *choose_tail(struct group *group, unsigned int position ) { + struct tail_stub *first_tail_stub; + struct tail_stub *tail_stub_ptr; + unsigned int ndx; + + if( group->tails_at_position[position] == NULL ) { + /* first_tail_stub == NULL + * this does not happen, because every position gets at least one tail of "" + * eg, even if the value is NULL. + * /head/1 (null) + * ...simple_tail "" + * ...value NULL + * paths without a position ( /head/tail ) are not added to any group + * We can't do anything with this, use seq::* or [*] only (no value) */ + fprintf(stderr,"# choose_tail() %s[%u] first_tail_stub is NULL (internal error)\n", group->head, position); + group->chosen_tail_state[position] = NO_CHILD_NODES; + return(NULL); + } + + first_tail_stub = group->first_tail[position]; + + /* First preference - if the first-tail+value is unique, use that */ + if( first_tail_stub->tail->tail_value_found == 1 ) { + group->chosen_tail_state[position] = FIRST_TAIL; + return(first_tail_stub->tail); + } + + /* Second preference - find a unique tail+value that has only one value for this position and has the tail existing for all other positions */ + for( tail_stub_ptr=first_tail_stub; tail_stub_ptr!=NULL; tail_stub_ptr=tail_stub_ptr->next) { + if( tail_stub_ptr->tail->tail_value_found == 1 ) { /* tail_stub_ptr->tail->value can be NULL, just needs to be unique */ + int found=1; + for( ndx=1; ndx <= group->max_position; ndx++ ) { + if( tail_stub_ptr->tail->tail_found_map[ndx] == 0 ) { + /* tail does not exist for every position within this group */ + found=0; + break; + } + } + if ( found ) { + /* This works only if chosen_tail->simple_tail is the first appearance of simple_tail at this position */ + struct tail_stub *tail_check_ptr; + for( tail_check_ptr=first_tail_stub; tail_check_ptr != tail_stub_ptr; tail_check_ptr=tail_check_ptr->next) { + if( strcmp(tail_check_ptr->tail->simple_tail, tail_stub_ptr->tail->simple_tail ) == 0 ) { + found=0; + } + } + } + if ( found ) { + group->chosen_tail_state[position] = CHOSEN_TAIL_START; + return(tail_stub_ptr->tail); + } + } /* if ... tail_value_found == 1 */ + } + + /* Third preference - first tail is not unique but could make a unique combination with another tail */ + struct subgroup *subgroup_ptr = find_or_create_subgroup(group, first_tail_stub->tail); + for( tail_stub_ptr=first_tail_stub->next; tail_stub_ptr!=NULL; tail_stub_ptr=tail_stub_ptr->next) { + /* for each tail at this position (other than the first) */ + /* Find a tail at this position where: + * a) tail+value is unique within this subgroup + * b) tail exists at all positions within this subgroup + */ + int found=1; + for(ndx=0; subgroup_ptr->matching_positions[ndx] != 0; ndx++ ) { + int pos=subgroup_ptr->matching_positions[ndx]; + if ( pos == position ) continue; + if( tail_stub_ptr->tail->tail_value_found_map[pos] != 0 ) { + /* tail+value is not unique within this subgroup */ + found=0; + break; + } + if( tail_stub_ptr->tail->tail_found_map[pos] == 0 ) { + /* tail does not exist for every position within this subgroup */ + found=0; + break; + } + } + if ( found ) { + /* This works only if chosen_tail->simple_tail is the first appearance of simple_tail at this position */ + struct tail_stub *tail_check_ptr; + for( tail_check_ptr=first_tail_stub; tail_check_ptr != tail_stub_ptr; tail_check_ptr=tail_check_ptr->next) { + if( strcmp(tail_check_ptr->tail->simple_tail, tail_stub_ptr->tail->simple_tail ) == 0 ) { + found=0; + } + } + } + if ( found ) { + group->chosen_tail_state[position] = CHOSEN_TAIL_PLUS_FIRST_TAIL_START; + return(tail_stub_ptr->tail); + } + } + /* Fourth preference (fallback) - use first_tail PLUS the position with the subgroup */ + group->chosen_tail_state[position] = FIRST_TAIL_PLUS_POSITION; + return(first_tail_stub->tail); +} + +/* simple_tail_expr() + * given a simple_tail of the form "/path" or "" + * return "path" or "." + */ +static const char *simple_tail_expr(char *simple_tail) { + if( *simple_tail == '/' ) { + /* usual case - .../123/... or /label[123]/... */ + return(simple_tail+1); + } else if ( *simple_tail == '\0' ) { + /* path ending in /123 or /label[123] */ + return("."); + } else { + /* unreachabe ? */ + return(simple_tail); + } +} + +/* Write out the path-segment, up to and including the [ expr ] (if required) */ +static void output_segment(struct path_segment *ps_ptr, struct augeas_path_value *path_value_seg) { + char *last_c, *str; + struct group *group; + struct tail *chosen_tail; + unsigned int position; + chosen_tail_state_t chosen_tail_state; + struct tail_stub *first_tail; + + char *value_qq = path_value_seg->value_qq; + + /* print segment possibly followed by * or seq::* */ + last_c=ps_ptr->segment; + for(str=ps_ptr->segment; *str; last_c=str++) /* find end of string */ + ; + if(*last_c=='/') { + /* sequential position .../123 */ + if ( noseq ) + printf("%s*", ps_ptr->segment); + else + printf("%sseq::*", ps_ptr->segment); + } else { + /* label with a position .../label[123], or no position ... /last */ + printf("%s", ps_ptr->segment); + } + group = ps_ptr->group; + if( group == NULL ) { + /* last segment .../last_tail No position, nothing else to print */ + return; + } + + /* apply "chosen_tail" criteria here */ + position = ps_ptr->position; + chosen_tail = group->chosen_tail[position]; + if( chosen_tail == NULL ) { + /* This should not happen */ + fprintf(stderr,"chosen_tail==NULL ???\n"); + } + + first_tail = find_first_tail(group->tails_at_position[position]); + chosen_tail_state = group->chosen_tail_state[position]; + + + switch( chosen_tail_state ) { + case CHOSEN_TAIL_START: + group->chosen_tail_state[position] = CHOSEN_TAIL_WIP; + __attribute__ ((fallthrough)); /* drop through */ + case FIRST_TAIL: + case CHOSEN_TAIL_DONE: + case FIRST_TAIL_PLUS_POSITION: + if ( chosen_tail->value == NULL ) { + printf("[%s]", simple_tail_expr(chosen_tail->simple_tail)); + } else if ( use_regexp ) { + printf("[%s=~regexp(%*s)]", + simple_tail_expr(chosen_tail->simple_tail), + -(group->pretty_width_ct[position]), /* minimum field width */ + chosen_tail->value_re + ); + } else { + printf("[%s=%*s]", + simple_tail_expr(chosen_tail->simple_tail), + -(group->pretty_width_ct[position]), /* minimum field width */ + chosen_tail->value_qq + ); + } + if ( chosen_tail_state == FIRST_TAIL_PLUS_POSITION ) { + /* no unique tail+value - duplicate or overlapping positions */ + printf("[%u]", group->subgroup_position[position] ); + } + break; + case CHOSEN_TAIL_WIP: + if ( chosen_tail->value == NULL ) { + /* theoretically possible - how to test? */ + printf("[%s or count(%s)=0]", + simple_tail_expr(chosen_tail->simple_tail), + simple_tail_expr(chosen_tail->simple_tail)); + } else if ( use_regexp ) { + printf("[%s=~regexp(%*s) or count(%s)=0]", + simple_tail_expr(chosen_tail->simple_tail), + -(group->pretty_width_ct[position]), /* minimum field width */ + chosen_tail->value_re, + simple_tail_expr(chosen_tail->simple_tail)); + } else { + printf("[%s=%*s or count(%s)=0]", + simple_tail_expr(chosen_tail->simple_tail), + -(group->pretty_width_ct[position]), /* minimum field width */ + chosen_tail->value_qq, + simple_tail_expr(chosen_tail->simple_tail)); + } + if ( strcmp(chosen_tail->simple_tail, ps_ptr->simplified_tail) == 0 && strcmp(chosen_tail->value_qq, value_qq) == 0 ) { + group->chosen_tail_state[position] = CHOSEN_TAIL_DONE; + } + break; + case CHOSEN_TAIL_PLUS_FIRST_TAIL_START: + if ( first_tail->tail->value == NULL && use_regexp ) { + /* test with /etc/sudoers */ + printf("[%s and %s=~regexp(%s)]", + simple_tail_expr(first_tail->tail->simple_tail), + simple_tail_expr(chosen_tail->simple_tail), + chosen_tail->value_re + ); + + } else if ( first_tail->tail->value == NULL && ! use_regexp ) { + /* test with /etc/sudoers */ + printf("[%s and %s=%s]", + simple_tail_expr(first_tail->tail->simple_tail), + simple_tail_expr(chosen_tail->simple_tail), + chosen_tail->value_qq + ); + } else if ( use_regexp ) { + printf("[%s=~regexp(%*s) and %s=~regexp(%s)]", + simple_tail_expr(first_tail->tail->simple_tail), + -(group->pretty_width_ct[position]), /* minimum field width */ + first_tail->tail->value_re, + simple_tail_expr(chosen_tail->simple_tail), + chosen_tail->value_re ); + } else { + printf( "[%s=%*s and %s=%s]", + simple_tail_expr(first_tail->tail->simple_tail), + -(group->pretty_width_ct[position]), /* minimum field width */ + first_tail->tail->value_qq, + simple_tail_expr(chosen_tail->simple_tail), + chosen_tail->value_qq ); + } + group->chosen_tail_state[position] = CHOSEN_TAIL_PLUS_FIRST_TAIL_WIP; + break; + case CHOSEN_TAIL_PLUS_FIRST_TAIL_WIP: + if ( first_tail->tail->value == NULL && use_regexp ) { + printf("[%s and ( %s=~regexp(%s) or count(%s)=0 )]", + simple_tail_expr(first_tail->tail->simple_tail), + simple_tail_expr(chosen_tail->simple_tail), + chosen_tail->value_re, + simple_tail_expr(chosen_tail->simple_tail) + ); + } else if ( first_tail->tail->value == NULL && ! use_regexp ) { + printf("[%s and ( %s=%s or count(%s)=0 )]", + simple_tail_expr(first_tail->tail->simple_tail), + simple_tail_expr(chosen_tail->simple_tail), + chosen_tail->value_qq, + simple_tail_expr(chosen_tail->simple_tail) + ); + } else if ( use_regexp ) { + printf("[%s=~regexp(%*s) and ( %s=~regexp(%s) or count(%s)=0 ) ]", + simple_tail_expr(first_tail->tail->simple_tail), + -(group->pretty_width_ct[position]), /* minimum field width */ + first_tail->tail->value_re, + simple_tail_expr(chosen_tail->simple_tail), + chosen_tail->value_re, + simple_tail_expr(chosen_tail->simple_tail) + ); + } else { + printf("[%s=%*s and ( %s=%s or count(%s)=0 ) ]", + simple_tail_expr(first_tail->tail->simple_tail), + -(group->pretty_width_ct[position]), /* minimum field width */ + first_tail->tail->value_qq, + simple_tail_expr(chosen_tail->simple_tail), + chosen_tail->value_qq, + simple_tail_expr(chosen_tail->simple_tail) + ); + } + if ( strcmp(chosen_tail->simple_tail, ps_ptr->simplified_tail) == 0 && strcmp(chosen_tail->value_qq, value_qq) == 0 ) { + group->chosen_tail_state[position] = CHOSEN_TAIL_PLUS_FIRST_TAIL_DONE; + } + break; + case CHOSEN_TAIL_PLUS_FIRST_TAIL_DONE: + if ( first_tail->tail->value == NULL && use_regexp ) { + printf("[%s and %s=~regexp(%s)]", + simple_tail_expr(first_tail->tail->simple_tail), + simple_tail_expr(chosen_tail->simple_tail), + chosen_tail->value_re + ); + } else if ( first_tail->tail->value == NULL && ! use_regexp ) { + printf("[%s and %s=%s]", + simple_tail_expr(first_tail->tail->simple_tail), + simple_tail_expr(chosen_tail->simple_tail), + chosen_tail->value_qq + ); + } else if ( use_regexp ) { + printf("[%s=~regexp(%*s) and %s=~regexp(%s)]", + simple_tail_expr(first_tail->tail->simple_tail), + -(group->pretty_width_ct[position]), /* minimum field width */ + first_tail->tail->value_re, + simple_tail_expr(chosen_tail->simple_tail), + chosen_tail->value_re + ); + } else { + printf("[%s=%*s and %s=%s]", + simple_tail_expr(first_tail->tail->simple_tail), + -(group->pretty_width_ct[position]), /* minimum field width */ + first_tail->tail->value_qq, + simple_tail_expr(chosen_tail->simple_tail), + chosen_tail->value_qq + ); + } + break; + case NO_CHILD_NODES: + if(*last_c!='/') { + printf("[*]"); /* /head/label with no child nodes */ + } + break; + default: + /* unreachable */ + printf("[ %s=%s ]", simple_tail_expr(chosen_tail->simple_tail),chosen_tail->value_qq); + } +} + +static void output_path(struct augeas_path_value *path_value_seg) { + struct path_segment *ps_ptr; + printf("set "); + for( ps_ptr=path_value_seg->segments; ps_ptr != NULL; ps_ptr=ps_ptr->next) { + output_segment(ps_ptr, path_value_seg); + } + if( path_value_seg->value_qq != NULL ) { + printf(" %s\n", path_value_seg->value_qq); + } else { + printf("\n"); + } +} + +static void output(void) { + int ndx; /* index to matches() */ + struct augeas_path_value *path_value_seg; + char *value; + for( ndx=0; ndxvalue; + if( value != NULL && *value == '\0' ) + value = NULL; + if(verbose) { + if ( value == NULL ) + fprintf(stdout,"# %s\n", path_value_seg->path); + else + fprintf(stdout,"# %s %s\n", path_value_seg->path, path_value_seg->value_qq); + } + /* weed out null paths here, eg + * /head/123 (null) + * /head/123/tail (null) + * /head/path (null) + * ie. if value==NULL AND this node has child nodes + * does not apply if there is no + * /head/path/tail + */ + if ( value == NULL && ndx < num_matched-1 ) { + if(str_ischild(all_augeas_paths[ndx]->path, all_augeas_paths[ndx+1]->path)) { + continue; + } + } + output_path(path_value_seg); + if( pretty ) { + if( ndx < num_matched-1 ) { + /* fixme - do we just need to compare the position? */ + struct group *this_group, *next_group; + this_group = all_augeas_paths[ndx]->segments->group; + next_group = all_augeas_paths[ndx+1]->segments->group; + if ( this_group != next_group + || ( this_group != NULL && all_augeas_paths[ndx]->segments->position != all_augeas_paths[ndx+1]->segments->position ) + ) { + /* New group, put in a newline for visual seperation */ + printf("\n"); + } + } + } + } +} + +static void choose_re_width(struct group *group) { + unsigned int position; + /* For each position, compare the value of chosen_tail with + * all other matching simple_tails in the group, to find the minimum + * required length of the RE + */ + for(position=1; position<=group->max_position; position++) { + unsigned int max_re_width_ct=0; + unsigned int max_re_width_ft=0; + unsigned int re_width; + struct tail *chosen_tail = group->chosen_tail[position]; + struct tail *first_tail = group->first_tail[position]->tail; + struct tail *tail_ptr; + for(tail_ptr = group->all_tails; tail_ptr != NULL; tail_ptr = tail_ptr->next) { + if ( tail_ptr != chosen_tail ) { + if( strcmp(tail_ptr->simple_tail, chosen_tail->simple_tail) == 0 ) { + value_cmp(tail_ptr->value, chosen_tail->value, &re_width); + if( re_width + 1 > max_re_width_ct ) { + max_re_width_ct = re_width+1; + } + } + } + if( group->chosen_tail_state[position] == CHOSEN_TAIL_PLUS_FIRST_TAIL_START && chosen_tail != first_tail ) { + /* 3rd preference, we need an re_width for both the chosen_tail and the first_tail */ + /* In theory, the first_tail of this position may be present in other positions, but may not be first */ + if ( tail_ptr != first_tail ) { + if( strcmp(tail_ptr->simple_tail, first_tail->simple_tail) == 0 ) { + value_cmp(tail_ptr->value, first_tail->value, &re_width); + if( re_width + 1 > max_re_width_ft ) { + max_re_width_ft = re_width+1; + } + } + } + } /* If 3rd preference */ + } /* for each tail in group->all_tails */ + max_re_width_ct = MAX(max_re_width_ct,use_regexp); + max_re_width_ft = MAX(max_re_width_ft,use_regexp); + group->re_width_ct[position] = max_re_width_ct; + group->re_width_ft[position] = max_re_width_ft; + chosen_tail->value_re = regexp_value( chosen_tail->value, max_re_width_ct ); + if ( group->chosen_tail_state[position] == CHOSEN_TAIL_PLUS_FIRST_TAIL_START ) { + /* otherwise, max_re_width_ft=0, and we don't need first_tail->value_re at all */ + if ( chosen_tail == first_tail ) { + /* if chosen_tail == first_tail, we would overwrite chosen_tail->value_re */ + first_tail->value_re = chosen_tail->value_re; + } else { + first_tail->value_re = regexp_value( first_tail->value, max_re_width_ft ); + } + } + } /* for position 1..max_position */ +} + +static void choose_pretty_width(struct group *group) { + unsigned int position; + int value_len; + for(position=1; position<=group->max_position; position++) { + struct tail *pretty_tail; + if( group->chosen_tail_state[position] == CHOSEN_TAIL_PLUS_FIRST_TAIL_START ) { + pretty_tail = group->first_tail[position]->tail; + } else { + pretty_tail = group->chosen_tail[position]; + } + if( use_regexp ) { + value_len = pretty_tail->value_re == NULL ? 0 : strlen(pretty_tail->value_re); + } else { + value_len = pretty_tail->value_qq == NULL ? 0 : strlen(pretty_tail->value_qq); + } + group->pretty_width_ct[position] = value_len; + } + /* find the highest pretty_width_ct for each unique chosen_tail->simple_tail in the group */ + for(position=1; position<=group->max_position; position++) { + unsigned int max_width=0; + unsigned int pos_search; + char *chosen_simple_tail = group->chosen_tail[position]->simple_tail; + for(pos_search=position; pos_search <= group->max_position; pos_search++) { + if(strcmp( group->chosen_tail[pos_search]->simple_tail, chosen_simple_tail) == 0 ) { + value_len = group->pretty_width_ct[pos_search]; + if( value_len <= MAX_PRETTY_WIDTH ) { + /* If we're already over the limit, do not pad everything else out too */ + max_width = MAX(max_width, value_len); + } + group->pretty_width_ct[pos_search] = max_width; /* so we can start at position+1 */ + } + } + max_width = MIN(max_width,MAX_PRETTY_WIDTH); + group->pretty_width_ct[position] = max_width; + } /* for position 1..max_position */ +} + +/* populate group->chosen_tail[] and group->first_tail[] arrays */ +/* Also call choose_re_width() and choose_pretty_width() to populate group->re_width_ct[] ..->re_width_ft[] and ..->pretty_width_ft[] */ +static void choose_all_tails(void) { + int ndx; /* index to all_groups() */ + unsigned int position; + struct group *group; + for(ndx=0; ndxmax_position; position++) { + /* find_first_tail() - find first "significant" tail + * populate group->first_tail[] before calling choose_tail() + * We need these values for find_or_create_subgroup() + */ + group->first_tail[position] = find_first_tail(group->tails_at_position[position]); + } + for(position=1; position<=group->max_position; position++) { + group->chosen_tail[position] = choose_tail(group, position); + } + if( use_regexp ) { + choose_re_width(group); + } + if( pretty ) { + choose_pretty_width(group); + } + } +} + +/* Create a quoted value from the value, using single quotes if possible + * Quotes are not strictly required for the value, but they _are_ required + * for values within the path-expressions + */ +static char *quote_value(char *value) { + char *s, *t, *value_qq, quote; + int len=0; + int has_q=0; + int has_qq=0; + int has_special=0; + int has_nl=0; + int new_len; + if(value==NULL) + return(NULL); + for(s = value, len=0; *s; s++, len++) { + switch(*s) { + case '"': has_qq++; break; + case '\'': has_q++; break; + case ' ': + case '/': + case '*': + case '.': + case ':': + has_special++; break; + case '\n': + case '\t': + case '\\': + has_nl++; break; + default: + ; + } + } + if( has_q == 0 ) { + /* Normal case, no single-quotes within the value */ + new_len = len+2+has_nl; + quote='\''; + } else if ( has_qq == 0 ) { + new_len = len+2+has_nl; + quote='"'; + } else { + /* This needs a bugfix in augeas */ + new_len = len+2+has_q+has_nl; + quote='\''; + } + value_qq = malloc( sizeof(char) * ++new_len); /* don't forget the \0 */ + CHECK_OOM( ! value_qq, exit_oom, "in quote_value()"); + + t=value_qq; + *t++ = quote; + for(s = value; *s; s++, t++) { + if ( *s == quote ) { + *t++ = '\\'; + *t =quote; + continue; + } else if ( *s == '\n' ) { + *t++ = '\\'; + *t = 'n'; + continue; + } else if ( *s == '\t' ) { + *t++ = '\\'; + *t = 't'; + continue; + } else if ( *s == '\\' ) { + *t++ = '\\'; + *t = '\\'; + continue; + } + *t = *s; + } + *t++ = quote; + *t++ = '\0'; + return(value_qq); +} + +/* Create a quoted regular expression from the value, using single quotes if possible + */ +static char *regexp_value(char *value, int max_len) { + char *s, *t, *value_re, quote; + int len=0; + int has_q=0; + int has_qq=0; + int has_special=0; + int has_nl=0; + int new_len; + if(value==NULL) + return(NULL); + for(s = value, len=0; *s; s++, len++) { + switch(*s) { + case '"': has_qq++; break; + case '\'': has_q++; break; + case '*': + case '?': + case '.': + case '[': + case ']': + case '(': + case ')': + case '^': + case '$': + case '|': + has_special++; break; + case '\n': + case '\t': + has_nl++; break; + case '\\': + has_special+=2; break; + default: + ; + } + } + len++; /* don't forget the \0 */ + if( has_q == 0 ) { + /* Normal case, no single-quotes within the value */ + new_len = len+2+has_nl+has_special*2; + quote='\''; + } else if ( has_qq == 0 ) { + new_len = len+2+has_nl+has_special*2; + quote='"'; + } else { + /* This needs a bugfix in augeas */ + new_len = len+2+has_q+has_nl+has_special*2; + quote='\''; + } + value_re = malloc( sizeof(char) * new_len); + CHECK_OOM( ! value_re, exit_oom, "in regexp_value()"); + + t=value_re; + *t++ = quote; + for(s = value; *s; s++, t++) { + if ( *s == quote ) { + *t++ = '\\'; + *t =quote; + continue; + } else if ( *s == '\n' ) { + *t++ = '\\'; + *t = 'n'; + continue; + } else if ( *s == '\t' ) { + *t++ = '\\'; + *t = 't'; + continue; + } else if ( *s == '\\' || *s == ']' ) { + *t = '.'; + continue; + } + switch(*s) { + /* Special handling for ] */ + case ']': + *t = '.'; continue; + case '[': + *t++ = '\\'; + break; + case '*': + case '?': + case '.': + case '(': + case ')': + case '^': + case '$': + case '|': + *t++ = '\\'; + *t++ = '\\'; + break; + case '\\': + case '\n': + case '\t': + break; /* already dealt with above */ + default: + ; + } + *t = *s; + if( ( s - value ) + 1 >= max_len && *(s+1)!='\0' && *(s+2)!='\0' && *(s+3)!='\0' ) { + /* don't append .* if there are only one or two chars left in the string */ + t++; + *t++='.'; + *t++='*'; + break; + } + } + *t++ = quote; + *t++ = '\0'; + return(value_re); +} + +static void usage(const char *progname) { + if(progname == NULL) + progname = "augprint"; + fprintf(stdout, "Usage:\n\n%s [--target=realname] [--lens=Lensname] [--pretty] [--regexp[=n]] [--noseq] /path/filename\n\n",progname); + fprintf(stdout, " -t, --target ... use this as the filename in the output set-commands\n"); + fprintf(stdout, " this filename also implies the default lens to use\n"); + fprintf(stdout, " -l, --lens ... override the default lens and target and use this one\n"); + fprintf(stdout, " -p, --pretty ... make the output more readable\n"); + fprintf(stdout, " -r, --regexp ... use regexp() in path-expressions instead of absolute values\n"); + fprintf(stdout, " if followed by a number, this is the minimum length of the regexp to use\n"); + fprintf(stdout, " -s, --noseq ... use * instead of seq::* (useful for compatability with augeas < 1.13.0)\n"); + fprintf(stdout, " -h, --help ... this message\n"); + fprintf(stdout, " -V, --version ... print augeas version information and exit.\n"); + fprintf(stdout, " /path/filename ... full pathname to the file being analysed (required)\n\n"); + fprintf(stdout, "%s will generate a script of augtool set-commands suitable for rebuilding the file specified\n", progname); + fprintf(stdout, "If --target is specified, then the lens associated with the target will be use to parse the file\n"); + fprintf(stdout, "If --lens is specified, then the given lens will be used, overriding the default, and --target\n\n"); + fprintf(stdout, "Examples:\n"); + fprintf(stdout, "\t%s --target=/etc/squid/squid.conf /etc/squid/squid.conf.new\n", progname); + fprintf(stdout, "\t\tOutput an augtool script for re-creating /etc/squid/squid.conf.new at /etc/squid/squid.conf\n\n"); + fprintf(stdout, "\t%s --lens=simplelines /etc/hosts\n", progname); + fprintf(stdout, "\t\tOutput an augtool script for /etc/hosts using the lens simplelines instead of the default for /etc/hosts\n\n"); + fprintf(stdout, "\t%s --regexp=12 /etc/hosts\n", progname); + fprintf(stdout, "\t\tUse regular expressions in the resulting augtool script, each being at least 12 chars long\n"); + fprintf(stdout, "\t\tIf the value is less than 12 chars, use the whole value in the expression\n"); + fprintf(stdout, "\t\tRegular expressions longer than 12 chars may be generated, if the 12 char regexp\n"); + fprintf(stdout, "\t\twould be match more than one value\n"); +} + +static void print_version_info(const char *progname) { + const char *version; + int r; + + r = aug_get(aug, "/augeas/version", &version); + if (r != 1) + goto error; + + fprintf(stderr, "%s %s \n", progname, version); + return; + error: + fprintf(stderr, "Something went terribly wrong internally - please file a bug\n"); +} + +int main(int argc, char **argv) { + int opt; + char *augeas_root = getenv("AUGEAS_ROOT"); + char *inputfile = NULL; + char *target_file = NULL; + char *program_name = basename(argv[0]); + char *value; /* result of aug_get() */ + + while (1) { + int option_index = 0; + static struct option long_options[] = { + {"help", no_argument, &help, 1 }, + {"version", no_argument, &print_version, 1 }, + {"verbose", no_argument, &verbose, 1 }, + {"debug", no_argument, &debug, 1 }, + {"lens", required_argument, 0, 0 }, + {"noseq", no_argument, &noseq, 1 }, + {"seq", no_argument, &noseq, 0 }, + {"target", required_argument, 0, 0 }, + {"pretty", no_argument, &pretty, 1 }, + {"regexp", optional_argument, &use_regexp, 1 }, + {0, 0, 0, 0 } /* marker for end of data */ + }; + + opt = getopt_long(argc, argv, "vdhVl:sSr::pt:", long_options, &option_index); + if (opt == -1) + break; + + switch (opt) { + case 0: + if(debug) { + fprintf(stderr,"option %d %s", option_index, long_options[option_index].name); + if(optarg) fprintf(stderr," with arg %s", optarg); + fprintf(stderr,"\n"); + } + if (strcmp(long_options[option_index].name, "lens") == 0 ) { + lens = optarg; + flags |= AUG_NO_MODL_AUTOLOAD; + } else if (strcmp(long_options[option_index].name, "target") == 0) { + target_file = optarg; + if( *target_file != '/' ) { + fprintf(stderr,"%s: Error: target \"%s\" must be an absolute path\neg.\n\t--target=/etc/%s\n", program_name, target_file, target_file); + exit(1); + } + } else if (strcmp(long_options[option_index].name, "regexp") == 0) { + if(optarg) { + int optarg_int = strtol(optarg, NULL, 0); + if(optarg_int > 0) + use_regexp = optarg_int; + /* else use the default 1 set by getopt() */ + } else { + use_regexp = 8; + } + } + break; + + case 'h': + help=1; + break; + case 'V': + print_version=1; + break; + case 'v': + verbose=1; + break; + case 'd': + debug=1; + fprintf(stderr,"option d with value '%s'\n", optarg); + break; + case 'S': + noseq=0; + break; + case 's': + noseq=1; + break; + case 'l': + lens = optarg; + flags |= AUG_NO_MODL_AUTOLOAD; + break; + case 't': + target_file = optarg; + if( *target_file != '/' ) { + fprintf(stderr,"%s: Error: target \"%s\" must be an absolute path\neg.\n\t--target=/etc/%s\n", program_name, target_file, target_file); + exit(1); + } + break; + case 'r': + if(optarg) { + int optarg_int = strtol(optarg, NULL, 0); + use_regexp = optarg_int > 0 ? optarg_int : 8; + } + use_regexp = use_regexp ? use_regexp : 8; + break; + + case '?': /* unknown option */ + break; + + default: + fprintf(stderr,"?? getopt returned character code 0x%x ??\n", opt); + } + } + + if( help ) { + usage(program_name); + exit(0); + } + if( print_version ) { + aug = aug_init(NULL, loadpath, flags|AUG_NO_ERR_CLOSE|AUG_NO_LOAD|AUG_NO_MODL_AUTOLOAD); + print_version_info(program_name); + exit(0); + } + if (optind == argc-1) { + /* We need exactly one non-option argument - the input filename */ + if( *argv[optind] == '/' ) { + /* filename is an absolute path - use it verbatim */ + inputfile = argv[optind]; + } else { + /* filename is a relative path - prepend the current PWD */ + int result = asprintf(&inputfile, "%s/%s", getenv("PWD"), argv[optind] ); + CHECK_OOM( result < 0, exit_oom, NULL); + } + if(debug) { + fprintf(stderr,"non-option ARGV-elements: "); + while (optind < argc) + fprintf(stderr,"%s ", argv[optind++]); + fprintf(stderr,"\n"); + } + } else if( optind == argc ) { + /* No non-option args given (missing inputfile) */ + fprintf(stderr,"Missing command-line argument\nPlease specify a filename to read eg.\n\t%s %s\n", program_name, "/etc/hosts"); + fprintf(stderr, "\nTry '%s --help' for more information.\n", program_name); + exit(1); + } else { + /* Too many args - we only want one */ + fprintf(stderr,"Too many command-line arguments\nPlease specify only one filename to read eg.\n\t%s %s\n", program_name, "/etc/hosts"); + fprintf(stderr, "\nTry '%s --help' for more information.\n", program_name); + exit(1); + } + + cleanup_filepath(inputfile); + char *inputfile_real; + if( augeas_root != NULL ) { + int result = asprintf(&inputfile_real, "%s/%s", augeas_root, inputfile ); + if ( result == -1 ) { + perror(program_name); + exit(1); + } + } else { + inputfile_real = inputfile; + } + if( access(inputfile_real, F_OK|R_OK) ) { + fprintf(stderr, "%s: Could not access file %s: %s\n", program_name, inputfile_real, strerror(errno)); + exit(1); + } + + aug = aug_init(NULL, loadpath, flags|AUG_NO_ERR_CLOSE|AUG_NO_LOAD); + + if ( target_file != NULL && lens == NULL ) { + /* Infer the lens which applies to the --target_file option */ + lens = find_lens_for_path(target_file); + } + + if ( lens != NULL ) { + /* Explict lens given, or inferred from --target */ + char *filename; + if ( aug_transform(aug, lens, inputfile, 0) != 0 ) { + fprintf(stderr, "%s\n", aug_error_details(aug)); + exit(1); + } + if ( target_file ) { + filename = target_file; + } else { + filename = inputfile; + } + printf("setm /augeas/load/*[incl='%s' and label() != '%s']/excl '%s'\n", filename, lens, filename); + printf("transform %s incl %s\n", lens, filename); + printf("load-file %s\n", filename); + + } else { + /* --lens not specified, print the default lens as a comment if --verbose specified */ + if( verbose ) { + char *default_lens; + default_lens = find_lens_for_path( inputfile ); + printf("# Using default lens: %s\n# transform %s incl %s\n", default_lens, default_lens, inputfile); + } + } + + if ( aug_load_file(aug, inputfile) != 0 || aug_error_details(aug) != NULL ) { + const char *msg; + fprintf(stderr, "%s: Failed to load file %s\n", program_name, inputfile); + msg = aug_error_details(aug); + if(msg) { + fprintf(stderr,"%s\n",msg); + } else { + msg = aug_error_message(aug); + if(msg) + fprintf(stderr,"%s\n",msg); + msg = aug_error_minor_message(aug); + if(msg) + fprintf(stderr,"%s\n",msg); + } + exit(1); + } + + if ( target_file ) { + /* Rename the tree from inputfile to target_file, if specified */ + move_tree(inputfile, target_file); + } + + /* There is a subtle difference between "/files//(star)" and "/files/descendant::(star)" in the order that matches appear */ + /* descendant::* is better suited, as it allows us to prune out intermediate nodes with null values (directory-like nodes) */ + /* These would be created implicity by "set" */ + num_matched = aug_match(aug, "/files/descendant::*", &all_matches); + if( num_matched == 0 ) { + if( lens == NULL ) + lens = find_lens_for_path(inputfile); + fprintf(stderr,"%s: Failed to parse file %s using lens %s\n", program_name, inputfile, lens); + exit(1); + } + all_augeas_paths = (struct augeas_path_value **) malloc( sizeof(struct augeas_path_value *) * num_matched); + CHECK_OOM( all_augeas_paths == NULL, exit_oom, NULL); + + for (int ndx=0; ndx < num_matched; ndx++) { + all_augeas_paths[ndx] = (struct augeas_path_value *) malloc( sizeof(struct augeas_path_value)); + CHECK_OOM( all_augeas_paths[ndx] == NULL, exit_oom, NULL); + all_augeas_paths[ndx]->path = all_matches[ndx]; + aug_get(aug, all_matches[ndx], (const char **) &value ); + all_augeas_paths[ndx]->value = value; + all_augeas_paths[ndx]->value_qq = quote_value(value); + all_augeas_paths[ndx]->segments = split_path(all_augeas_paths[ndx]); + } + choose_all_tails(); + output(); + + exit(0); +} + diff --git a/src/augprint.h b/src/augprint.h new file mode 100644 index 000000000..a55e2a4bd --- /dev/null +++ b/src/augprint.h @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2022 George Hansper + * ----------------------------------------------------------------------- + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + * + * Author: George Hansper + * ----------------------------------------------------------------------- + * + Have: + /head/label_a[pos1]/mid/label_b[pos2]/tail value_a1_b1 + /head/label_a[pos3]/mid/label_b[pos4]/tail value_a2_b1 + + +-------------------------------------------------------+ + | augeas_path_value | + | path = "/head/label_a[pos1]/mid/label_b[pos2]/tail" | + | value = "value_a1_b1" | + | value_qq = "'value_a1_b1'" or "\"value_a1_b1\"" | + | segments --. | + +---------------\---------------------------------------+ + \ + +----------------------------------------+ +--------------------------------------------+ + | path_segment | | path_segment | + | head = "/head/label_a" | | head = "/head/label_a[pos1]/mid/label_b" | + | segment = "/head/label_a" | | seqment = "/mid/label_b" | + | simplified_tail = "mid/label_b/tail" | | simplified_tail = "tail" | + | position = (int) pos1 | | position = (int) pos2 | + | next ------------------------------------->| next --> NULL | + | group --. | | group --. | + +------------\---------------------------+ +------------\-------------------------------+ + \ \ + +-----------------------------+ +--------------------------------------------+ + | group | | group | + | head = "/head/label_a" | | head = "/head/label_a[pos1]/mid/label_b" | + | max_position | | max_position | + | chosen_tail[] | | chosen_tail[] | array of *tail, index is position + | tails_at_position[]----------------. | tails_at_position[]----------------. | array of *tail_stub lists, index is position + | all_tails ---. | \ | all_tails ---. \ | linked list, unique to group + +-----------------\-----------+ \ +-----------------\----------------------\---+ + \ \ \ \ + +------------------------------------+ \ +------------------------------------+ \ + | tail |-+ | | tail |-+ | + | simple_tail = "mid/label_b/tail" | | | | simple_tail = "tail" | | | + | value = "value_a1_b1" | | | | value = "value_a1_b1" | | | + | next (next in all_tails list) | | | | next (next in all_tails list) | | | + | tail_value_found | | | | tail_value_found | | | count of matching tail+value + | tail_value_found_map[] | | | | tail_value_found_map[] | | | per-position count of tail+value + | tail_found_map[] | | | | tail_found_map[] | | | per-position count of matching tail + +------------------------------------+ | | +------------------------------------+ | | + +------------------------------------+ | +------------------------------------+ | + (linked-list) ^ | (linked-list) ^ | + | | | | + | .-----------' | .------------' + | | | | + | | | | + | v | v + +---------------------|--------------+ +---------------------|--------------+ + | tail_stub | |-+ | tail_stub | |-+ + | *tail (ptr) ---' | | | *tail (ptr) ---' | | + | next (in order of appearance) | | | next (in order of appearance) | | + +------------------------------------+ | +------------------------------------+ | + +------------------------------------+ +------------------------------------+ + (linked-list) (linked-list) + +all_tails is a linked-list of (struct tail), in no particular order, where the combination of tail+value is unique in this list + +all_tails list is unique to a group, and the (struct tail) records are not shared outside the group +The (struct tail) records are shared within the group, across all [123] positions + +Each (struct_tail) contains three counters: +* tail_value_found + This is the number of times this tail+value combination appears within the group + If this counter >1, this indicates a duplicate tail+value, ie two (or more) identical entries within the group +* tail_value_found_map + This is similar to tail_value_found, but there is an individiual counter for each position within the group +* tail_found_map + This is the number of times this tail (regardless of value) appears for each position within the group + +There is a (struct tail_stub) record for _every_ tail that we find for this group, including duplicates + +The (struct group) record keeps an array tails_at_position[] which is indexed by position +Each array-element points to a linked-list of tail_stub records, which contain +a pointer to a (struct tail) record from the all_tails linked list +The tails_at_position[position] linked-list give us a complete list of all the tail+value records +for this position in the group, in their original order of appearance +*/ + +/* all_tails record */ +struct tail { + char *simple_tail; + char *value; + char *value_qq; /* The value, quoted and escaped as-needed */ + char *value_re; /* The value expressed as a regular-expression, long enough to uniquely identify the value */ + struct tail *next; /* next all_tails record */ + unsigned int tail_value_found; /* number of times we have seen this tail+value within this group, (used by 1st preference) */ + unsigned int *tail_value_found_map; /* Array, indexed by position, number of times we have seen this tail+value within this group (used by 3rd preference) */ + unsigned int *tail_found_map; /* Array, indexed by position, number of times we have seen this tail (regardless of value) within this group (used by 2nd preference) */ +}; + +/* Linked list of pointers into the all_tails list + * One such linked-list exists for each position within the group + * Each list begins at group->tails_at_position[position] + */ +struct tail_stub { + struct tail *tail; + struct tail_stub *next; +}; + +/* subgroup exists only to analyse the 3rd preference + * it maps a subset of positions within a group + */ +struct subgroup { + struct tail *first_tail; + unsigned int *matching_positions; /* zero-terminated array of positions with the same first_tail */ + struct subgroup *next; +}; + +typedef enum { + NOT_DONE=0, + FIRST_TAIL=1, /* 1st preference */ + CHOSEN_TAIL_START=4, /* 2nd preference - unique tail found for this position */ + CHOSEN_TAIL_WIP=5, /* 2nd preference */ + CHOSEN_TAIL_DONE=6, /* 2nd preference */ + CHOSEN_TAIL_PLUS_FIRST_TAIL_START=8, /* 3rd preference - unique tail found in a subgroup with a common first_tail */ + CHOSEN_TAIL_PLUS_FIRST_TAIL_WIP=9, /* 3rd preference */ + CHOSEN_TAIL_PLUS_FIRST_TAIL_DONE=10, /* 3rd preference */ + FIRST_TAIL_PLUS_POSITION=12, /* Fallback - use first_tail subgroup and append a position */ + NO_CHILD_NODES=16, /* /head/123 with no child nodes */ +} chosen_tail_state_t; + +struct group { + char *head; + struct tail *all_tails; /* Linked list */ + struct tail_stub **tails_at_position; /* array of linked-lists, index is position */ + struct tail **chosen_tail; /* array of (struct tail) pointers, index is position */ + struct tail_stub **first_tail; /* array of (struct tail_stub) pointers, index is position */ + unsigned int max_position; /* highest position seen for this group */ + unsigned int position_array_size; /* array size for arrays indexed by position, >= max_position+1, used for malloc() */ + chosen_tail_state_t *chosen_tail_state; /* array, index is position */ + struct subgroup *subgroups; /* Linked list, subgroups based on common first-tail - used only for 3rd preference and fallback */ + unsigned int *subgroup_position; /* array, position within subgroup for this position - used only for fallback */ + /* For --pretty */ + unsigned int *pretty_width_ct; /* array, index is position, value width to use for --pretty */ + /* For --regexp */ + unsigned int *re_width_ct; /* array, index is position, matching width to use for --regexp */ + unsigned int *re_width_ft; /* array, index is position, matching width to use for --regexp */ +}; + +struct path_segment { + char *head; + char *segment; + char *simplified_tail; + unsigned int position; + struct group *group; + struct path_segment *next; +}; + +/* Results of aug_match() and aug_get() - one record per path returned by aug_match() */ +struct augeas_path_value { + char *path; + char *value; + char *value_qq; /* value in quotes - used in path-expressions, and as the value being assigned */ + /* result of split_path() */ + struct path_segment *segments; +}; diff --git a/tests/Makefile.am b/tests/Makefile.am index 220182401..19c8fbfaa 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -284,6 +284,7 @@ check_SCRIPTS = \ test-events-saved.sh test-save-mode.sh test-unlink-error.sh \ test-augtool-empty-line.sh test-augtool-modify-root.sh \ test-span-rec-lens.sh test-nonwritable.sh test-augmatch.sh \ + test-augprint.sh \ test-function-modified.sh EXTRA_DIST = \ diff --git a/tests/test-augprint.sh b/tests/test-augprint.sh new file mode 100755 index 000000000..cfd63e184 --- /dev/null +++ b/tests/test-augprint.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +# Test that augprint produces the expected output, and that the output +# can be used to reconstruct the original file + +root=$abs_top_builddir/build/test-augprint +augeas_root=$abs_top_builddir/tests/test-augprint + +export AUGEAS_LENS_LIB=$abs_top_builddir/lenses +# ------------- /etc/hosts ------------ +mkdir -p $root/etc +AUGEAS_ROOT=$augeas_root augprint --pretty --verbose /etc/hosts > $root/etc.hosts.augtool +AUGEAS_ROOT=$root augtool --load-file=/etc/hosts -f $root/etc.hosts.augtool --noload --autosave 1>/dev/null +diff -bu $augeas_root/etc.hosts.pretty.augtool $root/etc.hosts.augtool || exit 1 +diff -bu -B $augeas_root/etc/hosts $root/etc/hosts || exit 1 + +AUGEAS_ROOT=$augeas_root augprint --regexp=2 /etc/hosts > $root/etc.hosts.augtool +AUGEAS_ROOT=$root augtool --load-file=/etc/hosts -f $root/etc.hosts.augtool --noload --autosave 1>/dev/null +diff -bu $augeas_root/etc.hosts.regexp.augtool $root/etc.hosts.augtool || exit 1 +diff -bu -B $augeas_root/etc/hosts $root/etc/hosts || exit 1 + +# ------------- /etc/squid/squid.conf and /etc/pam,d/systemd-auth ------------ +for test_file in /etc/squid/squid.conf /etc/pam.d/system-auth ; do + mkdir -p $(dirname $root/$test_file) + output=${test_file//\//.} + output=${output#.} + AUGEAS_ROOT=$augeas_root augprint $test_file > $root/${output}.augtool + AUGEAS_ROOT=$root augtool --load-file=$test_file -f $root/${output}.augtool --noload --autosave 1>/dev/null + diff -bu -B $augeas_root/$test_file $root/$test_file || exit 1 +done + diff --git a/tests/test-augprint/etc.hosts.pretty.augtool b/tests/test-augprint/etc.hosts.pretty.augtool new file mode 100644 index 000000000..ca9cdc5a0 --- /dev/null +++ b/tests/test-augprint/etc.hosts.pretty.augtool @@ -0,0 +1,158 @@ +# Using default lens: Hosts +# transform Hosts incl /etc/hosts +# /files/etc +# /files/etc/hosts +# /files/etc/hosts/#comment[1] '/etc/hosts for testing specific functionality of augprint' +set /files/etc/hosts/#comment[.='/etc/hosts for testing specific functionality of augprint'] '/etc/hosts for testing specific functionality of augprint' + +# /files/etc/hosts/1 +# /files/etc/hosts/1/ipaddr '127.0.0.1' +set /files/etc/hosts/seq::*[ipaddr='127.0.0.1' ]/ipaddr '127.0.0.1' +# /files/etc/hosts/1/canonical 'localhost' +set /files/etc/hosts/seq::*[ipaddr='127.0.0.1' ]/canonical 'localhost' +# /files/etc/hosts/1/alias[1] 'localhost4' +set /files/etc/hosts/seq::*[ipaddr='127.0.0.1' ]/alias[.='localhost4' ] 'localhost4' +# /files/etc/hosts/1/alias[2] 'localhost.localdomain' +set /files/etc/hosts/seq::*[ipaddr='127.0.0.1' ]/alias[.='localhost.localdomain'] 'localhost.localdomain' +# /files/etc/hosts/1/#comment 'ipv4' +set /files/etc/hosts/seq::*[ipaddr='127.0.0.1' ]/#comment 'ipv4' + +# /files/etc/hosts/2 +# /files/etc/hosts/2/ipaddr '::1' +set /files/etc/hosts/seq::*[ipaddr='::1' ]/ipaddr '::1' +# /files/etc/hosts/2/canonical 'localhost' +set /files/etc/hosts/seq::*[ipaddr='::1' ]/canonical 'localhost' +# /files/etc/hosts/2/alias 'localhost6' +set /files/etc/hosts/seq::*[ipaddr='::1' ]/alias 'localhost6' +# /files/etc/hosts/2/#comment 'ipv6' +set /files/etc/hosts/seq::*[ipaddr='::1' ]/#comment 'ipv6' + +# /files/etc/hosts/#comment[2] '"double-quoted"' +set /files/etc/hosts/#comment[.='"double-quoted"' ] '"double-quoted"' + +# /files/etc/hosts/#comment[3] "'single quoted'" +set /files/etc/hosts/#comment[.="'single quoted'" ] "'single quoted'" + +# /files/etc/hosts/#comment[4] 'Comment\ttab\t\ttabx2' +set /files/etc/hosts/#comment[.='Comment\ttab\t\ttabx2'] 'Comment\ttab\t\ttabx2' + +# /files/etc/hosts/#comment[5] 'Comment \\backslash \\\\double-backslash' +set /files/etc/hosts/#comment[.='Comment \\backslash \\\\double-backslash'] 'Comment \\backslash \\\\double-backslash' + +# /files/etc/hosts/#comment[6] 'Repeated comment' +set /files/etc/hosts/#comment[.='Repeated comment' ][1] 'Repeated comment' + +# /files/etc/hosts/#comment[7] 'First preference, unique first tail (/ipaddr)' +set /files/etc/hosts/#comment[.='First preference, unique first tail (/ipaddr)'] 'First preference, unique first tail (/ipaddr)' + +# /files/etc/hosts/3 +# /files/etc/hosts/3/ipaddr '192.0.2.1' +set /files/etc/hosts/seq::*[ipaddr='192.0.2.1' ]/ipaddr '192.0.2.1' +# /files/etc/hosts/3/canonical 'example.com' +set /files/etc/hosts/seq::*[ipaddr='192.0.2.1' ]/canonical 'example.com' +# /files/etc/hosts/3/alias[1] 'www.example.com' +set /files/etc/hosts/seq::*[ipaddr='192.0.2.1' ]/alias[.='www.example.com'] 'www.example.com' +# /files/etc/hosts/3/alias[2] 'ftp.example.com' +set /files/etc/hosts/seq::*[ipaddr='192.0.2.1' ]/alias[.='ftp.example.com'] 'ftp.example.com' + +# /files/etc/hosts/4 +# /files/etc/hosts/4/ipaddr '192.0.2.2' +set /files/etc/hosts/seq::*[ipaddr='192.0.2.2' ]/ipaddr '192.0.2.2' +# /files/etc/hosts/4/canonical 'example.com' +set /files/etc/hosts/seq::*[ipaddr='192.0.2.2' ]/canonical 'example.com' +# /files/etc/hosts/4/alias[1] 'www.example.com' +set /files/etc/hosts/seq::*[ipaddr='192.0.2.2' ]/alias[.='www.example.com'] 'www.example.com' +# /files/etc/hosts/4/alias[2] 'ftp.example.com' +set /files/etc/hosts/seq::*[ipaddr='192.0.2.2' ]/alias[.='ftp.example.com'] 'ftp.example.com' + +# /files/etc/hosts/#comment[8] 'Second preference, unique tail+value for /alias[1]' +set /files/etc/hosts/#comment[.='Second preference, unique tail+value for /alias[1]'] 'Second preference, unique tail+value for /alias[1]' + +# /files/etc/hosts/5 +# /files/etc/hosts/5/ipaddr '192.0.2.77' +set /files/etc/hosts/seq::*[alias='find_this1']/ipaddr '192.0.2.77' +# /files/etc/hosts/5/canonical 'second' +set /files/etc/hosts/seq::*[alias='find_this1' or count(alias)=0]/canonical 'second' +# /files/etc/hosts/5/alias[1] 'find_this1' +set /files/etc/hosts/seq::*[alias='find_this1' or count(alias)=0]/alias[.='find_this1'] 'find_this1' +# /files/etc/hosts/5/alias[2] 'alias77' +set /files/etc/hosts/seq::*[alias='find_this1']/alias[.='alias77' ] 'alias77' +# /files/etc/hosts/5/#comment 'add another tail (this comment)' +set /files/etc/hosts/seq::*[alias='find_this1']/#comment 'add another tail (this comment)' + +# /files/etc/hosts/6 +# /files/etc/hosts/6/ipaddr '192.0.2.77' +set /files/etc/hosts/seq::*[alias='find_this2']/ipaddr '192.0.2.77' +# /files/etc/hosts/6/canonical 'second' +set /files/etc/hosts/seq::*[alias='find_this2' or count(alias)=0]/canonical 'second' +# /files/etc/hosts/6/alias 'find_this2' +set /files/etc/hosts/seq::*[alias='find_this2' or count(alias)=0]/alias 'find_this2' + +# /files/etc/hosts/#comment[9] 'Third preference, unique (first-tail /ipaddr + tail+value /alias[1] )' +set /files/etc/hosts/#comment[.='Third preference, unique (first-tail /ipaddr + tail+value /alias[1] )'] 'Third preference, unique (first-tail /ipaddr + tail+value /alias[1] )' + +# /files/etc/hosts/7 +# /files/etc/hosts/7/ipaddr '192.0.2.33' +set /files/etc/hosts/seq::*[ipaddr='192.0.2.33' and alias='alias1']/ipaddr '192.0.2.33' +# /files/etc/hosts/7/canonical 'third' +set /files/etc/hosts/seq::*[ipaddr='192.0.2.33' and ( alias='alias1' or count(alias)=0 ) ]/canonical 'third' +# /files/etc/hosts/7/alias 'alias1' +set /files/etc/hosts/seq::*[ipaddr='192.0.2.33' and ( alias='alias1' or count(alias)=0 ) ]/alias 'alias1' + +# /files/etc/hosts/8 +# /files/etc/hosts/8/ipaddr '192.0.2.33' +set /files/etc/hosts/seq::*[ipaddr='192.0.2.33' and alias='alias2']/ipaddr '192.0.2.33' +# /files/etc/hosts/8/canonical 'third' +set /files/etc/hosts/seq::*[ipaddr='192.0.2.33' and ( alias='alias2' or count(alias)=0 ) ]/canonical 'third' +# /files/etc/hosts/8/alias 'alias2' +set /files/etc/hosts/seq::*[ipaddr='192.0.2.33' and ( alias='alias2' or count(alias)=0 ) ]/alias 'alias2' + +# /files/etc/hosts/9 +# /files/etc/hosts/9/ipaddr '192.0.2.34' +set /files/etc/hosts/seq::*[ipaddr='192.0.2.34' and alias='alias1']/ipaddr '192.0.2.34' +# /files/etc/hosts/9/canonical 'third' +set /files/etc/hosts/seq::*[ipaddr='192.0.2.34' and ( alias='alias1' or count(alias)=0 ) ]/canonical 'third' +# /files/etc/hosts/9/alias 'alias1' +set /files/etc/hosts/seq::*[ipaddr='192.0.2.34' and ( alias='alias1' or count(alias)=0 ) ]/alias 'alias1' + +# /files/etc/hosts/10 +# /files/etc/hosts/10/ipaddr '192.0.2.34' +set /files/etc/hosts/seq::*[ipaddr='192.0.2.34' and alias='alias2']/ipaddr '192.0.2.34' +# /files/etc/hosts/10/canonical 'third' +set /files/etc/hosts/seq::*[ipaddr='192.0.2.34' and ( alias='alias2' or count(alias)=0 ) ]/canonical 'third' +# /files/etc/hosts/10/alias 'alias2' +set /files/etc/hosts/seq::*[ipaddr='192.0.2.34' and ( alias='alias2' or count(alias)=0 ) ]/alias 'alias2' + +# /files/etc/hosts/#comment[10] 'Third preference for first one, Fourth preference (fallback) for second and third' +set /files/etc/hosts/#comment[.='Third preference for first one, Fourth preference (fallback) for second and third'] 'Third preference for first one, Fourth preference (fallback) for second and third' + +# /files/etc/hosts/11 +# /files/etc/hosts/11/ipaddr '192.0.2.99' +set /files/etc/hosts/seq::*[ipaddr='192.0.2.99' and canonical='third']/ipaddr '192.0.2.99' +# /files/etc/hosts/11/canonical 'third' +set /files/etc/hosts/seq::*[ipaddr='192.0.2.99' and ( canonical='third' or count(canonical)=0 ) ]/canonical 'third' +# /files/etc/hosts/11/alias 'abc' +set /files/etc/hosts/seq::*[ipaddr='192.0.2.99' and canonical='third']/alias 'abc' + +# /files/etc/hosts/12 +# /files/etc/hosts/12/ipaddr '192.0.2.99' +set /files/etc/hosts/seq::*[ipaddr='192.0.2.99'][2]/ipaddr '192.0.2.99' +# /files/etc/hosts/12/canonical 'fourth' +set /files/etc/hosts/seq::*[ipaddr='192.0.2.99'][2]/canonical 'fourth' +# /files/etc/hosts/12/alias[1] 'abc' +set /files/etc/hosts/seq::*[ipaddr='192.0.2.99'][2]/alias[.='abc'] 'abc' +# /files/etc/hosts/12/alias[2] 'def' +set /files/etc/hosts/seq::*[ipaddr='192.0.2.99'][2]/alias[.='def'] 'def' + +# /files/etc/hosts/13 +# /files/etc/hosts/13/ipaddr '192.0.2.99' +set /files/etc/hosts/seq::*[ipaddr='192.0.2.99'][3]/ipaddr '192.0.2.99' +# /files/etc/hosts/13/canonical 'fourth' +set /files/etc/hosts/seq::*[ipaddr='192.0.2.99'][3]/canonical 'fourth' +# /files/etc/hosts/13/alias[1] 'abc' +set /files/etc/hosts/seq::*[ipaddr='192.0.2.99'][3]/alias[.='abc'] 'abc' +# /files/etc/hosts/13/alias[2] 'def' +set /files/etc/hosts/seq::*[ipaddr='192.0.2.99'][3]/alias[.='def'] 'def' + +# /files/etc/hosts/#comment[11] 'Repeated comment' +set /files/etc/hosts/#comment[.='Repeated comment' ][2] 'Repeated comment' diff --git a/tests/test-augprint/etc.hosts.regexp.augtool b/tests/test-augprint/etc.hosts.regexp.augtool new file mode 100644 index 000000000..0eaa5199d --- /dev/null +++ b/tests/test-augprint/etc.hosts.regexp.augtool @@ -0,0 +1,59 @@ +set /files/etc/hosts/#comment[.=~regexp('/e.*')] '/etc/hosts for testing specific functionality of augprint' +set /files/etc/hosts/seq::*[ipaddr=~regexp('12.*')]/ipaddr '127.0.0.1' +set /files/etc/hosts/seq::*[ipaddr=~regexp('12.*')]/canonical 'localhost' +set /files/etc/hosts/seq::*[ipaddr=~regexp('12.*')]/alias[.=~regexp('localhost4')] 'localhost4' +set /files/etc/hosts/seq::*[ipaddr=~regexp('12.*')]/alias[.=~regexp('localhost\\..*')] 'localhost.localdomain' +set /files/etc/hosts/seq::*[ipaddr=~regexp('12.*')]/#comment 'ipv4' +set /files/etc/hosts/seq::*[ipaddr=~regexp('::1')]/ipaddr '::1' +set /files/etc/hosts/seq::*[ipaddr=~regexp('::1')]/canonical 'localhost' +set /files/etc/hosts/seq::*[ipaddr=~regexp('::1')]/alias 'localhost6' +set /files/etc/hosts/seq::*[ipaddr=~regexp('::1')]/#comment 'ipv6' +set /files/etc/hosts/#comment[.=~regexp('"d.*')] '"double-quoted"' +set /files/etc/hosts/#comment[.=~regexp("'s.*")] "'single quoted'" +set /files/etc/hosts/#comment[.=~regexp('Comment\tt.*')] 'Comment\ttab\t\ttabx2' +set /files/etc/hosts/#comment[.=~regexp('Comment .*')] 'Comment \\backslash \\\\double-backslash' +set /files/etc/hosts/#comment[.=~regexp('Re.*')][1] 'Repeated comment' +set /files/etc/hosts/#comment[.=~regexp('Fi.*')] 'First preference, unique first tail (/ipaddr)' +set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.1')]/ipaddr '192.0.2.1' +set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.1')]/canonical 'example.com' +set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.1')]/alias[.=~regexp('ww.*')] 'www.example.com' +set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.1')]/alias[.=~regexp('ft.*')] 'ftp.example.com' +set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.2')]/ipaddr '192.0.2.2' +set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.2')]/canonical 'example.com' +set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.2')]/alias[.=~regexp('ww.*')] 'www.example.com' +set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.2')]/alias[.=~regexp('ft.*')] 'ftp.example.com' +set /files/etc/hosts/#comment[.=~regexp('Se.*')] 'Second preference, unique tail+value for /alias[1]' +set /files/etc/hosts/seq::*[alias=~regexp('find_this1')]/ipaddr '192.0.2.77' +set /files/etc/hosts/seq::*[alias=~regexp('find_this1') or count(alias)=0]/canonical 'second' +set /files/etc/hosts/seq::*[alias=~regexp('find_this1') or count(alias)=0]/alias[.=~regexp('fi.*')] 'find_this1' +set /files/etc/hosts/seq::*[alias=~regexp('find_this1')]/alias[.=~regexp('al.*')] 'alias77' +set /files/etc/hosts/seq::*[alias=~regexp('find_this1')]/#comment 'add another tail (this comment)' +set /files/etc/hosts/seq::*[alias=~regexp('find_this2')]/ipaddr '192.0.2.77' +set /files/etc/hosts/seq::*[alias=~regexp('find_this2') or count(alias)=0]/canonical 'second' +set /files/etc/hosts/seq::*[alias=~regexp('find_this2') or count(alias)=0]/alias 'find_this2' +set /files/etc/hosts/#comment[.=~regexp('Third preference,.*')] 'Third preference, unique (first-tail /ipaddr + tail+value /alias[1] )' +set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.33') and alias=~regexp('alias1')]/ipaddr '192.0.2.33' +set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.33') and ( alias=~regexp('alias1') or count(alias)=0 ) ]/canonical 'third' +set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.33') and ( alias=~regexp('alias1') or count(alias)=0 ) ]/alias 'alias1' +set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.33') and alias=~regexp('alias2')]/ipaddr '192.0.2.33' +set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.33') and ( alias=~regexp('alias2') or count(alias)=0 ) ]/canonical 'third' +set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.33') and ( alias=~regexp('alias2') or count(alias)=0 ) ]/alias 'alias2' +set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.34') and alias=~regexp('alias1')]/ipaddr '192.0.2.34' +set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.34') and ( alias=~regexp('alias1') or count(alias)=0 ) ]/canonical 'third' +set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.34') and ( alias=~regexp('alias1') or count(alias)=0 ) ]/alias 'alias1' +set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.34') and alias=~regexp('alias2')]/ipaddr '192.0.2.34' +set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.34') and ( alias=~regexp('alias2') or count(alias)=0 ) ]/canonical 'third' +set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.34') and ( alias=~regexp('alias2') or count(alias)=0 ) ]/alias 'alias2' +set /files/etc/hosts/#comment[.=~regexp('Third preference .*')] 'Third preference for first one, Fourth preference (fallback) for second and third' +set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.99') and canonical=~regexp('th.*')]/ipaddr '192.0.2.99' +set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.99') and ( canonical=~regexp('th.*') or count(canonical)=0 ) ]/canonical 'third' +set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.99') and canonical=~regexp('th.*')]/alias 'abc' +set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.99')][2]/ipaddr '192.0.2.99' +set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.99')][2]/canonical 'fourth' +set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.99')][2]/alias[.=~regexp('abc')] 'abc' +set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.99')][2]/alias[.=~regexp('def')] 'def' +set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.99')][3]/ipaddr '192.0.2.99' +set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.99')][3]/canonical 'fourth' +set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.99')][3]/alias[.=~regexp('abc')] 'abc' +set /files/etc/hosts/seq::*[ipaddr=~regexp('192\\.0\\.2\\.99')][3]/alias[.=~regexp('def')] 'def' +set /files/etc/hosts/#comment[.=~regexp('Re.*')][2] 'Repeated comment' diff --git a/tests/test-augprint/etc/hosts b/tests/test-augprint/etc/hosts new file mode 100644 index 000000000..7fa9469c3 --- /dev/null +++ b/tests/test-augprint/etc/hosts @@ -0,0 +1,31 @@ +# /etc/hosts for testing specific functionality of augprint + +127.0.0.1 localhost localhost4 localhost.localdomain # ipv4 +::1 localhost localhost6 # ipv6 + +# "double-quoted" +# 'single quoted' +# Comment tab tabx2 +# Comment \backslash \\double-backslash +# Repeated comment + +# First preference, unique first tail (/ipaddr) +192.0.2.1 example.com www.example.com ftp.example.com +192.0.2.2 example.com www.example.com ftp.example.com + +# Second preference, unique tail+value for /alias[1] +192.0.2.77 second find_this1 alias77 # add another tail (this comment) +192.0.2.77 second find_this2 + +# Third preference, unique (first-tail /ipaddr + tail+value /alias[1] ) +192.0.2.33 third alias1 +192.0.2.33 third alias2 +192.0.2.34 third alias1 +192.0.2.34 third alias2 + +# Third preference for first one, Fourth preference (fallback) for second and third +192.0.2.99 third abc +192.0.2.99 fourth abc def +192.0.2.99 fourth abc def + +# Repeated comment diff --git a/tests/test-augprint/etc/pam.d/system-auth b/tests/test-augprint/etc/pam.d/system-auth new file mode 100644 index 000000000..6a42b108e --- /dev/null +++ b/tests/test-augprint/etc/pam.d/system-auth @@ -0,0 +1,29 @@ +# typical pam.d config file - a real mixed bag for augprint (copied from package pam-1.5.2 for fedora 35) + +auth required pam_env.so +auth required pam_faildelay.so delay=2000000 +auth sufficient pam_fprintd.so +auth [default=1 ignore=ignore success=ok] pam_succeed_if.so uid >= 1000 quiet +auth [default=1 ignore=ignore success=ok] pam_localuser.so +auth sufficient pam_unix.so nullok try_first_pass +auth requisite pam_succeed_if.so uid >= 1000 quiet_success +auth sufficient pam_sss.so forward_pass +auth required pam_deny.so + +account required pam_unix.so +account sufficient pam_localuser.so +account sufficient pam_succeed_if.so uid < 1000 quiet +account [default=bad success=ok user_unknown=ignore] pam_sss.so +account required pam_permit.so + +password requisite pam_pwquality.so try_first_pass local_users_only +password sufficient pam_unix.so sha512 shadow nullok try_first_pass use_authtok +password sufficient pam_sss.so use_authtok +password required pam_deny.so + +session optional pam_keyinit.so revoke +session required pam_limits.so +-session optional pam_systemd.so +session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid +session required pam_unix.so +session optional pam_sss.so diff --git a/tests/test-augprint/etc/squid/squid.conf b/tests/test-augprint/etc/squid/squid.conf new file mode 100644 index 000000000..c0015f656 --- /dev/null +++ b/tests/test-augprint/etc/squid/squid.conf @@ -0,0 +1,54 @@ +# Typical squid.conf settings + +cache_mgr root@example.com + +acl localnet src 0.0.0.1-0.255.255.255 # RFC 1122 "this" network (LAN) +acl localnet src 10.0.0.0/8 # RFC 1918 local private network (LAN) +acl localnet src 100.64.0.0/10 # RFC 6598 shared address space (CGN) +acl localnet src 169.254.0.0/16 # RFC 3927 link-local (directly plugged) machines +acl localnet src 172.16.0.0/12 # RFC 1918 local private network (LAN) +acl localnet src 192.168.0.0/16 # RFC 1918 local private network (LAN) +acl localnet src fc00::/7 # RFC 4193 local private network range +acl localnet src fe80::/10 # RFC 4291 link-local (directly plugged) machines + +acl SSL_ports port 443 +acl Safe_ports port 80 # http +acl Safe_ports port 21 # ftp +acl Safe_ports port 443 # https +acl Safe_ports port 70 # gopher +acl Safe_ports port 210 # wais +acl Safe_ports port 1025-65535 # unregistered ports +acl Safe_ports port 280 # http-mgmt +acl Safe_ports port 488 # gss-http +acl Safe_ports port 591 # filemaker +acl Safe_ports port 777 # multiling http +acl CONNECT method CONNECT + +# Deny requests to certain unsafe ports +http_access deny !Safe_ports + +# Deny CONNECT to other than secure SSL ports +http_access deny CONNECT !SSL_ports + +# Only allow cachemgr access from localhost +http_access allow localhost manager +http_access deny manager + +# Disallow proxied access to localhost ports (eg cups) +http_access deny to_localhost # built-in acl as of Squid 3.2 +http_access allow localhost # built-in acl as of Squid 3.2 +http_access allow localnet + +# And finally deny all other access to this proxy +http_access deny all + +# Squid normally listens to port 3128 +http_port 3128 + +cache_dir ufs /var/spool/squid 100 16 256 + +refresh_pattern ^ftp: 1440 20% 10080 +refresh_pattern ^gopher: 1440 0% 1440 +refresh_pattern -i (/cgi-bin/|\?) 0 0% 0 +refresh_pattern . 0 20% 4320 +