MKNBI-MGL(8)

NAME
mknbi-mgl - MaKe NetBoot Image for MGL program

SYNOPSIS

mknbi-mgl [OPTIONS] [<source file> [output file>]]

DESCRIPTION
mknbi-mgl converts a program, written in the Pascal-like Menu Generation Language (or MGL for short), into a boot image file which is directly loadable by diskless systems using a bootrom which is compliant with the netboot specification.

OPTIONS

-b, --batch-sys SYS
    Lets mknbi-mgl operate in batch mode. It will look up the given system name in a database file and generate a netbootable image according to the parameters specified in the database entry. If this option is not given, mknbi-mgl will operate according to the other command line options. If giving any other options in addition to -b or --batch-sys, the parameters found in the database will override those processed from the command line.

-C, --config-file FILE
    Specifies the pathname of the configuration file. If this option is not given, the value of the NETBOOT_CONFIG environment variable or a compiled-in default will be used.

-D, --database FILE|DBSPEC
    Pathname of database file (or database specification) to be used together with the -b or --batch-sys options. If this option is not given, the value of the NETBOOT_DATABASE environment variable or a compiled-in default will be used. See the netboot(5) man page for a further explanation of different ways to specify a database.

-F, --logfile
    Name of log file to write debugging output to. The default is to print all verbose output to stdout.

-h, --help
    Print short usage information.

-H, --home-dir
    Specifies the home directory. If this command line option is not given, either the homedir configuration file parameter or the current user's home directory will be used.

-i, --infile FILE
    Pathname of input file. If not given, standard input is used.

-I, --includes PATH
    Path to search for MGL include files. It is possible to specify more than one path with this option. However, all path names have to be seperated by a colon (like in the PATH environment variable). mknbi-mgl then searches within all directories for required include files in the order the directory names appear in the PATH value. If this command line option is not specified, the value of the includes parameter in the configuration file or a compiled-in default will be used.

-l, --loader FILE
    Specifies the name of the primary netboot loader file. If this option is not given, mknbi-mgl will use a built-in loader depending on the -t or --filetype command line option. Specifying an external loader file is usually only necessary for debugging purposes.

-L, --libdir DIR
    Pathname of the non-shared-data directory. If this option is not given, the value of the NETBOOT_LIB environment variable or a compiled-in default will be used.

-N, --datadir DIR
    Pathname of the shared-data directory. If this option is not given, the value of the NETBOOT_DATA environment variable or a compiled-in default will be used.

-o, --outfile FILE
    Pathname of output file, which is lateron loaded by the bootrom via TFTP.

-r, --runtime FILE
    Specifies the name of the MGL runtime binary file. Giving this option is usually not necessary because mknbi-mgl can use an internally builtin runtime binary.

-t, --filetype STRING
    With this option it is possible to specify the type of output file generated by mknbi-mgl. The following types are available:

    plain
        This file type does not contain any primary boot loader. Programs generated with this file type can not be run without a debugger.

    netboot
        The netboot file type allows the mknbi-mgl output file to be used as a boot image file for a netboot bootrom.

    pxe With this file type the resulting output file can be used as a boot image file for commercial PXE bootroms.

    dos This will produce an output file which is in DOS .EXE format, and which can be executed directly on any DOS system. However, the DOS image does not have access to any network functions. It is therefore not possible to load a new operating system with the MGL program. The resulting DOS program expects the name of a binary file on it's command line, which contains the contents of a BOOTP/DHCP packet. It's contents will then be provided to the MGL program. If no such binary file name has been specified, the DOS program provides a simple default BOOTP/DHCP packet for use by the MGL code.

    The default file type depends on the outtype parameter in the configuration file, or to netboot if no default type has been specified.

-v, --version
    Print version information.

-x, --verbose
    Verbose output to let you know what mknbi-mgl is doing. Each -x increases the verbosity level.

MGL SYNTAX
The Menu Generation Language MGL is very similar to the Pascal programming language. However, it does not support the full Pascal command set, while it adds some commands which allow an easier design of menus.

  Comments
Comments are delimited with curled brackets, e.g. in the same way as in standard Pascal. In addition, comments can also be delimited with (* and *). However, in addition to the Pascal notation, MGL also treats everything following a double-slash as a comment up to the end of the line.

  Comment commands
