mael -- infer dynamic precedences between convergent scripts.




Mael (the ``Maelstrom'') is a tool for dynamically determining precedences between convergent scripts. It runs these scripts and examines their exit codes, interpreting 0 as success and nonzero as failure. It continues trying to execute scripts that fail in a round-robin fashion until either all scripts succeed or it has completed n+1 full cycles, where n is the number of input scripts. It can be shown that this is equivalent with trying the scripts in all possible permutation orders.

The input to mael is a set of scripts that assure various configuration parameters for a system or network. For mael to work properly, these scripts must satisfy some simple criteria:

A script should know whether it succeeded in making desired changes or not.

A script should not make a change if not needed.

Scripts should not undo the changes of other scripts.

These limits allow mael to make some assumptions about script behavior. Once a script succeeds, it has successfully made its desired changes. No script will undo those changes, so a script need only succeed exactly once to accomplish its task. This is an ideal case, and special operators in the configuration file allow one to encode deviations from this ideal for script behavior.


mael is a self-contained Perl-5 script. All one need do is to edit the first line of mael to point to an appropriate Perl. Mael only uses modules Getopt::Long and Data::Dumper, which are available from CPAN.

Command-line options

Change the configuration file to be another file. The default is mael.conf in the user's current directory.

Turn on debugging writes. This provides an enormous amount of internal detail. Most people should use --verbose instead.

Print the result of parsing the configuration file without interpreting the file.

Check scripts for being executable at compile-time. This will raise an error if any fail to be programs. This precludes any scripts that write others, e.g.

Print a verbose log of actions as they occur. This shows how mael works and what it's doing at any given time.


Mael's configuration file mael.conf looks much like a Makefile with some important differences. First, mael.conf defines only precedences between scripts, unlike a Makefile, which defines precedences between outcomes. The simplest mael.conf is nothing more than a list of scripts to try, one per line. In the absence of any other ordering they will be tried in the sequence in which they appear. Repeating lines has no effect, as two identical lines are interpreted as representing the same script.

Defining precedence

By default, all commands in mael.conf have the same precedence and their order of appearance in the file is their execution order. One may advise mael of possible precedence constraints by means of three precedence operators. If A and B are commands, then

means that it is likely that B must follow A. This is treated as 'advisory'. Mael will try them in the suggested order before trying other orders.

means that 'B must clean up after A'. Mael will react by invoking a cleanup cycle after finishing execution, in which it redoes any B's whose successes preceded success of A's. Normally, once a script succeeds, it's done.

This is used to compensate for lack of homogeneity between scripts. If two scripts change the same parameter, one must decide which script's changes will be permanent. The last script to be called will 'win' and its changes will persist.

means that 'A must always precede B'. Mael will not even try B until A succeeds the first time. This is similar to ':' in make.

This is used to compensate for scripts whose actions require specific preconditions of which the script is not completely aware. By coding these preconditions one assures that it will not be invoked until the environment is ready for it.

Ideally, one need only specify precedences with ':'; other operators are mainly intended to cope with deficiencies of existing scripts.


Mael supports simple variable substitutions in the configuration file. One sets a variable with an assignment statement <name> = <value> where 'name' is the name of the value and 'value' is a string value corresponding to that name. Once set, variables can be substituted into any context by use of standard shell substitution syntax. $(name) represents the value of the variable 'name'.

Variables are mainly useful for declaring precedences between a complex set of scripts. If one places the complete invocation of each script into a variable, one can declare multiple precedence relationships without copying the invocations over several times.

Compound precedence statements

One can declare several precedence relationships in one line of a configuration file. The character ';' separates several commands to which the same precedence directive applies.














expands to mean all of the following relationships:


Commands are separated by ':::', '::', ':', or ';'. The strongest precedence is ';', while the weakest is ':::'. Thus




This is nothing more than syntactic sugar.

Long lines can be broken by using \ at the end of each line as a continuation character.

        B \
        :: \

is the same as the statement


Conditional execution

A third syntax available in the configuration file allows conditional execution of commands depending upon the outcome of others. This execution is not related to precedence, but occurs immediately after the affected command has succeeded or failed. The syntax:

        A || B

works as expected, so that B is only executed if A fails. Likewise

        A && B

only executes B if A succeeds. Commands can also be 'negated' with '!', which reverses the sense of these directives. So

        !A || B

only executes B if A succeeds, and is thus equivalent with

        A && B

The most powerful syntax is that of explicit exit-code dispatching. The syntax:

        A [ 12=>C; 0=>D ]

executes C if A's exit code is 12, and D if A's exit code is 0. The default values for exit codes are 0, 1, 2, ..., so that

        A [ C; D ]

is equivalent with

        A [ 0=>C; 1=>D ]

Limits on exit code dispatching are mainly intended to increase code readability. A specific command can only have one associated exit code action, either ||, &&, or []. Specifying two different actions is an error.

Code dispatching requests can be embedded into any line of a configuration file and can be nested. The results of nesting are the same as the results of not nesting the declarations. For example,

        A [ B ; C ] :: D : E && F ::: G || H

means exactly the same thing as

        A [ B ; C ]
        A :: D
        D : E
        E && F
        F ::: G
        G || H

where ||, &&, and [] always have higher precedence than :, ::, :::.


Alva L. Couch,,, Copyright 2001 by Alva L. Couch.


Oct 31, 2001