MKNBI-MGL(8)
πΈ MKNBI-MGL(8) β MaKe NetBoot Image for MGL program πΈ
π NAME π
mknbi-mgl β MaKe NetBoot Image for MGL program π
π SYNOPSIS π
mknbi-mgl [OPTIONS] [<source file> [output file>]]
π DESCRIPTION π
okay so bestie, mknbi-mgl takes a program written in the Pascal-like Menu Generation Language β MGL for short β and converts it into a boot image file. That image is directly loadable by diskless systems using a bootrom thatβs compliant with the netboot specification. slay. β¨
β¨ OPTIONS β¨
-b,--batch-sys SYSβ letsmknbi-mgloperate in batch mode. It looks up the given system name in a database file and generates a netbootable image according to the parameters specified in the database entry. If this option is not given,mknbi-mgloperates according to the other command line options. If you give any other options in addition to-bor--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 theNETBOOT_CONFIGenvironment 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-bor--batch-sysoptions. If this option is not given, the value of theNETBOOT_DATABASEenvironment variable or a compiled-in default will be used. See thenetboot(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 thehomedirconfiguration 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 thePATHenvironment variable).mknbi-mglthen searches within all directories for required include files in the order the directory names appear in thePATHvalue. If this command line option is not specified, the value of theincludesparameter 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-mglwill use a built-in loader depending on the-tor--filetypecommand 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 theNETBOOT_LIBenvironment 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 theNETBOOT_DATAenvironment 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 becausemknbi-mglcan use an internally builtin runtime binary.-t,--filetype STRINGβ with this option it is possible to specify the type of output file generated bymknbi-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 themknbi-mgloutput 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.EXEformat, 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 whatmknbi-mglis doing. Each-xincreases 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. cute, right? π
π¬ 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. so // is your bestie too. π¬
π€ 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-icommand 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 theSTACKSIZEparameter, which is also the default, is8192(e.g. 8kB), and the maximum value is32768(e.g. 32kB). If multipleHcommands 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β ASCII2β UTF-83β 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 theScommand. 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 theBcommand 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. no cap. π«
π§© 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>β the character represented by that octal number
Character constants are written in single quotes, and ngl you can use the exact same backslash notation you'd use in strings inside them too π β but bestie, a character constant literally cannot be longer than one character, that's the whole vibe.
Any character inside a string or a character constant gets converted from the host representation into whatever byte representation your target (e.g. an Intel PC) architecture uses. The catch: only characters defined in the ASCII character set will display correctly on the target system π. All the special characters β block graphics, national language characters, the whole squad β will not be converted. You gotta define those yourself with the backslash notation above, matching the standard PC character set. No cap.
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. And heads up β unlike Pascal, MGL is case sensitive, so these boolean constant values have to be given in all lower case fr fr. True is not it. π
β¨ 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 generates a new variable named <varname> of type <type>. That <type> can be a standard type, or a <typename> you defined in a type section as explained above π
π function
A function is defined the same way as in Pascal. After the keyword function you give the function name, 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. And since passing an argument by value requires copying it onto the stack (which is space and time consuming with strings π©), there's an additional keyword: const. If it's given instead of the var keyword in front of an argument specification, that 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 β periodt.
Within a function, the name of the function serves as a variable for the return value. So assigning a value to <funcname> is literally you setting the return value of the function β¨
After the function header, you can define variables, types, constants, procedures and functions local to that function. The function body starts with the keyword begin and ends with the keyword end. In between those two keywords, only commands are allowed, and all commands have to be seperated from each other using a semicolon. Cute shortcut: the keyword begin can be abbreviated with a { character, and the keyword end can be abbreviated with a } character. Iconic.
π procedure
A procedure is like a function that does not return a value. So 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. Obviously π
π¦ 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. Slay.
The following basic types are already predefined:
integerβ a scalar type with an internal data width of two bytes. Its range of values is from-maxintto+maxint, wheremaxintis a predefined constant with a value of32767.stringβ different from Pascal, which only knows about strings of a constant length. Strings in MGL can have a variable length. The maximum length for a string is255characters, and you can reduce that by appending a maximum length in square brackets to the type identifier:string[16]therefore limits the maximum length of a string to16characters πcharβ a scalar type with an internal data width of one byte. Its ordinal numbers range from0to255.booleanβ a scalar type with an internal data width of one byte. Its only possible ordinal numbers are0and1.
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's 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. Main character energy from Mon ngl π
β Subrange
A subrange type is based upon one of the basic scalar types integer, char and boolean. It's 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 β yes, really, don't do it π. Subrange types can be useful for specifying array subscripts. Even character or boolean subrange types can be used for subscripts. Versatile much?
π§ User defined non-scalar types
User defined non-scalar types can be records and arrays:
- Record β a record is a collection of elements with different types. It's 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 theendkeyword. Unlike Pascal, MGL does not implement variant record elements (which are similar to unions in C). Sorry bestie. - Array β an array is a collection of elements with the same type. It's defined with the keyword
arrayfollowed by a scalar type in square brackets, the keywordof, 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's 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 its argument, and sqr returns the square value of its argument. In addition to these operations it's 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. Bit manipulation ate, honestly π«Ά
πΈ 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. No error, just vibes βοΈ
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's 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
Okay so bestie, characters are literally social β they can be added together just like strings π. But plot twist: the result is always a string, no cap. Same energy when you add a character to a string β char plus string, still giving string.
The other iconic char move is multiplication (*). Left side of the * has to be a character value, right side has to be a numerical value. The character gets repeated as many times as that number says. So yes, the result is always a string too. Periodt.
And then there's the special one: the chr function π§ β it converts a numerical value into a character. Unlike Pascal (sorry Pascal), only the lower 8 bits of the numerical value matter, and no runtime error gets generated even if the number is bigger than the largest ordinal value for characters. She just vibes and truncates, iykyk.
π Boolean operations
The normal boolean operations and, or, xor and not can be applied to boolean values, and the result is boolean again β main character energy, always comes back as itself β¨.
Comparisons (like =, <>, >, <, >=, <=) always give a boolean result regardless of the types involved. But there's a rule: you can only compare two values which are assignable to each other. And in contrast to Pascal, comparing arrays and records is not possible π.
There's also a predefined boolean function: odd. It returns true if its numerical argument is odd, and false if its argument is even. Simple, iconic, no notes.
π General scalar operations
For all scalar values, the ord function returns their ordinal values. The result of this function is always numerical.
Then we've got succ and pred β they increase respectively decrease the corresponding scalar value to the next ordinal value. And heads up: it will result in a runtime error to increase or decrease out of the range of the corresponding scalar type. So like, don't do that π.
When the result of an expression turns out to be constant, it gets 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's 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 withbeginandend, as long as the boolean expression returns true π.repeat <commands> until <boolean expression>β repeat the commands until the boolean expression returns true. In contrast towhile, this command does not allow the<commands>block to be delimited bybeginandend.if <boolean expression> then <command 1> else <command 2>β execute command 1 if the boolean expression returns true, otherwise execute command 2. Both commands can also be command blocks delimited bybeginandend. The keywordthenis optional and can be left out. However, if a command 2 is specified, theelsekeyword is required. With all these commands affecting program flow, the keywordsbeginandendcan be replaced with the characters{and}respectively π.print <expression> at [ <x> , <y> ]β prints the result of the expression onto the screen at the position pointed to by the two numerical expressionsxandy. The position specification is optional; if it's not given, the expression gets 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 gets 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+/- maxintis not possible, so a runtime error will not occur with an integer variable π. When no timeout is given, thegetfunction 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β a complex command which operates similar to thegetcommand. However, it only allows entering a numerical character fromxandycoordinates and with a timeout given by the numerical expression. Depending on what key the user pressed, a specific item gets 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.selectoperates in an endless loop, which can only be left by using the keywordsbreak,returnandrestartπ.item <constant numerical expression> : <commands>β the way you define an item forselect(this is the first of the two forms). The constant numerical expression has to have a value from0to9, corresponding to the keys which are accepted byselect. There is no limit concerning the number of commands to be executed when the corresponding item is selected. They must not be bracketed bybeginandend.default : <commands>β the other form of defining an item forselect.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, 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 like111.222.111.222or 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 expressionsxandyπΈ.restartβ continue operation at the beginning of the current procedure or function π.breakβ leave the innermostwhile,repeatorselectcommand and continue execution at the end of the loop π .returnβ terminate the current function or procedure and return to the caller.<variable> := <value>β 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. So 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 π. Yes bestie, you can absolutely shadow the builtins.
π 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's 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's 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β Tag12: the hostname of the client. This tag is only present in the BOOTP record if you specified thehnflag in/etc/bootptabon the server (or the equivalent specification in the DHCP server configuration).BOOTP_DOMAINβ Tag15: the domain name of the client. This tag is only present if you specified thedntag in/etc/bootptabon the server.BOOTP_ROOTPATHβ Tag17: the path to the root directory of the client. This tag is only present if you specified therptag in/etc/bootptabon 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 πΈ
okay so this one's short and sweet bestie β mknbi-mgl doesn't have its own little ecosystem of env vars, nope. It just uses all the environment variables that netboot uses, and those are fully documented over in the netboot(5) man page. So if you're like "what does this variable even do π" β that's your field trip. Go read netboot(5). π
π BUGS π
ngl, the author is fully upfront about the messy parts, and honestly? respect.
- The code generator needs to be cleaned up. Like, it works, but it's giving "my room during finals week" energy. π
- There is almost no optimization. None. Zero. It's not out here being efficient, it's just out here being correct-ish.
- Error handling and reporting has to be better β right now the error messages are not serving the way they should. π
- And because this is alpha code, there are probably still many undiscovered bugs in the compiler. Not "maybe." Probably. So likeβ¦ vibe check your output before you ship it, bestie. π«Ά
fr fr: alpha code means alpha code. Don't come crying to the author when the compiler does compiler things.
π₯ SEE ALSO π₯
(these two are your besties for the whole netboot ecosystem β go say hi π)
π AUTHOR π
mknbi-mgl was written by Gero Kuhlmann <gero@gkminix.han.de>. Iconic. We stan. π
β¨ aaaand that's the whole page, bestie β¨ You made it to the very end β main character energy fr. π This little rewrite is serving the 04 Dec 2004 era of MKNBI-MGL(8), so if you're out here reading it decades later, just know the vibes are preserved but the bugs are also preserved. π slay, go compile something. π¦
π¦ where this page comes from
- nixos 26.05: netboot 0.10.2
- nixpkgs unstable: netboot 0.10.2
- Copyright (C) 1997-2007 Gero Kuhlmann <gero@gkminix.han.de>
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms