Vsftpd: add isolate and isolate_network options

(* Parse vsftpd.conf *)
module Vsftpd =
  autoload xfm

(* The code in parseconf.c does not seem to allow for trailing whitespace *)
(* in the config file                                                     *)
let eol = Util.del_str "\n"
let empty = Util.empty
let comment = Util.comment

let bool_option_re = /anonymous_enable|isolate|isolate_network|local_enable|pasv_enable|port_enable|chroot_local_user|write_enable|anon_upload_enable|anon_mkdir_write_enable|anon_other_write_enable|chown_uploads|connect_from_port_20|xferlog_enable|dirmessage_enable|anon_world_readable_only|async_abor_enable|ascii_upload_enable|ascii_download_enable|one_process_model|xferlog_std_format|pasv_promiscuous|deny_email_enable|chroot_list_enable|setproctitle_enable|text_userdb_names|ls_recurse_enable|log_ftp_protocol|guest_enable|userlist_enable|userlist_deny|use_localtime|check_shell|hide_ids|listen|port_promiscuous|passwd_chroot_enable|no_anon_password|tcp_wrappers|use_sendfile|force_dot_files|listen_ipv6|dual_log_enable|syslog_enable|background|virtual_use_local_privs|session_support|download_enable|dirlist_enable|chmod_enable|secure_email_list_enable|run_as_launching_user|no_log_lock|ssl_enable|allow_anon_ssl|force_local_logins_ssl|force_local_data_ssl|ssl_sslv2|ssl_sslv3|ssl_tlsv1|tilde_user_enable|force_anon_logins_ssl|force_anon_data_ssl|mdtm_write|lock_upload_files|pasv_addr_resolve|debug_ssl|require_cert|validate_cert|require_ssl_reuse/

let uint_option_re = /accept_timeout|connect_timeout|local_umask|anon_umask|ftp_data_port|idle_session_timeout|data_connection_timeout|pasv_min_port|pasv_max_port|anon_max_rate|local_max_rate|listen_port|max_clients|file_open_mode|max_per_ip|trans_chunk_size|delay_failed_login|delay_successful_login|max_login_fails|chown_upload_mode/

let str_option_re = /secure_chroot_dir|ftp_username|chown_username|xferlog_file|vsftpd_log_file|message_file|nopriv_user|ftpd_banner|banned_email_file|chroot_list_file|pam_service_name|guest_username|userlist_file|anon_root|local_root|banner_file|pasv_address|listen_address|user_config_dir|listen_address6|cmds_allowed|hide_file|deny_file|user_sub_token|email_password_file|rsa_cert_file|dsa_cert_file|ssl_ciphers|rsa_private_key_file|dsa_private_key_file|ca_certs_file/

let bool_value_re = /[yY][eE][sS]|[tT][rR][uU][eE]|1|[nN][oO]|[fF][aA][lL][sS][eE]|0/

let option (k:regexp) (v:regexp) = [ key k . Util.del_str "=" . store v . eol ]

let bool_option = option bool_option_re bool_value_re

let str_option = option str_option_re /[^\n]+/

let uint_option = option uint_option_re /[0-9]+/

let lns = (bool_option|str_option|uint_option|comment|empty)*

let filter = (incl "/etc/vsftpd.conf") . (incl "/etc/vsftpd/vsftpd.conf")

let xfm = transform lns filter

(* Parse vsftpd.conf *)
module Vsftpd =
  autoload xfm

(* The code in parseconf.c does not seem to allow for trailing whitespace *)
(* in the config file                                                     *)
let eol = Util.del_str "\n"
let empty = Util.empty
let comment = Util.comment

let bool_option_re = /anonymous_enable|local_enable|pasv_enable|port_enable|chroot_local_user|write_enable|anon_upload_enable|anon_mkdir_write_enable|anon_other_write_enable|chown_uploads|connect_from_port_20|xferlog_enable|dirmessage_enable|anon_world_readable_only|async_abor_enable|ascii_upload_enable|ascii_download_enable|one_process_model|xferlog_std_format|pasv_promiscuous|deny_email_enable|chroot_list_enable|setproctitle_enable|text_userdb_names|ls_recurse_enable|log_ftp_protocol|guest_enable|userlist_enable|userlist_deny|use_localtime|check_shell|hide_ids|listen|port_promiscuous|passwd_chroot_enable|no_anon_password|tcp_wrappers|use_sendfile|force_dot_files|listen_ipv6|dual_log_enable|syslog_enable|background|virtual_use_local_privs|session_support|download_enable|dirlist_enable|chmod_enable|secure_email_list_enable|run_as_launching_user|no_log_lock|ssl_enable|allow_anon_ssl|force_local_logins_ssl|force_local_data_ssl|ssl_sslv2|ssl_sslv3|ssl_tlsv1|tilde_user_enable|force_anon_logins_ssl|force_anon_data_ssl|mdtm_write|lock_upload_files|pasv_addr_resolve|debug_ssl|require_cert|validate_cert|require_ssl_reuse/

let uint_option_re = /accept_timeout|connect_timeout|local_umask|anon_umask|ftp_data_port|idle_session_timeout|data_connection_timeout|pasv_min_port|pasv_max_port|anon_max_rate|local_max_rate|listen_port|max_clients|file_open_mode|max_per_ip|trans_chunk_size|delay_failed_login|delay_successful_login|max_login_fails|chown_upload_mode/

let str_option_re = /secure_chroot_dir|ftp_username|chown_username|xferlog_file|vsftpd_log_file|message_file|nopriv_user|ftpd_banner|banned_email_file|chroot_list_file|pam_service_name|guest_username|userlist_file|anon_root|local_root|banner_file|pasv_address|listen_address|user_config_dir|listen_address6|cmds_allowed|hide_file|deny_file|user_sub_token|email_password_file|rsa_cert_file|dsa_cert_file|ssl_ciphers|rsa_private_key_file|dsa_private_key_file|ca_certs_file/

let bool_value_re = /[yY][eE][sS]|[tT][rR][uU][eE]|1|[nN][oO]|[fF][aA][lL][sS][eE]|0/

let option (k:regexp) (v:regexp) = [ key k . Util.del_str "=" . store v . eol ]

let bool_option = option bool_option_re bool_value_re

let str_option = option str_option_re /[^\n]+/

let uint_option = option uint_option_re /[0-9]+/

let lns = (bool_option|str_option|uint_option|comment|empty)*

let filter = (incl "/etc/vsftpd.conf") . (incl "/etc/vsftpd/vsftpd.conf")

let xfm = transform lns filter


yum.aug: added pluginconf.d/ to search path