In order to modify the behaviour of the MGL compiler, it is possible to specify single-letter commands within bracket-delimited comments. Immediately following the open bracket and an optional space or tab has to come a dollar sign, then the command letter and, following another space or tab, a command argument. The following commands are currently implemented:

    {$I "FILE"}
        Include another file into the current MGL source. If the file name of the include file is not absolute, the file will be searched along the path specified with the -i command line option.

    {$H STACKSIZE}
        Specify the stack size to use for the generated MGL program. The builtin default should usually be enough, but in some situations it might be necessary to increase the stack size. However you should note that the maximum size of the data segment including the stack can not be larger than 64 kB. The minimum value for the STACKSIZE parameter, which is also the default, is 8192 (e.g. 8kB), and the maximum value is 32768 (e.g. 32kB). If multiple H commands are given in an MGL source file, only the last occurring commands will have an effect.

    {$C CHARSETNUM}
        Change the character set of the input file. The following numbers are supported:

            1 - ASCII

            2 - UTF-8

            3 - LATIN-1 (aka ISO-8859-1)

            4 - LATIN-9 (aka ISO-8859-15)

        It is only possible to use this command on a system which uses ASCII as it's base character set, and it does not work on EBCDIC systems. Using this command is useful if character constants in the MGL source file contain non-ASCII characters of foreign languages. mknbi-mgl will convert those characters so that they look correct on the target system which runs the MGL boot image. However, you should note that the target system can only display a limited set of characters, and all characters which can not be displayed, will show up as an inverted question mark.

    {$S [+|-]}
        The MGL compiler can decode strings in two different ways. One way is to write strings like in Pascal, with a single quote at the beginning and the end, and just as character constants as well. This mode can be activated using a "-" argument to the S command. With a "+" argument it is possible to modify the string handling so that string constants can be written like in C, with a double quote at either side. This makes it possible to write character and string constants differently within the MGL code. The C mode is activated by default.

    {$B [+|-]}
        With MGL it is possible to directly call functions built into the runtime module by prepending the string "_builtin_" to the name of the function. This is necessary in case a builtin function has been redefined in the MGL source code. Using the "-" argument to the B command it is possible to turn off the decoding of the "_builtin_" prefix.

  Reserved words
The following words are reserved and can not be used as identifiers: array, at, begin, break, const, default, do, else, end, function, if, item, of, procedure, record, repeat, restart, return, select, then, type, until, var, while, with. Unlike Pascal MGL is case sensitive.

  General layout of a program
An MGL program consists of declaration blocks like Pascal. Those declaration blocks are constant declarations, type declarations, variable declarations, function declarations and procedure declarations. All declaration blocks can be mixed and don't have to follow in any specific order. Each block has to be terminated by a semicolon, except for the program block which has to end with a dot. Every declaration block has to be introduced by a keyword:

const
    Define symbols with constant values. Each such definition has to look like:

    <constname> = <value>;

    The value can be any scalar or string expression, but has to be constant. Variable names in constant declarations are not allowed. For string constants the string value has to be delimited by double quotes, which is unlike Pascal. You can use a backslash to define special characters within the string:

    \\r Carriage return

    \\t Tab

    \\n New line

    \\b Back space

    \\f Form feed

    \\<octal number>
        Character represented by the octal number

    Character constants are specified using single quotes, while it is possible to use the same backslash notation as with strings. However, the length of a character constant cannot exceed one character.

    Any character within a string or character constant will be converted from the host representation into a byte representation used on the target (e.g. Intel PC) architecture. However, only characters defined in the ASCII character set will be displayed correctly on the target system. All special characters including block graphic and national language characters will not be converted. These have to be defined by using the backslash notation mentioned above according to the standard PC character set.

    Boolean constants are represented by the values true and false. These are predefined constant values, where the ordinal number of false is 0, and the ordinal number of true is 1. Note that unlike Pascal, MGL is case sensitive, and these boolean constant values have to be given in all lower case.

type
    Define a new type identifier. Assigning a type to an identifier looks like:

    <typename> = <type>;

    See below for a further discussion about types.

var Define a new variable. The syntax is:

    <varname> : <type>;

    This will generate a new variable with the name <varname> of type <type>. <type> can be a standard type or a <typename> defined in a type section as explained above.

