As of version 2.0 fli4l is split into modules (packages), i.e.
With the base package fli4l acts as a pure Ethernet router. For ISDN and/or DSL the packages isdn and/or dsl have to be unpacked to the fli4l directory. The same applies for the other packages.
Depending on the current configuration a file called rc.cfg and two archives rootfs.img and opt.img will be generated which contain all required configuration informations and files. These files are generated using mkfli4l which reads the individual package files and checks for configuration errors.
mkfli4l will accept the parameters listed in table 8.1. If omitted the default values noted in brackets are used. A complete list of all options (Table 8.1) is displayed when executing
mkfli4l -h.
Option | Meaning | |
-c, - -config | Declaration of the directory mkfli4l will scan for package config files (default: config) | |
-x, - -check | Declaration of the directory mkfli4l will scan for files needed for package error checking (<package>.txt, <package>.exp and <package>.ext; default: check) | |
-l, - -log | Declaration of the log file to which mkfli4l will log error messages and warnings (default: img/mkfli4l.log) | |
-p, - -package | Declaration of the packages to be checked, this option may be used more than once in case of a desired check for several packages in conjunction. If using -p, however, the file <check_dir>/base.exp will always be read first to provide the common regular expressions provided by the base package. Hence, this file must exist. | |
-i, - -info | Provides information on the check (which files are read, which tests are run, which uncommon things happened during the review process) | |
-v, - -verbose | More verbose variant of option -i | |
-h, - -help | Displays the help text | |
-d, - -debug | Allows for debugging the review process. This is meant to be a help for package developers wishing to know in detail how the process of package checking is working. | |
Debug Option | Meaning | |
check | show check process | |
zip-list | show generation of zip list | |
zip-list-skipped | show skipped files | |
zip-list-regexp | show regular expressions for ziplist | |
opt-files | check all files in opt/<package>.txt | |
ext-trace | show trace of extended checks |
A package can contain multiple OPTs, if it contains only one, however, it is appropriate to name the package like the OPT. Below <PACKAGE> is to be replaced by the respective package name. A package consists of the following parts:
The individual parts are described in more detail below.
The user's changes to the package's configuration are made in the file config/<PACKAGE>.txt. All the OPT's variables should begin with the name of the OPT, for example:
#------------------------------------------------------------------- # Optional package: TELNETD #------------------------------------------------------------------- OPT_TELNETD='no' # install telnetd: yes or no TELNETD_PORT='23' # telnet port, see also FIREWALL_DENY_PORT_x
An OPT should be prefixed by a header in the configuration file (see above). This increases readability, especially as a package indeed can contain multiple OPTs. Variables associated to the OPT should -- again in the interest of readability -- not be indented further. Comments and blank lines are allowed, with comments always starting in column 33. If a variable including its content has more than 32 characters, the comment should be inserted with a row offset, starting in column 33. Longer comments are spread over multiple lines, each starting at column 33. All this increases easy review of the configuration file.
All values following the equal sign must be enclosed in quotes8.1not doing so can lead to problems during system boot.
Activated variables (see below), will be transferred to rc.cfg, everything else will be ignored. The only exceptions are variables by the name of <PACKAGE>_DO_DEBUG. These are used for debugging and are transferred as is.
The file opt/<PACKAGE>.txt contains instructions that describe
Based on this information mkfli4l will generate the archives needed.
Blank lines and lines beginning with ``#'' are ignored. In one of the first lines the version of the package file format should be noted as follows:
<first column> <second column> <third column> opt_format_version 1 -
The remaining lines have the following syntax:
<first column> <second column> <third column> <columns following> Variable Value File Options
If multiple variables should be tested for the same value a list of variables (separated by commas) can be used instead. It is sufficient in this case if at least one variable contains the value required in the second column. It is important not to use spaces between the individual variables!
In OPT variables (ie variables that begin with OPT_ and typically have the type YESNO), the prefix ``OPT_'' can be omitted. It does not matter whether variables are noted in upper- or lowercase (or mixed).
It is possible to write a ``!'' in front of the value. In this case, the test is negated, meaning the file is only copied if the variable does not contain the value.
If the file name is prefixed with a ``rootfs:'' the file is included in the list of files to be copied to the RootFS. The prefix will be stripped before.
If the file is located below the current configuration directory it is added to the list of files to be copied from there, otherwise the file found below opt is taken. Those files are not allowed to have a rootfs: prefix.
If the file to copy is a kernel module the actual kernel version may be substituted by ${KERNEL_VERSION}. mkfli4l will then pick the version from the configuration and place it there. Using this you may provide modules for several kernel versions for the package and the module matching the current kernel version will be copied to the router. For kernel modules the path may be omitted, mkfli4l will find the module using modules.dep and modules.alias, see the section ``Automatically Resolving Dependencies for Kernel Modules''.
Option | Meaning | Default Value | ||||||||||
type= | Type of the Entry:
This option has to be placed in front when given. The type ``local'' represents the type of an object existing in the file system and hence matches ``file'', ``dir'', ``node'' or ``symlink'' (depends). All other types except for ``file'' can create entries in the archive that do not have to exist in the local file system. This can i.e. be used to create devices files in the RootFS archive. |
local | ||||||||||
uid= | The file owner, either numeric or as a name from passwd | root | ||||||||||
gid= | File group, either numeric or as a name from group | root | ||||||||||
mode= | Access rights |
Files and Devices:
rw-r--r-- (644)
Directories: rwxr-xr-x (755)
Links: rwxrwxrwx (777)
|
||||||||||
flags=
(type=file) |
Conversions before inclusion in the archive:
|
|||||||||||
name= | Alternative name for inclusion of the entry in the archive | |||||||||||
devtype=
(type=node) |
Descibes the type of the device (``c'' for character and ``b'' für block oriented devices). Has to be placed in second position. | |||||||||||
major=
(type=node) |
Decribes the so-called ``Major'' number of the device file. Has to be placed in third position. | |||||||||||
minor=
(type=node) |
Decribes the so-called ``Minor'' number of the device file. Has to be placed in fourth position. | |||||||||||
linktarget=
(type=symlink) |
Describes the target of the symbolic link. Has to be placed in second position. | |||||||||||
Some examples:
OPT_TELNETD='yes'
, set its
uid/gid to root and the rights to 755 (rwxr-xr-x
)
telnetd yes files/usr/sbin/in.telnetd mode=755
r-xr-xr-x
) and convert the
file to *nix format while stripping all superfluous chars
base yes etc/rc0.d/rc500.killall mode=555 flags=sh
PCMCIA_PCIC='i82365'
, set
uid/gid to root and the rights to 644 (rw-r--r--
)
pcmcia_pcic i82365 files/lib/modules/${KERNEL_VERSION}/pcmcia/i82365.ko
rw-r--r--
)
net_drv_% 3c503 3c503.ko
powermanagement !none etc/rc.d/rc100.pm mode=555 flags=sh
myopta,myoptb yes files/usr/local/bin/myopt-common.sh mode=555 flags=sh
This example is only an abbreviation for:
myopta yes files/usr/local/bin/myopt-common.sh mode=555 flags=sh myoptb yes files/usr/local/bin/myopt-common.sh mode=555 flags=sh
And the latter is a shorthand notation for:
opt_myopta yes files/usr/local/bin/myopt-common.sh mode=555 flags=sh opt_myoptb yes files/usr/local/bin/myopt-common.sh mode=555 flags=sh
base yes rootfs:files/usr/bin/beep.sh mode=555 flags=sh name=bin/beep
The files will be copied only if the above conditions are met and
OPT_PACKAGE='yes'
of the corresponding package is set. What OPT variable is
referenced is decribed in the file check/<PACKAGE>.txt.
If a variable is referenced in a package that is not defined by the package itself, it may happen that the corresponding package is not installed. This would result in an error message from mkfli4l, as it expects that all of the variables referenced by opt/<PACKAGE>.txt are defined.
To handle this situation correctly the ``weak'' declaration has been introduced. It has the following format:
weak <Variable> -
By this the variable it is defined (if not already existing) and its value is set to ``undefined''. Please note that the prefix ``OPT_" must be provided (if existing) because else a variable without this prefix will be created.
An example taken from opt/rrdtool.txt:
weak opt_openvpn - [...] openvpn yes files/usr/lib/collectd/openvpn.so
Without the weak definition mkfli4l would display an error message when using the package ``rrdtool'' while the ``openvpn'' package is not activated. By using the weak definition no error message is raised even in the case that the ``openvpn'' package does not exist.
In some situations it is desired to replace original files with configuration-specific files for inclusion in the archive, i.e. host keys, own firewall scripts, ... mkfli4l supports this scenario by checking whether a file can be found in the configuration directory and, if so, including this one instead in the file list for opt.img resp. rootfs.img.
Another option to add configuration-specific files to an archive is decribed in the section Extended Checks of the Configuration.
net_drv_% ne2k-pci ne2k-pci.ko
triggers that both 8390.ko and crc32.ko are included in the archive because ne2k_pci depends on both of them.
The necessary entries from modules.dep and modules.alias are included in the RootFS and can be used by modprobe for loading the drivers.
By the help of check/<PACKAGE>.txt the content of variables can be checked for validity. In earlier version of the program mkfli4l this check was hard coded there but it was outsourced to the check files in the course of modularizing fli4l. This file contains a line for each variable in the config files. These lines consist of four to five columns which have the following functions:
Often the problem occurs that certain variables describe options that are needed only in some situations. Therefore variables may be marked as optional. Optional variables are identified by the prefix ``+''. They may then exist, but do not have to. Arrays can also use a ``++'' prefix. Prefixed with a ``+'' the array can exist or be entirely absent. Prefixed with a``++'' in addition some elements of the array may be missing.
If a variable does not depend on any OPT variables, it is considered active. If it is depending on an OPT variable, it is precisely active if
In all other cases the variable is inactive.
Hint: Inactive OPT variables will be set back to ``no'' by
mkfli4l if set to ``yes'' in the configuration file, an appropriate
warning will be generated then (i.e. OPT_Y='yes'
is ignored, because
OPT_X='no'
). For transitive dependency chains (OPT_Z depends
on OPT_Y which in turn depends on OPT_X) this will only work
reliable, if the names of all OPT-variables start with ``OPT_''.
For compatibility with future versions of fli4l the variable specified here must be identical with the variable in OPT_VARIABLE where the last ``%'' is replaced by an ``N'' and everything following is removed. An array HOST_%_IP4 must have the N-Variable HOST_N assigned and an array PF_USR_CHAIN_%_RULE_% hence the N-variable PF_USR_CHAIN_%_RULE_N, and this N-variable itself is an array variable with the corresponding N-variable PF_USR_CHAIN_N. All other namings of the N variables will be incompatible with future versions of fli4l!
Name | Meaning |
NONE | No error checking will be done |
YESNO | The variable must be ``yes'' or ``no'' |
NOTEMPTY | The variable can't be empty |
NOBLANK | The variable can't contain spaces |
NUMERIC | The variable must be numeric |
IPADDR | The variable must be an IP address |
DIALMODE | The variable must be ``on'', ``off'' or ``auto'' |
I values are prefixed by ``WARN_'' an illegal content will not raise an error message and abort the build by mkfli4l, but only display a warning.
The possible checks are defined by regular expressions in check/base.exp. This file may be extended and now contains some new checking routines, for example: HEX, NUMHEX, IP_ROUTE, DISK and PARTITION.
The number of expressions may be extended at any time for the future needs of package developers. Provide feedback!
In addition, regular expressions can also be directly defined in the check-files, even relations to existing expressions can be made. Instead of YESNO you could, for example also write
RE:yes|no.This is useful if a test is performed only once and is relatively easy. For more details see the next chapter.
Hint: At the moment this does not work for array variables. Additionally, the variable can't be optional (no ``+'' in front of the variable name).
Example:
OPT_TELNETD - - YESNO "no"
If OPT_TELNETD is missing in the config file, ``no'' will be assumed and written as a value to rc.cfg.
The percent sign thingie is best decribed with an example. Let's assume check/base.txt amongst others has the following content:
NET_DRV_N - - NUMERIC NET_DRV_% - NET_DRV_N NONE NET_DRV_%_OPTION - NET_DRV_N NONE
This means that depending on the value of NET_DRV_N the variables NET_DRV_N, NET_DRV_1_OPTION, NET_DRV_2_OPTION, NET_DRV_3_OPTION, a.s.o. will be checked.
In version 2.0 only the above mentioned value ranges for variable checks existed: NONE, NOTEMPTY, NUMERIC, IPADDR, YESNO, NOBLANK, DIALMODE. Checking was hard-coded to mkfli4l, not expandable and restricted to essential ``data types'' which could be evaluated with reasonable efforts.
As of version 2.1 this checking has been reimplemented. The aim of the new implementation is a more flexible testing of variables, that is also able to examine more complex expressions. Therefore, regular expressions are used that can be stored in one or more separate files. This on one hand makes it possible to examine variables that are not checked for the moment and on the other hand, developers of optional packages can now define own terms in order to check the configuration of their packages.
A description of regular expressions can be found via ``man 7 regex''
or i.e. here:
http://unixhelp.ed.ac.uk/CGI/man-cgi?regex+7.
Specification of regular expressions can be accomplished in two ways:
This file can be found in the check directory and has the same name as the package containing it, i.e. check/base.exp. It contains definitions for expressions that can be referenced in the file check/<PACKAGE>.txt. check/base.exp for example at the moment contains definitions for the known tests and check/isdn.exp a definition for the variable ISDN_CIRC_x_ROUTE (the absence of this check was the trigger for the changes).
The syntax is as follows (again, double quotes can be used if needed):
<Name> = '<Regular Expression>' : '<Error Message>'as an example check/base.exp:
NOTEMPTY = '.*[^ ]+.*' : 'should not be empty' YESNO = 'yes|no' : 'only yes or no are allowed' NUMERIC = '0|[1-9][0-9]*' : 'should be numeric (decimal)' OCTET = '1?[0-9]?[0-9]|2[0-4][0-9]|25[0-5]' : 'should be a value between 0 and 255' IPADDR = '((RE:OCTET)\.){3}(RE:OCTET)' : 'invalid ipv4 address' EIPADDR = '()|(RE:IPADDR)' : 'should be empty or contain a valid ipv4 address' NOBLANK = '[^ ]+' : 'should not contain spaces' DIALMODE = 'auto|manual|off' : 'only auto, manual or off are allowed' NETWORKS = '(RE:NETWORK)([[:space:]]+(RE:NETWORK))*' : 'no valid network specification, should be one or more network address(es) followed by a CIDR netmask, for instance 192.168.6.0/24'
The regular expressions can also include already existing definitions by a reference. These are then pasted to substitute the reference. This makes it easier to construct regular expressions. The references are inserted by '(RE: Reference)'. (See the definition of the term NETWORKS above for an appropriate example.)
The error messages tend to be too long. Therefore, they may be displayed on multiple lines. The lines afterwards always have to start with a space or tab then. When reading the file check/<PACKAGE>.exp superfluous whitespaces are reduced to one and tabs are replaced by spaces. An entry in check/<PACKAGE>.exp could look like this:
NUM_HEX = '0x[[:xdigit:]]+' : 'should be a hexadecimal number (a number starting with "0x")'
Some expressions occur but once and are not worth defining a regular expression in a check/<PACKAGE>.exp file. You can simply write this expression to the check file for example:
# Variable OPT_VARIABLE VARIABLE_N VALUE MOUNT_BOOT - - RE:ro|rw|no
MOUNT_BOOT can only take the value ``ro'', ``rw'' or ``no'', everything else will be denied.
If you want to refer to existing regular expressions, simply add a reference via `(RE:...)''. Example:
# Variable OPT_VARIABLE VARIABLE_N VALUE LOGIP_LOGDIR OPT_LOGIP - RE:(RE:ABS_PATH)|auto
If an optional package adds an additional value for a variable which will be examined by a regular expression, then the regular expression has to be expanded. This is done simply by defining the new possible values by a regular expression (as described above) and complement the existing regular expression in a separate check/<PACKAGE>.exp file. That an existing expression is modified is indicated by a leading ``+''. The new expression complements the existing expression by appending the new value to the existing value(s) as an alternative. If another expression makes use of the complemented expression, the supplement is also there. The specified error message is simply appended to the end of the existing one.
Using the Ethernet driver as an example this could look like here:
NET_DRV = '3c503|3c505|3c507|...' : 'invalid ethernet driver, please choose one' ' of the drivers in config/base.txt'
PCMCIA_NET_DRV = 'pcnet_cs|xirc2ps_cs|3c574_cs|...' : '' +NET_DRV = '(RE:PCMCIA_NET_DRV)' : ''
Now PCMCIA drivers can be chosen in addition.
If you have extended NET_DRV with the PCMCIA drivers as shown above, but the package ``pcmcia'' has been deactivated, you still could select a PCMCIA driver in config/base.txt without an error message generated when creating the archives. To prevent this, you may let the regular expression depend on a YESNO variable in the configuration. For this purpose, the name of the variable that determines whether the expression is extended is added with brackets immediately after the name of the expression. If the variable is active and has the value ``yes'', the term is extended, otherwise not.
PCMCIA_NET_DRV = 'pcnet_cs|xirc2ps_cs|3c574_cs|...' : '' +NET_DRV(OPT_PCMCIA) = '(RE:PCMCIA_NET_DRV)' : ''
If specifying OPT_PCMCIA='no'
and using i.e. the PCMCIA driver
xirc2ps_cs in
config/base.txt, an error message will
be generated during archive build.
Hint: This does not work if the variable is not set explicitely in the configuration file but gets its value by a default setting in check/<PACKAGE>.txt. In this case the variable hence has to be set explicitely and the default setting has to be avoided if necessary.
Alternatively, you may also use arbitrary values of variables as conditions, the syntax looks like this:
+NET_DRV(KERNEL_VERSION=~'^3\.16\..*$') = ...
If KERNEL_VERSION matches the given regular expression (if any of the
kernels of the 3.16 line is used) then the list of network driver allowed is
extended with the drivers mentioned.
Hint: This does not work if the variable is not set explicitely in the configuration file but gets its value by a default setting in check/<PACKAGE>.txt. In this case the variable hence has to be set explicitely and the default setting has to be avoided if necessary.
If the checking process detects an error, an error message of the following kind is displayed:
Error: wrong value of variable HOSTNAME: '' (may not be empty) Error: wrong value of variable MOUNT_OPT: 'rx' (user supplied regular expression)
For the first error, the term was defined in a check/<PACKAGE>.exp file and an explanation of the error is displayed. In the second case the term was specified directly in a check/<PACKAGE>.txt file, so there is no additional explanation of the error cause.
Regular expressions are defined as follows:
Regular expression: One or more alternatives, separated by '|', i.e. ``ro|rw|no''. If one option matches, the whole term matches (in this case ``ro'', ``rw'' and ``no'' are valid expressions).
An alternative is a concatenation of several sections that are simply added.
A section is an ``atom'', followed by a single ``*'', ``+'', ``?'' or ``{min, max}''. The meaning is as follows:
An ``atom'' is a
An expression in square brackets indicates the following:
Let's have a look at some examples!
NUMERIC: A numeric value consists of at least one, but otherwise any number of digits. ``At least one'' is expressed with a ``+'', one digit was already in an example above. So this results in:
NUMERIC = '[0-9]+'or alternatively
NUMERIC = '[[:digit:]]+'
NOBLANK: A value that does not contain spaces, is any char (except for the char ``space'') and any number of them:
NOBLANK = '[^ ]*'
or, if the value is not allowed to be empty:
NOBLANK = '[^ ]+'
IPADDR: Let's have a look at an example with an IP4-address. An
ipv4 address consists of four ``Octets'', divided by dots (``.''). An
octet is a number between 0 and 255. Let's define an octet at first.
It may be
a number between 0 and 9: | [0-9] |
a number between 10 and 99: | [1-9][0-9] |
a number between 100 and 199: | 1[0-9][0-9] |
a number between 200 and 249: | 2[0-4][0-9] |
a number between 250 and 255: | 25[0-5] |
All are alternatives hence we concatenate them with ``|'' forming one expression: ``[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]'' and get an octet. Now we compose an IP4 address, four octets divided by dots (the dot must be masked with a backslash, because else it would represent an arbitrary char). Based on the syntax of an exp-file it would look like this:
OCTET = '[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]' IPADDR = '((RE:OCTET)\.){3}(RE:OCTET)'
If you want to design and test regular expressions, you can use the ``regexp'' program located in the unix or windows directory of the package ``base''. It accepts the following syntax:
usage: regexp [-c <check dir>] <regexp> <string>
The parameters explained in short:
'...'
or "..."
if in doubt, with double quotes needed only if single
quotes are used in the expression itself)
This may for example look like here:
./i586-linux-regexp -c ../check '[0-9]' 0 adding user defined regular expression='[0-9]' ('^([0-9])$') checking '0' against regexp '[0-9]' ('^([0-9])$') '[0-9]' matches '0' ./i586-linux-regexp -c ../check '[0-9]' a adding user defined regular expression='[0-9]' ('^([0-9])$') checking 'a' against regexp '[0-9]' ('^([0-9])$') regex error 1 (No match) for value 'a' and regexp '[0-9]' ('^([0-9])$') ./i586-linux-regexp -c ../check IPADDR 192.168.0.1 using predefined regular expression from base.exp adding IPADDR='((RE:OCTET)\.){3}(RE:OCTET)' ('^(((1?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.){3}(1?[0-9]?[0-9]|2[0-4][0-9]|25[0-5]))$') 'IPADDR' matches '192.168.0.1' ./i586-linux-regexp -c ../check IPADDR 192.168.0.256 using predefined regular expression from base.exp adding IPADDR='((RE:OCTET)\.){3}(RE:OCTET)' ('^(((1?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.){3}(1?[0-9]?[0-9]|2[0-4][0-9]|25[0-5]))$') regex error 1 (No match) for value '192.168.0.256' and regexp '((RE:OCTET)\.){3}(RE:OCTET)' (unknown:-1) wrong value of variable cmd_var: '192.168.0.256' (invalid ipv4 address)
Sometimes it is necessary to perform more complex checks. Examples of such complex things would be i.e. dependencies between packages or conditions that must be satisfied only when variables take certain values. For example if a PCMCIA ISDN adapter is used the package ``pcmcia'' has to be installed, too.
In order to perform these checks you may write small tests to check/<PACKAGE>.ext (also called ext-script). The language consists of the following elements:
~
, copy_pending, samenet, subnet
Concerning data types please note that variables, based on the associated regular expression are permanently assigned to a data type:
This means, among other things, that a variable of type ENUMERIC can not be used as an index when accessing an array variable, even if you have checked at first that it is not empty. The following code thus does not work as expected:
# TEST should be a variable of type ENUMERIC if (test != "") then # Error: You can't use a non-numeric ID in a numeric # context. Check type of operand. set i=my_array[test] # Error: You can't use a non-numeric ID in a numeric # context. Check type of operand. set j=test+2 fi
A solution for this problem is offered by split:
if (test != "") then # all elemente of test_% are numeric split(test, test_%, ' ', numeric) # OK set i=my_array[test_%[1]] # OK set j=test_%[1]+2 fi
At various points strings are needed, such as when a Warning should be issued. In some cases described in this documentation, such a string is scanned for variables. If found, these are replaced by their contents or other attributes. This replacement is called variable substitution.
This will be illustrated by an example. Assume this configuration:
# config/base.txt HOSTNAME='fli4l' # config/dns_dhcp.txt HOST_N='1' # Number of hosts HOST_1_NAME='client' HOST_1_IP4='192.168.1.1'
Then the character strings are rewritten as follows, if variable substitution is active in this context:
"My router is called $HOSTNAME" # --> "My router is called fli4l" "HOSTNAME is part of the package %{HOSTNAME}" # --> "HOSTNAME is part of the package base" "@HOST_N is $HOST_N" # --> " # Number of hosts is 1"
As you can see, there are basically three options for replacement:
Hint: Elements of array variables can not be integrated into strings this way, because there is no possibility to provide an index.
In general, only constants can be used for variable substitution, strings that come from a variable remain unchanged. An example will make this clear - assume the following configuration:
HOSTNAME='fli4l' TEST='${HOSTNAME}'
This code:
warning "${TEST}"
leads to the following output:
Warning: ${HOSTNAME}
It will not display:
Warning: fli4l
In the following sections it will be explicitly noted under which conditions strings are subject of variable substitution.
For instance, an OPT may declare that it provides a Printer service or a Webserver service. Only one package can provide a certain service. This prevents i.e. that two web servers are installed in parallel, which is not possible for obvious reasons, since the two servers would both register port 80. In addition, the current version of the service is provided so that updates can be triggered. The version number consists of two or three numbers separated by dots, such as ``4.0'' or ``2.1.23''.
Services typically originate from OPTs, not from packages. For example
the package ``tools'' has a number of programs that each have their own
provides statement defined if activated by OPT_...='yes'
.
The syntax is as follows:
provides <Name> version <Version>
Example from package ``easycron'':
provides cron version 3.10.0
The version number should be incemented by the OPT-developer in the third component, if only functional enhancements have been made and the OPT's interface is still. The version number should be increased in the first or second component when the interface has changed in any incompatible way (eg. due to variable renaming, path changes, missing or renamed utilities, etc.).
If another service is needed to provide the own function (eg. a web server) this dependency to a specific version may be defined here. The version can be given with two (i.e. ``2.1'') or three numbers (i.e. ``2.1.11'') while the two-number version accepts all versions starting with this number and the three-number version only accepting just the specified one. A list of version numbers may also be specified if multiple versions of the service are compatible with the package.
The syntax is as follows:
depends on <Name> version <Version>+
An example: Package ``server'' contains:
provides server version 1.0.1
A Package ``client'' with the following depends-instruction is given:8.2
depends on server version 1.0 # OK, '1.0' matches '1.0.1' depends on server version 1.0.1 # OK, '1.0.1' matches '1.0.1' depends on server version 1.0.2 # Error, '1.0.2' does not match with '1.0.1' depends on server version 1.1 # Error, '1.1' does not match with '1.0.1' depends on server version 1.0 1.1 # OK, '1.0' matches '1.0.1' depends on server version 1.0.2 1.1 # Error, neither '1.0.2' nor '1.1' are matching # '1.0.1'
Using these three functions users may be warned, signalized an errors or stop the test immediately. The syntax is as follows:
warning "text"
error "text"
fatal_error "text"
All strings passed to these funtions are subject of variable substitution.
If for any reason a temporary variable is required it can be created by ``set var [= value]''. The variable can not be a configuration variable! 8.3 If you omit the ``= value'' part the variable is simply set to ``yes'' so it may be tested in an if-statement. If an assignment part is given, anything may be specified after the equal sign: normal variables, indexed variables, numbers, strings and version numbers.
Please note that by the assignment also the type of the temporary variable
is defined. If a number is assigned mkfli4l ``remembers'' that the variable
contains a number and later on allows calculations with it. Trying to do
calculations with variables of other types will fail.
Example:
set i=1 # OK, i is a numeric variable set j=i+1 # OK, j is a numeric variable and contains the value 2 set i="1" # OK, i now is a string variable set j=i+1 # Error "You can't use a non-numeric ID in a numeric # context. Check type of operand." # --> no calculations with strings!
You may also define temporary arrays (see below). Example:
set prim_%[1]=2 set prim_%[2]=3 set prim_%[3]=5 warning "${prim_n}"
The number of array elements is kept by mkfli4l in the variable prim_n. The code above hence leads to the following output:
Warning: 3
If the right side of an assignment is a string constant, it is subject of variable substitution at the time of assignment. The following example demonstrates this. The code:
set s="a" set v1="$s" # v1="a" set s="b" set v2="$s" # v2="b" if (v1 == v2) then warning "equal" else warning "not equal" fi
will output ``not equal'', because the variables v1 and v2
replace the content of the variable s already at the time of assignment.
Hint: A variable set in a script is visible while processing further scripts - currently there exists no such thing as local variables. Since the order of processing scripts of different packages is not defined, you should never rely on any variable having values defined by another package.
If you want to access elements of a %-variable (of an array) you have to use the original name of the variable like mentioned in the file check/<PACKAGE>.txt and add an index for each ``%'' sign by using ``[Index]''.
Example: If you want to access the elements of variable PF_USR_CHAIN_%_RULE_% you need two indices because the variable has two ``%'' signs. All elements may be printed for example using the following code (the foreach-loop is exlained in see below):
foreach i in pf_usr_chain_n do # only one index needed, only one '%' in the variable's name set j_n=pf_usr_chain_%_rule_n[i] # Attention: a # foreach j in pf_usr_chain_%_rule_n[i] # is not possible, hence the use of j_n! foreach j in j_n do # two indices needed, two '%' in the variable's name set rule=pf_usr_chain_%_rule_%[i][j] warning "Rule $i/$j: ${rule}" done done
With this sample configuration
PF_USR_CHAIN_N='2' PF_USR_CHAIN_1_NAME='usr-chain_a' PF_USR_CHAIN_1_RULE_N='2' PF_USR_CHAIN_1_RULE_1='ACCEPT' PF_USR_CHAIN_1_RULE_2='REJECT' PF_USR_CHAIN_2_NAME='usr-chain_b' PF_USR_CHAIN_2_RULE_N='1' PF_USR_CHAIN_2_RULE_1='DROP'
the following output is printed:
Warning: Rule 1/1: ACCEPT Warning: Rule 1/2: REJECT Warning: Rule 2/1: DROP
Alternatively, you can iterate directly over all values of the array (but the exact indices of the entries are not always known, because this is not required):
foreach rule in pf_usr_chain_%_rule_% do warning "Rule %{rule}='${rule}'" done
That produces the following output with the sample configuration from above:
Warning: Rule PF_USR_CHAIN_1_RULE_1='ACCEPT' Warning: Rule PF_USR_CHAIN_1_RULE_2='REJECT' Warning: Rule PF_USR_CHAIN_2_RULE_1='DROP'
The second example nicely shows the meaning of the %<Name>-syntax: Within the string %rule is substitued by the name of the variable in question (for example PF_USR_CHAIN_1_RULE_1), while $rule is substituted by its content (i.e. ACCEPT).
Some variables contain passswords that should not be noted in plain text in rc.cfg. These variables can be encrypted by the use of crypt and are transferred to a format also needed on the router. Use this like here:
crypt (<Variable>)
The crypt function is the only point at which a configuration variable can be changed.
stat is used to query file properties. At the moment only file size can be accessed. If checking for files under the current configuration directory you may use the internal variable config_dir. The Syntax:
stat (<file name>, <key>)
The command looks like this (the parameters used are only examples):
foreach i in openvpn_%_secret do stat("${config_dir}/etc/openvpn/$i.secret", keyfile) if (keyfile_res != "OK") then error "OpenVPN: missing secretfile <config>/etc/openvpn/$i.secret" fi done
The example checks whether a file exists in the current configuration directory.
If OPENVPN_1_SECRET='test'
is set in the configuration file, the loop
in the first run checks for the existence of the file etc/openvpn/test.secret
in the current configuration directory.
After the call two variables are defined:
It may for example look like this:
stat ("unix/Makefile", test) if ("$test_res" == "OK") then warning "test_size = $test_size" else error "Error '$test_res' while trying to get size of 'unix/Makefile'" fi
A file name passed as a string constant is subject of variable substitution.
If you wish to search a file via ``grep''8.4 you may use the fgrep command. The syntax is:
fgrep (<File name>, <RegEx>)
If the file <File name> does not exist mkfli4l will abort with a fatal error! If it is not sure if the file exists, test this before with stat. After calling fgrep the search result is present in an array called FGREP_MATCH_%, with its index x as usual ranging from one to FGREP_MATCH_N. FGREP_MATCH_1 points to the whole range of the line the regular expression has matched, while FGREP_MATCH_2 to FGREP_MATCH_N contain the n-1 th part in brackets.
A first example will illustrate the use. The file opt/etc/shells contains the line:
/bin/sh
The following code
fgrep("opt/etc/shells", "^/(.)(.*)/") foreach v in FGREP_MATCH_% do warning "%v='$v'" done
produces this output:
Warning: FGREP_MATCH_1='/bin/' Warning: FGREP_MATCH_2='b' Warning: FGREP_MATCH_3='in'
The RegEx has (only) matched with ``/bin/'' (only this part of the line is contained in the variable FGREP_MATCH_1). The first bracketed part in the expression only matches the first char after the first ``/'', this is why only ``b'' is contained in FGREP_MATCH_2. The second bracketed part contains the rest after ``b'' up to the last ``/'', hence ``in'' is noted in variable FGREP_MATCH_3.
The following second example demonstrates an usual use of fgrep taken from check/base.ext. It will be tested if all tmpl:-references given in PF_FORWARD_x are really present.
foreach n in pf_forward_n do set rule=pf_forward_%[n] if (rule =~ "tmpl:([^[:space:]]+)") then foreach m in match_% do stat("$config_dir/etc/fwrules.tmpl/$m", tmplfile) if(tmplfile_res == "OK") then add_to_opt "etc/fwrules.tmpl/$m" else stat("opt/etc/fwrules.tmpl/$m", tmplfile) if(tmplfile_res == "OK") then add_to_opt "etc/fwrules.tmpl/$m" else fgrep("opt/etc/fwrules.tmpl/templates", "^$m[[:space:]]+") if (fgrep_match_n == 0) then error "Can't find tmpl:$m for PF_FORWARD_${n}='$rule'!" fi fi fi done fi done
Both a filename value as well as a regular expression passed as a string constant are subject to variable substitution.
Often variables can be assigned with several parameters, which then have to be split apart again in the startup scripts. If it is desired to split these previously and perform tests on them split can be used. The syntax is like this:
split (<String>, <Array>, <Separator>)
The string can be specified by a variable or directly as a constant. mkfli4l splits it where a separator is found and generates an element of the array for each part. You may iterate over these elements later on and perform tests. If nothing is found between two separators an array element with an empty string as its value is created. The exception is `` '': Here all spaces are deleted and no empty variable is created.
If the elements generated by such a split should be in a numeric context (e.g. as indices) this has to be specified when calling split. This is done by the additional attribute ``numeric''. Such a call looks as follows:
split (<String>, <Array>, <Separator>, numeric)
An example:
set bar="1.2.3.4" split (bar, tmp_%, '.', numeric) foreach i in tmp_% do warning "%i = $i" done
the output looks like this:
Warning: TMP_1 = 1 Warning: TMP_2 = 2 Warning: TMP_3 = 3 Warning: TMP_4 = 4
Hint: If using the ``numeric'' variant mkfli4l will not check the generated string parts for really being numeric! If you use such a non-numeric construct later in a numeric context (i.e. in an addtion) mkfli4l will raise a fatal error. Example:
set bar="a.b.c.d" split (bar, tmp_%, '.', numeric) # Error: invalid number 'a' set i=tmp_%[1]+1
A string constant passed to split in the first parameter is subject of variable substitution.
The function add_to_opt can add additional files to the
Opt- or RootFS-Archives. All files under opt/ or from
the configuration directory may be chosen. There is no limitation to only
files from a specific package. If a file is found under opt/ as
well as in the configuration directory, add_to_opt will prefer
the latter. The function add_to_opt is typically used if complex
logical rules decide if and what files have to be included in the archives.
The syntax looks like this:
add_to_opt <File> [<Flags>]
Flags are optional. The defaults from table 8.2 are used if no flags are given.
See an example from the package ``sshd'':
if (opt_sshd) then foreach pkf in sshd_public_keyfile_% do stat("$config_dir/etc/ssh/$pkf", publickeyfile) if(publickeyfile_res == "OK") then add_to_opt "etc/ssh/$pkf" "mode=400 flags=utxt" else error "sshd: missing public keyfile %pkf=$pkf" fi done fi
stat at first checks for the file existing
in the configuration directory. If it is, it will be included in the archive,
if not, mkfli4l will abort with an error message.
Hint: Also for add_to_opt mkfli4l will first
check if the file to be copied can be found
in the configuration directory.
Filenames as well flags passed as string constants are subject of variable substitution.
if (expr) then statement else statement fi
A classic case distinction, as we know it. If the condition is true, the then part is executed, if the condition is wrong the else part.
If you want to run tests on array variables, you have to test every single variable. The foreach loop in two variants for this.
foreach <control variable> in <array variable> do <instruction> done foreach <control variable> in <array variable-1> <array variable-2> ... do <instruction> done
This loop iterates over all of the specified array variables, each starting with the first to the last element, the number of elements in this array is taken from the N-variable associated with this array. The control variable takes the values of the respective array variables. It should be noted that when processing optional array variables that are not present in the configuration, an empty element is generated. You may have to take this into account in the script, for example like this:
foreach i in template_var_opt_% do if (i != "") then warning "%i is present (%i='$i')" else warning "%i is undefined (empty)" fi done
As you also can see in the example, the name of the respective array variables can be determined with the %<control variable> construction.
The instruction in the loop may be one of the above control elements or functions (if, foreach, provides, depends, ...).
If you want to access exactly one element of an array, you can address it using the syntax <Array>[<Index>]. The index can be a normal variable, a numeric constant or again an indexed array.
foreach <control variable> in <N-variable> do <instruction> done
This loop executes from 1 to the value that is given in the N-variable. You can use the control variable to index array variables. So if you want to iterate over not only one but more array variables at the same time all controlled by the same N-variable you take this variant of the loop and use the control variable for indexing multiple array variables. Example:
foreach i in host_n do set name=host_%_name[i] set ip4=host_%_ip4[i] warning "$i: name=$name ip4=$ip4" done
The resulting content of the HOST_%_NAME- and HOST_%_IP4-arrays for this example:
Warning: 1: name=berry ip4=192.168.11.226 Warning: 2: name=fence ip4=192.168.11.254 Warning: 3: name=sandbox ip4=192.168.12.254
Expressions link values and operators to a new value. Such a value can be an normal variable, an array element, or a constant (Number, string or version number). All string constants in expressions are subject to variable substitution.
Operators allow just about everything you could want from a programming language. A test for the equality of two variables could look like this:
var1 == var2 "$var1" == "$var2"
It should be noted that the comparison is done depending on the type that was defined for the variable in check/<PACKAGE>.txt. If one of the two variables is numeric the comparison is made numeric-based, meaning that the strings are converted to numbers and then compared. Otherwise, the comparison is done string-based; comparing "05" == "5" gives the result ``false'', a comparison "18" < "9" ``true'' due to the lexicographical string order: the digit ``1'' precedes the digit ``9'' in the ASCII character set.
For the comparison of version numbers the construct numeric(version) is introduced, which generates the numeric value of a version number for comparison purposes. Here applies:
numeric(version) := major * 10000 + minor * 1000 + sub
whereas ``major'' is the first component of the version number, ``minor'' the second and ``sub'' the third. If ``sub'' is missing the term in the addition above is omitted (in other words ``sub'' will be equalled to zero).
A complete list of all expressions can be found in table 8.3. ``val'' stands for any value of any type, ``number'' for a numeric value and ``string''for a string.
Expression | true if |
id | id == ``yes'' |
val == val | values of identical type are equal |
val != val | values of identical type are unequal |
val == number | numeric value of val == number |
val != number | numeric value of val != number |
val < number | numeric value of val < number |
val > number | numeric value of val > number |
val == version | numeric(val) == numeric(version) |
val < version | numeric(val) < numeric(version) |
val > version | numeric(val) > numeric(version) |
val =~ string |
regular expression in string matches val |
( expr ) | Expression in brackets is true |
expr && expr | both expressions are true |
expr || expr | at least one of both expressions is true |
copy_pending(id) | see description |
samenet (string1, string2) | string1 describes the same net as string2 |
subnet (string1, string2) | string1 describes a subnet of string2 |
With the match operator =~
you can check whether a regular
expression matches the value of a variable. Furthermore, one can
also use the operator to extract subexpressions from a variable.
After successfully applying a regular expression on a variable
the array MATCH_% contains the parts found. May look
like this:
set foo="foobar12" if ( foo =~ "(foo)(bar)([0-9]*)" ) then foreach i in match_% do warning "match %i: $i" done fi
Calling mkfli4l then would lead to this output:
Warning: match MATCH_1: foo Warning: match MATCH_2: bar Warning: match MATCH_3: 12
When using =~
you may take all existing regular expressions
into account. If you i.e. want to check whether a PCMCIA Ethernet driver
is selected without OPT_PCMCIA being set to ``yes'', it might
look like this:
if (!opt_pcmcia) then foreach i in net_drv_% do if (i =~ "^(RE:PCMCIA_NET_DRV)$") then error "If you want to use ..." fi done fi
As demonstrated in the example, it is important to anchor the regular expression with ˆ and $ if intending to apply the expression on the complete variable. Otherwise, the match-expression already returns ``true'' if only a part of the variable is covered by the regular expression, which is certainly not desired in this case.
With the information gained during the checking process the function copy_pending tests if a file has been copied depending on the value of a variable or not. This can be used i.e. in order to test whether the driver specified by the user really exists and has been copied. copy_pending accepts the name to be tested in the form of a variable or a string. 8.5 In order to accomplish this copy_pending checks whether
copy_pending will return ``true'' if it detects that during the last step no file was copied, the copy process hence still is ``pending''.
A small example of the use of all these functions can be found in check/base.ext:
foreach i in net_drv_% do if (copy_pending("%i")) then error "No network driver found for %i='$i', check config/base.txt" fi done
Alle elements of the array NET_DRV_% are detected for which no copy action has been done because there is no corresponding entry existing in opt/base.txt.
For testing routes from time to time a test is needed whether two networks are identical or if one is a subnet of the other. The two functions samenet and subnet are of help here.
samenet (netz1, netz2)
returns ``true'' if both nets are identical and
subnet (net1, net2)
returns ``true'' if ``net1'' is a subnet of ``net2''.
If an OPT must pass other boot parameters to the kernel, in former times the variable KERNEL_BOOT_OPTION had to be checked whether the required parameter was included, and if necessary, a warning or error message had to be displayed. With the internal variable KERNEL_BOOT_OPTION_EXT you may add a necessary but missing option directly in an ext-script. An Example taken from check/base.ext:
if (powermanagement =~ "apm.*|none") then if ( ! kernel_boot_option =~ "acpi=off") then set kernel_boot_option_ext="${kernel_boot_option_ext} acpi=off" fi fi
This passes ``acpi=off'' to the kernel if no or ``APM''-type power management is desired.
Different kernel version lines often differ in some details:
These differences are mostly handled automatically by mkfli4l. To describe the available modules you can, on one hand expand tests dependant on the version (conditional regular expressions), or, on the other hand mkfli4l allows version dependant opt/<PACKAGE>.txt-files. These are then named opt/<PACKAGE>_<Kernel-Version>.txt, where the components of the kernel version are separated from each other by underscores. An example: the package ``base'' contains these files in its opt-directory:
the first file (base.txt) is always considered. Both
other files are only considered if the kernel version is called ``3.16(.*)''
resp. ``3.17(.*)''. As seen here, some parts of the version may be omitted
in file names, if a group of kernels should be addressed.
If KERNEL_VERSION='3.16.41'
is given, the following files (if existing)
are considered for the package <PACKAGE>:
Documentation should be placed in the files
HTML-files may be splitted, meaning one for each OPT contained. Nevertheless a file <PACKAGE>.html has to be created linking to the other files. Changes should be documented in:
The entire text documentation may not contain any tabs and has to have a line feed no later than after 79 characters. This ensures that the documentation can also be read correctly with an editor without automatic line feed.
Also a documentation in LATEX-format is possible, with HTML and PDF versions generated from it. The documentation of fli4l may serve as an example here. A documentation framework for required LATEX-macros can be found in the package ``template''. A brief description is to be found in the following subsections.
The fli4l documentation is currently available in the following languages: German (<LANGUAGE> = ``deutsch''), English (<LANGUAGE> = ``english'') and French (<LANGUAGE> = ``french''). It is the package developer's decision to document his package in any language. For the purposes of clarity it is recommended to create a documentation in German and/or English (ideally in both languages).
To create a documentation from LATEX-sources the following requirements apply:
The documentation files are named according to the following scheme:
The files should be stored in the directory fli4l/<PACKAGE>/doc/<SPRACHE>/tex/<PACKAGE>. For the package sshd this looks like here:
$ ls fli4l/doc/deutsch/tex/sshd/ Makefile sshd_appendix.tex sshd_main.tex sshd.tex
The Makefile is responsible for generating the documentation, the sshd.tex-file provides a framework for the actual documentation and the appendix, which is located in the other two files. See an example in the documentation of the package ``template''.
LATEX is, just like HTML, ``Tag-based'' , only that the tags
are called ``commands'' and have this format: \command
resp. \begin{environment}
...\end{environment}
By the help of commands you should rather emphasize the importance of the text less the display. It is therefore of advantage to use
\warning{Please do not...}
instead of
\emph{Please do not...}
.
Each command rsp. each environment may take some more parameters
noted like this: \command{parameter1}{parameter2}{parameterN}
.
Some commands have optional parameters in square (instead of curly)
brackets:
\command[optionalParameter]{parameter1}
... Usually only one optional parameter is used, in
rare cases there may be more.
Individual paragraphs in the document are separated by blank lines. Within these paragraphs LATEX itself takes care of line breaks and hyphenation.
The following characters have special meaning in LATEX and, if
occuring in normal text, must be masked prefixed by a \
:
# $ & _ % { }. ``~
'' and ``^
'' have to be
written as follows: \verb?~?
\verb?^?
The main LATEX-commands are explained in the documentation of the package ``template''.
All text files (both documentation and scripts, which later reside on the router) should be added to the package in DOS file format, with CR/LF instead of just LF at the end of a line. This ensures that Windows users can read the documentation even with ``notepad'' and that after changing a script under Windows everything still is executable on the router.
The scripts are converted to the required format during archive creation (see the description of the flags in table 8.2).
If a program from the package defines a new interface that other programs can use, please store the documentation for this interface in a separate documentation in doc/dev/<PACKAGE>.txt.
If a package also provides additional client programs, please store them in the directory windows/ for Windows clients and in the directory unix/ for *nix and Linux clients.
Customized programs and source code may be enclosed in the directory src/<PACKAGE>/. If the programs should be built like the rest of the fi4l programs, please have a look at the documentation of the ``src''-package .
All files, which will be copied to the router have to be stored under opt/etc/ and opt/files/. Be under
Scripts in opt/etc/boot.d/, opt/etc/rc.d/ and opt/etc/rc0.d/ have the following naming scheme:
rc<number>.<name>
The number defines the order of execution, the name gives a hint on what program/package is processed by this script.