(* Parsing yum's config files *)
module Yum =
  autoload xfm

  let eol = Util.del_str "\n"

  let key_re = /[^#;:= \t\n[\/]+/ - "baseurl"

  let eq = del /[ \t]*[:=][ \t]*/ "="
  let secname = /[^]\/]+/

  let value = /[^ \t\n][^\n]*/

  (* We really need to allow comments starting with REM and rem but that     *)
  (* leads to ambiguities with keys 'rem=' and 'REM=' The regular expression *)
  (* to do that cleanly is somewhat annoying to craft by hand; we'd need to  *)
  (* define KEY_RE as /[A-Za-z0-9]+/ - "REM" - "rem"                         *)
  let comment = [ del /([;#].*)?[ \t]*\n/ "\n" ]

  let list_sep = del /[ \t]*(,[ \t]*|\n[ \t]+)/ "\n\t"
  let list_value = store /[^ \t\n,]+/

  let kv_list(s:string) =
    [ key s . eq . list_value ] .
      [ list_sep . label s . list_value ]* . eol

  let kv = [ key key_re . eq . store value . eol ]

  let sechead = Util.del_str "[" . key secname . Util.del_str "]"
      . del /[ \t]*[;#]?.*/ ""
      . eol

  let entry = comment | kv

  (* A section is a section head, followed by any number of key value    *)
  (* entries, with comments and blank lines freely interspersed. The     *)
  (* continuation lines allowed for baseurl entries make this a little   *)
  (* more interesting: there can be at most one baseurl entry in each    *)
  (* section (more precisely, yum will only obey one of them, but we act *)
  (* as if yum would actually barf)                                      *)
  let section = [ sechead . (entry* | entry* . (kv_list "baseurl") . entry*)]

  let lns = (comment) * . (section) *

  let filter = (incl "/etc/yum.conf")
      . (incl "/etc/yum.repos.d/*")
      . (incl "/etc/yum/pluginconf.d/*")
      . Util.stdexcl

  let xfm = transform lns filter

(* Local Variables: *)
(* mode: caml       *)
(* End:             *)

(* Parsing yum's config files *)
module Yum =
  autoload xfm

  let eol = Util.del_str "\n"

  let key_re = /[^#;:= \t\n[\/]+/ - "baseurl"

  let eq = del /[ \t]*[:=][ \t]*/ "="
  let secname = /[^]\/]+/

  let value = /[^ \t\n][^\n]*/

  (* We really need to allow comments starting with REM and rem but that     *)
  (* leads to ambiguities with keys 'rem=' and 'REM=' The regular expression *)
  (* to do that cleanly is somewhat annoying to craft by hand; we'd need to  *)
  (* define KEY_RE as /[A-Za-z0-9]+/ - "REM" - "rem"                         *)
  let comment = [ del /([;#].*)?[ \t]*\n/ "\n" ]

  let list_sep = del /[ \t]*(,[ \t]*|\n[ \t]+)/ "\n\t"
  let list_value = store /[^ \t\n,]+/

  let kv_list(s:string) =
    [ key s . eq . list_value ] .
      [ list_sep . label s . list_value ]* . eol

  let kv = [ key key_re . eq . store value . eol ]

  let sechead = Util.del_str "[" . key secname . Util.del_str "]"
      . del /[ \t]*[;#]?.*/ ""
      . eol

  let entry = comment | kv

  (* A section is a section head, followed by any number of key value    *)
  (* entries, with comments and blank lines freely interspersed. The     *)
  (* continuation lines allowed for baseurl entries make this a little   *)
  (* more interesting: there can be at most one baseurl entry in each    *)
  (* section (more precisely, yum will only obey one of them, but we act *)
  (* as if yum would actually barf)                                      *)
  let section = [ sechead . (entry* | entry* . (kv_list "baseurl") . entry*)]

  let lns = (comment) * . (section) *

  let filter = (incl "/etc/yum.conf") . (incl "/etc/yum.repos.d/*")
      . Util.stdexcl

  let xfm = transform lns filter

(* Local Variables: *)
(* mode: caml       *)
(* End:             *)


FAI_DiskConfig: Fix invalid escape sequence \s

It should be expanded to [ \n\t].

Signed-off-by: Michael Chapman <a17fed27eaa842282862ff7c1b9c8395a26ac320@very.puzzling.org>

(*
Module: FAI_DiskConfig
 Parses disk_config files for FAI

Author: Raphael Pinson <raphink@gmail.com>

About: Reference
 This lens tries to keep as close as possible to the FAI wiki where possible:
 http://wiki.fai-project.org/wiki/Setup-storage#New_configuration_file_syntax

About: License
  This file is licensed under the LGPLv2+, like the rest of Augeas.

About: Lens Usage
*)

module FAI_DiskConfig =

(* autoload xfm *)

(************************************************************************
 * Group:                 USEFUL PRIMITIVES
 *************************************************************************)

(* Group: Generic primitives *)
(* Variable: eol *)
let eol = Util.eol

(* Variable: space *)
let space = Sep.space

(* Variable: empty *)
let empty = Util.empty

(* Variable: comment *)
let comment = Util.comment

(* Variable: tag
   A generic tag beginning with a colon *)
let tag (re:regexp) = [ Util.del_str ":" . key re ]

(* Variable: generic_opt
   A generic key/value option *)
let generic_opt (type:string) (kw:regexp) =
   [ key type . Util.del_str ":" . store kw ]

(* Variable: generic_opt_list
   A generic key/list option *)
let generic_opt_list (type:string) (kw:regexp) =
   [ key type . Util.del_str ":" . counter "locallist"
   . Build.opt_list [seq "locallist" . store kw] Sep.comma ]

(************************************************************************
 * Group:                      RECORDS
 *************************************************************************)

(* Group: volume *)

(* Variable: mountpoint_kw *)
let mountpoint_kw = "-" (* do not mount *)
         | "swap"       (* swap space *)
         (* fully qualified path; if :encrypt is given, the partition
          * will be encrypted, the key is generated automatically *)
         | /\/[^: \t\n]*/

(* Variable: encrypt
   encrypt tag *)
let encrypt = tag "encrypt"

(* Variable: mountpoint *)
let mountpoint = [ label "mountpoint" . store mountpoint_kw
                 (* encrypt is only for the fspath, but we parse it anyway *)
                 . encrypt?]

(* Variable: resize
   resize tag *)
let resize = tag "resize"

(* Variable: size_kw
   Regexps for size *)
let size_kw = /[0-9]+[kMGTP%]?(-([0-9]+[kMGTP%]?)?)?/
            | /-[0-9]+[kMGTP%]?/

(* Variable: size *)
let size = [ label "size" . store size_kw . resize? ]

(* Variable: filesystem_kw
   Regexps for filesystem *)
let filesystem_kw = "-"
         | "swap"
         (* NOTE: Restraining this regexp would improve perfs *)
         | (Rx.no_spaces - "-" - "swap") (* mkfs.xxx must exist *)

(* Variable: filesystem *)
let filesystem = [ label "filesystem" . store filesystem_kw ]

(* Variable: mount_option_value *)
let mount_option_value = [ label "value" . Util.del_str "="
                         . store /[^,= \t\n]+/ ]

(* Variable: mount_option
   Counting options *)
let mount_option = [ seq "mount_option"
                   . store /[^,= \t\n]+/
                   . mount_option_value? ]

(* Variable: mount_options
   An array of <mount_option>s *)
let mount_options = [ label "mount_options"
                    . counter "mount_option"
                    . Build.opt_list mount_option Sep.comma ]

(* Variable: fs_option *)
let fs_option =
     [ key /createopts|tuneopts/
     . Util.del_str "=\"" . store /[^"\n]*/ . Util.del_str "\"" ]

(* Variable: fs_options
   An array of <fs_option>s *)
let fs_options =
     (* options to append to mkfs.xxx and to the filesystem-specific
      * tuning tool *)
     [ label "fs_options" . Build.opt_list fs_option Sep.space ]

(* Variable: volume_full *)
let volume_full (type:lens) (third_field:lens) =
           [ type . space
           . mountpoint .space
           (* The third field changes depending on types *)
           . third_field . space
           . filesystem . space
           . mount_options
           . (space . fs_options)?
           . eol ]

(* Variable: name
   LVM volume group name *)
let name = [ label "name" . store /[^\/ \t\n]+/ ]

(* Variable: partition
   An optional partition number for <disk> *)
let partition = [ label "partition" . Util.del_str "." . store /[0-9]+/ ]

(* Variable: disk *)
let disk = [ label "disk" . store /[^\., \t\n]+/ . partition? ]

(* Variable: vg_option
   An option for <volume_vg> *)
let vg_option =
     [ key "pvcreateopts"
     . Util.del_str "=\"" . store /[^"\n]*/ . Util.del_str "\"" ]

(* Variable: volume_vg *)
let volume_vg = [ key "vg"
                . space . name
                . space . disk
                . (space . vg_option)?
                . eol ]

(* Variable: spare_missing *)
let spare_missing = tag /spare|missing/

(* Variable: disk_with_opt
   A <disk> with a spare/missing option for raids *)
let disk_with_opt = [ label "disk" . store /[^:\., \t\n]+/ . partition?
                    . spare_missing* ]

(* Variable: disk_list
   A list of <disk_with_opt>s *)
let disk_list = Build.opt_list disk_with_opt Sep.comma

(* Variable: type_label_lv *)
let type_label_lv = label "lv"
                    . [ label "vg" . store (/[^# \t\n-]+/ - "raw") ]
                    . Util.del_str "-"
                    . [ label "name" . store /[^ \t\n]+/ ]

(* Variable: volume_tmpfs *)
let volume_tmpfs =
           [ key "tmpfs" . space
           . mountpoint .space
           . size . space
           . mount_options
           . (space . fs_options)?
           . eol ]

(* Variable: volume_lvm *)
let volume_lvm = volume_full type_label_lv size  (* lvm logical volume: vg name and lv name *)
               | volume_vg

(* Variable: volume_raid *)
let volume_raid = volume_full (key /raid[0156]/) disk_list  (* raid level *)

(* Variable: device *)
let device = [ label "device" . store Rx.fspath ]

(* Variable: volume_cryptsetup *)
let volume_cryptsetup = volume_full (key ("swap"|"tmp"|"luks")) device

(* Variable: volume *)
let volume = volume_full (key "primary") size     (* for physical disks only *)
           | volume_full (key "logical") size     (* for physical disks only *)
           | volume_full (key "raw-disk") size

(* Variable: volume_or_comment
   A succesion of <volume>s and <comment>s *)
let volume_or_comment (vol:lens) =
      (vol|empty|comment)* . vol

(* Variable: disk_config_entry *)
let disk_config_entry (kw:regexp) (opt:lens) (vol:lens) =
                  [ key "disk_config" . space . store kw
                  . (space . opt)* . eol
                  . (volume_or_comment vol)? ]

(* Variable: lvmoption *)
let lvmoption =
     (* preserve partitions -- always *)
      generic_opt "preserve_always" /[^\/, \t\n-]+-[^\/, \t\n-]+(,[^\/, \t\n-]+-[^\/, \t\n-]+)*/
     (* preserve partitions -- unless the system is installed
      * for the first time *)
   | generic_opt "preserve_reinstall" /[^\/, \t\n-]+-[^\/, \t\n-]+(,[^\/, \t\n-]+-[^\/, \t\n-]+)*/
     (* attempt to resize partitions *)
   | generic_opt "resize" /[^\/, \t\n-]+-[^\/, \t\n-]+(,[^\/, \t\n-]+-[^\/, \t\n-]+)*/
     (* when creating the fstab, the key used for defining the device
      * may be the device (/dev/xxx), a label given using -L, or the uuid *)
   | generic_opt "fstabkey" /device|label|uuid/

(* Variable: raidoption *)
let raidoption =
     (* preserve partitions -- always *)
     generic_opt_list "preserve_always" (Rx.integer | "all")
     (* preserve partitions -- unless the system is installed
      * for the first time *)
   | generic_opt_list "preserve_reinstall" Rx.integer
     (* when creating the fstab, the key used for defining the device
      * may be the device (/dev/xxx), a label given using -L, or the uuid *)
   | generic_opt "fstabkey" /device|label|uuid/

(* Variable: option *)
let option =
     (* preserve partitions -- always *)
     generic_opt_list "preserve_always" (Rx.integer | "all")
     (* preserve partitions -- unless the system is installed
        for the first time *)
   | generic_opt_list "preserve_reinstall" Rx.integer
     (* attempt to resize partitions *)
   | generic_opt_list "resize" Rx.integer
     (* write a disklabel - default is msdos *)
   | generic_opt "disklabel" /msdos|gpt/
     (* mark a partition bootable, default is / *)
   | generic_opt "bootable" Rx.integer
     (* do not assume the disk to be a physical device, use with xen *)
   | [ key "virtual" ]
     (* when creating the fstab, the key used for defining the device
      * may be the device (/dev/xxx), a label given using -L, or the uuid *)
   | generic_opt "fstabkey" /device|label|uuid/
   | generic_opt_list "always_format" Rx.integer
   | generic_opt "sameas" Rx.fspath

let cryptoption =
     [ key "randinit" ]

(* Variable: disk_config *)
let disk_config =
    let other_label = Rx.fspath - "lvm" - "raid" - "end" - /disk[0-9]+/
                                - "cryptsetup" - "tmpfs" in
                  disk_config_entry "lvm" lvmoption volume_lvm
                | disk_config_entry "raid" raidoption volume_raid
                | disk_config_entry "tmpfs" option volume_tmpfs
                | disk_config_entry "end" option volume (* there shouldn't be an option here *)
                | disk_config_entry /disk[0-9]+/ option volume
                | disk_config_entry "cryptsetup" cryptoption volume_cryptsetup
                | disk_config_entry other_label option volume

(* Variable: lns
   The disk_config lens *)
let lns = (disk_config|comment|empty)*

(* let xfm = transform lns Util.stdexcl *)

(*
Module: FAI_DiskConfig
 Parses disk_config files for FAI

Author: Raphael Pinson <raphink@gmail.com>

About: Reference
 This lens tries to keep as close as possible to the FAI wiki where possible:
 http://wiki.fai-project.org/wiki/Setup-storage#New_configuration_file_syntax

About: License
  This file is licensed under the LGPLv2+, like the rest of Augeas.

About: Lens Usage
*)

module FAI_DiskConfig =

(* autoload xfm *)

(************************************************************************
 * Group:                 USEFUL PRIMITIVES
 *************************************************************************)

(* Group: Generic primitives *)
(* Variable: eol *)
let eol = Util.eol

(* Variable: space *)
let space = Sep.space

(* Variable: empty *)
let empty = Util.empty

(* Variable: comment *)
let comment = Util.comment

(* Variable: tag
   A generic tag beginning with a colon *)
let tag (re:regexp) = [ Util.del_str ":" . key re ]

(* Variable: generic_opt
   A generic key/value option *)
let generic_opt (type:string) (kw:regexp) =
   [ key type . Util.del_str ":" . store kw ]

(* Variable: generic_opt_list
   A generic key/list option *)
let generic_opt_list (type:string) (kw:regexp) =
   [ key type . Util.del_str ":" . counter "locallist"
   . Build.opt_list [seq "locallist" . store kw] Sep.comma ]

(************************************************************************
 * Group:                      RECORDS
 *************************************************************************)

(* Group: volume *)

(* Variable: mountpoint_kw *)
let mountpoint_kw = "-" (* do not mount *)
         | "swap"       (* swap space *)
         (* fully qualified path; if :encrypt is given, the partition
          * will be encrypted, the key is generated automatically *)
         | /\/[^: \t\n]*/

(* Variable: encrypt
   encrypt tag *)
let encrypt = tag "encrypt"

(* Variable: mountpoint *)
let mountpoint = [ label "mountpoint" . store mountpoint_kw
                 (* encrypt is only for the fspath, but we parse it anyway *)
                 . encrypt?]

(* Variable: resize
   resize tag *)
let resize = tag "resize"

(* Variable: size_kw
   Regexps for size *)
let size_kw = /[0-9]+[kMGTP%]?(-([0-9]+[kMGTP%]?)?)?/
            | /-[0-9]+[kMGTP%]?/

(* Variable: size *)
let size = [ label "size" . store size_kw . resize? ]

(* Variable: filesystem_kw
   Regexps for filesystem *)
let filesystem_kw = "-"
         | "swap"
         (* NOTE: Restraining this regexp would improve perfs *)
         | (Rx.no_spaces - "-" - "swap") (* mkfs.xxx must exist *)

(* Variable: filesystem *)
let filesystem = [ label "filesystem" . store filesystem_kw ]

(* Variable: mount_option_value *)
let mount_option_value = [ label "value" . Util.del_str "="
                         . store /[^,= \t\n]+/ ]

(* Variable: mount_option
   Counting options *)
let mount_option = [ seq "mount_option"
                   . store /[^,= \t\n]+/
                   . mount_option_value? ]

(* Variable: mount_options
   An array of <mount_option>s *)
let mount_options = [ label "mount_options"
                    . counter "mount_option"
                    . Build.opt_list mount_option Sep.comma ]

(* Variable: fs_option *)
let fs_option =
     [ key /createopts|tuneopts/
     . Util.del_str "=\"" . store /[^"\n]*/ . Util.del_str "\"" ]

(* Variable: fs_options
   An array of <fs_option>s *)
let fs_options =
     (* options to append to mkfs.xxx and to the filesystem-specific
      * tuning tool *)
     [ label "fs_options" . Build.opt_list fs_option Sep.space ]

(* Variable: volume_full *)
let volume_full (type:lens) (third_field:lens) =
           [ type . space
           . mountpoint .space
           (* The third field changes depending on types *)
           . third_field . space
           . filesystem . space
           . mount_options
           . (space . fs_options)?
           . eol ]

(* Variable: name
   LVM volume group name *)
let name = [ label "name" . store /[^\/ \t\n]+/ ]

(* Variable: partition
   An optional partition number for <disk> *)
let partition = [ label "partition" . Util.del_str "." . store /[0-9]+/ ]

(* Variable: disk *)
let disk = [ label "disk" . store /[^\., \t\n]+/ . partition? ]

(* Variable: vg_option
   An option for <volume_vg> *)
let vg_option =
     [ key "pvcreateopts"
     . Util.del_str "=\"" . store /[^"\n]*/ . Util.del_str "\"" ]

(* Variable: volume_vg *)
let volume_vg = [ key "vg"
                . space . name
                . space . disk
                . (space . vg_option)?
                . eol ]

(* Variable: spare_missing *)
let spare_missing = tag /spare|missing/

(* Variable: disk_with_opt
   A <disk> with a spare/missing option for raids *)
let disk_with_opt = [ label "disk" . store /[^:\., \t\n]+/ . partition?
                    . spare_missing* ]

(* Variable: disk_list
   A list of <disk_with_opt>s *)
let disk_list = Build.opt_list disk_with_opt Sep.comma

(* Variable: type_label_lv *)
let type_label_lv = label "lv"
                    . [ label "vg" . store (/[^# \t\n-]+/ - "raw") ]
                    . Util.del_str "-"
                    . [ label "name" . store /[^ \t\n]+/ ]

(* Variable: volume_tmpfs *)
let volume_tmpfs =
           [ key "tmpfs" . space
           . mountpoint .space
           . size . space
           . mount_options
           . (space . fs_options)?
           . eol ]

(* Variable: volume_lvm *)
let volume_lvm = volume_full type_label_lv size  (* lvm logical volume: vg name and lv name *)
               | volume_vg

(* Variable: volume_raid *)
let volume_raid = volume_full (key /raid[0156]/) disk_list  (* raid level *)

(* Variable: device *)
let device = [ label "device" . store Rx.fspath ]

(* Variable: volume_cryptsetup *)
let volume_cryptsetup = volume_full (key ("swap"|"tmp"|"luks")) device

(* Variable: volume *)
let volume = volume_full (key "primary") size     (* for physical disks only *)
           | volume_full (key "logical") size     (* for physical disks only *)
           | volume_full (key "raw-disk") size

(* Variable: volume_or_comment
   A succesion of <volume>s and <comment>s *)
let volume_or_comment (vol:lens) =
      (vol|empty|comment)* . vol

(* Variable: disk_config_entry *)
let disk_config_entry (kw:regexp) (opt:lens) (vol:lens) =
                  [ key "disk_config" . space . store kw
                  . (space . opt)* . eol
                  . (volume_or_comment vol)? ]

(* Variable: lvmoption *)
let lvmoption =
     (* preserve partitions -- always *)
      generic_opt "preserve_always" /[^\/, \t\n-]+-[^\/, \t\n-]+(,[^\/,\s\-]+-[^\/, \t\n-]+)*/
     (* preserve partitions -- unless the system is installed
      * for the first time *)
   | generic_opt "preserve_reinstall" /[^\/, \t\n-]+-[^\/, \t\n-]+(,[^\/, \t\n-]+-[^\/, \t\n-]+)*/
     (* attempt to resize partitions *)
   | generic_opt "resize" /[^\/, \t\n-]+-[^\/, \t\n-]+(,[^\/, \t\n-]+-[^\/, \t\n-]+)*/
     (* when creating the fstab, the key used for defining the device
      * may be the device (/dev/xxx), a label given using -L, or the uuid *)
   | generic_opt "fstabkey" /device|label|uuid/

(* Variable: raidoption *)
let raidoption =
     (* preserve partitions -- always *)
     generic_opt_list "preserve_always" (Rx.integer | "all")
     (* preserve partitions -- unless the system is installed
      * for the first time *)
   | generic_opt_list "preserve_reinstall" Rx.integer
     (* when creating the fstab, the key used for defining the device
      * may be the device (/dev/xxx), a label given using -L, or the uuid *)
   | generic_opt "fstabkey" /device|label|uuid/

(* Variable: option *)
let option =
     (* preserve partitions -- always *)
     generic_opt_list "preserve_always" (Rx.integer | "all")
     (* preserve partitions -- unless the system is installed
        for the first time *)
   | generic_opt_list "preserve_reinstall" Rx.integer
     (* attempt to resize partitions *)
   | generic_opt_list "resize" Rx.integer
     (* write a disklabel - default is msdos *)
   | generic_opt "disklabel" /msdos|gpt/
     (* mark a partition bootable, default is / *)
   | generic_opt "bootable" Rx.integer
     (* do not assume the disk to be a physical device, use with xen *)
   | [ key "virtual" ]
     (* when creating the fstab, the key used for defining the device
      * may be the device (/dev/xxx), a label given using -L, or the uuid *)
   | generic_opt "fstabkey" /device|label|uuid/
   | generic_opt_list "always_format" Rx.integer
   | generic_opt "sameas" Rx.fspath

let cryptoption =
     [ key "randinit" ]

(* Variable: disk_config *)
let disk_config =
    let other_label = Rx.fspath - "lvm" - "raid" - "end" - /disk[0-9]+/
                                - "cryptsetup" - "tmpfs" in
                  disk_config_entry "lvm" lvmoption volume_lvm
                | disk_config_entry "raid" raidoption volume_raid
                | disk_config_entry "tmpfs" option volume_tmpfs
                | disk_config_entry "end" option volume (* there shouldn't be an option here *)
                | disk_config_entry /disk[0-9]+/ option volume
                | disk_config_entry "cryptsetup" cryptoption volume_cryptsetup
                | disk_config_entry other_label option volume

(* Variable: lns
   The disk_config lens *)
let lns = (disk_config|comment|empty)*

(* let xfm = transform lns Util.stdexcl *)


Allow spaces after 'endscript' in logrotate.aug

(* Logrotate module for Augeas                *)
(* Author: Raphael Pinson <raphink@gmail.com> *)
(* Patches from:                              *)
(*   Sean Millichamp <sean@bruenor.org>       *)
(*                                            *)
(* Supported :                                *)
(*   - defaults                               *)
(*   - rules                                  *)
(*   - (pre|post)rotate entries               *)
(*                                            *)
(* Todo :                                     *)
(*                                            *)

module Logrotate =
   autoload xfm

   let sep_spc = Util.del_ws_spc
   let sep_val = del /[ \t]*=[ \t]*|[ \t]+/ " "
   let eol = Util.eol
   let num = /[0-9]+/
   let word = /[^,#= \n\t{}]+/
   let filename = /\/[^,#= \n\t{}]+/
   let size = num . /[kMG]?/

   (* define comments and empty lines *)
   let comment (indent:string) =
     let nl = Util.del_str "\n" in
     [ label "#comment" . del /[ \t]*/ indent . del /#[ \t]*/ "# " .  store /([^ \t\n].*)?/ . nl ]
   let empty   = [ del /[ \t]*\n/ "\n" ]

   (* Useful functions *)

   let list_item = [ sep_spc . key /[^\/+,# \n\t{}]+/ ]
   let select_to_eol (kw:string) (select:regexp) (indent:string) = [ del /[ \t]*/ indent . label kw . store select . eol ]
   let value_to_eol (kw:string) (value:regexp) (indent:string )  = [ del /[ \t]*/ indent . key kw . sep_val . store value . eol ]
   let flag_to_eol (kw:string) (indent:string)                   = [ del /[ \t]*/ indent . key kw . eol ]
   let list_to_eol (kw:string) (indent:string)                   = [ del /[ \t]*/ indent . key kw . list_item+ . eol ]

   (* Defaults *)

   let create (indent:string ) =
     let mode = sep_spc . [ label "mode" . store num ] in
     let owner = sep_spc . [ label "owner" . store word ] in
     let group = sep_spc . [ label "group" . store word ] in
     [ del /[ \t]*/ indent . key "create" .
         ( mode | mode . owner | mode . owner . group )?
		    . eol ]

   let tabooext (indent:string) = [ del /[ \t]*/ indent . key "tabooext" . ( sep_spc . store /\+/ )? . list_item+ . eol ]

   let attrs (indent:string) = select_to_eol "schedule" /(daily|weekly|monthly|yearly)/ indent
                | value_to_eol "rotate" num indent
		| create indent
		| flag_to_eol "nocreate" indent
		| value_to_eol "include" word indent
		| select_to_eol "missingok" /(no)?missingok/ indent
		| select_to_eol "compress" /(no)?compress/ indent
		| select_to_eol "delaycompress" /(no)?delaycompress/ indent
		| select_to_eol "ifempty" /(not)?ifempty/ indent
		| select_to_eol "sharedscripts" /(no)?sharedscripts/ indent
		| value_to_eol "size" size indent
		| tabooext indent
		| value_to_eol "olddir" word indent
		| flag_to_eol "noolddir" indent
		| value_to_eol "mail" word indent
		| flag_to_eol "mailfirst" indent
		| flag_to_eol "maillast" indent
		| flag_to_eol "nomail" indent
		| value_to_eol "errors" word indent
		| value_to_eol "extension" word indent
		| select_to_eol "dateext" /(no)?dateext/ indent
		| value_to_eol "compresscmd" word indent
		| value_to_eol "uncompresscmd" word indent
		| value_to_eol "compressext" word indent
		| list_to_eol "compressoptions" indent
		| select_to_eol "copy" /(no)?copy/ indent
		| select_to_eol "copytruncate" /(no)?copytruncate/ indent
		| value_to_eol "maxage" num indent
		| value_to_eol "minsize" size indent
		| select_to_eol "shred" /(no)?shred/ indent
		| value_to_eol "shredcycles" num indent
		| value_to_eol "start" num indent

   (* Define hooks *)

   let hook_lines =
     let line_re = /.*/ - /[ \t]*endscript[ \t]*/ in
       store ( line_re . ("\n" . line_re)* )? . del "\n" "\n"

   let hooks =
     let hook_names = /(pre|post)rotate|(first|last)action/ in
     [ del /[ \t]*/ "\t" . key hook_names . eol .
       hook_lines .
       del /[ \t]*endscript[ \t]*\n/ "\tendscript\n" ]

   (* Define rule *)

   let body = del /\{[ \t]*\n/ "{\n"
                       . ( comment "\t" | attrs "\t" | hooks | empty )*
                       . del /[ \t]*\}[ \t]*\n/ "}\n"

   let rule =
     [ label "rule" . Util.indent .
         [ label "file" . store filename ] .
	 [ del /[ \t\n]+/ " " . label "file" . store filename ]* .
	 del /[ \t\n]*/ " " . body ]

   let lns = ( comment "" | empty | attrs "" | rule )*

   let filter = incl "/etc/logrotate.d/*"
              . incl "/etc/logrotate.conf"
	      . Util.stdexcl

   let xfm = transform lns filter

(* Logrotate module for Augeas                *)
(* Author: Raphael Pinson <raphink@gmail.com> *)
(* Patches from:                              *)
(*   Sean Millichamp <sean@bruenor.org>       *)
(*                                            *)
(* Supported :                                *)
(*   - defaults                               *)
(*   - rules                                  *)
(*   - (pre|post)rotate entries               *)
(*                                            *)
(* Todo :                                     *)
(*                                            *)

module Logrotate =
   autoload xfm

   let sep_spc = Util.del_ws_spc
   let sep_val = del /[ \t]*=[ \t]*|[ \t]+/ " "
   let eol = Util.eol
   let num = /[0-9]+/
   let word = /[^,#= \n\t{}]+/
   let filename = /\/[^,#= \n\t{}]+/
   let size = num . /[kMG]?/

   (* define comments and empty lines *)
   let comment (indent:string) =
     let nl = Util.del_str "\n" in
     [ label "#comment" . del /[ \t]*/ indent . del /#[ \t]*/ "# " .  store /([^ \t\n].*)?/ . nl ]
   let empty   = [ del /[ \t]*\n/ "\n" ]

   (* Useful functions *)

   let list_item = [ sep_spc . key /[^\/+,# \n\t{}]+/ ]
   let select_to_eol (kw:string) (select:regexp) (indent:string) = [ del /[ \t]*/ indent . label kw . store select . eol ]
   let value_to_eol (kw:string) (value:regexp) (indent:string )  = [ del /[ \t]*/ indent . key kw . sep_val . store value . eol ]
   let flag_to_eol (kw:string) (indent:string)                   = [ del /[ \t]*/ indent . key kw . eol ]
   let list_to_eol (kw:string) (indent:string)                   = [ del /[ \t]*/ indent . key kw . list_item+ . eol ]

   (* Defaults *)

   let create (indent:string ) =
     let mode = sep_spc . [ label "mode" . store num ] in
     let owner = sep_spc . [ label "owner" . store word ] in
     let group = sep_spc . [ label "group" . store word ] in
     [ del /[ \t]*/ indent . key "create" .
         ( mode | mode . owner | mode . owner . group )?
		    . eol ]

   let tabooext (indent:string) = [ del /[ \t]*/ indent . key "tabooext" . ( sep_spc . store /\+/ )? . list_item+ . eol ]

   let attrs (indent:string) = select_to_eol "schedule" /(daily|weekly|monthly|yearly)/ indent
                | value_to_eol "rotate" num indent
		| create indent
		| flag_to_eol "nocreate" indent
		| value_to_eol "include" word indent
		| select_to_eol "missingok" /(no)?missingok/ indent
		| select_to_eol "compress" /(no)?compress/ indent
		| select_to_eol "delaycompress" /(no)?delaycompress/ indent
		| select_to_eol "ifempty" /(not)?ifempty/ indent
		| select_to_eol "sharedscripts" /(no)?sharedscripts/ indent
		| value_to_eol "size" size indent
		| tabooext indent
		| value_to_eol "olddir" word indent
		| flag_to_eol "noolddir" indent
		| value_to_eol "mail" word indent
		| flag_to_eol "mailfirst" indent
		| flag_to_eol "maillast" indent
		| flag_to_eol "nomail" indent
		| value_to_eol "errors" word indent
		| value_to_eol "extension" word indent
		| select_to_eol "dateext" /(no)?dateext/ indent
		| value_to_eol "compresscmd" word indent
		| value_to_eol "uncompresscmd" word indent
		| value_to_eol "compressext" word indent
		| list_to_eol "compressoptions" indent
		| select_to_eol "copy" /(no)?copy/ indent
		| select_to_eol "copytruncate" /(no)?copytruncate/ indent
		| value_to_eol "maxage" num indent
		| value_to_eol "minsize" size indent
		| select_to_eol "shred" /(no)?shred/ indent
		| value_to_eol "shredcycles" num indent
		| value_to_eol "start" num indent

   (* Define hooks *)

   let hook_lines =
     let line_re = /.*/ - /[ \t]*endscript[ \t]*/ in
       store ( line_re . ("\n" . line_re)* )? . del "\n" "\n"

   let hooks =
     let hook_names = /(pre|post)rotate|(first|last)action/ in
     [ del /[ \t]*/ "\t" . key hook_names . eol .
       hook_lines .
       del /[ \t]*endscript\n/ "\tendscript\n" ]

   (* Define rule *)

   let body = del /\{[ \t]*\n/ "{\n"
                       . ( comment "\t" | attrs "\t" | hooks | empty )*
                       . del /[ \t]*\}[ \t]*\n/ "}\n"

   let rule =
     [ label "rule" . Util.indent .
         [ label "file" . store filename ] .
	 [ del /[ \t\n]+/ " " . label "file" . store filename ]* .
	 del /[ \t\n]*/ " " . body ]

   let lns = ( comment "" | empty | attrs "" | rule )*

   let filter = incl "/etc/logrotate.d/*"
              . incl "/etc/logrotate.conf"
	      . Util.stdexcl

   let xfm = transform lns filter


Grub: handle "foreground" option

Grub: handle "foreground" option

Fixes RHBZ#1059383

(*
Module: Grub
  Parses grub configuration

Author: David Lutterkort <lutter@redhat.com>

About: License
   This file is licenced under the LGPL v2+, like the rest of Augeas.

About: Lens Usage
   To be documented
*)

module Grub =
  autoload xfm

    (* This only covers the most basic grub directives. Needs to be *)
    (* expanded to cover more (and more esoteric) directives        *)
    (* It is good enough to handle the grub.conf on my Fedora 8 box *)

(************************************************************************
 * Group:                 USEFUL PRIMITIVES
 *************************************************************************)

    (* View: value_to_eol *)
    let value_to_eol = store /[^= \t\n][^\n]*[^= \t\n]|[^= \t\n]/

    (* 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

    (* View: opt_ws *)
    let opt_ws = Util.del_opt_ws ""

    (* View: dels *)
    let dels (s:string) = Util.del_str s

    (* View: eq *)
    let eq = dels "="

    (* View: switch *)
    let switch (n:regexp) = dels "--" . key n

    (* View: switch_arg *)
    let switch_arg (n:regexp) = switch n . eq . store Rx.no_spaces

    (* View: value_sep *)
    let value_sep (dflt:string) = del /[ \t]*[ \t=][ \t]*/ dflt

    (* View: comment_re *)
    let comment_re = /([^ \t\n].*[^ \t\n]|[^ \t\n])/
                       - /# ## (Start|End) Default Options ##/

    (* View: comment *)
    let comment    =
        [ Util.indent . label "#comment" . del /#[ \t]*/ "# "
            . store comment_re . eol ]

    (* View: empty *)
    let empty   = Util.empty

(************************************************************************
 * Group:                 USEFUL FUNCTIONS
 *************************************************************************)

    (* View: command *)
    let command (kw:regexp) (indent:string) =
      Util.del_opt_ws indent . key kw

    (* View: kw_arg *)
    let kw_arg (kw:regexp) (indent:string) (dflt_sep:string) =
      [ command kw indent . value_sep dflt_sep . value_to_eol . eol ]

    (* View: kw_boot_arg *)
    let kw_boot_arg (kw:regexp) = kw_arg kw "\t" " "

    (* View: kw_menu_arg *)
    let kw_menu_arg (kw:regexp) = kw_arg kw "" " "

    (* View: password_arg *)
    let password_arg = [ key "password" .
      (spc . [ switch "md5" ])? .
      (spc . [ switch "encrypted" ])? .
      spc . store (/[^ \t\n]+/ - /--[^ \t\n]+/) .
      (spc . [ label "file" . store /[^ \t\n]+/ ])? .
      eol ]

    (* View: kw_pres *)
    let kw_pres (kw:string) = [ opt_ws . key kw . del_to_eol . eol ]

(************************************************************************
 * Group:                 BOOT ENTRIES
 *************************************************************************)

    (* View: device
     *  This is a shell-only directive in upstream grub; the grub versions
     *  in at least Fedora/RHEL use this to find devices for UEFI boot *)
    let device =
      [ command "device" "" . Sep.space . store /\([A-Za-z0-9_.-]+\)/ . spc .
        [ label "file" . value_to_eol ] . Util.eol ]

    (* View: color *)
    let color =
      (* Should we nail it down to exactly the color names that *)
      (* grub supports ? *)
      let color_name = store /[A-Za-z-]+/ in
      let color_spec =
        [ label "foreground" . color_name] .
        dels "/" .
        [ label "background" . color_name ] in
      [ opt_ws . key "color" .
        spc . [ label "normal" . color_spec ] .
        (spc . [ label "highlight" . color_spec ])? .
        eol ]

    (* View: serial *)
    let serial =
      [ command "serial" "" .
        [ spc . switch_arg /unit|port|speed|word|parity|stop|device/ ]* .
        eol ]

    (* View: terminal *)
    let terminal =
      [ command "terminal" "" .
          ([ spc . switch /dumb|no-echo|no-edit|silent/ ]
          |[ spc . switch_arg /timeout|lines/ ])* .
          [ spc . key /console|serial|hercules/ ]* . eol ]

    (* View: setkey *)
    let setkey = [ command "setkey" "" .
      ( spc . [ label "to" . store Rx.no_spaces ] .
        spc . [ label "from" . store Rx.no_spaces ] )? .
      eol ]

    (* View: menu_setting *)
    let menu_setting = kw_menu_arg "default"
                     | kw_menu_arg "fallback"
                     | kw_pres "hiddenmenu"
                     | kw_menu_arg "timeout"
                     | kw_menu_arg "splashimage"
                     | kw_menu_arg "gfxmenu"
                     | kw_menu_arg "foreground"
                     | kw_menu_arg "background"
                     | kw_menu_arg "verbose"
                     | serial
                     | terminal
                     | password_arg
                     | color
                     | device
                     | setkey

    (* View: title *)
    let title = del /title[ \t=]+/ "title " . value_to_eol . eol

    (* View: multiboot_arg
     *  Permits a second form for Solaris multiboot kernels that
     *  take a path (with a slash) as their first arg, e.g.
     *  /boot/multiboot kernel/unix another=arg *)
    let multiboot_arg = [ label "@path" .
                          store (Rx.word . "/" . Rx.no_spaces) ]

    (* View: kernel_args
        Parse the file name and args on a kernel or module line. *)
    let kernel_args =
      let arg = /[A-Za-z0-9_.$-]+/ - /type|no-mem-option/  in
      store /(\([a-z0-9,]+\))?\/[^ \t\n]*/ .
            (spc . multiboot_arg)? .
            (spc . [ key arg . (eq. store /([^ \t\n])*/)?])* . eol

    (* View: module_line
        Solaris extension adds module$ and kernel$ for variable interpolation *)
    let module_line =
      [ command /module\$?/ "\t" . spc . kernel_args ]

    (* View: map_line *)
    let map_line =
      [ command "map" "\t" . spc .
           [ label "from" . store /[()A-za-z0-9]+/ ] . spc .
           [ label "to" . store /[()A-za-z0-9]+/ ] . eol ]

    (* View: kernel *)
    let kernel =
        [ command /kernel\$?/ "\t" .
          (spc .
             ([switch "type" . eq . store /[a-z]+/]
             |[switch "no-mem-option"]))* .
          spc . kernel_args ]

    (* View: chainloader *)
    let chainloader =
      [ command "chainloader" "\t" .
          [ spc . switch "force" ]? . spc . store Rx.no_spaces . eol ]

    (* View: savedefault *)
    let savedefault =
      [ command "savedefault" "\t" . (spc . store Rx.integer)? . eol ]

    (* View: configfile *)
    let configfile =
      [ command "configfile" "\t" . spc . store Rx.no_spaces . eol ]

    (* View: boot_setting
        <boot> entries *)
    let boot_setting =
          let boot_arg_re = "root" | "initrd" | "rootnoverify" | "uuid"
                          | "findroot" | "bootfs" (* Solaris extensions *)
       in kw_boot_arg boot_arg_re
        | kernel
        | chainloader
        | kw_pres "quiet"  (* Seems to be a Ubuntu extension *)
        | savedefault
        | configfile
        | module_line
        | map_line
        | kw_pres "lock"
        | kw_pres "makeactive"

    (* View: boot *)
    let boot =
      let line = ((boot_setting|comment)* . boot_setting)? in
      [ label "title" . title . line ]

(************************************************************************
 * Group:                 DEBIAN-SPECIFIC SECTIONS
 *************************************************************************)

    (* View: debian_header
        Header for a <debian>-specific section *)
    let debian_header  = "## ## Start Default Options ##\n"

    (* View: debian_footer
        Footer for a <debian>-specific section *)
    let debian_footer  = "## ## End Default Options ##\n"

    (* View: debian_comment_re *)
    let debian_comment_re = /([^ \t\n].*[^ \t\n]|[^ \t\n])/
                            - "## End Default Options ##"

    (* View: debian_comment
        A comment entry inside a <debian>-specific section *)
    let debian_comment =
        [ Util.indent . label "#comment" . del /##[ \t]*/ "## "
            . store debian_comment_re . eol ]

    (* View: debian_setting_re *)
    let debian_setting_re = "kopt"
                          | "groot"
                          | "alternative"
                          | "lockalternative"
                          | "defoptions"
                          | "lockold"
                          | "xenhopt"
                          | "xenkopt"
                          | "altoptions"
                          | "howmany"
                          | "memtest86"
                          | "updatedefaultentry"
                          | "savedefault"
                          | "indomU"

    (* View: debian_entry *)
    let debian_entry   = [ Util.del_str "#" . Util.indent
                         . key debian_setting_re . del /[ \t]*=/ "="
                         . value_to_eol? . eol ]

    (* View: debian
        A debian-specific section, made of <debian_entry> lines *)
    let debian         = [ label "debian"
                    . del debian_header debian_header
                    . (debian_comment|empty|debian_entry)*
                    . del debian_footer debian_footer ]

(************************************************************************
 * Group:                 LENS AND FILTER
 *************************************************************************)

    (* View: lns *)
    let lns = (comment | empty | menu_setting | boot | debian)*

    (* View: filter *)
    let filter = incl "/boot/grub/grub.conf"
               . incl "/boot/grub/menu.lst"
               . incl "/etc/grub.conf"

    let xfm = transform lns filter

(*
Module: Grub
  Parses grub configuration

Author: David Lutterkort <lutter@redhat.com>

About: License
   This file is licenced under the LGPL v2+, like the rest of Augeas.

About: Lens Usage
   To be documented
*)

module Grub =
  autoload xfm

    (* This only covers the most basic grub directives. Needs to be *)
    (* expanded to cover more (and more esoteric) directives        *)
    (* It is good enough to handle the grub.conf on my Fedora 8 box *)

(************************************************************************
 * Group:                 USEFUL PRIMITIVES
 *************************************************************************)

    (* View: value_to_eol *)
    let value_to_eol = store /[^= \t\n][^\n]*[^= \t\n]|[^= \t\n]/

    (* 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

    (* View: opt_ws *)
    let opt_ws = Util.del_opt_ws ""

    (* View: dels *)
    let dels (s:string) = Util.del_str s

    (* View: eq *)
    let eq = dels "="

    (* View: switch *)
    let switch (n:regexp) = dels "--" . key n

    (* View: switch_arg *)
    let switch_arg (n:regexp) = switch n . eq . store Rx.no_spaces

    (* View: value_sep *)
    let value_sep (dflt:string) = del /[ \t]*[ \t=][ \t]*/ dflt

    (* View: comment_re *)
    let comment_re = /([^ \t\n].*[^ \t\n]|[^ \t\n])/
                       - /# ## (Start|End) Default Options ##/

    (* View: comment *)
    let comment    =
        [ Util.indent . label "#comment" . del /#[ \t]*/ "# "
            . store comment_re . eol ]

    (* View: empty *)
    let empty   = Util.empty

(************************************************************************
 * Group:                 USEFUL FUNCTIONS
 *************************************************************************)

    (* View: command *)
    let command (kw:regexp) (indent:string) =
      Util.del_opt_ws indent . key kw

    (* View: kw_arg *)
    let kw_arg (kw:regexp) (indent:string) (dflt_sep:string) =
      [ command kw indent . value_sep dflt_sep . value_to_eol . eol ]

    (* View: kw_boot_arg *)
    let kw_boot_arg (kw:regexp) = kw_arg kw "\t" " "

    (* View: kw_menu_arg *)
    let kw_menu_arg (kw:regexp) = kw_arg kw "" " "

    (* View: password_arg *)
    let password_arg = [ key "password" .
      (spc . [ switch "md5" ])? .
      (spc . [ switch "encrypted" ])? .
      spc . store (/[^ \t\n]+/ - /--[^ \t\n]+/) .
      (spc . [ label "file" . store /[^ \t\n]+/ ])? .
      eol ]

    (* View: kw_pres *)
    let kw_pres (kw:string) = [ opt_ws . key kw . del_to_eol . eol ]

(************************************************************************
 * Group:                 BOOT ENTRIES
 *************************************************************************)

    (* View: device
     *  This is a shell-only directive in upstream grub; the grub versions
     *  in at least Fedora/RHEL use this to find devices for UEFI boot *)
    let device =
      [ command "device" "" . Sep.space . store /\([A-Za-z0-9_.-]+\)/ . spc .
        [ label "file" . value_to_eol ] . Util.eol ]

    (* View: color *)
    let color =
      (* Should we nail it down to exactly the color names that *)
      (* grub supports ? *)
      let color_name = store /[A-Za-z-]+/ in
      let color_spec =
        [ label "foreground" . color_name] .
        dels "/" .
        [ label "background" . color_name ] in
      [ opt_ws . key "color" .
        spc . [ label "normal" . color_spec ] .
        (spc . [ label "highlight" . color_spec ])? .
        eol ]

    (* View: serial *)
    let serial =
      [ command "serial" "" .
        [ spc . switch_arg /unit|port|speed|word|parity|stop|device/ ]* .
        eol ]

    (* View: terminal *)
    let terminal =
      [ command "terminal" "" .
          ([ spc . switch /dumb|no-echo|no-edit|silent/ ]
          |[ spc . switch_arg /timeout|lines/ ])* .
          [ spc . key /console|serial|hercules/ ]* . eol ]

    (* View: setkey *)
    let setkey = [ command "setkey" "" .
      ( spc . [ label "to" . store Rx.no_spaces ] .
        spc . [ label "from" . store Rx.no_spaces ] )? .
      eol ]

    (* View: menu_setting *)
    let menu_setting = kw_menu_arg "default"
                     | kw_menu_arg "fallback"
                     | kw_pres "hiddenmenu"
                     | kw_menu_arg "timeout"
                     | kw_menu_arg "splashimage"
                     | kw_menu_arg "gfxmenu"
                     | kw_menu_arg "background"
                     | kw_menu_arg "verbose"
                     | serial
                     | terminal
                     | password_arg
                     | color
                     | device
                     | setkey

    (* View: title *)
    let title = del /title[ \t=]+/ "title " . value_to_eol . eol

    (* View: multiboot_arg
     *  Permits a second form for Solaris multiboot kernels that
     *  take a path (with a slash) as their first arg, e.g.
     *  /boot/multiboot kernel/unix another=arg *)
    let multiboot_arg = [ label "@path" .
                          store (Rx.word . "/" . Rx.no_spaces) ]

    (* View: kernel_args
        Parse the file name and args on a kernel or module line. *)
    let kernel_args =
      let arg = /[A-Za-z0-9_.$-]+/ - /type|no-mem-option/  in
      store /(\([a-z0-9,]+\))?\/[^ \t\n]*/ .
            (spc . multiboot_arg)? .
            (spc . [ key arg . (eq. store /([^ \t\n])*/)?])* . eol

    (* View: module_line
        Solaris extension adds module$ and kernel$ for variable interpolation *)
    let module_line =
      [ command /module\$?/ "\t" . spc . kernel_args ]

    (* View: map_line *)
    let map_line =
      [ command "map" "\t" . spc .
           [ label "from" . store /[()A-za-z0-9]+/ ] . spc .
           [ label "to" . store /[()A-za-z0-9]+/ ] . eol ]

    (* View: kernel *)
    let kernel =
        [ command /kernel\$?/ "\t" .
          (spc .
             ([switch "type" . eq . store /[a-z]+/]
             |[switch "no-mem-option"]))* .
          spc . kernel_args ]

    (* View: chainloader *)
    let chainloader =
      [ command "chainloader" "\t" .
          [ spc . switch "force" ]? . spc . store Rx.no_spaces . eol ]

    (* View: savedefault *)
    let savedefault =
      [ command "savedefault" "\t" . (spc . store Rx.integer)? . eol ]

    (* View: configfile *)
    let configfile =
      [ command "configfile" "\t" . spc . store Rx.no_spaces . eol ]

    (* View: boot_setting
        <boot> entries *)
    let boot_setting =
          let boot_arg_re = "root" | "initrd" | "rootnoverify" | "uuid"
                          | "findroot" | "bootfs" (* Solaris extensions *)
       in kw_boot_arg boot_arg_re
        | kernel
        | chainloader
        | kw_pres "quiet"  (* Seems to be a Ubuntu extension *)
        | savedefault
        | configfile
        | module_line
        | map_line
        | kw_pres "lock"
        | kw_pres "makeactive"

    (* View: boot *)
    let boot =
      let line = ((boot_setting|comment)* . boot_setting)? in
      [ label "title" . title . line ]

(************************************************************************
 * Group:                 DEBIAN-SPECIFIC SECTIONS
 *************************************************************************)

    (* View: debian_header
        Header for a <debian>-specific section *)
    let debian_header  = "## ## Start Default Options ##\n"

    (* View: debian_footer
        Footer for a <debian>-specific section *)
    let debian_footer  = "## ## End Default Options ##\n"

    (* View: debian_comment_re *)
    let debian_comment_re = /([^ \t\n].*[^ \t\n]|[^ \t\n])/
                            - "## End Default Options ##"

    (* View: debian_comment
        A comment entry inside a <debian>-specific section *)
    let debian_comment =
        [ Util.indent . label "#comment" . del /##[ \t]*/ "## "
            . store debian_comment_re . eol ]

    (* View: debian_setting_re *)
    let debian_setting_re = "kopt"
                          | "groot"
                          | "alternative"
                          | "lockalternative"
                          | "defoptions"
                          | "lockold"
                          | "xenhopt"
                          | "xenkopt"
                          | "altoptions"
                          | "howmany"
                          | "memtest86"
                          | "updatedefaultentry"
                          | "savedefault"
                          | "indomU"

    (* View: debian_entry *)
    let debian_entry   = [ Util.del_str "#" . Util.indent
                         . key debian_setting_re . del /[ \t]*=/ "="
                         . value_to_eol? . eol ]

    (* View: debian
        A debian-specific section, made of <debian_entry> lines *)
    let debian         = [ label "debian"
                    . del debian_header debian_header
                    . (debian_comment|empty|debian_entry)*
                    . del debian_footer debian_footer ]

(************************************************************************
 * Group:                 LENS AND FILTER
 *************************************************************************)

    (* View: lns *)
    let lns = (comment | empty | menu_setting | boot | debian)*

    (* View: filter *)
    let filter = incl "/boot/grub/grub.conf"
               . incl "/boot/grub/menu.lst"
               . incl "/etc/grub.conf"

    let xfm = transform lns filter


Suppress .rpmnew/save files

Suppress .rpmnew/save files

(* Simple utilities used in several places *)
module Util =
  let del_str (s:string) = del s s

  let del_ws = del /[ \t]+/
  let del_ws_spc = del_ws " "
  let del_ws_tab = del_ws "\t"

  (* Split (SEP . ELT)* into an array-like tree where each match for ELT *)
  (* appears in a separate subtree. The labels for the subtrees are      *)
  (* consecutive numbers, starting at 0                                  *)
  let split (elt:lens) (sep:lens) =
    let sym = gensym "split" in
    counter sym . ( [ seq sym . sep . elt ] ) *

  (* Exclusion for files that are commonly not wanted/needed              *)
  let stdexcl = (excl "*~") . (excl "*.rpmnew") . (excl "*.rpmsave")

(* Local Variables: *)
(* mode: caml       *)
(* End:             *)

(* Simple utilities used in several places *)
module Util =
  let del_str (s:string) = del s s

  let del_ws = del /[ \t]+/
  let del_ws_spc = del_ws " "
  let del_ws_tab = del_ws "\t"

  (* Split (SEP . ELT)* into an array-like tree where each match for ELT *)
  (* appears in a separate subtree. The labels for the subtrees are      *)
  (* consecutive numbers, starting at 0                                  *)
  let split (elt:lens) (sep:lens) =
    let sym = gensym "split" in
    counter sym . ( [ seq sym . sep . elt ] ) *

  (* Exclusion for files that are commonly not wanted/needed              *)
  let stdexcl = excl "*~"

(* Local Variables: *)
(* mode: caml       *)
(* End:             *)


Sshd: add test showing how to add a Subsystem

module Test_sshd =

  let accept_env = "Protocol 2
AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT
AcceptEnv LC_IDENTIFICATION LC_ALL\n"

  test Sshd.lns get accept_env =
    { "Protocol" = "2" }
    { "AcceptEnv"
        { "1" = "LC_PAPER" }
        { "2" = "LC_NAME" }
        { "3" = "LC_ADDRESS" }
        { "4" = "LC_TELEPHONE" }
        { "5" = "LC_MEASUREMENT" } }
    { "AcceptEnv"
        { "6" = "LC_IDENTIFICATION" }
        { "7" = "LC_ALL" } }

  test Sshd.lns get "HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_dsa_key\n" =
    { "HostKey" = "/etc/ssh/ssh_host_rsa_key" }
    { "HostKey" = "/etc/ssh/ssh_host_dsa_key" }

  test Sshd.lns put accept_env after
      rm "AcceptEnv";
      rm "AcceptEnv";
      set "Protocol" "1.5";
      set "X11Forwarding" "yes"
   = "Protocol 1.5\nX11Forwarding yes\n"

  test Sshd.lns get "AuthorizedKeysFile  %h/.ssh/authorized_keys\n" =
    { "AuthorizedKeysFile" = "%h/.ssh/authorized_keys" }

  test Sshd.lns get "Subsystem sftp /usr/lib/openssh/sftp-server\n" =
    { "Subsystem"
	{ "sftp" = "/usr/lib/openssh/sftp-server" } }

  let match_blocks = "X11Forwarding yes
Match User sarko Group pres.*
  Banner /etc/bienvenue.txt
  X11Forwarding no
Match User bush Group pres.* Host white.house.*
Banner /etc/welcome.txt
"
  test Sshd.lns get match_blocks =
    { "X11Forwarding" = "yes"}
      { "Match"
	  { "Condition" { "User" = "sarko"   }
	                { "Group" = "pres.*" } }
	  { "Settings"  { "Banner" = "/etc/bienvenue.txt" }
       	                { "X11Forwarding" = "no" } } }
      { "Match"
	  { "Condition" { "User" = "bush"    }
	                { "Group" = "pres.*" }
	                { "Host"  = "white.house.*" } }
	  { "Settings"  { "Banner" = "/etc/welcome.txt" } } }

  test Sshd.lns put match_blocks after
    insb "Subsystem" "/Match[1]";
    set "/Subsystem/sftp" "/usr/libexec/openssh/sftp-server"
  = "X11Forwarding yes
Subsystem sftp /usr/libexec/openssh/sftp-server
Match User sarko Group pres.*
  Banner /etc/bienvenue.txt
  X11Forwarding no
Match User bush Group pres.* Host white.house.*
Banner /etc/welcome.txt\n"

(* Local Variables: *)
(* mode: caml       *)
(* End:             *)

module Test_sshd =

  let accept_env = "Protocol 2
AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT
AcceptEnv LC_IDENTIFICATION LC_ALL\n"

  test Sshd.lns get accept_env =
    { "Protocol" = "2" }
    { "AcceptEnv"
        { "1" = "LC_PAPER" }
        { "2" = "LC_NAME" }
        { "3" = "LC_ADDRESS" }
        { "4" = "LC_TELEPHONE" }
        { "5" = "LC_MEASUREMENT" } }
    { "AcceptEnv"
        { "6" = "LC_IDENTIFICATION" }
        { "7" = "LC_ALL" } }

  test Sshd.lns get "HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_dsa_key\n" =
    { "HostKey" = "/etc/ssh/ssh_host_rsa_key" }
    { "HostKey" = "/etc/ssh/ssh_host_dsa_key" }

  test Sshd.lns put accept_env after
      rm "AcceptEnv";
      rm "AcceptEnv";
      set "Protocol" "1.5";
      set "X11Forwarding" "yes"
   = "Protocol 1.5\nX11Forwarding yes\n"

  test Sshd.lns get "AuthorizedKeysFile  %h/.ssh/authorized_keys\n" =
    { "AuthorizedKeysFile" = "%h/.ssh/authorized_keys" }

  test Sshd.lns get "Subsystem sftp /usr/lib/openssh/sftp-server\n" =
    { "Subsystem"
	{ "sftp" = "/usr/lib/openssh/sftp-server" } }

  let match_blocks = "X11Forwarding yes
Match User sarko Group pres.*
  Banner /etc/bienvenue.txt
  X11Forwarding no
Match User bush Group pres.* Host white.house.*
Banner /etc/welcome.txt
"
  test Sshd.lns get match_blocks =
    { "X11Forwarding" = "yes"}
      { "Match"
	  { "Condition" { "User" = "sarko"   }
	                { "Group" = "pres.*" } }
	  { "Settings"  { "Banner" = "/etc/bienvenue.txt" }
       	                { "X11Forwarding" = "no" } } }
      { "Match"
	  { "Condition" { "User" = "bush"    }
	                { "Group" = "pres.*" }
	                { "Host"  = "white.house.*" } }
	  { "Settings"  { "Banner" = "/etc/welcome.txt" } } }

(* Local Variables: *)
(* mode: caml       *)
(* End:             *)


Support for SLES

Support for SLES

Added SLES vhosts path

(* Apache HTTPD lens for Augeas

Authors:
  David Lutterkort <lutter@redhat.com>
  Francis Giraldeau <francis.giraldeau@usherbrooke.ca>
  Raphael Pinson <raphink@gmail.com>

About: Reference
  Online Apache configuration manual: http://httpd.apache.org/docs/trunk/

About: License
    This file is licensed under the LGPL v2+.

About: Lens Usage
  Sample usage of this lens in augtool

  Apache configuration is represented by two main structures, nested sections
  and directives. Sections are used as labels, while directives are kept as a
  value. Sections and directives can have positional arguments inside values
  of "arg" nodes. Arguments of sections must be the firsts child of the
  section node.

  This lens doesn't support automatic string quoting. Hence, the string must
  be quoted when containing a space.

  Create a new VirtualHost section with one directive:
  > clear /files/etc/apache2/sites-available/foo/VirtualHost
  > set /files/etc/apache2/sites-available/foo/VirtualHost/arg "172.16.0.1:80"
  > set /files/etc/apache2/sites-available/foo/VirtualHost/directive "ServerAdmin"
  > set /files/etc/apache2/sites-available/foo/VirtualHost/*[self::directive="ServerAdmin"]/arg "admin@example.com"

About: Configuration files
  This lens applies to files in /etc/httpd and /etc/apache2. See <filter>.

*)

module Httpd =

autoload xfm

(******************************************************************
 *                           Utilities lens
 *****************************************************************)
let dels (s:string)     = del s s

(* deal with continuation lines *)
let sep_spc    =  del /([ \t]+|[ \t]*\\\\\r?\n[ \t]*)/ " "

let sep_osp             = Sep.opt_space
let sep_eq              = del /[ \t]*=[ \t]*/ "="

let nmtoken             = /[a-zA-Z:_][a-zA-Z0-9:_.-]*/
let word                = /[a-zA-Z][a-zA-Z0-9._-]*/

let comment             = Util.comment
let eol                 = Util.doseol
let empty               = Util.empty_dos
let indent              = Util.indent

(* borrowed from shellvars.aug *)
let char_arg_dir  = /[^\\ '"\t\r\n]|\\\\"|\\\\'/
let char_arg_sec  = /[^ '"\t\r\n>]|\\\\"|\\\\'/
let cdot = /\\\\./
let cl = /\\\\\n/
let dquot =
     let no_dquot = /[^"\\\r\n]/
  in /"/ . (no_dquot|cdot|cl)* . /"/
let squot =
     let no_squot = /[^'\\\r\n]/
  in /'/ . (no_squot|cdot|cl)* . /'/
let comp = /[<>=]?=/

(******************************************************************
 *                            Attributes
 *****************************************************************)

let arg_dir = [ label "arg" . store (char_arg_dir+|dquot|squot) ]
let arg_sec = [ label "arg" . store (char_arg_sec+|comp|dquot|squot) ]

let argv (l:lens) = l . (sep_spc . l)*

let directive = [ indent . label "directive" . store word .
                  (sep_spc . argv arg_dir)? . eol ]

let section (body:lens) =
    (* opt_eol includes empty lines *)
    let opt_eol = del /([ \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 kword = key word in
    let dword = del word "a" in
        [ indent . dels "<" . square kword inner dword . del ">" ">" . eol ]

let rec content = section (content|directive)

let lns = (content|directive|comment|empty)*

let filter = (incl "/etc/apache2/apache2.conf") .
             (incl "/etc/apache2/httpd.conf") .
             (incl "/etc/apache2/ports.conf") .
             (incl "/etc/apache2/conf.d/*") .
             (incl "/etc/apache2/conf-available/*.conf") .
             (incl "/etc/apache2/mods-available/*") .
             (incl "/etc/apache2/sites-available/*") .
             (incl "/etc/apache2/vhosts.d/*.conf") .
             (incl "/etc/httpd/conf.d/*.conf") .
             (incl "/etc/httpd/httpd.conf") .
             (incl "/etc/httpd/conf/httpd.conf") .
             Util.stdexcl

let xfm = transform lns filter

(* Apache HTTPD lens for Augeas

Authors:
  David Lutterkort <lutter@redhat.com>
  Francis Giraldeau <francis.giraldeau@usherbrooke.ca>
  Raphael Pinson <raphink@gmail.com>

About: Reference
  Online Apache configuration manual: http://httpd.apache.org/docs/trunk/

About: License
    This file is licensed under the LGPL v2+.

About: Lens Usage
  Sample usage of this lens in augtool

  Apache configuration is represented by two main structures, nested sections
  and directives. Sections are used as labels, while directives are kept as a
  value. Sections and directives can have positional arguments inside values
  of "arg" nodes. Arguments of sections must be the firsts child of the
  section node.

  This lens doesn't support automatic string quoting. Hence, the string must
  be quoted when containing a space.

  Create a new VirtualHost section with one directive:
  > clear /files/etc/apache2/sites-available/foo/VirtualHost
  > set /files/etc/apache2/sites-available/foo/VirtualHost/arg "172.16.0.1:80"
  > set /files/etc/apache2/sites-available/foo/VirtualHost/directive "ServerAdmin"
  > set /files/etc/apache2/sites-available/foo/VirtualHost/*[self::directive="ServerAdmin"]/arg "admin@example.com"

About: Configuration files
  This lens applies to files in /etc/httpd and /etc/apache2. See <filter>.

*)

module Httpd =

autoload xfm

(******************************************************************
 *                           Utilities lens
 *****************************************************************)
let dels (s:string)     = del s s

(* deal with continuation lines *)
let sep_spc    =  del /([ \t]+|[ \t]*\\\\\r?\n[ \t]*)/ " "

let sep_osp             = Sep.opt_space
let sep_eq              = del /[ \t]*=[ \t]*/ "="

let nmtoken             = /[a-zA-Z:_][a-zA-Z0-9:_.-]*/
let word                = /[a-zA-Z][a-zA-Z0-9._-]*/

let comment             = Util.comment
let eol                 = Util.doseol
let empty               = Util.empty_dos
let indent              = Util.indent

(* borrowed from shellvars.aug *)
let char_arg_dir  = /[^\\ '"\t\r\n]|\\\\"|\\\\'/
let char_arg_sec  = /[^ '"\t\r\n>]|\\\\"|\\\\'/
let cdot = /\\\\./
let cl = /\\\\\n/
let dquot =
     let no_dquot = /[^"\\\r\n]/
  in /"/ . (no_dquot|cdot|cl)* . /"/
let squot =
     let no_squot = /[^'\\\r\n]/
  in /'/ . (no_squot|cdot|cl)* . /'/
let comp = /[<>=]?=/

(******************************************************************
 *                            Attributes
 *****************************************************************)

let arg_dir = [ label "arg" . store (char_arg_dir+|dquot|squot) ]
let arg_sec = [ label "arg" . store (char_arg_sec+|comp|dquot|squot) ]

let argv (l:lens) = l . (sep_spc . l)*

let directive = [ indent . label "directive" . store word .
                  (sep_spc . argv arg_dir)? . eol ]

let section (body:lens) =
    (* opt_eol includes empty lines *)
    let opt_eol = del /([ \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 kword = key word in
    let dword = del word "a" in
        [ indent . dels "<" . square kword inner dword . del ">" ">" . eol ]

let rec content = section (content|directive)

let lns = (content|directive|comment|empty)*

let filter = (incl "/etc/apache2/apache2.conf") .
             (incl "/etc/apache2/httpd.conf") .
             (incl "/etc/apache2/ports.conf") .
             (incl "/etc/apache2/conf.d/*") .
             (incl "/etc/apache2/conf-available/*.conf") .
             (incl "/etc/apache2/mods-available/*") .
             (incl "/etc/apache2/sites-available/*") .
             (incl "/etc/httpd/conf.d/*.conf") .
             (incl "/etc/httpd/httpd.conf") .
             (incl "/etc/httpd/conf/httpd.conf") .
             Util.stdexcl

let xfm = transform lns filter


Mysql: include /etc/my.cnf used on Fedora/RHEL

Fixes https://bugzilla.redhat.com/show_bug.cgi?id=688053

(* MySQL module for Augeas                    *)
(* Author: Tim Stoop <tim@kumina.nl>          *)
(* Heavily based on php.aug by Raphael Pinson *)
(* <raphink@gmail.com>                        *)
(*                                            *)

module MySQL =
  autoload xfm

(************************************************************************
 * INI File settings
 *************************************************************************)
let comment  = IniFile.comment IniFile.comment_re "#"

let sep      = IniFile.sep IniFile.sep_re IniFile.sep_default

let entry    = [ key IniFile.entry_re . sep . IniFile.sto_to_comment . (comment|IniFile.eol) ] |
               [ key IniFile.entry_re . store // . (comment|IniFile.eol) ] |
               [ key /\![A-Za-z][A-Za-z0-9\._-]+/ . del / / " " . store /\/[A-Za-z0-9\.\/_-]+/ . (comment|IniFile.eol) ] |
               comment

(************************************************************************
 * sections, led by a "[section]" header
 * We can't use titles as node names here since they could contain "/"
 * We remove #comment from possible keys
 * since it is used as label for comments
 * We also remove / as first character
 * because augeas doesn't like '/' keys (although it is legal in INI Files)
 *************************************************************************)
let title   = IniFile.indented_title_label "target" IniFile.record_label_re
let record  = IniFile.record title entry

let lns    = IniFile.lns record comment

let filter = (incl "/etc/mysql/my.cnf")
             . (incl "/etc/mysql/conf.d/*.cnf")
             . (incl "/etc/my.cnf")
             . Util.stdexcl

let xfm = transform lns filter

(* MySQL module for Augeas                    *)
(* Author: Tim Stoop <tim@kumina.nl>          *)
(* Heavily based on php.aug by Raphael Pinson *)
(* <raphink@gmail.com>                        *)
(*                                            *)

module MySQL =
  autoload xfm

(************************************************************************
 * INI File settings
 *************************************************************************)
let comment  = IniFile.comment IniFile.comment_re "#"

let sep      = IniFile.sep IniFile.sep_re IniFile.sep_default

let entry    = [ key IniFile.entry_re . sep . IniFile.sto_to_comment . (comment|IniFile.eol) ] |
               [ key IniFile.entry_re . store // . (comment|IniFile.eol) ] |
               [ key /\![A-Za-z][A-Za-z0-9\._-]+/ . del / / " " . store /\/[A-Za-z0-9\.\/_-]+/ . (comment|IniFile.eol) ] |
               comment

(************************************************************************
 * sections, led by a "[section]" header
 * We can't use titles as node names here since they could contain "/"
 * We remove #comment from possible keys
 * since it is used as label for comments
 * We also remove / as first character
 * because augeas doesn't like '/' keys (although it is legal in INI Files)
 *************************************************************************)
let title   = IniFile.indented_title_label "target" IniFile.record_label_re
let record  = IniFile.record title entry

let lns    = IniFile.lns record comment

let filter = (incl "/etc/mysql/my.cnf")
             . (incl "/etc/mysql/conf.d/*.cnf")
             . Util.stdexcl

let xfm = transform lns filter


Cron: Fix parsing of numeric fields

Cron: Fix parsing of numeric fields

In:

  let num = /[0-9\*][0-9\/,-\*]*/

the hyphen was treated as a range, not a literal character. This
erroneously allowed a whole bunch of characters (including all uppercase
letters) in numeric fields.

Signed-off-by: Michael Chapman <a17fed27eaa842282862ff7c1b9c8395a26ac320@very.puzzling.org>

(*
Module: Cron
 Parses /etc/cron.d/*, /etc/crontab

Author: Raphael Pinson <raphink@gmail.com>

About: Reference
 This lens tries to keep as close as possible to `man 5 crontab` where
 possible.

About: License
  This file is licensed under the LGPLv2+, like the rest of Augeas.

About: Lens Usage
  Sample usage of this lens in augtool

    * Get the entry that launches '/usr/bin/ls'
      >  match '/files/etc/crontab/entry[. = "/usr/bin/ls"]'

About: Configuration files
  This lens applies to /etc/cron.d/* and /etc/crontab. See <filter>.
*)

module Cron =
  autoload xfm

(************************************************************************
 * Group:                 USEFUL PRIMITIVES
 *************************************************************************)

(* Group: Generic primitives *)

(* Variable: eol *)
let eol     = Util.eol

(* Variable: indent *)
let indent  = Util.indent

(* Variable: comment *)
let comment = Util.comment

(* Variable: empty *)
let empty   = Util.empty

(* Variable: num *)
let num        = /[0-9*][0-9\/,*-]*/

(* Variable: alpha *)
let alpha      = /[A-Za-z]{3}/

(* Variable: alphanum *)
let alphanum   = (num|alpha) . ("-" . (num|alpha))?

(* Group: Separators *)

(* Variable: sep_spc *)
let sep_spc = Util.del_ws_spc

(* Variable: sep_eq *)
let sep_eq  = Util.del_str "="

(************************************************************************
 * Group:                       ENTRIES
 *************************************************************************)

(************************************************************************
 * View: shellvar
 *   A shell variable in crontab
 *************************************************************************)

let shellvar =
  let key_re = Shellvars.key_re - "entry" in
  [ key key_re . sep_eq . Shellvars.simple_value . eol ]

(* View: minute *)
let minute     = [ label "minute"       . store num ]

(* View: hour *)
let hour       = [ label "hour"         . store num ]

(* View: dayofmonth *)
let dayofmonth = [ label "dayofmonth" . store num ]

(* View: month *)
let month      = [ label "month"        . store alphanum ]

(* View: dayofweek *)
let dayofweek  = [ label "dayofweek"  . store alphanum ]

(* View: user *)
let user       = [ label "user"         . store Rx.word ]

(************************************************************************
 * View: time
 *   Time in the format "minute hour dayofmonth month dayofweek"
 *************************************************************************)
let time        = [ label "time" .
                  minute . sep_spc . hour  . sep_spc . dayofmonth
                         . sep_spc . month . sep_spc . dayofweek ]

(* Variable: the valid values for schedules *)
let schedule_re = "reboot" | "yearly" | "annually" | "monthly"
                | "weekly" | "daily"  | "midnight" | "hourly"

(************************************************************************
 * View: schedule
 *   Time in the format "@keyword"
 *************************************************************************)
let schedule    = [ label "schedule" . Util.del_str "@"
                   . store schedule_re ]

(************************************************************************
 * View: entry
 *   A crontab entry
 *************************************************************************)

let entry       = [ label "entry" . indent
                   . ( time | schedule )
                   . sep_spc . user
                   . sep_spc . store Rx.space_in . eol ]

(*
 * View: lns
 *   The cron lens
 *)
let lns = ( empty | comment | shellvar | entry )*

(* Variable: filter *)
let filter =
  incl "/etc/cron.d/*" .
  incl "/etc/crontab" .
  Util.stdexcl

let xfm = transform lns filter

(*
Module: Cron
 Parses /etc/cron.d/*, /etc/crontab

Author: Raphael Pinson <raphink@gmail.com>

About: Reference
 This lens tries to keep as close as possible to `man 5 crontab` where
 possible.

About: License
  This file is licensed under the LGPLv2+, like the rest of Augeas.

About: Lens Usage
  Sample usage of this lens in augtool

    * Get the entry that launches '/usr/bin/ls'
      >  match '/files/etc/crontab/entry[. = "/usr/bin/ls"]'

About: Configuration files
  This lens applies to /etc/cron.d/* and /etc/crontab. See <filter>.
*)

module Cron =
  autoload xfm

(************************************************************************
 * Group:                 USEFUL PRIMITIVES
 *************************************************************************)

(* Group: Generic primitives *)

(* Variable: eol *)
let eol     = Util.eol

(* Variable: indent *)
let indent  = Util.indent

(* Variable: comment *)
let comment = Util.comment

(* Variable: empty *)
let empty   = Util.empty

(* Variable: num *)
let num        = /[0-9\*][0-9\/,-\*]*/

(* Variable: alpha *)
let alpha      = /[A-Za-z]{3}/

(* Variable: alphanum *)
let alphanum   = (num|alpha) . ("-" . (num|alpha))?

(* Group: Separators *)

(* Variable: sep_spc *)
let sep_spc = Util.del_ws_spc

(* Variable: sep_eq *)
let sep_eq  = Util.del_str "="

(************************************************************************
 * Group:                       ENTRIES
 *************************************************************************)

(************************************************************************
 * View: shellvar
 *   A shell variable in crontab
 *************************************************************************)

let shellvar =
  let key_re = Shellvars.key_re - "entry" in
  [ key key_re . sep_eq . Shellvars.simple_value . eol ]

(* View: minute *)
let minute     = [ label "minute"       . store num ]

(* View: hour *)
let hour       = [ label "hour"         . store num ]

(* View: dayofmonth *)
let dayofmonth = [ label "dayofmonth" . store num ]

(* View: month *)
let month      = [ label "month"        . store alphanum ]

(* View: dayofweek *)
let dayofweek  = [ label "dayofweek"  . store alphanum ]

(* View: user *)
let user       = [ label "user"         . store Rx.word ]

(************************************************************************
 * View: time
 *   Time in the format "minute hour dayofmonth month dayofweek"
 *************************************************************************)
let time        = [ label "time" .
                  minute . sep_spc . hour  . sep_spc . dayofmonth
                         . sep_spc . month . sep_spc . dayofweek ]

(* Variable: the valid values for schedules *)
let schedule_re = "reboot" | "yearly" | "annually" | "monthly"
                | "weekly" | "daily"  | "midnight" | "hourly"

(************************************************************************
 * View: schedule
 *   Time in the format "@keyword"
 *************************************************************************)
let schedule    = [ label "schedule" . Util.del_str "@"
                   . store schedule_re ]

(************************************************************************
 * View: entry
 *   A crontab entry
 *************************************************************************)

let entry       = [ label "entry" . indent
                   . ( time | schedule )
                   . sep_spc . user
                   . sep_spc . store Rx.space_in . eol ]

(*
 * View: lns
 *   The cron lens
 *)
let lns = ( empty | comment | shellvar | entry )*

(* Variable: filter *)
let filter =
  incl "/etc/cron.d/*" .
  incl "/etc/crontab" .
  Util.stdexcl

let xfm = transform lns filter


OpenShift_Quickstarts: Use Json.lns lens

(*
Module: OpenShift_Quickstarts
  Parses
    - /etc/openshift/quickstarts.json

Author: Brian Redbeard <redbeard@dead-city.org>

About: License
   This file is licenced under the LGPL v2+, conforming to the other components
   of Augeas.

About: Lens Usage
  Sample usage of this lens in augtool:

    * Get your current setup
      > print /files/etc/openshift/quickstarts.json
      ...

    * Delete the quickstart named WordPress
      > rm /files/etc/openshift/quickstarts.json/array/dict[*]/entry/dict/entry[*][string = 'WordPress']/../../../

  Saving your file:

      > save

About: Configuration files
        /etc/openshift/quickstarts.json - Quickstarts available via the
            OpenShift Console.

About: Examples
   The <Test_OpenShift_Quickstarts> file contains various examples and tests.
*)
module OpenShift_Quickstarts =
    autoload xfm

(* View: lns *)
let lns = Json.lns

(* Variable: filter *)
let filter = incl "/etc/openshift/quickstarts.json"

let xfm = transform lns filter
(* vim: set ts=4  expandtab  sw=4: *)

(*
Module: OpenShift_Quickstarts
  Parses
    - /etc/openshift/quickstarts.json

Author: Brian Redbeard <redbeard@dead-city.org>

About: License
   This file is licenced under the LGPL v2+, conforming to the other components
   of Augeas.

About: Lens Usage
  Sample usage of this lens in augtool:

    * Get your current setup
      > print /files/etc/openshift/quickstarts.json
      ...

    * Delete the quickstart named WordPress
      > rm /files/etc/openshift/quickstarts.json/array/dict[*]/entry/dict/entry[*][string = 'WordPress']/../../../

  Saving your file:

      > save

About: Configuration files
        /etc/openshift/quickstarts.json - Quickstarts available via the
            OpenShift Console.

About: Examples
   The <Test_OpenShift_Quickstarts> file contains various examples and tests.
*)
module OpenShift_Quickstarts =
    autoload xfm

(* View: json *)
let json = Json.lns

(* View: lns *)
let lns = (Util.empty | json )*

(* Variable: filter *)
let filter = incl "/etc/openshift/quickstarts.json"

let xfm = transform lns filter
(* vim: set ts=4  expandtab  sw=4: *)


Httpd: add test to verify that issue #140 is fixed

Issue: https://github.com/hercules-team/augeas/issues/140

module Test_httpd =

(* Check that we can iterate on directive *)
let _ = Httpd.directive+

(* Check that we can do a non iterative section *)
let _ = Httpd.section Httpd.directive

(* directives testing *)
let d1 = "ServerRoot \"/etc/apache2\"\n"
test Httpd.directive get d1 =
  { "directive" = "ServerRoot"
    { "arg" = "\"/etc/apache2\"" }
  }

(* simple quotes *)
let d1s = "ServerRoot '/etc/apache2'\n"
test Httpd.directive get d1s =
  { "directive" = "ServerRoot"
    { "arg" = "'/etc/apache2'" }
  }

let d2 = "ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/\n"
test Httpd.directive get d2 =
  { "directive" = "ScriptAlias"
    { "arg" = "/cgi-bin/" }
    { "arg" = "/usr/lib/cgi-bin/" }
  }

let d3 = "LockFile /var/lock/apache2/accept.lock\n"
test Httpd.directive get d3 =
  { "directive" = "LockFile"
    { "arg" = "/var/lock/apache2/accept.lock" }
  }

let c1 = "
<IfModule>
</IfModule>
"
let c1_put =
"
<IfModule foo bar>
</IfModule>
"

test Httpd.lns get c1 = { }{ "IfModule" }

test Httpd.lns put c1 after set "/IfModule/arg[1]" "foo";
                            set "/IfModule/arg[2]" "bar" = c1_put

let c2 = "
<IfModule !mpm_winnt.c>
  <IfModule !mpm_netware.c>
    LockFile /var/lock/apache2/accept.lock
  </IfModule>
</IfModule>
"

test Httpd.lns get c2 =
  {  }
  { "IfModule"
    { "arg" = "!mpm_winnt.c" }
    { "IfModule"
      { "arg" = "!mpm_netware.c" }
      { "directive" = "LockFile"
        { "arg" = "/var/lock/apache2/accept.lock" }
      }
    }
  }

(* arguments must be the first child of the section *)
test Httpd.lns put c2 after rm "/IfModule/arg";
                            insb "arg" "/IfModule/*[1]";
                            set "/IfModule/arg" "foo"  =
"
<IfModule foo>
  <IfModule !mpm_netware.c>
    LockFile /var/lock/apache2/accept.lock
  </IfModule>
</IfModule>
"

let c3 = "
<IfModule mpm_event_module>
    StartServers          2
    MaxClients          150
    MinSpareThreads      25
    MaxSpareThreads      75
    ThreadLimit          64
    ThreadsPerChild      25
    MaxRequestsPerChild   0
</IfModule>
"

test Httpd.lns get c3 =
  {  }
  { "IfModule"
    { "arg" = "mpm_event_module" }
    { "directive" = "StartServers"
      { "arg" = "2" }
    }
    { "directive" = "MaxClients"
      { "arg" = "150" }
    }
    { "directive" = "MinSpareThreads"
      { "arg" = "25" }
    }
    { "directive" = "MaxSpareThreads"
      { "arg" = "75" }
    }
    { "directive" = "ThreadLimit"
      { "arg" = "64" }
    }
    { "directive" = "ThreadsPerChild"
      { "arg" = "25" }
    }
    { "directive" = "MaxRequestsPerChild"
      { "arg" = "0" }
    }
  }

let c4 = "
<Files ~ \"^\.ht\">
    Order allow,deny
    Deny from all
    Satisfy all
</Files>
"

test Httpd.lns get c4 =
  {  }
  { "Files"
    { "arg" = "~" }
    { "arg" = "\"^\.ht\"" }
    { "directive" = "Order"
      { "arg" = "allow,deny" }
    }
    { "directive" = "Deny"
      { "arg" = "from" }
      { "arg" = "all" }
    }
    { "directive" = "Satisfy"
      { "arg" = "all" }
    }
  }

let c5 = "LogFormat \"%{User-agent}i\" agent\n"
test Httpd.lns get c5 =
  { "directive" = "LogFormat"
    { "arg" = "\"%{User-agent}i\"" }
    { "arg" = "agent" }
  }

let c7 = "LogFormat \"%v:%p %h %l %u %t \\"%r\\" %>s %O \\"%{Referer}i\\" \\"%{User-Agent}i\\"\" vhost_combined\n"
test Httpd.lns get c7 =
  { "directive" = "LogFormat"
    { "arg" = "\"%v:%p %h %l %u %t \\"%r\\" %>s %O \\"%{Referer}i\\" \\"%{User-Agent}i\\"\"" }
    { "arg" = "vhost_combined" }
  }

let c8 = "IndexIgnore .??* *~ *# RCS CVS *,v *,t \n"
test Httpd.directive get c8 =
  { "directive" = "IndexIgnore"
    { "arg" = ".??*" }
    { "arg" = "*~" }
    { "arg" = "*#" }
    { "arg" = "RCS" }
    { "arg" = "CVS" }
    { "arg" = "*,v" }
    { "arg" = "*,t" }
  }

(* FIXME: not yet supported:
 * The backslash "\" may be used as the last character on a line to indicate
 * that the directive continues onto the next line. There must be no other
 * characters or white space between the backslash and the end of the line.
 *)
let multiline = "Options Indexes \
FollowSymLinks MultiViews
"

test Httpd.directive get multiline =
  { "directive" = "Options"
    { "arg" = "Indexes" }
    { "arg" = "FollowSymLinks" }
    { "arg" = "MultiViews" }
  }

let conf2 = "<VirtualHost *:80>
    ServerAdmin webmaster@localhost

    DocumentRoot /var/www
    <Directory />
        Options FollowSymLinks
        AllowOverride None
    </Directory>
    <Directory /var/www/>
        Options Indexes FollowSymLinks MultiViews
        AllowOverride None
        Order allow,deny
        allow from all
    </Directory>

    ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
    <Directory \"/usr/lib/cgi-bin\">
        AllowOverride None
        Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
        Order allow,deny
        Allow from all
    </Directory>

    ErrorLog /var/log/apache2/error.log

    # Possible values include: debug, info, notice, warn, error, crit,
    # alert, emerg.
    LogLevel warn

    CustomLog /var/log/apache2/access.log combined

    SSLRequireSSL

    Alias /doc/ \"/usr/share/doc/\"
    <Directory \"/usr/share/doc/\">
        Options Indexes MultiViews FollowSymLinks
        AllowOverride None
        Order deny,allow
        Deny from all
        Allow from 127.0.0.0/255.0.0.0 ::1/128
    </Directory>

</VirtualHost>
"

test Httpd.lns get conf2 =
   { "VirtualHost"
    { "arg" = "*:80" }
    { "directive" = "ServerAdmin"
      { "arg" = "webmaster@localhost" }
    }
    {  }
    { "directive" = "DocumentRoot"
      { "arg" = "/var/www" }
    }
    { "Directory"
      { "arg" = "/" }
      { "directive" = "Options"
        { "arg" = "FollowSymLinks" }
      }
      { "directive" = "AllowOverride"
        { "arg" = "None" }
      }
    }
    { "Directory"
      { "arg" = "/var/www/" }
      { "directive" = "Options"
        { "arg" = "Indexes" }
        { "arg" = "FollowSymLinks" }
        { "arg" = "MultiViews" }
      }
      { "directive" = "AllowOverride"
        { "arg" = "None" }
      }
      { "directive" = "Order"
        { "arg" = "allow,deny" }
      }
      { "directive" = "allow"
        { "arg" = "from" }
        { "arg" = "all" }
      }
    }
    {  }
    { "directive" = "ScriptAlias"
      { "arg" = "/cgi-bin/" }
      { "arg" = "/usr/lib/cgi-bin/" }
    }
    { "Directory"
      { "arg" = "\"/usr/lib/cgi-bin\"" }
      { "directive" = "AllowOverride"
        { "arg" = "None" }
      }
      { "directive" = "Options"
        { "arg" = "+ExecCGI" }
        { "arg" = "-MultiViews" }
        { "arg" = "+SymLinksIfOwnerMatch" }
      }
      { "directive" = "Order"
        { "arg" = "allow,deny" }
      }
      { "directive" = "Allow"
        { "arg" = "from" }
        { "arg" = "all" }
      }
    }
    {  }
    { "directive" = "ErrorLog"
      { "arg" = "/var/log/apache2/error.log" }
    }
    {  }
    { "#comment" = "Possible values include: debug, info, notice, warn, error, crit," }
    { "#comment" = "alert, emerg." }
    { "directive" = "LogLevel"
      { "arg" = "warn" }
    }
    {  }
    { "directive" = "CustomLog"
      { "arg" = "/var/log/apache2/access.log" }
      { "arg" = "combined" }
    }
    {  }
    { "directive" = "SSLRequireSSL" }
    {  }
    { "directive" = "Alias"
      { "arg" = "/doc/" }
      { "arg" = "\"/usr/share/doc/\"" }
    }
    { "Directory"
      { "arg" = "\"/usr/share/doc/\"" }
      { "directive" = "Options"
        { "arg" = "Indexes" }
        { "arg" = "MultiViews" }
        { "arg" = "FollowSymLinks" }
      }
      { "directive" = "AllowOverride"
        { "arg" = "None" }
      }
      { "directive" = "Order"
        { "arg" = "deny,allow" }
      }
      { "directive" = "Deny"
        { "arg" = "from" }
        { "arg" = "all" }
      }
      { "directive" = "Allow"
        { "arg" = "from" }
        { "arg" = "127.0.0.0/255.0.0.0" }
        { "arg" = "::1/128" }
      }
    }
    {  }
  }

(* Eol comment *)
test Httpd.lns get "<a> # a comment
MyDirective Foo
</a>\n" =
  { "a"
    { "#comment" = "a comment" }
    { "directive" = "MyDirective" { "arg" = "Foo" } } }

test Httpd.lns get "<a>
# a comment
</a>\n" =
  { "a" { "#comment" = "a comment" } }

(* Test: Httpd.lns
     Newlines inside quoted value (GH issue #104) *)
test Httpd.lns get "Single 'Foo\\
bar'
Double \"Foo\\
bar\"\n" =
  { "directive" = "Single"
    { "arg" = "'Foo\\\nbar'" } }
  { "directive" = "Double"
    { "arg" = "\"Foo\\\nbar\"" } }

(* Test: Httpd.lns
     Support >= in tags (GH #154) *)
let versioncheck = "
<IfVersion = 2.1>
<IfModule !proxy_ajp_module>
LoadModule proxy_ajp_module modules/mod_proxy_ajp.so
</IfModule>
</IfVersion>

<IfVersion >= 2.4>
<IfModule !proxy_ajp_module>
LoadModule proxy_ajp_module modules/mod_proxy_ajp.so
</IfModule>
</IfVersion>
"

test Httpd.lns get versioncheck =
  { }
  { "IfVersion"
    { "arg" = "=" }
    { "arg" = "2.1" }
    { "IfModule"
      { "arg" = "!proxy_ajp_module" }
      { "directive" = "LoadModule"
        { "arg" = "proxy_ajp_module" }
        { "arg" = "modules/mod_proxy_ajp.so" }
      }
    }
  }
  {}
  { "IfVersion"
    { "arg" = ">=" }
    { "arg" = "2.4" }
    { "IfModule"
      { "arg" = "!proxy_ajp_module" }
      { "directive" = "LoadModule"
        { "arg" = "proxy_ajp_module" }
        { "arg" = "modules/mod_proxy_ajp.so" }
      }
    }
  }

(* GH #220 *)
let double_comment = "<IfDefine Foo>
##
## Comment
##
</IfDefine>\n"

test Httpd.lns get double_comment =
  { "IfDefine"
    { "arg" = "Foo" }
    { "#comment" = "#" }
    { "#comment" = "# Comment" }
    { "#comment" = "#" }
  }

let single_comment = "<IfDefine Foo>
#
## Comment
##
</IfDefine>\n"

test Httpd.lns get single_comment =
  { "IfDefine"
    { "arg" = "Foo" }
    { "#comment" = "# Comment" }
    { "#comment" = "#" }
  }

let single_empty = "<IfDefine Foo>
#

</IfDefine>\n"
test Httpd.lns get single_empty =
  { "IfDefine"
    { "arg" = "Foo" }
  }

let eol_empty = "<IfDefine Foo> #
</IfDefine>\n"
test Httpd.lns get eol_empty =
  { "IfDefine"
    { "arg" = "Foo" }
  }

(* Issue #140 *)
test Httpd.lns get "<IfModule mod_ssl.c>
    # one comment
    # another comment
</IfModule>\n" =
  { "IfModule"
    { "arg" = "mod_ssl.c" }
    { "#comment" = "one comment" }
    { "#comment" = "another comment" }
  }


Group: add test for disabled account ('!' in password field)

module Test_group =

let conf = "bin:x:2:
audio:x:29:joe
avahi-autoipd:!:113:bill,martha
"

test Group.lns get conf =
   { "bin"
     { "password" = "x" }
     { "gid" = "2" } }
   { "audio"
     { "password" = "x" }
     { "gid" = "29" }
     { "user" = "joe" } }
   { "avahi-autoipd"
     { "password" = "!" }
     { "gid" = "113" }
     { "user" = "bill"}
     { "user" = "martha"} }

(* Password field can be empty *)
test Group.lns get "root::0:root\n" =
  { "root"
    { "password" = "" }
    { "gid" = "0" }
    { "user" = "root" } }

(* Password field can be disabled by ! or * *)
test Group.lns get "testgrp:!:0:testusr\n" =
  { "testgrp"
    { "password" = "!" }
    { "gid" = "0" }
    { "user" = "testusr" } }

test Group.lns get "testgrp:*:0:testusr\n" =
  { "testgrp"
    { "password" = "*" }
    { "gid" = "0" }
    { "user" = "testusr" } }

module Test_group =

let conf = "bin:x:2:
audio:x:29:joe
avahi-autoipd:x:113:bill,martha
"

test Group.lns get conf =
   { "bin"
     { "password" = "x" }
     { "gid" = "2" } }
   { "audio"
     { "password" = "x" }
     { "gid" = "29" }
     { "user" = "joe" } }
   { "avahi-autoipd"
     { "password" = "x" }
     { "gid" = "113" }
     { "user" = "bill"}
     { "user" = "martha"} }

(* Password field can be empty *)
test Group.lns get "root::0:root\n" =
  { "root"
    { "password" = "" }
    { "gid" = "0" }
    { "user" = "root" } }

(* Password field can be disabled by ! or * *)
test Group.lns get "testgrp:!:0:testusr\n" =
  { "testgrp"
    { "password" = "!" }
    { "gid" = "0" }
    { "user" = "testusr" } }

test Group.lns get "testgrp:*:0:testusr\n" =
  { "testgrp"
    { "password" = "*" }
    { "gid" = "0" }
    { "user" = "testusr" } }