irc:
channels:
- "irc.freenode.org#augeas"
-script: ./configure && make && make check && ./src/try valgrind
+script: ./configure && make && make check VERBOSE=1 && ./src/try valgrind
+1.11.0 - 2018-08-24
+ - General changes/additions
+ * augmatch: add a --quiet option; make the exit status useful to tell
+ whether there was a match or not
+ * Drastically reduce the amount of memory needed to evaluate complex
+ path expressions against large files (Issue #569)
+ * Fix a segfault on OSX when 'augmatch' is run without any
+ arguments (Issue #556)
+ - API changes
+ * aug_source did not in fact return the source; and always returned
+ NULL for that. That has been fixed.
+ - Lens changes/additions
+ * Chrony: add new options supported in chrony 3.2 and 3.3 (Miroslav Lichvar)
+ * Dhclient: fix parsing of append/prepend and similar directives
+ (John Morrissey)
+ * Fstab: allow leading whitespace in mount entry lines
+ (Pino Toscano) (Issue #544)
+ * Grub: tolerate some invalid entries. Those invalid entries
+ get mapped to '#error' nodes
+ * Httpd: accept comments with whitespace right after a tag
+ opening a section (Issue #577)
+ * Json: allow escaped slashes in strings (Issue #557)
+ * Multipath: accept regular expressions for devnode, wwid, and property
+ in blacklist and blacklist_exceptions sections (Issue #564)
+ * Nginx: parse /etc/nginx/sites-enabled (plumbeo)
+ allow semicolons inside double quoted strings in
+ simple directives, and allow simple directives without
+ an argument (Issue #566)
+ * Redis: accept the 'bind' statement with multiple IP addresses
+ (yannh) (Issue #194)
+ * Rsyslog: support include() directive introduced in rsyslog 8.33
+ * Strongswan: new lens (Kaarle Ritvanen)
+ * Systemd: do not try to treat *.d or *.wants directories as
+ configuration files (Issue #548)
+
1.10.1 - 2018-01-29
- API changes
* Fix a symbol versioning mistake in libfa that unnecessarily broke ABI
-AC_INIT(augeas, 1.10.1)
+AC_INIT(augeas, 1.11.0)
AC_CONFIG_SRCDIR([src/augeas.c])
AC_CONFIG_AUX_DIR([build/ac-aux])
AM_CONFIG_HEADER([config.h])
fi
dnl Version info in libtool's notation
-AC_SUBST([LIBAUGEAS_VERSION_INFO], [24:0:24])
-AC_SUBST([LIBFA_VERSION_INFO], [6:1:5])
+AC_SUBST([LIBAUGEAS_VERSION_INFO], [24:1:24])
+AC_SUBST([LIBFA_VERSION_INFO], [6:2:5])
AC_GNU_SOURCE
# Alter Topic Type: [name]
# Creates a new topic type or alters one from the main file. Each type gets
# its own index and behavior settings. Its name can have letters, numbers,
-# spaces, and these characters: - / . '
+# spaces, and these charaters: - / . '
#
# Plural: [name]
# Sets the plural name of the topic type, if different.
Extension: aug
Block Comment: (* *)
+ Augeas Test Prototype Enders: \n let test module filter
Augeas Lens Prototype Enders: \n let test module filter
Augeas Variable Prototype Enders: \n let test module filter
- Augeas Test Prototype Enders: \n let test module filter
Perl Package: NaturalDocs::Languages::Augeas
File: Yaml (yaml.aug)
File: Cron_User (cron_user.aug)
File: Oz (oz.aug)
+ File: Getcap (getcap.aug)
+ File: Rancid (rancid.aug)
+ File: Rtadvd (rtadvd.aug)
+ File: Strongswan (strongswan.aug)
+ File: Termcap (termcap.aug)
} # Group: Specific Modules
Group: Generic Modules {
# Alter Topic Type: [name]
# Creates a new topic type or alters one from the main file. Each type gets
# its own index and behavior settings. Its name can have letters, numbers,
-# spaces, and these characters: - / . '
+# spaces, and these charaters: - / . '
#
# Plural: [name]
# Sets the plural name of the topic type, if different.
(* Variable: cmd_options
Server/Peer/Pool options with values
*)
- let cmd_options = "key"
+ let cmd_options = "asymmetry"
+ | "key"
| /maxdelay((dev)?ratio)?/
| /(min|max)poll/
| /(min|max)samples/
| "maxsources"
+ | "mindelay"
| "offset"
| "polltarget"
| "port"
Server/Peer/Pool options without values
*)
let cmd_flags = "auto_offline"|"iburst"|"noselect"|"offline"|"prefer"
- |"require"|"trust"|"xleave"
+ |"require"|"trust"|"xleave"|"burst"
(* Variable: ntp_source
Server/Peer/Pool key names
HW timestamping options with values
*)
let hwtimestamp_options = "minpoll"|"precision"|"rxcomp"|"txcomp"
+ |"rxfilter"
(* Variable: hwtimestamp_flags
HW timestamping options without values
*)
let refclock_options = "refid"|"lock"|"poll"|"dpoll"|"filter"|"rate"
|"minsamples"|"maxsamples"|"offset"|"delay"
- |"precision"|"maxdispersion"
+ |"precision"|"maxdispersion"|"stratum"|"width"
(* Variable: refclock_flags
refclock options without values
*)
- let refclock_flags = "noselect"|"prefer"|"require"|"trust"
+ let refclock_flags = "noselect"|"pps"|"prefer"|"require"|"tai"|"trust"
(* Variable: flags
Options without values
let hwtimestamp = [ Util.indent . key "hwtimestamp"
. space . [ label "interface" . store no_space ]
. ( space . ( [ key hwtimestamp_flags ]
- | [ key hwtimestamp_options . space . store number ] )
+ | [ key hwtimestamp_options . space
+ . store no_space ] )
)*
. eol ]
(* View: istepslew
let stmt_hash_re = "send"
| "option"
-let stmt_hash = [ key stmt_hash_re
- . sep_spc
- . ( [ key word . sep_spc . sto_to_spc_noeval ]
+let stmt_args = ( [ key word . sep_spc . sto_to_spc_noeval ]
| [ key word . sep_spc . (rfc_code|eval) ] )
. sep_scl
- . comment_or_eol ]
+ . comment_or_eol
+
+let stmt_hash = [ key stmt_hash_re
+ . sep_spc
+ . stmt_args ]
let stmt_opt_mod_re = "append"
| "prepend"
| "default"
| "supersede"
-let stmt_opt_mod = [ key stmt_opt_mod_re . sep_spc . stmt_hash ]
+let stmt_opt_mod = [ key stmt_opt_mod_re
+ . sep_spc
+ . stmt_args ]
(************************************************************************
* BLOCK STATEMENTS
Build.opt_list lns comma
let record = [ seq "mntent" .
+ Util.indent .
[ label "spec" . store spec ] . sep_tab .
[ label "file" . store file ] . sep_tab .
comma_sep_list "vfstype" .
(* View: eol *)
let eol = Util.eol
- (* View: del_to_eol *)
- let del_to_eol = del /[^ \t\n]*/ ""
-
(* View: spc *)
let spc = Util.del_ws_spc
eol ]
(* View: kw_pres *)
- let kw_pres (kw:string) = [ opt_ws . key kw . del_to_eol . eol ]
+ let kw_pres (kw:string) = [ opt_ws . key kw . eol ]
+
+ (* View: error
+ * Parse a line that looks almost like a valid setting, but isn't,
+ * into an '#error' node. Any line that starts with letters, but not
+ * anything matching kw, is considered an error line.
+ *
+ * Parameters:
+ * kw:regexp - the valid keywords that are _not_ considered an
+ * error
+ *)
+ let error (kw:regexp) =
+ let not_kw = /[a-zA-Z]+/ - kw in
+ [ label "#error" . Util.del_opt_ws "\t"
+ . store (not_kw . /([^a-zA-Z\n].*[^ \t\n])?/) . eol ]
+
(************************************************************************
* Group: BOOT ENTRIES
spc . [ label "from" . store Rx.no_spaces ] )? .
eol ]
- (* View: menu_setting *)
- let menu_setting = kw_menu_arg "default"
+ (* View: menu_entry *)
+ let menu_entry = kw_menu_arg "default"
| kw_menu_arg "fallback"
| kw_pres "hiddenmenu"
| kw_menu_arg "timeout"
| device
| setkey
+ (* View: menu_error
+ * Accept lines not matching menu_entry and stuff them into
+ * '#error' nodes
+ *)
+ let menu_error =
+ let kw = /default|fallback|hiddenmenu|timeout|splashimage|gfxmenu/
+ |/foreground|background|verbose|boot|password|title/
+ |/serial|setkey|terminal|color|device/ in
+ error kw
+
+ (* View: menu_setting
+ * a valid menu setting or a line that looks like one but is an #error
+ *)
+ let menu_setting = menu_entry | menu_error
+
(* View: title *)
let title = del /title[ \t=]+/ "title " . value_to_eol . eol
let configfile =
[ command "configfile" "\t" . spc . store Rx.no_spaces . eol ]
- (* View: boot_setting
+ (* View: boot_entry
<boot> entries *)
- let boot_setting =
+ let boot_entry =
let boot_arg_re = "root" | "initrd" | "rootnoverify" | "uuid"
| "findroot" | "bootfs" (* Solaris extensions *)
in kw_boot_arg boot_arg_re
| kw_pres "makeactive"
| password_arg
+ (* View: boot_error
+ * Accept lines not matching boot_entry and stuff them into
+ * '#error' nodes
+ *)
+ let boot_error =
+ let kw = /lock|uuid|password|root|initrd|rootnoverify|findroot|bootfs/
+ |/configfile|chainloader|title|boot|quiet|kernel|module/
+ |/makeactive|savedefault|map/ in
+ error kw
+
+ (* View: boot_setting
+ * a valid boot setting or a line that looks like one but is an #error
+ *)
+ let boot_setting = boot_entry | boot_error
+
(* View: boot *)
let boot =
let line = ((boot_setting|comment)* . boot_setting)? in
let section (body:lens) =
(* opt_eol includes empty lines *)
- let opt_eol = del /([ \t]*#?\r?\n)*/ "\n" in
+ let opt_eol = del /([ \t]*#?[ \t]*\r?\n)*/ "\n" in
let inner = (sep_spc . argv arg_sec)? . sep_osp .
dels ">" . opt_eol . ((body|comment) . (body|empty|comment)*)? .
indent . dels "</" in
let lbrack = Util.del_str "[" . comments
let rbrack = Util.del_str "]"
-let str_store = Quote.dquote . store /([^\\\\"]|\\\\("|n|r|t|\\\\))*/ . Quote.dquote (* " Emacs, relax *)
+(* This follows the definition of 'string' at https://www.json.org/
+ It's a little wider than what's allowed there as it would accept
+ nonsensical \u escapes *)
+let str_store = Quote.dquote . store /([^\\"]|\\\\["\/bfnrtu\\])*/ . Quote.dquote
let number = [ label "number" . store /-?[0-9]+(\.[0-9]+)?([eE][+-]?[0-9]+)?/
. comments ]
[ ikey k . ws . bare . eol ]
|[ ikey k . ws . quoted . eol ]
-let wwid = kv "wwid" (Rx.word|"*")
-
(* Settings that can be changed in various places *)
let common_setting =
qstr "path_selector"
(* The blacklist and blacklist_exceptions sections *)
let blacklist =
let setting =
- wwid
- |qstr "devnode"
+ qstr /devnode|wwid|property/
|device in
section /blacklist(_exceptions)?/ setting
(* A multipath subsection *)
let multipath =
let setting =
- wwid
+ kv "wwid" (Rx.word|"*")
|qstr "alias"
|common_setting in
section "multipath" setting
let simple =
let kw = word - block_re_all
in let mask = [ label "mask" . Util.del_str "/" . store Rx.integer ]
- in let sto = store /[^ \t\n;][^;]*/ . Sep.semicolon
- in [ Util.indent . key kw . mask? . Sep.space . sto . (Util.eol|Util.comment_eol) ]
+ in let sto = store /[^ \t\n;#]([^";#]|"[^"]*\")*/
+ in [ Util.indent .
+ key kw . mask? .
+ (Sep.space . sto)? . Sep.semicolon .
+ (Util.eol|Util.comment_eol) ]
(* View: server
A simple server entry *)
let filter = incl "/etc/nginx/nginx.conf"
. incl "/etc/nginx/conf.d/*.conf"
. incl "/etc/nginx/sites-available/*"
+ . incl "/etc/nginx/sites-enabled/*"
. incl "/usr/portage/www-servers/nginx/files/nginx.conf"
. incl "/usr/local/etc/nginx/nginx.conf"
. incl "/usr/local/etc/nginx/conf.d/*.conf"
. incl "/usr/local/etc/nginx/sites-available/*"
+ . incl "/usr/local/etc/nginx/sites-enabled/*"
let xfm = transform lns filter
*)
let standard_entry =
let reserved_k = "save" | "rename-command" | "slaveof"
- | "client-output-buffer-limit"
- in let entry_noempty = [ indent . key k . del_ws_spc
+ | "bind" | "client-output-buffer-limit"
+ in let entry_noempty = [ indent . key (k - reserved_k) . del_ws_spc
. Quote.do_quote_opt_nil (store v) . eol ]
in let entry_empty = [ indent . key (k - reserved_k) . del_ws_spc
. dquote . store "" . dquote . eol ]
*)
let slaveof_entry = [ indent . key slaveof . del_ws_spc . ip . del_ws_spc . port . eol ]
+(* View: bind_entry
+The "bind" entry can be passed one or several ip addresses. A bind
+statement "bind ip1 ip2 .. ipn" results in a tree
+{ "bind" { "ip" = ip1 } { "ip" = ip2 } ... { "ip" = ipn } }
+*)
+let bind_entry =
+ let ip = del_ws_spc . Quote.do_quote_opt_nil (store Rx.ip) in
+ indent . [ key "bind" . [ label "ip" . ip ]+ ] . eol
+
let renamecmd = /rename-command/
let from = [ label "from" . Quote.do_quote_opt_nil (store Rx.word) ]
let to = [ label "to" . Quote.do_quote_opt_nil (store Rx.word) ]
| save_entry
| renamecmd_entry
| slaveof_entry
+ | bind_entry
| client_output_buffer_limit_entry
(* View: lns
let config_sep = del /[ \t]+|[ \t]*#.*\n[ \t]*/ " "
let config_object =
- [ key /action|global|input|module|parser|timezone/ .
+ [ key /action|global|input|module|parser|timezone|include/ .
Sep.lbracket .
config_object_param . ( config_sep . config_object_param )* .
Sep.rbracket . Util.comment_or_eol ]
--- /dev/null
+(*
+Module: Strongswan
+ Lens for parsing strongSwan configuration files
+
+Authors:
+ Kaarle Ritvanen <kaarle.ritvanen@datakunkku.fi>
+
+About: Reference
+ strongswan.conf(5)
+
+About: License
+ This file is licensed under the LGPL v2+
+*)
+
+module Strongswan =
+
+autoload xfm
+
+let ws = del /[\n\t ]*(#[\t ]*\n[\n\t ]*)*/
+
+let rec conf =
+ let name (sep:string) =
+ key (/[^\/.\{\}#\n\t ]+/ - /include/) . Util.del_ws_spc .
+ Util.del_str sep
+ in let val = store /[^\n\t ].*/ . Util.del_str "\n" . ws ""
+ in let sval = Util.del_ws_spc . val
+in (
+ [ Util.del_str "#" . label "#comment" . Util.del_opt_ws " " . val ] |
+ [ key "include" . sval ] |
+ [ name "=" . sval ] |
+ [ name "{" . ws "\n" . conf . Util.del_str "}" . ws "\n" ]
+)*
+
+let lns = ws "" . conf
+
+let xfm = transform lns (
+ incl "/etc/strongswan.d/*.conf" .
+ incl "/etc/strongswan.d/**/*.conf" .
+ incl "/etc/swanctl/conf.d/*.conf" .
+ incl "/etc/swanctl/swanctl.conf"
+)
. incl "/etc/systemd/system/*/*"
. incl "/etc/systemd/logind.conf"
. incl "/etc/sysconfig/*.systemd"
+ . excl "/lib/systemd/system/*.d"
+ . excl "/etc/systemd/system/*.d"
+ . excl "/lib/systemd/system/*.wants"
+ . excl "/etc/systemd/system/*.wants"
. Util.stdexcl
let xfm = transform lns filter
server ntp6.example.com maxdelay 2 iburst presend 2 xleave offset 1e-4
server ntp7.example.com iburst presend 2 offline prefer trust require
server ntp8.example.com minsamples 8 maxsamples 16 version 3
+server ntp9.example.com burst mindelay 0.1 asymmetry 0.5
peer ntpc1.example.com
pool pool1.example.com iburst maxsources 3
allow
maxchange 1000 1 2
maxdistance 1.0
maxdrift 100
-hwtimestamp eth0 minpoll -2 txcomp 300e-9 rxcomp 645e-9 nocrossts
+hwtimestamp eth0 minpoll -2 txcomp 300e-9 rxcomp 645e-9 nocrossts rxfilter all
initstepslew 30 foo.bar.com
initstepslew 30 foo.bar.com baz.quz.com
ratelimit interval 4 burst 16 leak 2
cmdratelimit
-refclock SHM 0 refid SHM0 delay 0.1 offset 0.2 noselect
+refclock SHM 0 refid SHM0 delay 0.1 offset 0.2 noselect tai stratum 3
+refclock SOCK /var/run/chrony-GPS.sock pps width 0.1
refclock PPS /dev/pps0 dpoll 2 poll 3 lock SHM0 rate 5 minsamples 8
smoothtime 400 0.001 leaponly
tempcomp /sys/class/hwmon/hwmon0/temp2_input 30 26000 0.0 0.000183 0.0
{ "maxsamples" = "16" }
{ "version" = "3" }
}
+ { "server" = "ntp9.example.com"
+ { "burst" }
+ { "mindelay" = "0.1" }
+ { "asymmetry" = "0.5" }
+ }
{ "peer" = "ntpc1.example.com" }
{ "pool" = "pool1.example.com"
{ "iburst" }
{ "txcomp" = "300e-9" }
{ "rxcomp" = "645e-9" }
{ "nocrossts" }
+ { "rxfilter" = "all" }
}
{ "initstepslew"
{ "threshold" = "30" }
{ "delay" = "0.1" }
{ "offset" = "0.2" }
{ "noselect" }
+ { "tai" }
+ { "stratum" = "3" }
+ }
+ { "refclock"
+ { "driver" = "SOCK" }
+ { "parameter" = "/var/run/chrony-GPS.sock" }
+ { "pps" }
+ { "width" = "0.1" }
}
{ "refclock"
{ "driver" = "PPS" }
{ "second" = "01" } } }
-test Dhclient.lns get "append option domain-name-servers 127.0.0.1;\n" =
+test Dhclient.lns get "append domain-name-servers 127.0.0.1;\n" =
{ "append"
- { "option"
- { "domain-name-servers" = "127.0.0.1" }
- }
- }
+ { "domain-name-servers" = "127.0.0.1" } }
-test Dhclient.lns put "" after set "/prepend/option/domain-name-servers" "127.0.0.1" =
- "prepend option domain-name-servers 127.0.0.1;\n"
+test Dhclient.lns put "" after set "/prepend/domain-name-servers" "127.0.0.1" =
+ "prepend domain-name-servers 127.0.0.1;\n"
(* When = is used before the value, it's an evaluated string, see dhcp-eval *)
test Dhclient.lns get "send dhcp-client-identifier = hardware;\n" =
{ "dump" = "1" }
{ "passno" = "1" } }
+ let leading_ws = " /dev/vg00/lv00\t /\t ext3\t defaults 1 1\n"
+
let trailing_ws = "/dev/vg00/lv00\t /\t ext3\t defaults 1 1 \t\n"
let gen_no_passno(passno:string) =
test Fstab.lns get simple = simple_tree
+ test Fstab.lns get leading_ws = simple_tree
+
test Fstab.lns get trailing_ws = simple_tree
test Fstab.lns get no_passno = no_passno_tree
{ "password" = "secret"
{ "md5" }
} }
+
+ (* Test parsing of invalid entries via menu_error *)
+ test Grub.lns get "default=0\ncrud=no\n" =
+ { "default" = "0" }
+ { "#error" = "crud=no" }
+
+ (* We handle some pretty bizarre bad syntax *)
+ test Grub.lns get "default=0
+crud no
+valid:nope
+nonsense = yes
+bad arg1 arg2 arg3=v\n" =
+ { "default" = "0" }
+ { "#error" = "crud no" }
+ { "#error" = "valid:nope" }
+ { "#error" = "nonsense = yes" }
+ { "#error" = "bad arg1 arg2 arg3=v" }
+
+ (* Test parsing of invalid entries via boot_error *)
+ test Grub.lns get "title test
+ root (hd0,0)
+ crud foo\n" =
+ { "title" = "test"
+ { "root" = "(hd0,0)" }
+ { "#error" = "crud foo" } }
{ "arg" = "X-Forwarded-Proto" }
{ "arg" = "https" }
{ "arg" = "expr=(%{HTTP:CF-Visitor}='{\"scheme\":\"https\"}')" } }
+
+(* Issue #577: we make the newline starting a section optional, including
+ an empty comment at the end of the line. This used to miss empty comments
+ with whitespace *)
+test Httpd.lns get "<If cond>#\n</If>\n" = { "If" { "arg" = "cond" } }
+
+test Httpd.lns get "<If cond># \n</If>\n" = { "If" { "arg" = "cond" } }
+
+test Httpd.lns get "<If cond>\n# \n</If>\n" = { "If" { "arg" = "cond" } }
+
+test Httpd.lns get "<If cond># text\n</If>\n" =
+ { "If"
+ { "arg" = "cond" }
+ { "#comment" = "text" } }
+
+test Httpd.lns get "<If cond>\n\t# text\n</If>\n" =
+ { "If"
+ { "arg" = "cond" }
+ { "#comment" = "text" } }
{ "entry" = "filesystem"
{ "string" = "ext3\\\" \\\\ \t \r\n SEC_TYPE=\\\"ext2" } }
{ } }
+
+test Json.str get "\"\\\"\"" = { "string" = "\\\"" }
+
+test Json.str get "\"\\\"" = *
+
+test Json.str get "\"\"\"" = *
+
+test Json.str get "\"\\u1234\"" = { "string" = "\u1234" }
+
+(* Allow spurious backslashes; Issue #557 *)
+test Json.str get "\"\\/\"" = { "string" = "\\/" }
+
+test lns get "{ \"download-dir\": \"\\/var\\/tmp\\/\" }" =
+ { "dict"
+ { "entry" = "download-dir"
+ { "string" = "\/var\/tmp\/" } } }
{ "path_grouping_policy" = "multibus" }
{ "polling_interval" = "9" }
{ "delay_watch_checks" = "10" }
- { "delay_wait_checks" = "10" } } }
\ No newline at end of file
+ { "delay_wait_checks" = "10" } } }
+
+test Multipath.lns get "blacklist {
+ devnode \".*\"
+ wwid \"([a-z]+|ab?c).*\"
+}\n" =
+ { "blacklist"
+ { "devnode" = ".*" }
+ { "wwid" = "([a-z]+|ab?c).*" } }
+
+test Multipath.lns get "blacklist {\n property \"[a-z]+\"\n}\n" =
+ { "blacklist"
+ { "property" = "[a-z]+" } }
+
+(* Check that '""' inside a string works *)
+test Multipath.lns get "blacklist {
+ device {
+ vendor SomeCorp
+ product \"2.5\"\" SSD\"
+ }
+}\n" =
+ { "blacklist"
+ { "device"
+ { "vendor" = "SomeCorp" }
+ { "product" = "2.5\"\" SSD" } } }
{ "::1" = "2" }
{ "2001:0db8::" = "1"
{ "mask" = "32" } } } }
+
+test lns get "add_header X-XSS-Protection \"1; mode=block\" always;\n" =
+ { "add_header" = "X-XSS-Protection \"1; mode=block\" always" }
+
+test lns get "location /foo {
+ root /var/www/html;
+ internal; # only valid in location blocks
+}\n" =
+ { "location"
+ { "#uri" = "/foo" }
+ { "root" = "/var/www/html" }
+ { "internal"
+ { "#comment" = "only valid in location blocks" } } }
{ "#comment" = "If you want you can bind a single interface, if the bind option is not" }
{ "#comment" = "specified all the interfaces will listen for incoming connections." }
{ }
- { "bind" = "127.0.0.1" }
+ { "bind" { "ip" = "127.0.0.1" } }
{ }
{ "#comment" = "Note: you can disable saving at all commenting all the \"save\" lines." }
{ }
Empty value (GH issue #115) *)
test Redis.lns get "notify-keyspace-events \"\"\n" =
{ "notify-keyspace-events" = "" }
+
+(* Test: Redis.lns
+ Multiple bind IP addresses (GH issue #194) *)
+test Redis.lns get "bind 127.0.0.1 \"::1\" 192.168.1.1\n" =
+ { "bind"
+ { "ip" = "127.0.0.1" }
+ { "ip" = "::1" }
+ { "ip" = "192.168.1.1" } }
+
+test Redis.lns get "bind 127.0.0.1\n bind 192.168.1.1\n" =
+ { "bind"
+ { "ip" = "127.0.0.1" } }
+ { "bind"
+ { "ip" = "192.168.1.1" } }
{ "SysSock.Use" = "off" }
{ "#comment" = "Turn off message reception via local log socket;" } }
{ "#comment" = "local messages are retrieved through imjournal now." }
+
+(* Added in rsyslog 8.33 *)
+test Rsyslog.lns get "include(file=\"/etc/rsyslog.d/*.conf\" mode=\"optional\")\n" =
+ { "include"
+ { "file" = "/etc/rsyslog.d/*.conf" }
+ { "mode" = "optional" } }
--- /dev/null
+(*
+ Some of the configuration snippets have been copied from the strongSwan
+ source tree.
+*)
+
+module Test_Strongswan =
+
+(* conf/strongswan.conf *)
+let default = "
+# strongswan.conf - strongSwan configuration file
+#
+# Refer to the strongswan.conf(5) manpage for details
+#
+# Configuration changes should be made in the included files
+
+charon {
+ load_modular = yes
+ plugins {
+ include strongswan.d/charon/*.conf
+ }
+}
+
+include strongswan.d/*.conf
+"
+
+test Strongswan.lns get default =
+ { "#comment" = "strongswan.conf - strongSwan configuration file" }
+ { "#comment" = "Refer to the strongswan.conf(5) manpage for details" }
+ { "#comment" = "Configuration changes should be made in the included files" }
+ { "charon"
+ { "load_modular" = "yes" }
+ { "plugins" { "include" = "strongswan.d/charon/*.conf" } }
+ }
+ { "include" = "strongswan.d/*.conf" }
+
+(* conf/strongswan.conf.5.head.in *)
+let man_example = "
+ a = b
+ section-one {
+ somevalue = asdf
+ subsection {
+ othervalue = xxx
+ }
+ # yei, a comment
+ yetanother = zz
+ }
+ section-two {
+ x = 12
+ }
+"
+
+test Strongswan.lns get man_example =
+ { "a" = "b" }
+ { "section-one"
+ { "somevalue" = "asdf" }
+ { "subsection" { "othervalue" = "xxx" } }
+ { "#comment" = "yei, a comment" }
+ { "yetanother" = "zz" }
+ }
+ { "section-two" { "x" = "12" } }
+
+test Strongswan.lns get "foo { bar = baz\n } quux {}\t#quuux\n" =
+ { "foo" { "bar" = "baz" } }
+ { "quux" }
+ { "#comment" = "quuux" }
Print only the value and not the label or the path of nodes.
+=item B<-q>, B<--quiet>
+
+Do not print anything. Exit with zero status if a match was found
+
=back
=head1 ENVIRONMENT VARIABLES
# show all the clients to which we are exporting /home
augmatch -eom 'dir["/home"]/client' /etc/exports
+=head1 EXIT STATUS
+
+The exit status is 0 when there was at least one match, 1 if there was no
+match, and 2 if an error occurred.
+
=head1 FILES
Lenses and schema definitions in F</usr/share/augeas/lenses> and
ERR_THROW(r == 0, aug, AUG_ENOMATCH, "There is no node matching %s",
path);
- tree_source(aug, match);
+ *file_path = tree_source(aug, match);
ERR_BAIL(aug);
result = 0;
* contain the path to the toplevel node of that file underneath /files. If
* it does not, *FILE_PATH will be NULL.
*
+ * The caller is responsible for freeing *FILE_PATH
+ *
* Returns:
* 0 on success, or a negative value on failure. It is an error if PATH
* matches more than one node.
#include <getopt.h>
#include <stdbool.h>
#include <ctype.h>
+#include <libgen.h>
#include "memory.h"
#include "augeas.h"
#include <locale.h>
+#define EXIT_TROUBLE 2
+
#define cleanup(_x) __attribute__((__cleanup__(_x)))
const char *progname;
" -e, --exact print only exact matches instead of the entire tree\n"
" starting at a match\n"
" -o, --only-value print only the values of tree nodes, but no path\n"
+" -q, --quiet do not print anything. Exit with zero status if a\n"
+" match was found\n"
" -r, --root ROOT use ROOT as the root of the filesystem\n"
" -I, --include DIR search DIR for modules; can be given mutiple times\n"
" -S, --nostdinc do not search the builtin default directories\n"
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
- exit(EXIT_FAILURE);
+ exit(EXIT_TROUBLE);
}
}
if (msg != NULL) {
fprintf(stderr, "%s\n", msg);
}
- exit(EXIT_FAILURE);
+ exit(EXIT_TROUBLE);
}
}
const char *msg, *line, *col;
aug_defvar(aug, "info", info);
+ free(info);
die(aug_ns_count(aug, "info") == 0, "file %s does not exist\n", file);
aug_defvar(aug, "error", "$info/error");
} else {
fprintf(stderr, "error reading %s: %s\n", file, msg);
}
- exit(EXIT_FAILURE);
+ exit(EXIT_TROUBLE);
}
/* We keep track of where we are in the tree when we are printing it by
}
/* Print the tree for file PATH (which must already start with /files), but
- * only the nodes matching MATCH */
-static void print(struct augeas *aug, const char *path, const char *match) {
+ * only the nodes matching MATCH.
+ *
+ * Return EXIT_SUCCESS if there was at least one match, and EXIT_FAILURE
+ * if there was none.
+ */
+static int print(struct augeas *aug, const char *path, const char *match) {
static const char *const match_var = "match";
- cleanup(freep) struct node *nodes = NULL;
+ struct node *nodes = NULL;
nodes = calloc(max_nodes, sizeof(struct node));
oom_when(nodes == NULL);
- aug_set(aug, "/augeas/context", path);
-
for (int i=0; i < max_nodes; i++) {
nodes[i].var = format("var%d", i);
}
aug_defvar(aug, nodes[0].var, prefix);
print_tree(aug, 0, prefix + strlen(path) + 1, nodes);
}
+ for (int i=0; i < max_nodes; i++) {
+ free(nodes[i].var);
+ }
+ free(nodes);
+
+ return (count == 0) ? EXIT_FAILURE : EXIT_SUCCESS;
}
/* Look at the filename and try to guess based on the extension. The
size_t matches_len = 0;
const char *match = "*";
bool print_lens = false;
+ bool quiet = false;
+ int result = EXIT_SUCCESS;
struct option options[] = {
{ "help", 0, 0, 'h' },
{ "root", 1, 0, 'r' },
{ "print-lens", 0, 0, 'L' },
{ "exact", 0, 0, 'e' },
+ { "quiet", 0, 0, 'q' },
{ 0, 0, 0, 0}
};
unsigned int flags = AUG_NO_LOAD|AUG_NO_ERR_CLOSE;
progname = basename(argv[0]);
setlocale(LC_ALL, "");
- while ((opt = getopt_long(argc, argv, "ahI:l:m:oSr:eL", options, NULL)) != -1) {
+ while ((opt = getopt_long(argc, argv, "ahI:l:m:oSr:eLq", options, NULL)) != -1) {
switch(opt) {
case 'I':
argz_add(&loadpath, &loadpath_len, optarg);
case 'e':
print_exact = true;
break;
+ case 'q':
+ quiet = true;
+ break;
default:
fprintf(stderr, "Try '%s --help' for more information.\n",
progname);
- exit(EXIT_FAILURE);
+ exit(EXIT_TROUBLE);
break;
}
}
fprintf(stderr, "Expected an input file\n");
fprintf(stderr, "Try '%s --help' for more information.\n",
progname);
- exit(EXIT_FAILURE);
+ exit(EXIT_TROUBLE);
}
const char *file = argv[optind];
check_load_error(aug, file);
char *path = format("/files%s", file);
+ aug_set(aug, "/augeas/context", path);
+
if (matches_len > 0) {
argz_stringify(matches, matches_len, '|');
match = matches;
}
- print(aug, path, match);
+ if (quiet) {
+ int n = aug_match(aug, match, NULL);
+ check_error(aug);
+ result = (n == 0) ? EXIT_FAILURE : EXIT_SUCCESS;
+ } else {
+ result = print(aug, path, match);
+ }
+ free(path);
+
+ return result;
}
/*
if (s != NULL) {
fprintf(stderr, "%s\n", s);
}
+ aug_close(aug);
exit(EXIT_FAILURE);
}
if ((path = strdup("*")) == NULL)
return NULL;
} else {
- CALLOC(path, end - text + 2);
- if (path == NULL)
+ if (ALLOC_N(path, end - text + 2) < 0)
return NULL;
strncpy(path, text, end - text);
strcat(path, "*");
/* strip off context if the user didn't give it */
if (ctx != NULL) {
- char *c = realloc(child, strlen(child)-strlen(ctx)+1);
- if (c == NULL) {
- free(child);
- return NULL;
- }
int ctxidx = strlen(ctx);
if (child[ctxidx] == SEP)
ctxidx++;
- strcpy(c, &child[ctxidx]);
+ char *c = strdup(&child[ctxidx]);
+ free(child);
+ if (c == NULL)
+ return NULL;
child = c;
}
*/
/* V_REGEXP -> V_STRING -> V_LENS */
-static struct value *lns_del(struct info *info,
- struct value *rxp, struct value *dflt) {
+static struct value *lns_del(struct info *info, struct value **argv) {
+ struct value *rxp = argv[0];
+ struct value *dflt = argv[1];
+
assert(rxp->tag == V_REGEXP);
assert(dflt->tag == V_STRING);
return lns_make_prim(L_DEL, ref(info),
}
/* V_REGEXP -> V_LENS */
-static struct value *lns_store(struct info *info, struct value *rxp) {
+static struct value *lns_store(struct info *info, struct value **argv) {
+ struct value *rxp = argv[0];
+
assert(rxp->tag == V_REGEXP);
return lns_make_prim(L_STORE, ref(info), ref(rxp->regexp), NULL);
}
/* V_STRING -> V_LENS */
-static struct value *lns_value(struct info *info, struct value *str) {
+static struct value *lns_value(struct info *info, struct value **argv) {
+ struct value *str = argv[0];
+
assert(str->tag == V_STRING);
return lns_make_prim(L_VALUE, ref(info), NULL, ref(str->string));
}
/* V_REGEXP -> V_LENS */
-static struct value *lns_key(struct info *info, struct value *rxp) {
+static struct value *lns_key(struct info *info, struct value **argv) {
+ struct value *rxp = argv[0];
+
assert(rxp->tag == V_REGEXP);
return lns_make_prim(L_KEY, ref(info), ref(rxp->regexp), NULL);
}
/* V_STRING -> V_LENS */
-static struct value *lns_label(struct info *info, struct value *str) {
+static struct value *lns_label(struct info *info, struct value **argv) {
+ struct value *str = argv[0];
+
assert(str->tag == V_STRING);
return lns_make_prim(L_LABEL, ref(info), NULL, ref(str->string));
}
/* V_STRING -> V_LENS */
-static struct value *lns_seq(struct info *info, struct value *str) {
+static struct value *lns_seq(struct info *info, struct value **argv) {
+ struct value *str = argv[0];
+
assert(str->tag == V_STRING);
return lns_make_prim(L_SEQ, ref(info), NULL, ref(str->string));
}
/* V_STRING -> V_LENS */
-static struct value *lns_counter(struct info *info, struct value *str) {
+static struct value *lns_counter(struct info *info, struct value **argv) {
+ struct value *str = argv[0];
+
assert(str->tag == V_STRING);
return lns_make_prim(L_COUNTER, ref(info), NULL, ref(str->string));
}
/* V_LENS -> V_LENS -> V_LENS -> V_LENS */
-static struct value *lns_square(struct info *info, struct value *l1,
- struct value *l2, struct value *l3) {
+static struct value *lns_square(struct info *info, struct value **argv) {
+ struct value *l1 = argv[0];
+ struct value *l2 = argv[1];
+ struct value *l3 = argv[2];
+
assert(l1->tag == V_LENS);
assert(l2->tag == V_LENS);
assert(l3->tag == V_LENS);
}
/* V_LENS -> V_STRING -> V_TREE */
-static struct value *lens_get(struct info *info, struct value *l,
- struct value *str) {
+static struct value *lens_get(struct info *info, struct value **argv) {
+ struct value *l = argv[0];
+ struct value *str = argv[1];
+
assert(l->tag == V_LENS);
assert(str->tag == V_STRING);
struct lns_error *err;
/* V_LENS -> V_TREE -> V_STRING -> V_STRING */
-static struct value *lens_put(struct info *info, struct value *l,
- struct value *tree, struct value *str) {
+static struct value *lens_put(struct info *info, struct value **argv) {
+ struct value *l = argv[0];
+ struct value *tree = argv[1];
+ struct value *str = argv[2];
+
assert(l->tag == V_LENS);
assert(tree->tag == V_TREE);
assert(str->tag == V_STRING);
}
/* V_STRING -> V_STRING -> V_TREE -> V_TREE */
-static struct value *tree_set_glue(struct info *info, struct value *path,
- struct value *val, struct value *tree) {
+static struct value *tree_set_glue(struct info *info, struct value **argv) {
// FIXME: This only works if TREE is not referenced more than once;
// otherwise we'll have some pretty weird semantics, and would really
// need to copy TREE first
+ struct value *path = argv[0];
+ struct value *val = argv[1];
+ struct value *tree = argv[2];
+
assert(path->tag == V_STRING);
assert(val->tag == V_STRING);
assert(tree->tag == V_TREE);
}
/* V_STRING -> V_TREE -> V_TREE */
-static struct value *tree_clear_glue(struct info *info, struct value *path,
- struct value *tree) {
+static struct value *tree_clear_glue(struct info *info, struct value **argv) {
// FIXME: This only works if TREE is not referenced more than once;
// otherwise we'll have some pretty weird semantics, and would really
// need to copy TREE first
+ struct value *path = argv[0];
+ struct value *tree = argv[1];
+
assert(path->tag == V_STRING);
assert(tree->tag == V_TREE);
/* Insert after */
/* V_STRING -> V_STRING -> V_TREE -> V_TREE */
-static struct value *tree_insa_glue(struct info *info, struct value *label,
- struct value *path, struct value *tree) {
+static struct value *tree_insa_glue(struct info *info, struct value **argv) {
+ struct value *label = argv[0];
+ struct value *path = argv[1];
+ struct value *tree = argv[2];
+
return tree_insert_glue(info, label, path, tree, 0);
}
/* Insert before */
/* V_STRING -> V_STRING -> V_TREE -> V_TREE */
-static struct value *tree_insb_glue(struct info *info, struct value *label,
- struct value *path, struct value *tree) {
+static struct value *tree_insb_glue(struct info *info, struct value **argv) {
+ struct value *label = argv[0];
+ struct value *path = argv[1];
+ struct value *tree = argv[2];
+
return tree_insert_glue(info, label, path, tree, 1);
}
/* V_STRING -> V_TREE -> V_TREE */
-static struct value *tree_rm_glue(struct info *info,
- struct value *path,
- struct value *tree) {
+static struct value *tree_rm_glue(struct info *info, struct value **argv) {
// FIXME: This only works if TREE is not referenced more than once;
// otherwise we'll have some pretty weird semantics, and would really
// need to copy TREE first
+ struct value *path = argv[0];
+ struct value *tree = argv[1];
+
assert(path->tag == V_STRING);
assert(tree->tag == V_TREE);
}
/* V_STRING -> V_STRING */
-static struct value *gensym(struct info *info, struct value *prefix) {
+static struct value *gensym(struct info *info, struct value **argv) {
+ struct value *prefix = argv[0];
+
assert(prefix->tag == V_STRING);
static unsigned int count = 0;
struct value *v;
}
/* V_STRING -> V_FILTER */
-static struct value *xform_incl(struct info *info, struct value *s) {
+static struct value *xform_incl(struct info *info, struct value **argv) {
+ struct value *s = argv[0];
+
assert(s->tag == V_STRING);
struct value *v = make_value(V_FILTER, ref(info));
v->filter = make_filter(ref(s->string), 1);
}
/* V_STRING -> V_FILTER */
-static struct value *xform_excl(struct info *info, struct value *s) {
+static struct value *xform_excl(struct info *info, struct value **argv) {
+ struct value *s = argv[0];
+
assert(s->tag == V_STRING);
struct value *v = make_value(V_FILTER, ref(info));
v->filter = make_filter(ref(s->string), 0);
}
/* V_LENS -> V_FILTER -> V_TRANSFORM */
-static struct value *xform_transform(struct info *info, struct value *l,
- struct value *f) {
+static struct value *xform_transform(struct info *info, struct value **argv) {
+ struct value *l = argv[0];
+ struct value *f = argv[1];
+
assert(l->tag == V_LENS);
assert(f->tag == V_FILTER);
if (l->lens->value || l->lens->key) {
return v;
}
-static struct value *sys_getenv(struct info *info, struct value *n) {
- assert(n->tag == V_STRING);
+static struct value *sys_getenv(struct info *info, struct value **argv) {
+ assert(argv[0]->tag == V_STRING);
struct value *v = make_value(V_STRING, ref(info));
- v->string = dup_string(getenv(n->string->str));
+ v->string = dup_string(getenv(argv[0]->string->str));
return v;
}
-static struct value *sys_read_file(struct info *info, struct value *n) {
+static struct value *sys_read_file(struct info *info, struct value **argv) {
+ struct value *n = argv[0];
+
assert(n->tag == V_STRING);
char *str = NULL;
/* V_LENS -> V_LENS */
static struct value *lns_check_rec_glue(struct info *info,
- struct value *l, struct value *r) {
+ struct value **argv) {
+ struct value *l = argv[0];
+ struct value *r = argv[1];
+
assert(l->tag == V_LENS);
assert(r->tag == V_LENS);
int check = typecheck_p(info);
*/
/* V_STRING -> V_UNIT */
-static struct value *pr_string(struct info *info, struct value *s) {
- printf("%s", s->string->str);
+static struct value *pr_string(struct info *info, struct value **argv) {
+ printf("%s", argv[0]->string->str);
return make_unit(ref(info));
}
/* V_REGEXP -> V_UNIT */
-static struct value *pr_regexp(struct info *info, struct value *r) {
- print_regexp(stdout, r->regexp);
+static struct value *pr_regexp(struct info *info, struct value **argv) {
+ print_regexp(stdout, argv[0]->regexp);
return make_unit(ref(info));
}
/* V_STRING -> V_UNIT */
-static struct value *pr_endline(struct info *info, struct value *s) {
- printf("%s\n", s->string->str);
+static struct value *pr_endline(struct info *info, struct value **argv) {
+ printf("%s\n", argv[0]->string->str);
return make_unit(ref(info));
}
/* V_TREE -> V_TREE */
static struct value *pr_tree(ATTRIBUTE_UNUSED struct info *info,
- struct value *t) {
- print_tree_braces(stdout, 0, t->origin);
- return ref(t);
+ struct value **argv) {
+ print_tree_braces(stdout, 0, argv[0]->origin);
+ return ref(argv[0]);
}
/*
}
/* V_LENS -> V_REGEXP */
-static struct value *lns_ctype(struct info *info, struct value *l) {
- return lns_value_of_type(info, l->lens->ctype);
+static struct value *lns_ctype(struct info *info, struct value **argv) {
+ return lns_value_of_type(info, argv[0]->lens->ctype);
}
/* V_LENS -> V_REGEXP */
-static struct value *lns_atype(struct info *info, struct value *l) {
- return lns_value_of_type(info, l->lens->atype);
+static struct value *lns_atype(struct info *info, struct value **argv) {
+ return lns_value_of_type(info, argv[0]->lens->atype);
}
/* V_LENS -> V_REGEXP */
-static struct value *lns_vtype(struct info *info, struct value *l) {
- return lns_value_of_type(info, l->lens->vtype);
+static struct value *lns_vtype(struct info *info, struct value **argv) {
+ return lns_value_of_type(info, argv[0]->lens->vtype);
}
/* V_LENS -> V_REGEXP */
-static struct value *lns_ktype(struct info *info, struct value *l) {
- return lns_value_of_type(info, l->lens->ktype);
+static struct value *lns_ktype(struct info *info, struct value **argv) {
+ return lns_value_of_type(info, argv[0]->lens->ktype);
}
/* V_LENS -> V_STRING */
-static struct value *lns_fmt_atype(struct info *info, struct value *l) {
+static struct value *lns_fmt_atype(struct info *info, struct value **argv) {
+ struct value *l = argv[0];
+
struct value *result = NULL;
char *s = NULL;
int r;
}
/* V_REGEXP -> V_STRING -> V_STRING */
-static struct value *rx_match(struct info *info,
- struct value *rx, struct value *s) {
+static struct value *rx_match(struct info *info, struct value **argv) {
+ struct value *rx = argv[0];
+ struct value *s = argv[1];
+
struct value *result = NULL;
const char *str = s->string->str;
struct re_registers regs;
return;
fp = fopen(fname, "w");
+ if (fp == NULL) {
+ free(fname);
+ return;
+ }
+
fa_dot(fp, fa);
fclose(fp);
free(fname);
static struct skel *make_skel(struct lens *lens) {
struct skel *skel;
enum lens_tag tag = lens->tag;
- CALLOC(skel, 1);
+ if (ALLOC(skel) < 0)
+ return NULL;
skel->tag = tag;
return skel;
}
seq = seq->next);
if (seq == NULL) {
- CALLOC(seq, 1);
+ if (ALLOC(seq) < 0)
+ return NULL;
seq->name = name;
seq->value = 1;
list_append(state->seqs, seq);
Allocate as needed. Return 0 on success, -1 on failure */
int pathjoin(char **path, int nseg, ...);
-/* Call calloc to allocate an array of N instances of *VAR */
-#define CALLOC(Var,N) do { (Var) = calloc ((N), sizeof (*(Var))); } while (0)
-
#define MEMZERO(ptr, n) memset((ptr), 0, (n) * sizeof(*(ptr)));
-#define MEMCPY(dest, src, n) memcpy((dest), (src), (n) * sizeof(*(src)))
-
#define MEMMOVE(dest, src, n) memmove((dest), (src), (n) * sizeof(*(src)))
/**
if (debugging("cf.jmt")) {
if (sA == NULL) {
+ char *s = format_lens(lens);
printf("add_lens: ");
print_regexp(stdout, lens->ctype);
- printf(" %s\n", format_lens(lens));
+ printf(" %s\n", s);
+ free(s);
} else {
+ char *s = format_lens(lens);
printf("add_lens: ");
flens(stdout, l);
- printf(" %u %s\n", sA->num, format_lens(lens));
+ printf(" %u %s\n", sA->num, s);
if (nullable) {
- printf("add_lens: // %s\n", format_lens(lens));
+ printf("add_lens: // %s\n", s);
}
+ free(s);
}
}
fa_isect = fa_intersect(fa_slash, fa_key);
if (! fa_is_basic(fa_isect, FA_EMPTY)) {
exn = make_exn_value(info,
- "The key regexp /%s/ matches a '/'", regexp->pattern->str);
+ "The key regexp /%s/ matches a '/' which is used to separate nodes.", regexp->pattern->str);
goto error;
}
fa_free(fa_isect);
lens->vtype = restrict_regexp(lens->regexp);
} else if (tag == L_VALUE) {
lens->vtype = make_regexp_literal(info, lens->string->str);
+ if (lens->vtype == NULL)
+ goto error;
}
return make_lens_value(lens);
struct { /* E_APP */
const struct func *func;
struct expr **args;
+ /* If fold is true, replace this function invocation
+ * with its value after the first time we evaluate this
+ * expression */
+ bool fold;
};
};
};
const char *name;
unsigned int arity;
enum type type;
+ bool pure; /* Result only depends on args */
const enum type *arg_types;
func_impl_t impl;
};
static const struct func builtin_funcs[] = {
{ .name = "last", .arity = 0, .type = T_NUMBER, .arg_types = NULL,
- .impl = func_last },
+ .impl = func_last, .pure = false },
{ .name = "position", .arity = 0, .type = T_NUMBER, .arg_types = NULL,
- .impl = func_position },
+ .impl = func_position, .pure = false },
{ .name = "label", .arity = 0, .type = T_STRING, .arg_types = NULL,
- .impl = func_label },
+ .impl = func_label, .pure = false },
{ .name = "count", .arity = 1, .type = T_NUMBER,
.arg_types = arg_types_nodeset,
- .impl = func_count },
+ .impl = func_count, .pure = false },
{ .name = "regexp", .arity = 1, .type = T_REGEXP,
.arg_types = arg_types_string,
- .impl = func_regexp },
+ .impl = func_regexp, .pure = true },
{ .name = "regexp", .arity = 1, .type = T_REGEXP,
.arg_types = arg_types_nodeset,
- .impl = func_regexp },
+ .impl = func_regexp, .pure = true },
{ .name = "regexp", .arity = 2, .type = T_REGEXP,
.arg_types = arg_types_string_string,
- .impl = func_regexp_flag },
+ .impl = func_regexp_flag, .pure = true },
{ .name = "regexp", .arity = 2, .type = T_REGEXP,
.arg_types = arg_types_nodeset_string,
- .impl = func_regexp_flag },
+ .impl = func_regexp_flag, .pure = true },
{ .name = "glob", .arity = 1, .type = T_REGEXP,
.arg_types = arg_types_string,
- .impl = func_glob },
+ .impl = func_glob, .pure = true },
{ .name = "glob", .arity = 1, .type = T_REGEXP,
.arg_types = arg_types_nodeset,
- .impl = func_glob },
+ .impl = func_glob, .pure = true },
{ .name = "int", .arity = 1, .type = T_NUMBER,
- .arg_types = arg_types_string, .impl = func_int },
+ .arg_types = arg_types_string, .impl = func_int, .pure = false },
{ .name = "int", .arity = 1, .type = T_NUMBER,
- .arg_types = arg_types_nodeset, .impl = func_int },
+ .arg_types = arg_types_nodeset, .impl = func_int, .pure = false },
{ .name = "int", .arity = 1, .type = T_NUMBER,
- .arg_types = arg_types_bool, .impl = func_int },
+ .arg_types = arg_types_bool, .impl = func_int, .pure = false },
{ .name = "not", .arity = 1, .type = T_BOOLEAN,
- .arg_types = arg_types_bool, .impl = func_not }
+ .arg_types = arg_types_bool, .impl = func_not, .pure = true }
};
#define RET_ON_ERROR \
break;
case E_APP:
eval_app(expr, state);
+ if (expr->fold) {
+ /* Do constant folding: replace the function application with
+ * a reference to the value that resulted from evaluating it */
+ for (int i=0; i < expr->func->arity; i++)
+ free_expr(expr->args[i]);
+ free(expr->args);
+ value_ind_t vind = state->values_used - 1;
+ expr->tag = E_VALUE;
+ expr->value_ind = state->values[vind];
+ }
break;
default:
assert(0);
if (f < ARRAY_CARDINALITY(builtin_funcs)) {
expr->func = builtin_funcs + f;
expr->type = expr->func->type;
+ expr->fold = expr->func->pure;
+ if (expr->fold) {
+ /* We only do constant folding for invocations of pure functions
+ * whose arguments are literal values. That misses opportunities
+ * for constant folding, e.g., "regexp('foo' + 'bar')" but is
+ * a bit simpler than doing full tracking of constants
+ */
+ for (int i=0; i < expr->func->arity; i++) {
+ if (expr->args[i]->tag != E_VALUE)
+ expr->fold = false;
+ }
+ }
} else {
STATE_ERROR(state, PATHX_ETYPE);
}
state->pos += 1;
locpath = parse_relative_location_path(state);
if (HAS_ERROR(state))
- return;
+ goto error;
struct step *step = make_step(DESCENDANT_OR_SELF, state);
if (HAS_ERROR(state))
goto error;
if (state->error != NULL)
return;
- CALLOC(state->error, 1);
+ if (ALLOC(state->error) < 0)
+ return;
state->error->lens = ref(lens);
state->error->pos = -1;
if (strlen(state->path) == 0) {
struct tree *tree, struct tree *follow,
char *enc, size_t start, size_t end) {
struct split *sp;
- CALLOC(sp, 1);
+ if (ALLOC(sp) < 0)
+ return NULL;
sp->tree = tree;
sp->follow = follow;
sp->enc = enc;
for (int i=0; i < lens->nchildren; i++) {
tail = split_append(&split, tail, NULL, NULL,
outer->enc, 0, 0);
+ if (tail == NULL)
+ goto error;
}
return split;
}
/* Escape special characters in text since it should be taken
literally */
- CALLOC(pattern, 2*strlen(text)+1);
+ if (ALLOC_N(pattern, 2*strlen(text)+1) < 0)
+ return NULL;
p = pattern;
for (const char *t = text; *t != '\0'; t++) {
if ((*t == '\\') && t[1]) {
*c = NULL;
- if (r->re == NULL)
- CALLOC(r->re, 1);
+ if (r->re == NULL) {
+ if (ALLOC(r->re) < 0)
+ return -1;
+ }
re_syntax_options = syntax;
if (r->nocase)
return NULL;
v = make_value(V_EXN, ref(info));
- CALLOC(v->exn, 1);
+ if (ALLOC(v->exn) < 0)
+ return info->error->exn;
v->exn->info = info;
v->exn->message = message;
if (vt->tag == T_STRING && t->tag == T_REGEXP) {
struct value *rxp = make_value(V_REGEXP, ref(v->info));
rxp->regexp = make_regexp_literal(v->info, v->string->str);
+ if (rxp->regexp == NULL) {
+ report_error(v->info->error, AUG_ENOMEM, NULL);
+ };
unref(v, value);
unref(vt, type);
return rxp;
}
len += (ntypes - 1) * 4 + 1;
char *allowed_names;
- CALLOC(allowed_names, len);
+ if (ALLOC_N(allowed_names, len) < 0)
+ return NULL;
for (int i=0; i < ntypes; i++) {
if (i > 0)
strcat(allowed_names, (i == ntypes - 1) ? ", or " : ", ");
static struct value *native_call(struct info *info,
struct native *func, struct ctx *ctx) {
- struct value *argv[func->argc];
+ struct value *argv[func->argc + 1];
struct binding *b = ctx->local;
- struct value *result;
for (int i = func->argc - 1; i >= 0; i--) {
argv[i] = b->value;
b = b->next;
}
+ argv[func->argc] = NULL;
- switch(func->argc) {
- case 0:
- result = ((impl0) *func->impl)(info);
- break;
- case 1:
- result = ((impl1) *func->impl)(info, argv[0]);
- break;
- case 2:
- result = ((impl2) *func->impl)(info, argv[0], argv[1]);
- break;
- case 3:
- result = ((impl3) *func->impl)(info, argv[0], argv[1], argv[2]);
- break;
- case 4:
- result = ((impl4) *func->impl)(info, argv[0], argv[1], argv[2], argv[3]);
- break;
- case 5:
- result = ((impl5) *func->impl)(info, argv[0], argv[1], argv[2], argv[3],
- argv[4]);
- break;
- default:
- assert(0);
- abort();
- break;
- }
-
- return result;
+ return func->impl(info, argv);
}
static void type_error1(struct info *info, const char *msg, struct type *type) {
const char *s2 = v2->string->str;
v = make_value(V_STRING, ref(info));
make_ref(v->string);
- CALLOC(v->string->str, strlen(s1) + strlen(s2) + 1);
+ if (ALLOC_N(v->string->str, strlen(s1) + strlen(s2) + 1) < 0)
+ goto error;
char *s = v->string->str;
strcpy(s, s1);
strcat(s, s2);
unref(v1, value);
unref(v2, value);
return v;
+ error:
+ return exp->info->error->exn;
}
static struct value *apply(struct term *app, struct ctx *ctx) {
int define_native_intl(const char *file, int line,
struct error *error,
struct module *module, const char *name,
- int argc, void *impl, ...) {
+ int argc, func_impl impl, ...) {
assert(argc > 0); /* We have no unit type */
assert(argc <= 5);
va_list ap;
for (int i=0; i < globbuf.gl_pathc; i++) {
char *name, *p, *q;
+ int res;
p = strrchr(globbuf.gl_pathv[i], SEP);
if (p == NULL)
p = globbuf.gl_pathv[i];
q = strchr(p, '.');
name = strndup(p, q - p);
name[0] = toupper(name[0]);
- if (load_module(aug, name) == -1)
- goto error;
+ res = load_module(aug, name);
free(name);
+ if (res == -1)
+ goto error;
}
globfree(&globbuf);
return 0;
struct type *type;
};
+/* The protoype for the implementation of a native/builtin function in the
+ * interpreter.
+ *
+ * The arguments are passed as a NULL-terminated array of values.
+ */
+typedef struct value *(*func_impl)(struct info *, struct value *argv[]);
+
struct native {
unsigned int argc;
struct type *type;
- struct value *(*impl)(void);
+ func_impl impl;
};
/* An exception in the interpreter. Some exceptions are reported directly
int define_native_intl(const char *fname, int line,
struct error *error,
struct module *module, const char *name,
- int argc, void *impl, ...);
+ int argc, func_impl impl, ...);
struct module *builtin_init(struct error *);
if (fchmod(fileno(fp), 0666 & ~curumsk) < 0) {
err_status = "create_chmod";
- return -1;
+ goto done;
}
}
VALGRIND=libtool --mode=execute valgrind --quiet --leak-check=full
valgrind:
- make check \
+ $(MAKE) $(MAKEFLAGS) check \
VALGRIND="$(VALGRIND)" \
AUGPARSE=$(abs_top_builddir)/src/augparse \
AUGTOOL=$(abs_top_builddir)/src/augtool
lens-sshd.sh \
lens-sssd.sh \
lens-star.sh \
+ lens-strongswan.sh \
lens-stunnel.sh \
lens-subversion.sh \
lens-sysconfig.sh \
test-save-empty.sh test-bug-1.sh test-idempotent.sh test-preserve.sh \
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-span-rec-lens.sh test-nonwritable.sh test-augmatch.sh
EXTRA_DIST = \
test-augtool root lens-test-1 \
aug_close(aug);
}
+/* Test aug_source */
+static void testAugSource(CuTest *tc) {
+ struct augeas *aug;
+ int r;
+ char *s;
+
+ aug = aug_init(root, loadpath, AUG_NO_STDINC|AUG_NO_LOAD);
+ CuAssertPtrNotNull(tc, aug);
+ CuAssertIntEquals(tc, AUG_NOERROR, aug_error(aug));
+
+ r = aug_load_file(aug, "/etc/hosts");
+ CuAssertIntEquals(tc, 0, r);
+
+ r = aug_source(aug, "/files/etc/hosts/1", &s);
+ CuAssertIntEquals(tc, 0, r);
+ CuAssertStrEquals(tc, "/files/etc/hosts", s);
+ free(s);
+
+ r = aug_source(aug, "/files/etc/fstab", &s);
+ CuAssertIntEquals(tc, -1, r);
+ CuAssertIntEquals(tc, AUG_ENOMATCH, aug_error(aug));
+ CuAssertPtrEquals(tc, NULL, s);
+
+ r = aug_source(aug, "/files[", &s);
+ CuAssertIntEquals(tc, -1, r);
+ CuAssertIntEquals(tc, AUG_EPATHX, aug_error(aug));
+ CuAssertPtrEquals(tc, NULL, s);
+
+ r = aug_source(aug, "/files/etc/hosts/*", &s);
+ CuAssertIntEquals(tc, -1, r);
+ CuAssertIntEquals(tc, AUG_EMMATCH, aug_error(aug));
+ CuAssertPtrEquals(tc, NULL, s);
+
+}
+
int main(void) {
char *output = NULL;
CuSuite* suite = CuSuiteNew();
SUITE_ADD_TEST(suite, testLoadBadPath);
SUITE_ADD_TEST(suite, testLoadBadLens);
SUITE_ADD_TEST(suite, testAugNs);
+ SUITE_ADD_TEST(suite, testAugSource);
abs_top_srcdir = getenv("abs_top_srcdir");
if (abs_top_srcdir == NULL)
--- /dev/null
+#!/bin/sh
+
+# Tests for augmatch
+
+TOPDIR=$(cd $(dirname $0)/.. && pwd)
+[ -n "$abs_top_srcdir" ] || abs_top_srcdir=$TOPDIR
+
+export AUGEAS_LENS_LIB=$abs_top_srcdir/lenses
+export AUGEAS_ROOT=$abs_top_srcdir/tests/root
+
+fail() {
+ echo "failed: $*"
+ exit 1
+}
+
+assert_eq() {
+ if [ "$1" != "$2" ]; then
+ shift 2
+ fail $*
+ fi
+}
+
+# print the tree for /etc/exports
+act=$(augmatch /etc/exports)
+assert_eq 23 $(echo "$act" | wc -l) "t1: expected 23 lines of output"
+
+# show only the entry for a specific mount
+act=$(augmatch -m 'dir["/home"]' /etc/exports)
+assert_eq 9 $(echo "$act" | wc -l) "t2: expected 9 lines of output"
+
+# show all the clients to which we are exporting /home
+act=$(augmatch -eom 'dir["/home"]/client' /etc/exports)
+exp=$(printf "207.46.0.0/16\n192.168.50.2/32\n")
+assert_eq "$exp" "$act" "t3: expected '$exp'"
+
+# report errors with exit code 2
+augmatch -m '**' /etc/exports >/dev/null 2>&1
+ret=$?
+assert_eq 2 $ret "t4: expected exit code 2 but got $ret"
+
+augmatch /etc >/dev/null 2>&1
+ret=$?
+assert_eq 2 $ret "t5: expected exit code 2 but got $ret"
+
+# test --quiet
+act=$(augmatch -q -m "dir['/local']" /etc/exports)
+ret=$?
+assert_eq '' "$act" "t6: expected no output"
+assert_eq 0 $ret "t6: expected exit code 0 but got $ret"
+
+act=$(augmatch -q -m "dir['/not_there']" /etc/exports)
+ret=$?
+assert_eq '' "$act" "t7: expected no output"
+assert_eq 1 $ret "t7: expected exit code 1 but got $ret"