function
    A function is defined the same way as in Pascal. Following the keyword function the function name has to be given, followed by the arguments in round brackets, a colon and the return type of the function:

    function <funcname> ( <arguments> ) : <type>;

    The arguments are specified like variable definitions, seperated by semicolons. By default, all arguments to a function are passed by value, including non-scalar arguments like strings. If the var keyword is given in front of an argument specification, that particular argument is passed by reference instead. Since passing an argument by value requires copying it onto the stack (which is space and time consuming with strings), there is an additional keyword: const. If it is given instead of the var keyword in front of an argument specification, this argument is passed to the function by reference, but cannot be altered by the function. This is an extension to the Pascal standard. Unlike Pascal, MGL does not support functions or procedures as arguments.

    Within a function, the name of the function serves as a variable for the return value. Therefore, assigning a value to <funcname> specifies the return value of the function.

    Following the function header the definition of variables, types, constants, procedures and functions local to that function is possible. The function body starts with the keyword begin and ends with the keyword end. In between these two keywords, only commands are allowed. All commands have to be seperated by each other using a semicolon. The keyword begin can be abbreviated with a { character, and the keyword end can be abbreviated with a } character.

procedure
    A procedure is like a function which does not return a value. Therefore, defining a procedure is very similar to defining a function:

    procedure <procname> ( <arguments> );

    Defining the arguments is the same as with functions. Of course, it is not possible to assign a value to <procname> because there is no return value.

  Data types
There are two general types: scalar and non-scalar. Scalar types are those which can be internally represented by a number. All others including records, arrays and strings are non-scalar. Each possible value of a scalar type has an ordinal number, so scalar types are defined by their range of ordinal numbers.

The following basic types are already predefined:

integer
    The integer type is a scalar type with an internal data width of two bytes. It's range of values is from -maxint to +maxint, where maxint is a predefined constant with a value of 32767.

string
    The string type is different from Pascal, which only knows about strings of a constant length. However, strings in MGL can have a variable length. The maximum length for a string is 255 characters. This can be reduced by appending a maximum length in square brackets to the type identifier: string[16] therefore limits the maximum length of a string to 16 characters.

char
    The character type is a scalar type with an internal data width of one byte. It's ordinal numbers range from 0 to 255.

boolean
    The boolean type is a scalar type with an internal data width of one byte. It's only possible ordinal numbers are 0 and 1.

Note that there is no floating point type in MGL. Like Pascal and unlike C, MGL is very strict regarding types. For example it is not possible to assign an integer value to a character variable.

In addition to these basic types, it is also possible to define additional scalar and non-scalar types. User defined scalar types can be enumerations or subrange types:

Enumeration
    The enumeration type consists of named elements, which have to be specified in round brackets. All named elements are separated by commas from each other, and get assigned an ordinal number starting at zero. The following declaration defines an enumeration type with five elements:

        ( Mon, Tue, Wed, Thu, Fri )

    where Mon gets the ordinal number 0, and Fri gets the ordinal number 4.

Subrange
    A subrange type is based upon one of the basic scalar types integer, char and boolean. It is specified by giving two constant values, seperated by three dots. For example, the following declaration defines a subrange type with 26 elements:

         'A'...'Z'

    This subrange type consists of character elements, ranging from the character 'A' to (and including) the character 'Z'. Declaring a variable with this type, and assigning it a value of 'a' will result in a runtime error. Subrange types can be useful for specifying array subscripts. Even character or boolean subrange types can be used for subscripts.

User defined non-scalar types can be records and arrays:

Record
    A record is a collection of elements with different types. It is defined with the keyword record. Following this keyword all elements of the record have to appear, with the same syntax as with variable declarations. The record definition is terminated by the end keyword. Unlike Pascal, MGL does not implement variant record elements (which are similar to unions in C).

Array
    An array is a collection of elements with the same type. It is defined with the keyword array followed by a scalar type in square brackets, the keyword of and the type specifier for the elements. The following are examples of array definitions:

        array [char] of integer

        array [10...20] of char

    In the first example an array of integers is defined with a number of 256 elements (the ordinal number range of the char type), while in the second example an array of characters is defined with a number of 11 elements. Note that unlike Pascal an array of characters is handled differently than a string type.

  Expressions
An expression is a set of operations applied to variables and constants. The operations allowed differ for the types involved in the expression. There is no possible operation for array and record types (which is in contrast to Pascal, where it is possible to compare variables with these types). The following operations are available:

Numerical operations
    These include addition ( + ), subtraction ( - ), multiplication ( * ), division ( / or div ) and the modulo operation ( % or mod ). All these operations can only be applied to numerical values (i.e. values of integer type and integer subrange types), and the result has the same type as the types involved in the operation. Also there are two predefined functions which operate on integer values: abs returns the absolute value of it's argument, and sqr returns the square value of it's argument. In addition to these operations it is also possible to apply boolean operations to numerical values: and, or, xor and not. These apply bitwise on the numerical values, and the result is numerical again.

String operations
    The only valid string operation is the addition ( + ). This will append the string on the right side of the plus sign at the end of the string on the left side. The result is always a string. If the result gets assigned to a variable, it will be automatically truncated if it exceeds the maximum length allowed for the variable.

    There are three special operations with string variables. When the variable name is followed by a dot and the keyword len, the result is a numerical value and specifies the actual length of the string. Similar to arrays, giving a numerical value in square brackets to a variable returns the character at the corresponding string position. It is an error to use a numerical value which is larger than the maximum string size. However, no error will be generated when an index is used which is beyond the actual end of the string, but still within the limits of the string variable. As an addition to this index operation, it is possible to extract a substring of a string variable by specifying two numerical index values, separated by a comma and surrounded by square brackets.

Character operations
    As with strings, characters can be added together. However, the result will always be a string. Similarily, a character can also be added to a string. Another operation with a character is the multiplication ( * ). On the left side of the multiplication sign has to be a character value, and on the right side has to be a numerical value. The character gets repeated as often as indicated by this numerical value. Therefore, the result is always a string.

    A special character operation is the chr function. It converts a numerical value into a character. In contrast to Pascal, only the lower 8 bits of the numerical value matter, and a runtime error will not be generated, if it is larger than the largest ordinal value for characters.

Boolean operations
    The normal boolean operations and, or, xor and not can be applied to boolean values. The result is boolean again. Also, comparisons (e.g. =, <>, >, <, >=, <=) always give a boolean result regardless of the types involved in the comparison. It is only possible to compare two values which are assignable to each other. In contrast to Pascal, a comparison of arrays and records is not possible.

    A predefined boolean function is odd, which returns true if it's numerical argument is odd, and false if it's argument is even.

General scalar operations
    For all scalar values the ord function returns their ordinal values. The result of this function is always numerical. The functions succ and pred increase resp. decrease the corresponding scalar value to the next ordinal value. It will result in a runtime error to increase or decrease out of the range of the corresponding scalar type.

When the result of an expression turns out to be constant, it will be collapsed by the compiler and treated as one single constant value. This is also true for most of the builtin functions like chr or ord. Whenever it is required to use a constant value, for example for defining an integer subrange type or for specifying a maximum length for a string, it is possible to use an expression which consists only of constant values. These can also be named constants like maxint or user defined constants.

  Commands
MGL knows of some commands in addition to Pascal to ease the generation of menus. Also some Pascal commands are not implemented, especially for and case. The following commands are implemented in MGL:

while <boolean expression> do
    Repeat the following command or block of commands, delimited with begin and end as long as the boolean expression returns true.

repeat <commands> until <boolean expression>
    Repeat the commands until the boolean expression returns true. In contrast to while this command does not allow the <commands> block to be delimited by begin and end.

if <boolean expression> then <command 1> else <command 2>
    Execute the command 1 if the boolean expression returns true. Otherwise execute the command 2. Both commands can also be command blocks delimited by begin and end. The keyword then is optional and can be left out. However, if a command 2 is specified the else keyword is required. With all these commands affecting program flow, the keywords begin and end can be replaced with the characters { and } respectively.

print <expression> at [ <x> , <y> ]
    This will print the result of the expression onto the screen at the position pointed to by the two numerical expressions x and y. The position specification is optional. If it's not given, the expression will be printed at the current cursor position. The expression to be printed has to result in a numerical, string or character type.

get <variable> at [ <x>, <y> ] with timeout <expression>
    Read a value into the variable using the timeout in seconds given by the numerical expression. If no coordinates are given the current cursor position will be used. The variable has to have a numerical, character or string type. It will be echoed to the console while reading. Note that you will get a runtime error when the user enters a numerical value which is larger than the range for the numerical variable. However, entering values larger than +/- maxint is not possible, so a runtime error will not occur with an integer variable. When no timeout is given the get function will wait forever for proper user input. If a timeout is given, the variable receives the value entered so far when the timeout occurs.

select at [ <x>, <y> ] with timeout <expression> of <items> end
    This is a complex command which operates similar to the get command. However, it does only allow entering a numerical character from x and y coordinates and with a timeout given by the numerical expression. Depending on what key the user pressed a specific item is selected. When no key is pressed, the default item is executed. If the user pressed a key for which no item is defined, no action is taken and another key press is waited for. select operates in an endless loop, which can only be left by using the keywords break, return and restart.

    An item is defined by using the command

    item <constant numerical expression> : <commands>

    or

    default : <commands>

    The constant numerical expression has to have a value from 0 to 9, corresponding to the keys which are accepted by select. There is no limit concerning the number of commands to be executed when the corresponding item is selected. They must not be bracketed by begin and end.

load <string expression> from <IP address> gateway <IP address>
    This command lets the program return to the bootrom and tell the bootrom to load the file named by the string expression from the system given by the first IP address and using a gateway given by the second IP address. The string expression does not have to be constant, but the IP addresses must be constant, because the runtime module presently lacks a hostname resolver. However, the IP addresses can either be specified in dotted format like 111.222.111.222 or as a string constant expression. If it's a string constant, the compiler treats it as a host name and resolves the name into an IP address at compile time.

gotoxy at [ <x> , <y> ]
    Move the cursor to the position on the screen given by the two numerical expressions x and y.

restart
    Continue operation at the beginning of the current procedure or function.

break
    Leave the innermost while, repeat or select command and continue execution at the end of the loop.

return
    Terminate the current function or procedure and return to the caller.

<variable> := <value>
    This will assign a value to a variable. The value has to be assignable to the variable regarding types.

  Scope of symbols
All symbols which are not keywords can be redefined in a different nesting level. While it's not possible to have two symbols with the same name within one procedure or function, the same symbol names can be used again in a different procedure, or in a procedure which is local to the current procedure. This also applies to internally defined symbols, which have an implicit nesting level of -1, which is lower than any other level. Therefore you can redefine internal procedures or functions like print or get.

  BOOTP or DHCP values
When the MGL program gets loaded and run by a bootrom, it gets passed the BOOTP answer or DHCP ack record the bootrom received from the boot server. It is possible to access the BOOTP tags in this record from within the program by using the special variable construct $[<expression>]. The expression has to be numerical, and defines the number of the tag in the BOOTP RFC vendor data area. The type of this special BOOTP variable is always a string. You can use this kind of variable within string expressions, or assign a value to it like with any other variable. When the MGL program terminates, the so modified BOOTP record gets passed back to the bootrom. Using this method it is possible to pass special BOOTP tags to the operating system loaded next by the bootrom. But note that the bootrom only allows a maximum size for the BOOTP record (which depends on the type of the bootrom used). When this space gets exceeded, the resulting tags are truncated.

To make it easier to access the BOOTP tags, some constants are predefined:

BOOTP_HOSTNAME
    Tag 12: The hostname of the client. This tag is only present in the BOOTP record if you specified the hn flag in /etc/bootptab on the server (or the equivalent specification in the DHCP server configuration).

BOOTP_DOMAIN
    Tag 15: The domain name of the client. This tag is only present if you specified the dn tag in /etc/bootptab on the server.

BOOTP_ROOTPATH
    Tag 17: The path to the root directory of the client. This tag is only present if you specified the rp tag in /etc/bootptab on the server.

So, for example to access the client's root directory on the server, you can use $[BOOTP_ROOTPATH] in your MGL program. In addition to this there are also two read-only variables predefined:

hostname
    This string variable gives the client's host name. It has the same value as $[BOOTP_HOSTNAME].

servername
    This string variable contains the name of the BOOTP server which answered the request of the boot rom. Since this name is not encoded as a tag in the BOOTP record, using this variable is the only way of accessing the server name.

DATABASE FILE
mknbi-mgl does not use any sections in the configuration file except the [general] section. However, for easier maintenance of different client configurations the database file can be used. The entries have to be preceded by a [mknbi-mgl:<system name>] tag and can contain the following parameters:

outfile
    Specifies the name of the output file

infile
    Specifies the name of the input source file

debug
    This boolean parameter controls the generation of debugging code.

noheader
    If the boolean argument of this parameter is true, no boot image header will be produced, so that the output file can be run as a COM program under DOS. This is useful for testing.

ENVIRONMENT VARIABLES
See the netboot(5) man page for a description of all environment variables used by mknbi-man.

BUGS
Need to cleanup the code generator. Also, there is almost no optimization, and error handling and reporting has to be better. Since this is alpha code, there are probably still many undiscovered bugs in the compiler.

SEE ALSO
netboot(5), netboot.config(5)

AUTHOR
mknbi-mgl was written by Gero Kuhlmann <gero@gkminix.han.de>.