boot.c
Skip to line: 5050 - 5100 - 5150 - 5200 - 5250 - 5300 - 5350 - 5400 - 5450 - 5500 - 5550 - 5600 - 5650 - 5700 - 5750 - 5800 - 5850 - 5900 - 5950 - 6000 - 6050 - 6100 - 6150 - 6200 - 6250 - 6300 - 6350 - 6400 - 6450 - 6500 - 6550 - 6600 - 6650 - 6700


Highlighted entries were made in the last day
Select a different time increment to highlight entries
Current GMT time: Dec 03 2024 23:02:48

If you have a comment for boot.c, please click here.
There was an error! 2002: php_network_getaddresses: getaddrinfo failed: Name or service not known
5000   /*      boot.c - Load and start Minix.             Author: Kees J. Bot
5001    *                                                         27 Dec 1991
5002    *
5003    * Copyright 1998 Kees J. Bot, All rights reserved.
5004    * This package may be freely used and modified except that changes that
5005    * do not increase the functionality or that are incompatible with the
5006    * original may not be released to the public without permission from the
5007    * author.  Use of so called "C beautifiers" is explicitly prohibited.
Expand/Collapse Item5008     */
The key to understanding the boot sequence is understanding the function boot() (line 6605).  boot() is called from boothead.s ; it is the first function from boot.c to be called. boot() initializes the system with calls to initialize(), get_parameters(), and r_super() before processing commands from the bootparams sector (see lines 5848-5864) and commands typed in by the user.

Study the code from boot() before returning to the beginning.

5009
5010 char version[]=         "2.11";
5011
Expand/Collapse Item5012    #define BIOS    (!UNIX)         /* Either uses BIOS or UNIX syscalls. */
boot.c is used for both the boot monitor and edparams, a utility program.  Since the boot monitor runs independently of any operating system, it relies on bios function calls for its input/output (BIOS=TRUE, UNIX=FALSE).  edparams is an application that depends on the kernel; it relies on the operating system for its input/output (UNIX=TRUE, BIOS=FALSE).

In Makefile, edparams.c is compiled with the -D option.  (Note that edparams.c is a link to boot.c.)  The -DUNIX option sets UNIX equal to 1 (TRUE).  boot.c, on the other hand, is not compiled with this option (see line 0126 of Makefile). UNIX is therefore undefined and FALSE.

Different sections of code are parsed depending on whether UNIX or BIOS is TRUE.  For example, if BIOS is TRUE, lines 5030-5031 are parsed and lines 5034-5039 are discarded.  If UNIX is TRUE, lines 5034-5039 are parsed and lines 5030-5031 are discarded.  I do not cover sections of code that are specific to UNIX; for example, I do not cover lines 5143-5268.

5013
Expand/Collapse Item5014  #define nil 0
#define is a preprocessor command.  The preprocessor replaces all occurrences of the first string (in this case, "nil") with the second string (in this case, "0") before compilation.

The string "nil" is used to increase readability.

Expand/Collapse Item5015    #define _POSIX_SOURCE   1
Expand/Collapse Item5016    #define _MINIX          1
_POSIX_SOURCE and _MINIX are macros that are used by library routines.  In fact, nearly all macros that begin with an underscore (_) are specific to library routines.

The POSIX standard was created to improve portability between UNIX systems.  The 12th paragraph of section 2.6.2, lines 01184-011200, and lines 01241-01245 in the minix code in Operating Systems describe the _POSIX_SOURCE and _MINIX macros.

Expand/Collapse Item5017    #include <stddef.h>
A function must be either defined or declared in a file before it can be used.  Header files (files ending in .h) make the task of declaring variables easier.

Before compilation begins, the preprocessor replaces any #include <filename.h> statement with the contents of filename.h.  If the filename is enclosed in < and >, the preprocessor searches for the file in a default directory (typically /usr/include) and other directories specified by the -I option of the compiler (see line 5030).  If the filename is quoted (see line 5041), the preprocessor looks for the include file in the same directory that the source file is found.  (In this case, boot.c is the source file and is found in the /usr/src/boot directory.)

As an example, strlen() (see line 5613) is declared in the file string.h (see line 5022).  string.h is located in the directory /usr/include.

Header files, in addition to containing function declarations, also frequently contain #defines.  For example, RATIO (see line 5130) is #defined in boot.h.  Since boot.h is quoted (see line 5044), the preprocessor searches for boot.h in the same directory as boot.c.  Indeed, both files are in the /usr/src/boot directory.

5018   #include <sys/types.h>
5019   #include <sys/stat.h>
5020   #include <stdlib.h>
5021   #include <limits.h>
5022   #include <string.h>
Expand/Collapse Item5023    #include <errno.h>
There was an error! 2002: php_network_getaddresses: getaddrinfo failed: Name or service not known
errno is declared in errno.h (see line 00230 in the book) as extern.  Memory for a variable can be allocated in only one file (i.e. the variable is "defined" once) but the variable must be declared as extern in every other file that accesses it.  However, memory is not allocated for errno in boothead.s, boot.c, bootimage.c, or rawfs.c.  If you understand how memory is allocated for errno, please submit a comment to the site which will be displayed below.
5024   #include <ibm/partition.h>
5025   #include <minix/config.h>
5026   #include <minix/type.h>
5027   #include <minix/const.h>
5028   #include <minix/minlib.h>
5029   #if BIOS
Expand/Collapse Item5030    #include <kernel/const.h>
If a filename is enclosed in < and >, the preprocessor searches for the file in the default directory (typically /usr/include) and other directories specified by the -I option of the compiler.  In Makefile , the parent directory (..) is specified by the -I option.  Makefile is found in /usr/src/boot; therefore, the parent directory is /usr/src.  After the preprocessor unsuccessfully looks for const.h in the /usr/include/kernel directory, the preprocessor looks for (and finds) const.h in the /usr/src/kernel directory.  Note that the preprocessor can't find const.h in /usr/src/kernel since this directory doesn't exist.
5031   #include <kernel/type.h>
5032   #endif
Expand/Collapse Item5033    #if UNIX
I do not cover sections of code that are specific to UNIX (lines 5034-5040).
5034   #include <stdio.h>
5035   #include <time.h>
5036   #include <unistd.h>
5037   #include <fcntl.h>
5038   #include <signal.h>
5039   #include <termios.h>
5040   #endif
5041   #include "rawfs.h"
Expand/Collapse Item5042    #undef EXTERN
Memory for a variable can be allocated in only one file (i.e. the variable is "defined") but the variable must be declared as extern in every other file that accesses it.  To accomplish this, the macro EXTERN is #defined as the empty string.  This prevents the EXTERN macro from being #defined as extern in boot.h when boot.h is #included in boot.c.  boot.h is also #included in bootimage.c.  Since EXTERN is not #defined (and is therefore undefined), EXTERN is replaced by extern in bootimage.c.  This mechanism ensures that memory for a variable is allocated only once.
5043   #define EXTERN  /* Empty */
5044   #include "boot.h"
5045
Expand/Collapse Item5046  #define arraysize(a)            (sizeof(a) / sizeof((a)[0]))
The sizeof operator returns the size of its argument (in this case, the array a and the first element of a).  For example, if a has 10 elements and each element is 2 bytes (a short):

arraysize(a) = sizeof(a)/sizeof(a[0]) = 20/2 = 10

Expand/Collapse Item5047    #define arraylimit(a)           ((a) + arraysize(a))
arraylimit(a) returns the address of the first byte after the last element of array a.

Let's say we have an array of shorts, short_array[], that has 10 elements.  As the above example shows, arraysize(short_array) = 10.  So if the address of the first byte of short_array[] was, for example, 0x1100000, then arraylimit(a) = a + arraysize(a) = 10 = 0x1100000 + 10 = 0x1100000 + 0xA = 0x110000A.  This is incorrect.

Pointer arithmetic can be confusing to the uninitiated.  The following program:

#include <stdio.h>

#define arraysize(a)    (sizeof(a)/sizeof((a)[0]))
#define arraylimit(a)   ((a) + arraysize(a))

int main()
{
  short short_array[5]= {1,2,3,4,5};
  int size_of_array;
  short *short_ptr;

  size_of_array= arraysize(short_array);
  printf("the size of short_array= %d\n", size_of_array);

  short_ptr= arraylimit(short_array);
  printf("the beginning of short_array is %p\n", short_array);
  printf("short_ptr= %p\n", short_ptr);

}

produces the output:

the size of short_array= 5
the beginning of short_array is 0xbffffc50
short_ptr= 0xbffffc5A

short_ptr equals 0xbffffc5A and not 0xbffffc55.  In other words, 0xbffffc50+5 (in this scenario) equals 0xbffffc5A and not 0xbffffc55.  Any value added to a pointer is first multiplied by the size of the type that the pointer references.  A short is 2 bytes and short_array has 5 elements.  So 0xbffffc50+2*5 = 0xbffffc50+10 = 0xbffffc50+0x0A = 0xbffffc5A.

Expand/Collapse Item5048    #define between(a, c, z)        ((unsigned) ((c) - (a)) <= ((z) - (a)))
between() returns TRUE if c is between a and z.
5049
5050   #if BIOS
Expand/Collapse Item5051    char *bios_err(int err)
The functions in boothead.s (for example, dev_open()) return an error code in ax if something goes wrong.  bios_err() converts this number to a readable string.

For example, on line 6131, the return value of dev_open() is stored in r.  If an error occurred, r will be nonzero.  On line 6133, bios_err() converts this number to a readable string so that printf() (on the next line:  line 6134) prints something meaningful.

5052   /* Translate BIOS error code to a readable string.  (This is a rare trait
5053    * known as error checking and reporting.  Take a good look at it, you
5054    * won't see it often.)
5055    */
5056   {
Expand/Collapse Item5057            static struct errlist {
The static declaration specifies that the variable (in this case, the array errlist[]) remains in existence after the function returns.  Since the contents of errlist[] don't change from one invocation of bios_err() to the next, it is preferable that the array remain in existence.  It saves the time for reinitialization that would otherwise be necessary.

The static declaration has a different meaning for external variables.  The scope of an external static variable is only within the file in which the variable is declared.  Note that "external" variables are variables that are declared outside of all functions.

5058                   int     err;
5059                   char    *what;
5060           } errlist[] = {
5061   #if !DOS
5062            { 0x00, "No error" },
5063            { 0x01, "Invalid command" },
5064            { 0x02, "Address mark not found" },
5065            { 0x03, "Disk write-protected" },
5066            { 0x04, "Sector not found" },
5067            { 0x05, "Reset failed" },
5068            { 0x06, "Floppy disk removed" },
5069            { 0x07, "Bad parameter table" },
5070            { 0x08, "DMA overrun" },
5071            { 0x09, "DMA crossed 64 KB boundary" },
5072            { 0x0A, "Bad sector flag" },
5073            { 0x0B, "Bad track flag" },
5074            { 0x0C, "Media type not found" },
5075            { 0x0D, "Invalid number of sectors on format" },
5076            { 0x0E, "Control data address mark detected" },
5077            { 0x0F, "DMA arbitration level out of range" },
5078            { 0x10, "Uncorrectable CRC or ECC data error" },
5079            { 0x11, "ECC corrected data error" },
5080            { 0x20, "Controller failed" },
5081            { 0x40, "Seek failed" },
5082            { 0x80, "Disk timed-out" },
5083            { 0xAA, "Drive not ready" },
5084            { 0xBB, "Undefined error" },
5085            { 0xCC, "Write fault" },
5086            { 0xE0, "Status register error" },
5087            { 0xFF, "Sense operation failed" }
5088   #else /* DOS */
5089            { 0x00, "No error" },
5090            { 0x01, "Function number invalid" },
5091            { 0x02, "File not found" },
5092            { 0x03, "Path not found" },
5093            { 0x04, "Too many open files" },
5094            { 0x05, "Access denied" },
5095            { 0x06, "Invalid handle" },
5096            { 0x0C, "Access code invalid" },
5097   #endif /* DOS */
5098           };
Expand/Collapse Item5099            struct errlist *errp;
errp is a pointer to a struct errlist (see line 5060).
5100
Expand/Collapse Item5101            for (errp= errlist; errp < arraylimit(errlist); errp++) {
Expand/Collapse Item5102                    if (errp->err == err) return errp->what;
Expand/Collapse Item5103            }
Expand/Collapse Item5104            return "Unknown error";
The for loop compares the argument err with the err field of each element of the array errlist[].  When a match is found, the string what is returned.  If the for loop goes through the entire array errlist[] without finding a match, bios_err() returns the string "Unknown error".
5105   }
5106
5107   char *unix_err(int err)
5108   /* Translate the few errors rawfs can give. */
5109   {
5110           switch (err) {
5111           case ENOENT:    return "No such file or directory";
5112           case ENOTDIR:   return "Not a directory";
5113           default:        return "Unknown error";
5114           }
5115   }
5116
5117   void rwerr(char *rw, off_t sec, int err)
5118   {
5119           printf("\n%s error 0x%02x (%s) at sector %ld absolute\n",
5120                   rw, err, bios_err(err), sec);
5121   }
5122
Expand/Collapse Item5123    void readerr(off_t sec, int err)      { rwerr("Read", sec, err); }
readerr() and writerr() provide convenient interfaces to rwerr() and bios_err().

Notice the order of the functions bios_err(), rwerr(), and readerr()readerr() calls rwerr() which calls bios_err().  A function must be declared or defined before it can be called.  bios_err() is defined (lines 5051-5105) before rwerr() calls it (line 5120) and rwerr() is defined (lines 5117-5121) before readerr() calls it.  The need to carefully order the functions can be avoided if the functions are declared in a header file #included at the beginning of the file.  (In this case, boot.h would be the appropriate header file.)

5124   void writerr(off_t sec, int err)      { rwerr("Write", sec, err); }
5125
Expand/Collapse Item5126    void readblock(off_t blk, char *buf)
readblock() reads blk into the buffer buf.  Note that blk is not an absolute block on the hard drive; blk is the block offset from the beginning of the partition.  The first sector in the partition is lowsec.  (2 sectors = 1 block = 2*512 bytes = 1024 bytes)
5127   /* Read blocks for the rawfs package. */
5128   {
5129           int r;
Expand/Collapse Item5130            u32_t sec= lowsec + blk * RATIO;
sec is the absolute sector address of the first sector of blk.
5131
Expand/Collapse Item5132            if ((r= readsectors(mon2abs(buf), sec, 1 * RATIO)) != 0) {
int readsectors(u32_t bufaddr, u32_t sector, U8_t count) reads count bytes beginning at absolute sector address sector into absolute memory address bufaddr.

u32_t mon2abs(void *ptr) converts the offset memory address ptr to an absolute memory address.

RATIO = 2.  Both sectors of blk are read.

5133                readerr(sec, r); exit(1);
5134           }
5135   }
5136
Expand/Collapse Item5137    #define istty           (1)
Expand/Collapse Item5138    #define alarm(n)        (0)
Expand/Collapse Item5139    #define pause()         (0)
istty, alarm(n), and pause() expand to TRUE, FALSE, and FALSE, respectively.
5140
5141   #endif /* BIOS */
5142
Expand/Collapse Item5143    #if UNIX
I do not cover UNIX-specific sections of code (lines 5143-5268).  I may cover these sections at a later time.
5144
5145   /* The Minix boot block must start with these bytes: */
5146   char boot_magic[] = { 0x31, 0xC0, 0x8E, 0xD8, 0xFA, 0x8E, 0xD0, 0xBC };
5147
5148   struct biosdev {
5149           char *name;             /* Name of device. */
5150           int device;             /* Device to edit parameters. */
5151   } bootdev;
5152
5153   struct termios termbuf;
5154   int istty;
5155
5156   void quit(int status)
5157   {
5158           if (istty) (void) tcsetattr(0, TCSANOW, &termbuf);
5159           exit(status);
5160   }
5161
5162   #define exit(s) quit(s)
5163
5164   void report(char *label)
5165   /* edparams: label: No such file or directory */
5166   {
5167           fprintf(stderr, "edparams: %s: %s\n", label, strerror(errno));
5168   }
5169
5170   void fatal(char *label)
5171   {
5172           report(label);
5173           exit(1);
5174   }
5175
5176   void *alloc(void *m, size_t n)
5177   {
5178           m= m == nil ? malloc(n) : realloc(m, n);
5179           if (m == nil) fatal("");
5180           return m;
5181   }
5182
5183   #define malloc(n)       alloc(nil, n)
5184   #define realloc(m, n)   alloc(m, n)
5185
5186   #define mon2abs(addr)   ((void *) (addr))
5187
5188   int rwsectors(int rw, void *addr, u32_t sec, int nsec)
5189   {
5190           ssize_t r;
5191           size_t len= nsec * SECTOR_SIZE;
5192
5193           if (lseek(bootdev.device, sec * SECTOR_SIZE, SEEK_SET) == -1)
5194                   return errno;
5195
5196           if (rw == 0) {
5197                   r= read(bootdev.device, (char *) addr, len);
5198           } else {
5199                   r= write(bootdev.device, (char *) addr, len);
5200           }
5201           if (r == -1) return errno;
5202           if (r != len) return EIO;
5203           return 0;
5204   }
5205
5206   #define readsectors(a, s, n)     rwsectors(0, (a), (s), (n))
5207   #define writesectors(a, s, n)    rwsectors(1, (a), (s), (n))
5208   #define readerr(sec, err)       (errno= (err), report(bootdev.name))
5209   #define writerr(sec, err)       (errno= (err), report(bootdev.name))
5210   #define putch(c)                putchar(c)
5211   #define unix_err(err)           strerror(err)
5212
5213   void readblock(off_t blk, char *buf)
5214   /* Read blocks for the rawfs package. */
5215   {
5216           errno= EIO;
5217           if (lseek(bootdev.device, blk * BLOCK_SIZE, SEEK_SET) == -1
5218                   || read(bootdev.device, buf, BLOCK_SIZE) != BLOCK_SIZE)
5219           {
5220                   fatal(bootdev.name);
5221           }
5222   }
5223
5224   int trapsig;
5225
5226   void trap(int sig)
5227   {
5228           trapsig= sig;
5229           signal(sig, trap);
5230   }
5231
5232   int escape(void)
5233   {
5234           if (trapsig == SIGINT) {
5235                   trapsig= 0;
5236                   return 1;
5237           }
5238           return 0;
5239   }
5240
5241   int getch(void)
5242   {
5243           char c;
5244
5245           fflush(stdout);
5246
5247           switch (read(0, &c, 1)) {
5248           case -1:
5249                   if (errno != EINTR) fatal("");
5250                   return(ESC);
5251           case 0:
5252                   if (istty) putch('\n');
5253                   exit(0);
5254           default:
5255                   if (istty && c == termbuf.c_cc[VEOF]) {
5256                           putch('\n');
5257                           exit(0);
5258                   }
5259                   return c & 0xFF;
5260           }
5261   }
5262
5263   #define get_tick()              ((u32_t) time(nil))
5264   #define clear_screen()          printf("[clear]");
5265   #define boot_device(device)     printf("[boot %s]\n", device);
5266   #define bootminix()             (run_trailer() && printf("[boot]\n"))
5267
5268   #endif /* UNIX */
5269
5270   char *readline(void)
5271   /* Read a line including a newline with echoing. */
5272   {
5273           char *line;
5274           size_t i, z;
5275           int c;
5276
5277           i= 0;
5278           z= 20;
Expand/Collapse Item5279            line= malloc(z * sizeof(char));
void *malloc(size_t size) allocates size bytes from the heap and returns a pointer to the first byte of the allocated memory. malloc() is declared in stdlib.h.    (The heap is the region between the stack and the bss.)

Space for 20 char's (20 bytes) is initially allocated.  If this is used up, another 20 bytes are allocated.  If the 40 bytes are used up, 40 additional bytes are allocated.  Each reallocation of memory doubles the total amount of memory allocated.  See lines 5298-5299.

5280
5281           do {
5282                   c= getch();
5283
Expand/Collapse Item5284                    if (strchr("\b\177\25\30", c) != nil) {
char *strchr(char *cs, char c)  returns a pointer to the first occurrence of c in cs or nil (0) if not present.  strchr() is declared in string.h.

ctrl-U and ctrl-X erase the entire line.

5285                           /* Backspace, DEL, ctrl-U, or ctrl-X. */
5286                           do {
5287                                   if (i == 0) break;
Expand/Collapse Item5288                                    printf("\b \b");
"\b \b" backspaces, prints a space, and then backspaces again.  This erases the previous character and backs up one space.  Note that no single ascii character can replace this sequence.
5289                                   i--;
5290                           } while (c == '\25' || c == '\30');
5291                   } else
Expand/Collapse Item5292                    if (c < ' ' && c != '\n') {
Expand/Collapse Item5293                           putch('\7');
5292-5293
'
\7' is the bell.  Backspace, DEL, ctrl-U, ctrl-X and newline are the only allowed control characters.  All other control characters are forbidden.

int putch(int c) prints c at the current cursor position.  If c is the bell, the speaker beeps and the cursor doesn't advance.

5294                   } else {
5295                          putch(c);
5296                           line[i++]= c;
5297                           if (i == z) {
5298                                   z*= 2;
5299                                   line= realloc(line, z * sizeof(char));
5300                           }
5301                   }
Expand/Collapse Item5302            } while (c != '\n');
The line is read until a newline ('\n') is typed.
Expand/Collapse Item5303            line[i]= 0;
A terminating 0 is appended to line.
5304           return line;
5305   }
5306
Expand/Collapse Item5307    int sugar(char *tok)
Commands for the boot monitor must be parsed into "tokens" and placed in a command chain before the commands can be processed.  Here are some sample commands for the boot monitor:

hd2a> rootdev = hd2a
hd2a> delay 5000
hd2a> dos(d, MS-DOS) { boot hd1 }
hd2a> minix(=, MINIX) { boot }
hd2a> main() { trap 5000 minix; menu }

and here is how the commands are broken up into tokens:

token1    token2     token3    token4
rootdev   =          hd2a      \n

token1    token2     token3
delay     5000       \n

token1    token2     token3    token4    token5    token6
dos       (d,MS-DOS) {         boot      hd1       }
token7
\n

token1    token2     token3    token4    token5    token6
minix     (=,MINIX)  {         boot      }         \n

token1    token2     token3    token4    token5    token6
main      ()         {         trap      5000      minix
token7    token8     token9    token10
;         menu       }          \n

These tokens are chained together with the struct token (lines 5356-5359).

Here is the first command (rootdev = hd2a).

Commands are separated not only by newlines ('\n') but also by semicolons(;).  For example, on lines 5861-5862, the tokens ":", "leader", and "main" are separated by semicolons.

Sector 1 (PARAMSEC) of any bootable partition is the "bootparams sector". (Sector 0 of any bootable partition is the bootblock code.) The PARAMSEC sector contains commands that were previously saved.  Before the boot monitor goes into interactive mode (i.e. before the prompt appears), the bootparams sector is tokenized (see lines 5848-5854) and the commands are processed.  Tokens in the bootparams sector are separated by newlines (see line 5920).

5308   /* Recognize special tokens. */
5309   {
Expand/Collapse Item5310            return strchr("=(){};\n", tok[0]) != nil;
char *strchr(char *cs, char c)  returns a pointer to the first occurrence of c in cs or nil (0) if not present.  strchr() is declared in string.h.  Note that only a single character (tok[0]) is being compared and not an entire string.
5311   }
5312
Expand/Collapse Item5313    char *onetoken(char **aline)
onetoken() is called on line 5372.  It is a little difficult (for people inexperienced with pointers) to understand why a pointer to a pointer (char **aline) is the parameter rather than just a pointer.

In the function tokenize() (line 5361), line must be advanced in each call to onetoken().  (Note that this refers to the variable line from tokenize(), not the variable line from onetoken().)

Study the following program:

#include <stdio.h>

int main()
{
  int var1 = 5;
  function1(var1);
  printf("var1 is equal to %d.", var1);
}

int function1(int var)
{
  var=3;
}

The output of this program is:
var1 is equal to 5.

The value of var1 cannot be changed by altering the value of var in function1().  The reason var1 can't be changed here is that function arguments in C are passed "by value."  Called functions are given the value of their arguments in temporary variables rather than the originals.  For this reason, changes to var in function1() are not reflected in var1 from main().

However, there is a way to change the value of var1:

#include <stdio.h>

int main()
{
  int var1 = 5;
  function1(&var1);
  printf("var1 is equal to %d." var1);
}

int function1(int *var)
{
  *var=3;
}

The output of this program is:
var1 is equal to 3.

Note that the value of var1 has been changed by passing in the address of var1 rather than var1 itself.  function1() dereferences var to change the value of var1.

Since the function onetoken() must change the value of line (see line 5372), the address of line is passed in rather than line itself.  Note that line from tokenize() is a pointer to a char; therefore a pointer to a pointer to a char is passed in.

When you see a pointer to a pointer as a parameter to a function, check to see if the function changes the value of a pointer by dereferencing the pointer to a pointer.  For example, onetoken() changes the value of line (the variable line from tokenize(), not onetoken()) by dereferencing aline on line 5350.

I understand that this stuff is confusing.  We'll run into this again; I'll continue to give detailed explanations in case you didn't understand it this time.

5314   /* Returns a string with one token for tokenize. */
5315   {
Expand/Collapse Item5316            char *line= *aline;
line keeps track of the current position in the token.  On line 5350, it's copied to *aline.
5317           size_t n;
Expand/Collapse Item5318            char *tok;
On lines 5345-5346, memory is allocated for tok and the token is copied to the allocated memory.  On line 5351, tok is returned.
5319
Expand/Collapse Item5320            /* Skip spaces and runs of newlines. */
Expand/Collapse Item5321            while (*line == ' '||(*line =='\n'&&line[1] == '\n')) line++;
All spaces are skipped.  If there are several newlines ('\n') in a row, every newline until the last is skipped.  The last newline is tokenized.

On line 5348, newlines are replaced with semicolons (;).  Semicolons and newlines have the same meaning here; they are both command separators.

5322
5323           *aline= line;
5324
5325           /* Don't do odd junk (nor the terminating 0!). */
5326           if ((unsigned) *line < ' ' && *line != '\n') return nil;
5327
Expand/Collapse Item5328            if (*line == '(') {
Study the examples in the comment for line 5307.  These examples will help you to understand what constitutes a single token.  Also, I refer to them in the next lines.

If the first character is a left parenthesis ('('), then the token is something like (d,MS-DOS), (=, MINIX), or () (see comments for line 5307).

5329                   /* Function argument, anything goes but () must match. */
5330                   int depth= 0;
5331
5332                   while ((unsigned) *line >= ' ') {
5333                           if (*line == '(') depth++;
5334                           if (*line++ == ')' && --depth == 0) break;
5335                   }
5336           } else
5337           if (sugar(line)) {
5338                   /* Single character token. */
5339                   line++;
Expand/Collapse Item5340            } else {
These are character strings like boot, minix, 5000, hd2a, etc. (see comments for line 5307).
5341                   /* Multicharacter token. */
5342                   do line++; while ((unsigned) *line > ' ' && !sugar(line));
5343           }
Expand/Collapse Item5344            n= line - *aline;
Expand/Collapse Item5345            tok= malloc((n + 1) * sizeof(char));
Expand/Collapse Item5346            memcpy(tok, *aline, n);
Expand/Collapse Item5347            tok[n]= 0;
Let's look at an example.  Suppose the token is "hd2a". line would have advanced four characters beyond *aline.  So 4 char's would be allocated for "hd2a" and one char for the terminating 0.
Expand/Collapse Item5348            if (tok[0] == '\n') tok[0]= ';';        /* ';' same as '\n' */
Newlines ('\n') and semicolons (;) have the same meaning - they're both command separators.  We standardize by converting newlines to semicolons.
5349
Expand/Collapse Item5350            *aline= line;
This advances *aline to point to the next token.  Since aline points to line (from tokenize(); see line 5372), advancing *aline advances line (from tokenize()).
5351           return tok;
5352   }
5353
Expand/Collapse Item5354    /* Typed commands form strings of tokens. */

5355
5356   typedef struct token {
5357           struct token    *next;  /* Next in a command chain. */
5358           char            *token;
5359   } token;
5360
Expand/Collapse Item5361    token **tokenize(token **acmds, char *line)
tokenize() is called for the first time on line 5854 in get_parameters()(line 5773) when the boot parameters from sector PARAMSEC (=1) are tokenized. cmds (line 5382) is a pointer to the first token in the command chain.  After sector PARAMSEC is tokenized, the string ":;leader;main" is tokenized and appended to the command chain (see lines 5861-5862).

Again, we see a function parameter (token **acmds) that is a pointer to a pointer.  We saw this previously with  onetoken() (line 5313).  And again, we wish to change a pointer variable (for example, cmds on line 5854) so we pass in a pointer to this pointer variable.

The pointer manipulations in tokenize() are extremely tricky.  I hope that this slide show helps.  This slide show assumes that a pointer to cmds is passed into acmds.  This is the case if the command chain is empty.  The command chain is empty before the boot parameters from sector PARAMSEC are tokenized (see line 5854).  The command chain is also empty before the boot monitor reads a line and tokenizes it (see lines 6591-6598).

tokenize slide show

5362  /* Takes a line apart to form tokens.  The tokens are inserted into a
5363    * command chain at *acmds.  Tokenize returns a reference to where
5364    * another line could be added.  Tokenize looks at spaces as token
5365    * separators and recognizes only ';', '=', '{', '}', and '\n' as single
5366    * character tokens.  One token is formed from '(' and ')' with anything
5367    * in between as long as more match */
5368   {
5369           char *tok;
5370           token *newcmd;
5371
5372           while ((tok= onetoken(&line)) != nil) {
5373                   newcmd= malloc(sizeof(*newcmd));
5374                   newcmd->token= tok;
5375                   newcmd->next= *acmds;
5376                   *acmds= newcmd;
5377                   acmds= &newcmd->next;
5378           }
5379           return acmds;
5380   }
5381
Expand/Collapse Item5382    token *cmds;            /* String of commands to execute. */
cmds points to the first token in the command chain.
5383   int err;                /* Set on an error. */
5384
Expand/Collapse Item5385    char *poptoken(void)
poptoken() removes the first token from the command chain and returns the string.  For example, if the command chain is:

token1    token2    token3    token4    token5    token6
rootdev   =         hd2a      ;         menu      ;

poptoken() would remove token1 from the command chain and return a pointer to "rootdev".

In the figure below, note that there are two memory objects, one beginning at address token1 and one beginning at address a (the string).  The two memory objects are independent.  Freeing (i.e. deallocating) the memory object that begins at address token1 does not free the memory object that begins at address a. poptoken() frees the memory for the token.  voidtoken() frees the memory for the token and the string.


5386   /* Pop one token off the command chain. */
5387   {
5388          token *cmd= cmds;
5389           char *tok= cmd->token;
5390
Expand/Collapse Item5391           cmds= cmd->next;
cmds is advanced to the next token.  In the example in the comments for line 5385, cmds would point to token2.
Expand/Collapse Item5392            free(cmd);
Note that the memory that cmd references is freed (i.e. deallocated) but the memory that tok references is not freed.
5393
5394           return tok;
5395   }
5396
Expand/Collapse Item5397    void voidtoken(void)
voidtoken() removes the first token from the command chain and frees the memory of the token's string.  After a command has been processed, voidtoken() removes the command from the command chain (see lines 6379, 6426).
5398   /* Remove one token from the command chain. */
5399   {
5400           free(poptoken());
5401   }
5402
Expand/Collapse Item5403    int interrupt(void)
interrupt() returns 1 (TRUE) if the ESC key has been pressed. interrupt() sets the variable err (line 5383).

Occasionally, the boot monitor needs to check if the ESC (escape) key has been pressed.  The boot monitor allows the delay command (see line 6225) and the menu command (see line 6270) to be escaped (see line 6225).  The delay msec command pauses for msec milliseconds before executing the next command in the command chain.  The menu command shows the selection of kernels that can be booted.  Escaping the menu command sends the user back to the boot monitor prompt.

The command echo \w also calls interrupt(). echo \w waits until the escape key is pressed or a newline is entered (see line 6486).

5404   /* Clean up after an ESC has been typed. */
5405   {
Expand/Collapse Item5406            if (escape()) {
escape() returns 1 (TRUE) if the ESC key has been pressed.
5407                   printf("[ESC]\n");
5408                  err= 1;
5409                   return 1;
5410           }
5411           return 0;
5412   }
5413
5414   #if BIOS
5415
5416   int activate;
5417
Expand/Collapse Item5418    struct biosdev {
Expand/Collapse Item5419            char name[6];
Expand/Collapse Item5420            int device, primary, secondary;
Expand/Collapse Item5421    } bootdev, tmpdev;
bootdev is the partition or drive (in the case of a floppy) from which this boot monitor program originated.  If the user chooses to boot another partition or drive, tmpdev is the chosen partition or drive (see exec_bootstrap() on line 6070).

bootdev is set in initialize() (line 5450). tmpdev is set in name2dev() (line 5979).  name will be something like "fd1" (second floppy drive) or "hd7" (second partition on second hard drive; see line 5549) or "hd3a" (first subpartition on third partition on first hard drive).

device, primary and secondary for "fd1", "hd7", and "hd3a" are:

name= "fd1"
device= 0x01
primary= -1
secondary= -1

name= "hd7"
device= 0x81
primary= 1
secondary= -1

name= "hd3a"
device= 0x80
primary= 2
secondary= 0

5422
Expand/Collapse Item5423    int get_master(char *master, struct part_entry **table, u32_t pos)
The master boot record (MBR) is found in the first sector (sector 0) of a partitioned disk.  If a partition has subpartitions, an MBR will also be found in the first sector of the partition.

An MBR has two parts: the code and the partition table.  The MBR is 1 sector (=512 bytes) long and the partition table starts at byte 0x1BE (=446).  A partition table has 4 entries.  Here is an entry from the partition table:

get_master() copies the entire MBR (code + partition table) from disk into the memory address referenced by master and copies the memory addresses of the 4 partition entries from the partition table into the array referenced by table.  The figure below shows the variables and memory layout after get_parameter() has finished the copy.

get_master() is called on line 5519 and line 6085.  On line 5519, get_master() is called to determine the name of the partition (e.g. "hd2a").  On line 6085, get_master() is called to determine where to find the bootstrap that corresponds to tmpdev (see line 5418).  The code on lines 5519 and 6085 is concerned only with the partition table and ignores the code from the MBR.

5424   /* Read a master boot sector and its partition table. */
5425   {
5426           int r, n;
Expand/Collapse Item5427            struct part_entry *pe, **pt;
struct part_entry is declared in <ibm/partition.h> (see line 04100 in the book).
5428
Expand/Collapse Item5429            if ((r= readsectors(mon2abs(master), pos, 1)) != 0) return r;
int readsectors(u32_t bufaddr, u32_t sector, U8_t count) reads count bytes beginning at absolute sector address sector into absolute memory address bufaddr.

u32_t mon2abs(void *ptr) converts the offset memory address ptr to an absolute memory address.

5430
Expand/Collapse Item5431            pe= (struct part_entry *) (master + PART_TABLE_OFF);
pe is initialized to point to the first partition table entry.

PART_TABLE_OFFSET and NR_PARTITIONS are #defined in <ibm/partition.h>. NR_PARTITIONS = 4.

Expand/Collapse Item5432            for (pt= table; pt < table + NR_PARTITIONS; pt++) *pt= pe++;

The for loop fills in the array at address f (see figure above).

5433
5434    /* DOS has the misguided idea that partition tables must be sorted. */
Expand/Collapse Item5435            if (pos != 0) return 0;         /* But only the primary. */
If a partition is subpartitioned and the partition has a master boot record, the partition table from the master boot record does not need to be sorted.
5436
Expand/Collapse Item5437            n= NR_PARTITIONS;
Here's a review of partition table entries:

The partition table entries are sorted with the bubble sort algorithm.  Here is how the bubble sort algorithm works:

Partition table entry 1 and partition table entry 2 are compared.  If the first entry has a greater lowsec (lowsec is the sector number of the first sector in a partition) or is not being used (sysind=NO_PART), the two entries are swapped.  If the first entry has a lower lowsec and is being used, the first two entries are not swapped.  Entries 2 and 3 are next compared and swapped if entry 2 has a greater lowsec or is not being used.  Entries 3 and 4 are compared and swapped if entry 3 has a greater lowsec or is not being used.  Next, the process starts again and entries 1 and 2 are compared and swapped if appropriate.

Here is a figure that describes the entire sorting process.  Four partitions with lowsec's of 1, 509, 890, and 1500 are sorted.  The partition with a lowsec of 890 is not being used (NP).


5438           do {
5439                   for (pt= table; pt < table + NR_PARTITIONS-1; pt++) {
5440                           if (pt[0]->sysind == NO_PART
5441                                   || (pt[0]->lowsec > pt[1]->lowsec
5442                                          && pt[1]->sysind != NO_PART)) {
5443                                   pe= pt[0]; pt[0]= pt[1]; pt[1]= pe;
5444                           }
5445                   }
5446           } while (--n > 0);
5447           return 0;
5448   }
5449
Expand/Collapse Item5450    void initialize(void)
initialize() is called from boot() on line 6609. initialize() does 3 things:

1)  Relocates the boot monitor to the upper end of low memory.  Low memory is memory that is under 640KB.

2)  Removes the boot monitor from the memory map that is passed to the kernel.  This prevents the kernel from allocating the memory that the boot monitor occupies.

3)  Initializes bootdev (see comments for line 5418).

5451   {
Expand/Collapse Item5452            char master[SECTOR_SIZE];
The master boot record (MBR) is copied into master[] on line 5519.  For a description of the MBR, see the comments for get_master() (line 5423).
Expand/Collapse Item5453            struct part_entry *table[NR_PARTITIONS];
table[] is an array of pointers to partition table entries (struct part_entry).  table[] has NR_PARTITIONS (=4) elements and is filled in by get_master() (see line 5519).
5454           int r, p;
Expand/Collapse Item5455            u32_t masterpos;
masterpos is the absolute sector number of a sector that holds a master boot record (MBR).  The first sector of a partitioned disk always holds an MBR.  If a partition has subpartitions, the first sector of the partition holds an MBR.
5456           static char sub[]= "a";
Expand/Collapse Item5457            char *argp;
argp is specific to DOS.
5458
Expand/Collapse Item5459    /* Copy the boot program to the far end of low memory, this must be
Expand/Collapse Item5460      * done to get out of the way of Minix, and to put the data area
Expand/Collapse Item5461      * cleanly inside a 64K chunk if using BIOS I/O (no DMA problems).
Expand/Collapse Item5462      */
mem[] is declared in boot.h and initialized in boothead.s. mem[] describes the available memory and is the "memory map" that is passed to the kernel.  The boot monitor is removed from the memory map on lines 5482-5488.

caddr, daddr and runsize are also declared in boot.h and initialized in boothead.scaddr is the absolute memory address of the beginning of the boot monitor.  Since the boot monitor is relocated to the upper end of low memory, caddr will change (see line 5473).  daddr is the absolute memory address of the beginning of the data segment of the boot monitor.  runsize is the size (in bytes) of the boot monitor.

5463           u32_t oldaddr= caddr;
5464           u32_t memend= mem[0].base + mem[0].size;
Expand/Collapse Item5465            u32_t newaddr= (memend - runsize) & ~0x0000FL;
The L in ~0x000FL stands for Long.  In Minix, the type long is 32-bits.  ~0x000FL = ~0x0000000F = 0xFFFFFFF0.  Note that ~0x000F (without the L) is equal to 0xFFF0 and not 0xFFFFFFF0.

Since memend and runsize are 32-bit values, newaddr = (memend-runsize) & ~0x000FL will be on a 16-byte boundary.  Since the boot monitor runs in real mode, the beginning of a segment (in this case, the code segment) must be on a 16-byte boundary.

5466   #if !DOS
Expand/Collapse Item5467            u32_t dma64k= (memend - 1) & ~0x0FFFFL;
There was an error! 2002: php_network_getaddresses: getaddrinfo failed: Name or service not known
(memend-1) is the last (upper) byte of low memory.  dma64k = (memend-1) & ~0x0FFFFL rounds down to the nearest 64K boundary.

The figure below demonstrates the scenario that we wish to avoid.

(daddr-caddr) is the size of the code segment.  Since newaddr + (size of code segment) < dma64k (and therefore the data segment crosses a 64KB boundary) in the figure above, an adjustment is necessary. The figure below shows the adjustment.

I don't understand why the data segment of the boot monitor isn't allowed to cross a 64KB boundary.  If you understand why the data segment isn't allowed to cross a 64KB boundary, please submit a comment to the site which will be displayed below.

5469           /* Check if data segment crosses a 64K boundary. */
5470           if (newaddr + (daddr - caddr) < dma64k) newaddr= dma64k - runsize;
5471   #endif
5472           /* Set the new caddr for relocate. */
5473           caddr= newaddr;
5474
5475           /* Copy code and data. */
Expand/Collapse Item5476            raw_copy(newaddr, oldaddr, runsize);
void raw_copy(u32_t dstaddr, u32_t srcaddr, u32_t count) copies count bytes from absolute memory address srcaddr to absolute memory address dstaddrraw_copy() copies the boot monitor from its current location to the upper end of low memory.
5477
5478           /* Make the copy running. */
Expand/Collapse Item5479            relocate();
relocate() forces the jump to the upper end of low memory. relocate() is an interesting function worthy of careful study.
5480
Expand/Collapse Item5481    #if !DOS
Expand/Collapse Item5482 /* Take the monitor out of the memory map if we have memory to spare,
Expand/Collapse Item5483    * and also keep the BIOS data area safe (1.5K), plus a bit extra for
Expand/Collapse Item5484    * where we may have to put a.out headers for older kernels.
Expand/Collapse Item5485    */
There was an error! 2002: php_network_getaddresses: getaddrinfo failed: Name or service not known
If there is any extended memory (mem[1].size > 0), the boot monitor is taken out of the memory map.  This protects the boot monitor from being overwritten and allows a return to the boot monitor.

If a return to the boot monitor (to either bios13 or int86) from the kernel is made in order to make a bios interrupt call, the bios interrupt vectors must be at memory locations 0x0000-0x03FF and the bios data area  must be at 0x0400-0x04FF.  This accounts for 1.25K.  I don't know about the next .25K and I'm not really sure why .5K is reserved for older a.out headers.  If you understand why the last .75K is reserved, please submit a comment to the site which will be displayed below.

5486           if (mem[1].size > 0) mem[0].size = newaddr;
5487          mem[0].base += 2048;
5488           mem[0].size -= 2048;
5489
Expand/Collapse Item5490            /* Set the parameters for the BIOS boot device. */
Expand/Collapse Item5491            (void) dev_open();
devopen() sets sectors and secspcyl for the floppy or hard drive specified by devicesectors is the number of sectors per track of the device. secspcyl is the number of sectors per cylinder of the device.

5492
Expand/Collapse Item5493            /* Find out what the boot device and partition was. */
Lines 5494-5552 fill in bootdev.  Here's a review of 3 possible sets of values for bootdev:

name= "fd1"
device= 0x01
primary= -1
secondary= -1

name= "hd7"
device= 0x81
primary= 1
secondary= -1

name= "hd3a"
device= 0x80
primary= 2
secondary= 0

Expand/Collapse Item5494            bootdev.name[0]= 0;
I'm not sure why name[0] is initialized to 0.  strcpy() on lines 5502 and 5548 doesn't require this initialization.
Expand/Collapse Item5495            bootdev.device= device;
device is set in boothead.s.  The bootstrap (bootblock.s) passes the device number in register dl to the secondary boot (the boot monitor - this program).  The bootstrap also passes the segment:offset address of the partition table entry of the booted partition in es:si.
Expand/Collapse Item5496            bootdev.primary= -1;
Expand/Collapse Item5497            bootdev.secondary= -1;
If device has no partitions (for example, if device is a floppy drive), primary remains -1.  If the hard drive partition is not subpartitioned, secondary remains -1.  (See the examples for the comment for line 5493.)
5498
Expand/Collapse Item5499            if (device < 0x80) {
If device is a floppy drive, name is set to either "fd0" or "fd1".  primary and secondary remain -1.
5500                   /* Floppy. */
5501                   strcpy(bootdev.name, "fd");
Expand/Collapse Item5502                    strcat(bootdev.name, ul2a10(bootdev.device));
char *strcat(s1, s2) concatenates string s2 to the end of string s1 and returns s1strcat() is declared in <string.h>.
5503                   return;
5504           }
5505
5506       /* Get the partition table from the very first sector, and determine
5507        * the partition we booted from using the information from the booted
5508        * partition entry as passed on by the bootstrap (rem_part).  All we
5509        * need from it is the partition offset.
5510        */
Expand/Collapse Item5511            raw_copy(mon2abs(&lowsec),
Expand/Collapse Item5512               vec2abs(&rem_part) + offsetof(struct part_entry, lowsec),
Expand/Collapse Item5513                    sizeof(lowsec));
rem_part is set in boothead.s. rem_part holds the segment:offset address of the partition table entry for the partition being booted.  raw_copy() copies the lowsec field of this partition table entry (see figure below) into the global variable lowsec . vec2abs() converts this segment:offset address into an absolute address.

void raw_copy(u32_t dstaddr, u32_t srcaddr, u32_t count) copies count bytes from absolute memory address srcaddr to absolute memory address dstaddr.

u32_t mon2abs(void *ptr) converts the offset memory address ptr to an absolute memory address.

int offsetof(struct struct1, field) returns the offset of field within struct1part_entry is shown on line 04104 in the book.  Since lowsec is the 9th field after 8 fields of type unsigned char, offsetof(struct part_entry, lowsec) returns 8.  (An unsigned char is 1 byte.)

Since lowsec has type u32_t, sizeof(lowsec) returns 4.

5514
Expand/Collapse Item5515            masterpos= 0;   /* Master bootsector position. */
At this point, it is known that a partition (or subpartition) from hard drive device has been booted and the lowest sector number (lowsec) of this partition (or subpartition) is also known.  However, the partition number (or the subpartition number) is not known.  In other words, it is not known if partition hd3 or subpartition hd2a or subpartition hd4b has been booted.

In order to determine the partition (or subpartition) number, the partition table is copied from the master boot record (MBR) on device's sector 0 and lowsec is compared with the lowsec field of the partition table entries.  If the active partition is subpartitioned, the partition table from the MBR on the first sector of the active partition must also be analyzed.

5516
Expand/Collapse Item5517            for (;;) {
The for loop loops around once if the booted partition is subpartitioned.  If the booted partition is not subpartitioned, the for loop does not loop around.
5518             /* Extract the partition table from the master boot sector. */
Expand/Collapse Item5519                    if ((r= get_master(master, table, masterpos)) != 0) {
int get_master(char *master, struct part_entry **table, u32_t pos) (line 5423) loads the master boot record (MBR) at disk sector number pos into master and loads table[] with pointers to the partition table entries from the partition table in the MBR.  get_master() also sorts the pointers according to the entry's lowsec.  After get_master() returns, the memory layout will be as shown in the figure below.


5520                       readerr(masterpos, r); exit(1);
5521                   }
5522
5523                   /* See if you can find "lowsec" back. */
5524                   for (p= 0; p < NR_PARTITIONS; p++) {
Expand/Collapse Item5525                       if (lowsec - table[p]->lowsec < table[p]->size) break;
If (lowsec - table[p]->lowsec < table[p].size), there are two possibilities (provided there is no error):

1)  lowsec == table[p]->lowsec

The partition or subpartition has been found (see line 5528).

2)  lowsec > table[p]->lowsec

table[p] points to the partition table entry whose partition contains the booted subpartition (i.e. the subpartition with a lowest sector of lowsec).

5526                   }
5527
5528                   if (lowsec == table[p]->lowsec) {       /* Found! */
5529                           if (bootdev.primary < 0)
5530                               bootdev.primary= p;
5531                           else
5532                               bootdev.secondary= p;
5533                           break;
5534                   }
5535
Expand/Collapse Item5536                    if (p == NR_PARTITIONS || bootdev.primary >= 0) {
Something went wrong.  A value of -1 for bootdev.device indicates an error.
5537                    /* The boot partition cannot be named, this only means
5538                     * that "bootdev" doesn't work.
5539                     */
5540                           bootdev.device= -1;
5541                           return;
5542                   }
5543
Expand/Collapse Item5544                    /* See if the primary partition is subpartitioned. */
Unless an error has occurred or will occur, the partition that contains the booted subpartition has been found.  The code loops around to line 5517 to look for the subpartition.
5545                   bootdev.primary= p;
5546                   masterpos= table[p]->lowsec;
5547           }
Expand/Collapse Item5548        strcpy(bootdev.name, "hd");
Expand/Collapse Item5549        strcat(bootdev.name, ul2a10((device - 0x80) * (1 + NR_PARTITIONS)
Expand/Collapse Item5550                                                + 1 + bootdev.primary));
Expand/Collapse Item5551        sub[0]= 'a' + bootdev.secondary;
Expand/Collapse Item5552        if (bootdev.secondary >= 0) strcat(bootdev.name, sub);
Here are 2 examples of (device, primary, secondary) triplets to name conversions:

name= "hd7"
device= 0x81
primary= 1
secondary= -1

name= "hd3a"
device= 0x80
primary= 2
secondary= 0

NR_PARTITIONS (=4) is declared in <ibm/partition.h>.

char *strcat(s1, s2) concatenates string s2 to the end of string s1 and returns s1strcat() is declared in <string.h>.

char *ul2a10(u32_t n) (line 5746) converts n (an unsigned long) to an ascii string (base 10).

5553
Expand/Collapse Item5554    #else /* DOS */
I do not cover DOS-specific sections of code (lines 5555-5593).  I may cover these sections at a later time.
5555      /* Take the monitor out of the memory map if we have memory to spare,
5556       * note that only half our PSP is needed at the new place, the first
5557       * half is to be kept in its place.
5558       */
5559           if (mem[1].size > 0) mem[0].size = newaddr + 0x80 - mem[0].base;
5560
5561           /* Parse the command line. */
5562           argp= PSP + 0x81;
5563           argp[PSP[0x80]]= 0;
5564           while (between('\1', *argp, ' ')) argp++;
5565           vdisk= argp;
5566           while (!between('\0', *argp, ' ')) argp++;
5567           while (between('\1', *argp, ' ')) *argp++= 0;
5568           if (*vdisk == 0) {
5569                   printf("\nUsage: boot <vdisk> [commands ...]\n");
5570                   exit(1);
5571           }
5572           drun= *argp == 0 ? "main" : argp;
5573
5574           if ((r= dev_open()) != 0) {
5575                   printf("\n%s: Error %02x (%s)\n", vdisk, r, bios_err(r));
5576                   exit(1);
5577           }
5578
5579           /* Find the active partition on the virtual disk. */
5580           if ((r= get_master(master, table, 0)) != 0) {
5581                   readerr(0, r); exit(1);
5582           }
5583
5584           strcpy(bootdev.name, "dosd0");
5585           bootdev.primary= -1;
5586           for (p= 0; p < NR_PARTITIONS; p++) {
5587              if (table[p]->bootind != 0 && table[p]->sysind == MINIX_PART) {
5588                           bootdev.primary= p;
5589                           bootdev.name[4]= '1' + p;
5590                           lowsec= table[p]->lowsec;
5591                           break;
5592               }
5593           }
5594   #endif /* DOS */
5595   }
5596
5597   #endif /* BIOS */
5598
Expand/Collapse Item5599    char null[]= "";        /* This kludge saves lots of memory. */
null[] is a single char, '\0'.  This is referred to as the "null string."

In order to signify that a string has no value, the string is set to the null string.  For example, on line 5693, the third argument for b_setenv() is the null string.  The third parameter to b_setenv()(line 5653) is char *arg and is used to pass a function argument.  Since b_setvar() (line 5690) is specifically for variables and for functions without arguments, passing in the null string for char *arg is appropriate.

A null string (i.e. a single char, '\0') could be created each time we wished to signify that a string had no value.  However, as the comment suggests, this would be a significant waste of memory.  On the other hand, care must be taken when memory is freed.  On line 5604, sfree() checks to make sure it's not deallocating the global variable null[], since many variables will undoubtedly be pointed to null[].

5600
Expand/Collapse Item5601    void sfree(char *s)
Expand/Collapse Item5602    /* Free a non-null string. */
For the reasons given for line 5599, care must be taken not to free null[].
5603   {
5604           if (s != nil && s != null) free(s);
5605   }
5606
Expand/Collapse Item5607    char *copystr(char *s)
Expand/Collapse Item5608    /* Copy a non-null string using malloc. */
copystr() allocates memory (using malloc()) for a new string and copies the string s (using strcpy()) to it.  If the string s is a null string, copystr() returns null.
5609   {
5610           char *c;
5611
5612           if (*s == 0) return null;
Expand/Collapse Item5613            c= malloc((strlen(s) + 1) * sizeof(char));
void *malloc(size_t size) allocates size bytes from the heap and returns a pointer to the first byte of the allocated memory. malloc() is declared in stdlib.h.  (The heap is the region between the stack and the bss.)
Expand/Collapse Item5614            strcpy(c, s);
char *strcpy(char *s1,char *s2) copies string s2 to string s1, including '\0' and returns s1.
5615           return c;
5616   }
5617
Expand/Collapse Item5618    int is_default(environment *e)
Lines 5618-5864 are principally concerned with environment variables and functions.  Environment variables and functions for the boot monitor can be broken into 3 categories:

1)  Variables and functions that the minix kernel needs.  Examples are processor, bus, video, and mem[].  These are system parameters that the boot monitor determined that the kernel needs to know.  params2params() in bootimage.c packages the environment variables so that they can be passed as an argument to the minix kernel .

2)  Variables and functions that the boot monitor needs.  These are typically commands that perform a small task.  Examples are leader, main, and trailer (see lines 5825).  The variable image (see line 5826) also falls into this category.

3)  Reserved names.  The names of built-in commands (like boot, menu, and ls) are protected and their function cannot be changed by the set command.

struct environment is declared in boot.h .  The flags field describes the behavior of the environment variable or function.  The following attributes (bits) are the most difficult to understand:

E_RESERVED(=0x04):  Environment variables and functions that have the E_RESERVED bit set cannot be altered.  These are the names of built-in commands (see lines 5837-5846).

E_STICKY(=0x07):  Once the E_STICKY bit is set for a variable or function, the bit cannot be unset.  However, the E_STICKY bit is meaningless otherwise.  The E_STICKY bit is never checked and none of the environment variables or functions set in get_parameters() (line 5773) have the E_STICKY bit set.  The E_STICKY bit was perhaps significant in an earlier version of the boot monitor.

E_SPECIAL(0x01):  An environment variable with the E_SPECIAL bit set cannot be changed to a function and an environment function with the E_SPECIAL bit set cannot be changed to a variable (see lines 5670-5675).  Also, an environment variable or function with the E_SPECIAL bit set "remembers" its initial value.  Before the value field of an E_SPECIAL environment variable or function is changed for the first time, the value field is copied to the defval field.  If the E_SPECIAL environment variable or function is later unset, instead of removing the variable or function from the environment, the defval field is copied back to the value field (see lines 5705-5712).

The defval field of an E_SPECIAL environment variable or function is initially nil and becomes nil again if the field value is restored with its default value (with the unset command).  is_default() can therefore look at the field defval to determine if the environment variable or function has its "default" value.

5619   {
5620           return (e->flags & E_SPECIAL) && e->defval == nil;
5621   }
5622
Expand/Collapse Item5623    environment **searchenv(char *name)
searchenv() searches through the environment variables and functions for name and returns a pointer to a pointer to the corresponding environment.
5624   {
Expand/Collapse Item5625            environment **aenv= &env;
After aenv is initialized to &env, the memory layout is as shown:


5626
Expand/Collapse Item5627            while (*aenv != nil && strcmp((*aenv)->name, name) != 0) {
Here is a slideshow that shows the while loop:
5628                   aenv= &(*aenv)->next;
5629           }
5630
5631           return aenv;
5632   }
5633
Expand/Collapse Item 5634    #define b_getenv(name)  (*searchenv(name))
b_getenv() is a macro that dereferences the return value of searchenv() (line 5623).  The return value of b_getenv() is a pointer to an environment rather than a pointer to a pointer to an environmentb_getenv() is used in the next function, b_value() (line 5637).
xvc
5635/* Return the environment *structure* belonging to name, nil if not found.*/
5636
Expand/Collapse Item5637    char *b_value(char *name)
Expand/Collapse Item5638    /* The value of a variable. */
b_value() returns the value of the environment variable name or nil (0) if it is a function or it doesn't exist.
5639   {
5640           environment *e= b_getenv(name);
5641
5642           return e == nil || !(e->flags & E_VAR) ? nil : e->value;
5643   }
5644
Expand/Collapse Item5645    char *b_body(char *name)
Expand/Collapse Item5646    /* The value of a function. */
b_body() returns the value of the environment function name or nil (0) if it is a variable or it doesn't exist.
5647   {
5648           environment *e= b_getenv(name);
5649
5650           return e == nil || !(e->flags & E_FUNCTION) ? nil : e->value;
5651   }
5652
Expand/Collapse Item5653    int b_setenv(int flags, char *name, char *arg, char *value)
Expand/Collapse Item5654 /* Change the value of an environment variable.  Returns the flags of the
Expand/Collapse Item5655     * variable if you are not allowed to change it, 0 otherwise.
Expand/Collapse Item5656     */
b_setenv() changes the value of an environment variable or function or creates a new environment variable or function.

The flags field of environment describes the behavior of the environment variable or function.  The following attributes (bits) are the most difficult to understand:

E_RESERVED(=0x04):  Environment variables and functions that have the E_RESERVED bit set cannot be altered.  These are the names of built-in commands (see lines 5837-5846).  (See comments for lines 5696-5699).

E_STICKY(=0x07):  Once the E_STICKY bit is set for a variable or function, the bit cannot be unset.  However, the E_STICKY bit is meaningless otherwise.  The E_STICKY bit is never checked and none of the environment variables or functions set in get_parameters() (line 5773) have the E_STICKY bit set.  The E_STICKY bit was perhaps significant in an earlier version of the boot monitor.

E_SPECIAL(0x01):  An environment variable with the E_SPECIAL bit set cannot be changed to a function and an environment function with the E_SPECIAL bit set cannot be changed to a variable (see lines 5670-5675).  Also, an environment variable or function with the E_SPECIAL bit set "remembers" its initial value.  Before the value field of an E_SPECIAL environment variable or function is changed for the first time, the value field is copied to the defval field.  If the E_SPECIAL environment variable or function is later unset, instead of removing the variable or function from the environment, the defval field is copied back to the value field (see lines 5705-5712).

5657   {
5658           environment **aenv, *e;
5659
Expand/Collapse Item5660            if (*(aenv= searchenv(name)) == nil) {
If *aenv==nil, the environment variable name does not exist and must be created.
Expand/Collapse Item5661                    e= malloc(sizeof(*e));
The sizeof operator is flexible.  The argument for sizeof can be a type (like int, char, etc.), a variable, or a dereferenced pointer (in this case, *e).

As noted above, sizeof is an operator and not a function.  What this means is that sizeof is a part of the C language itself, and not a function that needs to be declared in a header file.

Expand/Collapse Item5662                    e->name= copystr(name);
copystr() (line 5607) allocates memory (using malloc()) for a new string and copies the string name (using strcpy()) to it.
5663                   e->flags= flags;
5664                   e->defval= nil;
5665                   e->next= nil;
5666                   *aenv= e;
Expand/Collapse Item5667            } else {
The environment variable name already exists.  Be careful with E_RESERVED and E_SPECIAL environment variables and functions.
5668                   e= *aenv;
5669
Expand/Collapse Item5670                    /* Don't touch reserved names and don't change special
Expand/Collapse Item5671                     * variables to functions or vv.
Expand/Collapse Item5672                     */
E_RESERVED(=0x04):  Environment variables and functions that have the E_RESERVED bit set cannot be altered.  These are the names of built-in commands (see lines 5837-5846).  (See comments for lines 5696-5699).

E_SPECIAL(0x01):  An environment variable with the E_SPECIAL bit set cannot be changed to a function and an environment function with the E_SPECIAL bit set cannot be changed to a variable (see lines 5670-5675).  Also, an environment variable or function with the E_SPECIAL bit set "remembers" its initial value.  Before the value field of an E_SPECIAL environment variable or function is changed for the first time, the value field is copied to the defval field.  If the E_SPECIAL environment variable or function is later unset, instead of removing the variable or function from the environment, the defval field is copied back to the value field (see lines 5705-5712).

5673                   if (e->flags & E_RESERVED || (e->flags & E_SPECIAL
5674                           && (e->flags & E_FUNCTION) != (flags & E_FUNCTION)
5675                   )) return e->flags;
5676
Expand/Collapse Item5677                    e->flags= (e->flags & E_STICKY) | flags;
E_STICKY(=0x07):  Once the E_STICKY bit is set for a variable or function, the bit cannot be unset.  However, the E_STICKY bit is meaningless otherwise.  The E_STICKY bit is never checked and none of the environment variables or functions set in get_parameters() (line 5773) have the E_STICKY bit set.  The E_STICKY bit was perhaps significant in an earlier version of the boot monitor.
5678                   if (is_default(e)) {
Expand/Collapse Item5679                            e->defval= e->value;
As noted above, an environment variable or function with the E_SPECIAL bit set "remembers" its initial value.  Before the value field of an E_SPECIAL environment variable or function is changed for the first time, the value field is copied to the defval field.  If the E_SPECIAL environment variable or function is later unset, instead of removing the variable or function from the environment, the defval field is copied back to the value field (see lines 5705-5712).
5680                   } else {
Expand/Collapse Item5681                           sfree(e->value);
e->value gets its new value on line 5686.
5682                   }
Expand/Collapse Item5683                    sfree(e->arg);
e->arg gets its new value on line 5685.
5684           }
5685           e->arg= copystr(arg);
5686           e->value= copystr(value);
5687           return 0;
5688   }
5689
Expand/Collapse Item5690    int b_setvar(int flags, char *name, char *value)
Expand/Collapse Item5691    /* Set variable or simple function. */
arg for int b_setenv(int flags, char *name, char *arg, char *value) doesn't exist for a variable or a simple function (i.e. a function without arguments).  null is passed in for arg.
5692   {
5693           return b_setenv(flags, name, null, value);
5694   }
5695
Expand/Collapse Item5696    void b_unset(char *name)
Expand/Collapse Item5697 /*Remove a variable from the environment.  A special variable is reset to
Expand/Collapse Item5698     * its default value.
Expand/Collapse Item5699     */
E_RESERVED(=0x04):  Environment variables and functions that have the E_RESERVED bit set cannot be altered.  These are the names of built-in commands (see lines 5837-5846).

E_SPECIAL(0x01):  An environment variable with the E_SPECIAL bit set cannot be changed to a function and an environment function with the E_SPECIAL bit set cannot be changed to a variable (see lines 5670-5675).  Also, an environment variable or function with the E_SPECIAL bit set "remembers" its initial value.  Before the value field of an E_SPECIAL environment variable or function is changed for the first time, the value field is copied to the defval field.  If the E_SPECIAL environment variable or function is later unset, instead of removing the variable or function from the environment, the defval field is copied back to the value field (see lines 5705-5712).

Strangely enough, b_unset() does not check to see if it's unsetting an E_RESERVED environment variable.

hd2a>name1() {echo hello}
hd2a>name1
hello
hd2a>boot() {echo hello}
boot is a reserved word
hd2a>unset boot
hd2a>boot() {echo hello}
hd2a>boot

(system boots)

Since execute() (see line 6535) checks for built-in commands (like "boot") before user-defined commands, the built-in boot takes precedence over the user-defined boot and the system boots instead of echoing "hello".

5700   {
5701           environment **aenv, *e;
5702
5703           if ((e= *(aenv= searchenv(name))) == nil) return;
5704
5705           if (e->flags & E_SPECIAL) {
5706                   if (e->defval != nil) {
5707                          sfree(e->arg);
5708                           e->arg= null;
5709                          sfree(e->value);
5710                           e->value= e->defval;
5711                           e->defval= nil;
5712                   }
5713           } else {
5714                   sfree(e->name);
5715                   sfree(e->arg);
5716                   sfree(e->value);
Expand/Collapse Item5717                    *aenv= e->next;
B_unset slide show
5718                   free(e);
5719           }
5720   }
5721
Expand/Collapse Item5722    long a2l(char *a)
Expand/Collapse Item5723    /* Cheap atol(). */
a2l() converts a string (like "-1023") to a long.

I'm not sure why this function is described as "cheap."  Perhaps atol() (from <stdlib.h>) performs error-checking. a2l() performs no error-checking.

5724   {
5725           int sign= 1;
5726           long n= 0;
5727
5728           if (*a == '-') { sign= -1; a++; }
5729
Expand/Collapse Item5730            while (between('0', *a, '9')) n= n * 10 + (*a++ - '0');
between() (line 5048) returns TRUE if *a (the value at address a) is between '0' and '9'.

*a++ dereferences pointer a and then increments a.  It does not increment the value at address a.

5731
5732           return sign * n;
5733   }
5734
Expand/Collapse Item5735    char *ul2a(u32_t n, unsigned b)
Expand/Collapse Item5736    /* Transform a long number to ascii at base b, (b >= 8). */
ul2a() converts an unsigned long to an ascii string. b specifies the format of the number.  For example, if b=0x10=16, n=23 is converted to "17"; if b=0x08=8, n=23 is converted to "27".

The ascii string is held in num[], which is 12 char's (bytes) (see comments for line 5738).  If b < 8, 12 bytes is not enough to hold the largest possible value of n.

5737   {
Expand/Collapse Item5738            static char num[(CHAR_BIT * sizeof(n) + 2) / 3 + 1];
CHAR_BIT (=8) is declared in <limits.h>.  It is the number of bits in a char.

(CHAR_BIT*sizeof(n)+2)/3 + 1 = (8*4+2)/3 + 1
 = 34/3 + 1 = 12
(Remember, this is integer math - in other words, fractions are discarded.)

An internal static variable retains its value from one invocation of the function to the next.  The static declaration has a different meaning for external variables (see comments for line 5057).

Expand/Collapse Item5739            char *a= arraylimit(num) - 1;
The least significant digit is determined first.  We start at the end of num[] and work our way back.

% is the modulus (remainder) operator.  11%3=2.

*a-- dereferences pointer a and then decrements a. It does not decrement the value at address a.

5740           static char hex[16] = "0123456789ABCDEF";
5741
5742           do *--a = hex[(int) (n % b)]; while ((n/= b) > 0);
5743           return a;
5744   }
5745
5746   char *ul2a10(u32_t n)
5747   /* Transform a long number to ascii at base 10. */
5748   {
5749           return ul2a(n, 10);
5750   }
5751
Expand/Collapse Item5752    unsigned a2x(char *a)
Expand/Collapse Item5753    /* Ascii to hex. */
a2x() converts ascii strings in hexadecimal notation to unsigned's.  The comment "Ascii to hex" is a little confusing.
5754   {
5755           unsigned n= 0;
5756           int c;
5757
5758           for (;;) {
5759                   c= *a;
5760                   if (between('0', c, '9')) c= c - '0' + 0x0;
5761                   else
5762                   if (between('A', c, 'F')) c= c - 'A' + 0xA;
5763                   else
5764                   if (between('a', c, 'f')) c= c - 'a' + 0xa;
5765                   else
5766                           break;
Expand/Collapse Item5767                    n= (n<<4) | c;
Shifting a value to the left 4 bits is the equivalent of multiplying the value by 16.
5768                   a++;
5769           }
5770           return n;
5771   }
5772
Expand/Collapse Item5773    void get_parameters(void)
get_parameters() is called in boot() (see line 6611).  The name get_parameters() is a little misleading since it sets many environment variables and functions in addition to getting parameters (from the bootparams sector - see line 5848).
5774   {
Expand/Collapse Item5775            char params[SECTOR_SIZE + 1];
readsectors() (see line 5849) reads the bootparams sector into params[].  The "+1" is for the terminating '\0'.
Expand/Collapse Item5776            token **acmds;
cmds (line 5382) points to the first token.  acmds points to where another token and another line can be added (see line 5854).
5777           int r;
5778           memory *mp;
Expand/Collapse Item5779            static char bus_type[][4] = {
An internal static variable (like the array bus_type[][]) retains its value from one invocation of the function to the next.  The static declaration has a different meaning for global variables (see comments for line 5057).

bus_type[0][0] = 'x'; bus_type[0][1] = 't'; bus_type[0][2] = '\0'

bus_type could have been declared as bus_type[3][4] instead of bus_type[][4] since there are 3 rows: "xt", "at", and "mca".  Instead, the compiler determines the number of rows and does the work for us.  4 is the number of char's (bytes) that the largest row ("mca") takes up ('m', 'c', 'a', '\0').

5780                   "xt", "at", "mca"
5781           };
5782           static char vid_type[][4] = {
5783                   "mda", "cga", "ega", "ega", "vga", "vga"
5784           };
5785           static char vid_chrome[][6] = {
5786                   "mono", "color"
5787           };
5788
Expand/Collapse Item5789            /* Variables that Minix needs: */
The E_SPECIAL and E_RESERVED variables are set here.  Here's a review:

E_SPECIAL(0x01):  An environment variable with the E_SPECIAL bit set in the flags field (of environment cannot be changed to a function and an environment function with the E_SPECIAL bit set cannot be changed to a variable (see lines 5670-5675).  Also, an environment variable or function with the E_SPECIAL bit set "remembers" its initial value.  Before the value field of an E_SPECIAL environment variable or function is changed for the first time, the value field is copied to the defval field.  If the E_SPECIAL environment variable or function is later unset, instead of removing the variable or function from the environment, the defval field is copied back to the value field (see lines 5705-5712).

E_RESERVED(=0x04):  Environment variables and functions that have the E_RESERVED bit set cannot be altered.  These are the names of built-in commands (see lines 5837-5846).  (See comments for lines 5696-5699).

params2params() in bootimage.c packages the environment variables so that they can be passed as an argument to the minix kernel.

A good description of these environment variables and boot monitor commands can be found in the man page.

5790           b_setvar(E_SPECIAL|E_VAR|E_DEV, "rootdev", "ram");
5791           b_setvar(E_SPECIAL|E_VAR|E_DEV, "ramimagedev", "bootdev");
5792           b_setvar(E_SPECIAL|E_VAR, "ramsize", "0");
5793   #if BIOS
Expand/Collapse Item5794            b_setvar(E_SPECIAL|E_VAR, "processor", ul2a10(getprocessor()));
get_processor() returns 86 for an 8086, 286 for an 80286, etc.

ula10() (line 5746) converts an unsigned long to an ascii string with base 10.

5795           b_setvar(E_SPECIAL|E_VAR, "bus", bus_type[get_bus()]);
5796           b_setvar(E_SPECIAL|E_VAR, "video", vid_type[get_video()]);
5797           b_setvar(E_SPECIAL|E_VAR, "chrome", vid_chrome[get_video() & 1]);
5798           params[0]= 0;
Expand/Collapse Item5799            for (mp= mem; mp < arraylimit(mem); mp++) {
mem[] was set in boothead.s.  The value field of environment for memory will be something like:  "800:7FF00,100000:800000,1000000:400000" (Note that this is a string).  The first of the three (base,size) pairs is for lower memory (memory under 1MB).  The second (base, size) pair is for memory between 1MB and 16MB.  The third (base, size) pair is for memory greater than 16MB.  If the memory of the second and third regions is contiguous, then the memory from the third region is aggregated into the second region.  If there is no memory above 1MB, then there will only be a single (base, size) pair.
5800                   if (mp->size == 0) continue;
5801                   if (params[0] != 0) strcat(params, ",");
5802                   strcat(params, ul2a(mp->base, 0x10));
5803                   strcat(params, ":");
5804                   strcat(params, ul2a(mp->size, 0x10));
5805           }
5806           b_setvar(E_SPECIAL|E_VAR, "memory", params);
Expand/Collapse Item5807    #if DOS
I do not cover DOS-specific sections of code (line 5808).  I may cover these sections at a later time.
5808           b_setvar(E_SPECIAL|E_VAR, "dosd0", vdisk);
5809   #else /* !DOS */
Expand/Collapse Item5810            /* Obsolete memory size variables. */
memory (see line 5806) replaces memsize and emssize.
5811           b_setvar(E_SPECIAL|E_VAR, "memsize",
5812                               ul2a10((mem[0].base + mem[0].size) / 1024));
5813           b_setvar(E_SPECIAL|E_VAR, "emssize", ul2a10(mem[1].size / 1024));
5814   #endif
5815
5816   #endif
Expand/Collapse Item5817    #if UNIX
I do not cover UNIX-specific sections of code (lines 5818-5822).  I may cover these sections at a later time.
5818           b_setvar(E_SPECIAL|E_VAR, "processor", "?");
5819           b_setvar(E_SPECIAL|E_VAR, "bus", "?");
5820           b_setvar(E_SPECIAL|E_VAR, "video", "?");
5821           b_setvar(E_SPECIAL|E_VAR, "chrome", "?");
5822           b_setvar(E_SPECIAL|E_VAR, "memory", "?");
5823   #endif
5824
5825           /* Variables boot needs: */
Expand/Collapse Item5826            b_setvar(E_SPECIAL|E_VAR, "image", "minix");
minix is not an image file but a directory (/minix).  The boot monitor searches through the /minix directory and boots the image file with the most recent modification time.  To boot a specific image, reset image:

hd2a> image = /minix/minix_386_09282000

Expand/Collapse Item5827            b_setvar(E_SPECIAL|E_FUNCTION, "leader",
Expand/Collapse Item5828                    "echo \\cMinix boot monitor \\v\\n"
Expand/Collapse Item5829                    "\\nPress ESC to enter the monitor");
leader is the second to last command that the boot monitor runs at system power-up and main is the last (see line 5862).

In order to create a string with a backslash ('\') followed by a character, the backslash must be preceded by another backslash.  For example, "\\n" is a string consisting of a backslash followed by the character 'n' followed by the terminating 0.  In contrast, "\n" is a string consisting of a newline followed by the terminating 0.

For the boot monitor command echo, \c clears the screen, \v prints the version number, and \n prints a newline (see lines 6465-6498).

Expand/Collapse Item5830            b_setvar(E_SPECIAL|E_FUNCTION, "main", "menu");
main is the last command that the boot monitor runs at system power-up.  By default, main is the menu command.  The menu command shows a simple menu (see line 6235).

If you wish to go directly to the minix OS at system power-up, replace menu with boot:

hd2a>main() {boot}
hd2a>save

The next time the system is powered up, it will go directly to the minix OS.  To reset boot to menu:

hd2a>unset boot
hd2a>save

trailer clears the screen.

run_trailer() (line 6573) inserts trailer into the command chain and processes the commands in the command chain.  run_trailer() is called from bootimage.c after loading the OS image and before jumping to the kernel.

5831           b_setvar(E_SPECIAL|E_FUNCTION, "trailer", "echo \\c");
5832
Expand/Collapse Item5833            /* Default menu function: */
By default, \1 is the only command that the menu command (see line 6235) shows, since \1 is the only environment function with an arg that is not nil.  (Note that \1 is set using b_setenv() (line 5653) and the other environment variables and functions are set using b_setvar() (line 5690).

I'm not sure why this function is named "\1".  However, since "\1" is never printed to the screen, it doesn't matter (see menu() on line 6235).

5834           b_setenv(E_RESERVED|E_FUNCTION, "\1", "=,Start Minix", "boot");
5835
Expand/Collapse Item5836            /* Reserved names: */
E_RESERVED environment variables and functions cannot be altered.  These are the names of built-in commands.  (See comments for lines 5696-5699).
5837           b_setvar(E_RESERVED, "boot", null);
5838           b_setvar(E_RESERVED, "menu", null);
5839           b_setvar(E_RESERVED, "set", null);
5840           b_setvar(E_RESERVED, "unset", null);
5841           b_setvar(E_RESERVED, "save", null);
5842           b_setvar(E_RESERVED, "ls", null);
5843           b_setvar(E_RESERVED, "echo", null);
5844           b_setvar(E_RESERVED, "trap", null);
5845           b_setvar(E_RESERVED, "help", null);
5846           b_setvar(E_RESERVED, "exit", null);
5847
Expand/Collapse Item5848            /* Tokenize bootparams sector. */
Expand/Collapse Item5849         if ((r= readsectors(mon2abs(params), lowsec+PARAMSEC, 1)) != 0) {
Expand/Collapse Item5850                readerr(lowsec+PARAMSEC, r);
Expand/Collapse Item5851                exit(1);
Expand/Collapse Item5852            }
Expand/Collapse Item5853            params[SECTOR_SIZE]= 0;
Expand/Collapse Item5854            acmds= tokenize(&cmds, params);
int readsectors(u32_t bufaddr, u32_t sector, U8_t count) reads count bytes beginning at absolute sector address sector into absolute memory address bufaddr.

PARAMSEC=1.  The first sector (sector 0) of an active partition or subpartition is the bootstrap sector and the second sector (sector 1) is the bootparams sector.  The environment variables and functions from this sector are tokenized and put in the command chain on line 5854.  Environment variables and functions are saved (with the save command - see line 5889) to this sector and separated by newlines (see line 5920).

params[] is an array of size SECTOR_SIZE+1.  The last byte is the terminating 0.

5855
5856           /* Stuff the default action into the command chain. */
5857   #if UNIX
5858           (void) tokenize(acmds, ":;");
5859   #elif DOS
5860           tokenize(tokenize(acmds, ":;leader;"), drun);
5861   #else /* BIOS */
Expand/Collapse Item5862            (void) tokenize(acmds, ":;leader;main");
The last two commands that the boot monitor processes at system start-up are the leader and main commands (see lines 5827-5830).

There doesn't seem to be much point to the ":" command (see lines 6549-6550).

5863   #endif
5864   }
5865
Expand/Collapse Item5866    void remote_code(void)
Expand/Collapse Item5867    /* A rebooting Minix returns a bit of code for the monitor. */
remote_code() is called from boot() (see line 6621).  The "code" that is returned is actually a string of boot monitor commands separated by semicolons (;).  Here is an example of returned code:

var1=5;trailer;menu

5868   {
5869   #if BIOS
5870           if (reboot_code != 0) {
Expand/Collapse Item5871                    char code[SECTOR_SIZE + 2];
The second to last byte of code[] is for the terminating 0 (see line 5874) and the last byte is for the terminating semicolon (see line 5875).
5872
Expand/Collapse Item5873                    raw_copy(mon2abs(code), reboot_code, SECTOR_SIZE);
reboot_code is declared in boot.h and is the return value of minix().
5874                   code[SECTOR_SIZE]= 0;
5875                   strcat(code, ";");
5876                   (void) tokenize(&cmds, code);
5877                   reboot_code= 0;
5878           }
5879   #endif
5880   }
5881
Expand/Collapse Item5882    char *addptr;
addptr is used as a pointer in the params[] array (see lines 5900-5901).  As environment variables and functions are copied to params[], addptr is advanced (see lines 5886 and 5920).
5883
Expand/Collapse Item5884    void addparm(char *n)
addparm() copies one char of an environment variable or function into params[].  If *n==0, the end of the variable or function name, arg, or value has been reached (see lines 5906, 5909, 5912, and 5915).  If *addptr==0, the end of params[] has been reached (see lines 5900 and 5916).
5885   {
5886           while (*n != 0 && *addptr != 0) *addptr++ = *n++;
5887   }
5888
Expand/Collapse Item5889    void save_parameters(void)
Expand/Collapse Item5890    /* Save nondefault environment variables to the bootparams sector. */
The following is true for nondefault environment variables and functions:

1)  The E_RESERVED bit is not set for flags (see comment for line 5618).

2)  If the E_SPECIAL bit is set, the defval field must not be nil (see comment for line 5618).

5891   {
5892           environment *e;
Expand/Collapse Item5893            char params[SECTOR_SIZE + 1];
All nondefault environment variables and functions are copied to params[], which is then copied to the bootparams sector.  The last byte is the terminating 0 (see line 5900).

save_parameters() is invoked from execute() (see line 6544) by the save command.

5894           int r;
5895
5896           /* Default filling: */
Expand/Collapse Item5897            memset(params, '\n', SECTOR_SIZE);
void *memset(void *s, unsigned char c, size_t n) places character c into the first n characters of s and returns s. memset() is declared in <string.h>.
5898
Expand/Collapse Item5899            /* Don't touch the 0! */
Expand/Collapse Item5900            params[SECTOR_SIZE]= 0;
Expand/Collapse Item5901            addptr= params;
addptr is used as a pointer in the params[] array (see lines 5900-5901).  As environment variables and functions are copied to params[], addptr is advanced (see lines 5886 and 5920).
5902
Expand/Collapse Item5903            for (e= env; e != nil; e= e->next) {
Expand/Collapse Item5904                    if (e->flags & E_RESERVED || is_default(e)) continue;
This for loop loops through the environment variables and functions.  Only the nondefault environment variables are copied to params[].

is_default() (line 5618) returns TRUE if the E_SPECIAL bit of flags for the environment variable or function is set and its defval field is nil (see comment for line 5618).

5905
Expand/Collapse Item5906                    addparm(e->name);
Environment variables in params[] have either the form:

var1=value

or (if the environment variable is a device):

var2=d value

Environment functions in params[] have the form:

function1(arg)value

value is a string of commands separated by spaces.

5907                   if (e->flags & E_FUNCTION) {
5908                           addparm("(");
5909                           addparm(e->arg);
5910                           addparm(")");
5911                   } else {
5912                           addparm((e->flags & (E_DEV|E_SPECIAL)) != E_DEV
5913                                                           ? "=" : "=d ");
5914                   }
5915                   addparm(e->value);
5916                   if (*addptr == 0) {
5917                           printf("The environment is too big\n");
5918                           return;
5919                   }
Expand/Collapse Item5920                    *addptr++= '\n';
Environment variables and functions in params[] are separated by newlines ('\n').
5921           }
5922
5923           /* Save the parameters on disk. */
Expand/Collapse Item5924            if ((r= writesectors(mon2abs(params), lowsec+PARAMSEC, 1)) != 0) {
int writesectors(u32_t bufaddr, u32_t sector, u8_t count) copies count disk sectors from device beginning with absolute sector number sector into absolute memory address bufaddr.

u32_t mon2abs(void *ptr) converts the offset memory address ptr to an absolute memory address.

The first sector of the active partition is lowsec.

PARAMSEC=1.  The first sector (sector 0) of an active partition or subpartition is the bootstrap sector and the second sector (sector 1) is the bootparams sector.

5925                   writerr(lowsec+PARAMSEC, r);
5926                   printf("Can't save environment\n");
5927           }
5928   }
5929
Expand/Collapse Item5930    void show_env(void)
Expand/Collapse Item5931    /* Show the environment settings. */
show_env() is invoked from execute() (see line 6545) by the set command.  show_env() prints all environment variables and functions whose flag field doesn't have the E_RESERVED bit set.
5932   {
5933           environment *e;
5934
5935           for (e= env; e != nil; e= e->next) {
5936                   if (e->flags & E_RESERVED) continue;
Expand/Collapse Item5937                    if (!istty && is_default(e)) continue;
istty=1 (TRUE)  if we're using the BIOS (see line 5137). show_env() (for BIOS) therefore prints out all E_SPECIAL environment variables and functions (that aren't also E_RESERVED).
5938
Expand/Collapse Item5939                    if (e->flags & E_FUNCTION) {
Environment functions are printed in the format:

name(arg) value

is_default() (line 5618) returns TRUE if the E_SPECIAL bit of flags for the environment variable or function is set and its defval field is nil (see comment for line 5618).

Default environment variables are printed in the format:

name=(value)

Nondefault environment variable are printed in the format:

name=value

5940                           printf("%s(%s) %s\n", e->name, e->arg, e->value);
5941                   } else {
5942                         printf(is_default(e) ? "%s = (%s)\n" : "%s = %s\n",
5943                                   e->name, e->value);
5944                   }
5945           }
5946   }
5947
Expand/Collapse Item5948    int numprefix(char *s, char **ps)
Expand/Collapse Item5949 /* True iff s is a string of digits.*ps will be set to the first nondigit
Expand/Collapse Item5950     * if non-nil, otherwise the string should end.
Expand/Collapse Item5951     */
Since we wish to modify a pointer to a char, the second parameter (ps) is a pointer to a pointer to a char.

numprefix() is called on lines 5967 and 6022.  Line 6022 is the more interesting case, since the second argument isn't nil.  On line 6022, a reference to s (a pointer to a char) is the second argument; in other words, the second argument is a pointer to a pointer to a char.

5952   {
5953           char *n= s;
5954
Expand/Collapse Item5955            while (between('0', *n, '9')) n++;
between() returns TRUE if *n (the value at address n) is between '0' and '9'.
5956
5957           if (n == s) return 0;
5958
5959           if (ps == nil) return *n == 0;
5960
Expand/Collapse Item5961            *ps= n;
On line 6022, this assignment changes the value of s. s is set to the address of the first nondigit.
5962           return 1;
5963   }
5964
5965   int numeric(char *s)
5966   {
Expand/Collapse Item5967            return numprefix(s, (char **) nil);
Since numprefix() (line 5948) expects a pointer to a pointer to a char as the second argument, nil is cast to a pointer to a pointer to a char.
5968   }
5969
5970   #if BIOS
5971
Expand/Collapse Item5972  /* Device numbers of standard Minix devices. */
SCSI drives have the same numbering convention as hard drive partitions and subpartitions, but begin at 0x0A00 instead of 0x0300.

Up until this point, device values of 0x00 and 0x01 referred to the first and second floppy drives and 0x80, 0x81, 0x82, and 0x83 referred to the first through fourth hard drives.  These are the values that the bios for IBM-compatibles expects.  However, since minix runs on several different platforms, an architecture-independent numbering system for the devices is needed.  This architecture-independent numbering system is explained in section 5.6.9 (beginning with the 4th paragraph) in Operating Systems.

5973   #define DEV_RAM         0x0100
5974   #define DEV_FD0         0x0200
5975   #define DEV_HD0         0x0300
5976   #define DEV_SD0         0x0A00
5977   #define minor_1a           128
5978
Expand/Collapse Item5979    dev_t name2dev(char *name)
name2dev() accomplishes 2 things:

1) Fills in the global variable tmpdev (line 5418) so that boot_device() (line 6115) knows which partition to boot.

2) Returns the architecture-independent device number for the device.  This value is used in bootimage.c to set the value of the E_DEV environment variables that are passed to the minix kernel.

The architecture-independent device numbers are:
SCSI drives have the same numbering convention as hard drive partitions and subpartitions, but begin at 0x0A00 instead of 0x0300.

Up until this point, device values of 0x00 and 0x01 referred to the first and second floppy drives and 0x80, 0x81, 0x82, and 0x83 referred to the first through fourth hard drives.  These are the values that the bios for IBM-compatibles expects.  However, since minix runs on several different platforms, an architecture-independent numbering system for the devices is needed.

Note that the values for tmpdev.device and bootdev.device continue to be 0x00 and 0x01 for floppy drives and 0x80, 0x81, 0x82, and 0x83 for hard drives.  Only the return value for name2dev() is an architecture-independent device number.

If name cannot be resolved to an architecture-independent device number, -1 is returned (see line 6060).

Here are some possible values for name:

"ram"                    "hd6"
"
fd0"                    "hd6a"
"
fd1"                    "sd1"
"
hd1"                    "sd1a"
"
hd1a"                  "sd6"
                               "
sd6a"

Note that "/dev/" is redundant in name.  In other words, "/dev/hd1a" and "hd1a" are the same device.

5980   /* Translate, say, /dev/hd3 to a device number.  If the name can't be
5981    * found on the boot device, then do some guesswork.  The global structure
5982    * "tmpdev" will be filled in based on the name, so that "boot hd6" knows
5983    * what device to boot without interpreting device numbers.
5984    */
5985   {
Expand/Collapse Item5986            dev_t dev;
dev is the architecture-independent device number.


Expand/Collapse Item5987            ino_t ino;
ino_t is declared in <sys/types.h>ino is the i-node number of a (normal or special) file or directory.  Read the I-nodes section on page 418 of Operating Systems and section 5.6.4 to better understand the function of i-nodes.
Expand/Collapse Item5988            int drive;
drive isn't used in name2dev().
Expand/Collapse Item5989            struct stat st;
stat is declared in <sys/stat.h> (see line 02300 in the book).  r_stat() (see line 6048) returns information about an i-node in st.
5990           char *n, *s;
5991
5992         /* "boot *hd3" means: make partition 3 active before you boot it. */
Expand/Collapse Item5993            if ((activate= (name[0] == '*'))) name++;
activate (line 5416) is a global variable that indicates if tmpdev should be made active (see lines 6096-6110).
5994
Expand/Collapse Item5995      /* The special name "bootdev" must be translated to the boot device. */
bootdev is set in initialize() (see line 5493) and is the drive or partition from which this code originated.
5996           if (strcmp(name, "bootdev") == 0) {
5997                   if (bootdev.device == -1) {
5998                           printf("The boot device could not be named\n");
5999                           errno= 0;
6000                           return -1;
6001                   }
6002                   name= bootdev.name;
6003           }
6004
6005        /* If our boot device doesn't have a file system, or we want to know
6006            * what a name means for the BIOS, then we need to interpret the
6007            * device name ourselves: "fd" = floppy, "hd" = hard disk, etc.
6008            */
Expand/Collapse Item6009            tmpdev.device= tmpdev.primary= tmpdev.secondary= -1;
Expand/Collapse Item6010            dev= -1;
Here are some possible values for tempdev:

device, primary and secondary for "fd1", "hd7", and "hd3a" are:

name= "fd1"
device= 0x01
primary= -1
secondary= -1

name= "hd7"
device= 0x81
primary= 1
secondary= -1

name= "hd3a"
device= 0x80
primary= 2
secondary= 0

device, primary, secondary and dev are all initialized to -1.

6011           n= name;
Expand/Collapse Item6012            if (strncmp(n, "/dev/", 5) == 0) n+= 5;
int strncmp(char *s1, char *s2, size_t n) compares at most n characters of string s1 to string s2 and returns 0 if the first n characters of the two strings are the same. strncmp() is declared in <string.h>.

The following two commands are identical:

hd2a>boot hd2
hd2a>boot /dev/hd2

Since "/dev/" is redundant, it is ignored by advancing n five characters.

6013
Expand/Collapse Item6014            if (strcmp(n, "ram") == 0) {
The next few if statements determine if name is a ram disk, a floppy drive, a hard drive partition or subpartition, or a SCSI drive partition or subpartition.  Some possible values for name include:

"ram"                    "hd6"
"
fd0"                    "hd6a"
"
fd1"                    "sd1"
"
hd1"                    "sd1a"
"
hd1a"                  "sd6"
                               "
sd6a"

int strcmp(char *s1, char *s2) compares string s1 to string s2 and returns 0 if the two strings are the same.

6015                   dev= DEV_RAM;
6016           } else
6017           if (n[0] == 'f' && n[1] == 'd' && numeric(n+2)) {
6018                   /* Floppy. */
6019                  tmpdev.device= a2l(n+2);
6020                   dev= DEV_FD0 + tmpdev.device;
6021           } else
Expand/Collapse Item6022     if ((n[0] == 'h' || n[0] == 's') && n[1] == 'd' && numprefix(n+2, &s)
Expand/Collapse Item6023                 && (*s == 0 || (between('a', *s, 'd') && s[1] == 0))
numprefix() returns the address of the first nondigit from the string n+2 in s.  The value at address s (*s) is either 'a', 'b', 'c', 'd', or '\0'; if the value at address s is '\0', name refers to a partition and not a subpartition.
6024           ) {
6025                   /* Hard disk. */
6026                   dev= a2l(n+2);
Expand/Collapse Item6027                   tmpdev.device= dev / (1 + NR_PARTITIONS);
NR_PARTITIONS is #defined in <ibm/partition.h>. NR_PARTITIONS = 4.

Again, here are some possible values of device, primary, and secondary:

name= "hd7"
device= 0x81
primary= 1
secondary= -1

name= "hd3a"
device= 0x80
primary= 2
secondary= 0

6028                   tmpdev.primary= (dev % (1 + NR_PARTITIONS)) - 1;
6029                   if (*s != 0) {
6030                           /* Subpartition. */
6031                           tmpdev.secondary= *s - 'a';
6032                           dev= minor_1a
6033                                   + (tmpdev.device * NR_PARTITIONS
6034                                           + tmpdev.primary) * NR_PARTITIONS
6035                                   + tmpdev.secondary;
6036                   }
6037                   tmpdev.device+= 0x80;
Expand/Collapse Item6038                    dev+= n[0] == 'h' ? DEV_HD0 : DEV_SD0;
Again, here are possible values of dev:

6039           }
6040
6041        /* Look the name up on the boot device for the UNIX device number. */
Expand/Collapse Item6042            if (fsok) {
Expand/Collapse Item6043                    /* The current working directory is "/dev". */
fsok is set in boot() (see line 6615).  fsok is nonzero (TRUE) if a minix version 1 (SUPER_MAGIC) or minix version 2 (SUPER_V2) file system is on device (the floppy drive, hard drive, or scsi drive from which this code originated).

The code inside the if block (lines 6043-6055) appears to be an alternative to the calculation that is completed on line 6038.  Both methods determine the value of dev.

Expand/Collapse Item6044                    ino= r_lookup(r_lookup(ROOT_INO, "dev"), name);
ROOT_INO (=1) is declared in "rawfs.h". ROOT_INO is the i-node number of the root directory ("/").

ino_t r_lookup(ino_t cwd, char *path) translates path to an i-node number.  r_lookup() begins its search in the directory whose i-node is cwd.

r_lookup(ROOT_INO, "dev") returns the i-node number of the directory /dev and the outer r_lookup() returns the i-node number of the file name located in the directory /dev.

6045
6046                   if (ino != 0) {
6047                        /* Name has been found, extract the device number. */
Expand/Collapse Item6048                            r_stat(ino, &st);
void r_stat(ino_t file, struct stat *stp) returns information in *stp about the file whose i-node number is file (see comments for line 5989).  Note that, in this case, *stp is st. r_stat() is declared in "rawfs.h".

The only 2 fields of the stat struct (declared in <sys/stat.h>; see line 02300 in the book) that are of interest here are st_mode and st_rdevS_ISBLK returns TRUE if st_mode corresponds to a block device.  S_ISBLK is #defined in <sys/stat.h>.  (Remember that disk drives are examples of block devices; printers and modems are examples of character devices.) st_rdev is the architecture-independent device number.

6049                           if (!S_ISBLK(st.st_mode)) {
6050                                 printf("%s is not a block device\n", name);
6051                                   errno= 0;
6052                                   return (dev_t) -1;
6053                           }
6054                           dev= st.st_rdev;
6055                    }
6056           }
6057
Expand/Collapse Item6058            if (tmpdev.primary < 0) activate= 0;    /* Careful now! */
A partition or subpartition is activated (made bootable) by modifying its partition table entry (see lines 6096-6110).  If primary == -1, tmpdev is not a hard drive (or scsi drive) partition or subpartition.  So the asterik (*) in the command:

hd2a>boot *fd1

is meaningless and ignored.

6059
6060           if (dev == -1) {
6061                   printf("Can't recognize '%s' as a device\n", name);
6062                   errno= 0;
6063           }
6064           return dev;
6065   }
6066
6067   #if !DOS
Expand/Collapse Item6068    #define B_NOSIG         -1      /* "No signature" error code. */
SIGNATURE (AA55) must be at an offset of SIGNATOFF (510) within a bootstrap or master boot record (MBR).  SIGNATURE and SIGNATOFF are #defined in boot.h .
6069
Expand/Collapse Item6070    int exec_bootstrap(void)
Expand/Collapse Item6071 /* Load boot sector from the disk or floppy described by tmpdev and
Expand/Collapse Item6072     execute it.*/
exec_bootstrap() is called from boot_device() (see line 6131).  boot_device() first calls name2dev() to fill in the global variable tmpdev (line 5418); exec_bootstrap() then boots the device described by tmpdev.

exec_bootstrap() does not return unless something goes wrong.

6073   {
Expand/Collapse Item6074            int r, n, dirty= 0;
If a partition table entry is changed in memory, dirty is set to 1 so that the changes are written back to disk (see line 6109).
6075           char master[SECTOR_SIZE];
Expand/Collapse Item6076          struct part_entry *table[NR_PARTITIONS], dummy, *active= &dummy;
get_master() (line 5423) writes a master boot record (MBR) to master[] (see line 6085).  An MBR is the first sector on a hard drive and, if a partition is subpartitioned, the first sector in a partition.  An MBR consists of code and a partition table.  The code for the minix MBR is in masterboot.s.  The code for the minix bootstrap is in bootblock.s.

The partition table is located at offset 460 within the master boot record.  get_master() (line 5423) fills in table[] with pointers to the partition table entries.  The memory layout after the call to get_master() is shown below:

struct part_entry is declared in <ibm/partition.h> (see line 04100 in the book).

6077           u32_t masterpos;
6078
Expand/Collapse Item6079            active->lowsec= 0;
Since active points to dummy (line 6076), this line fills in dummy.lowsec.
6080
Expand/Collapse Item6081            /* Select a partition table entry. */
Expand/Collapse Item6082            while (tmpdev.primary >= 0) {
The first time through the while block (lines 6083-6093), the master boot record (MBR) of the hard drive is loaded and examined .  The (optional) second time through the while block, the MBR for a partition is then loaded and examined.
6083                   masterpos= active->lowsec;
6084
6085                if ((r= get_master(master, table, masterpos)) != 0) return r;
6086
6087                   active= table[tmpdev.primary];
6088
6089                   /* How does one check a partition table entry? */
Expand/Collapse Item6090                    if (active->sysind == NO_PART) return B_NOSIG;
NO_PART indicates that a partition or subpartition is not being used.  If it's not being used, there is (of course) no signature
6091
6092                tmpdev.primary= tmpdev.secondary;
6093                tmpdev.secondary= -1;
6094           }
6095
Expand/Collapse Item6096            if (activate && !active->bootind) {
If an entry is changed in the partition table in memory, then the change needs to be written back to disk (see line 6109).
6097                   for (n= 0; n < NR_PARTITIONS; n++) table[n]->bootind= 0;
6098                   active->bootind= ACTIVE_FLAG;
6099                   dirty= 1;
6100           }
6101
6102           /* Read the boot sector. */
Expand/Collapse Item6103            if ((r= readsectors(BOOTPOS, active->lowsec, 1)) != 0) return r;
active points to the partition table entry of the partition or subpartition that we wish to boot.  active->lowsec is the first sector of this partition or subpartition and contains the bootstrap and is loaded into memory at address BOOTPOS (=0x07C00). BOOTPOS is #defined in boot.h.
6104
Expand/Collapse Item6105            /* Check signature word. */
Expand/Collapse Item6106            if (get_word(BOOTPOS+SIGNATOFF) != SIGNATURE) return B_NOSIG;
SIGNATURE (AA55) must be at an offset of SIGNATOFF (510) within a bootstrap or master boot record (MBR).  SIGNATURE and SIGNATOFF are #defined in boot.h .
6107
Expand/Collapse Item6108            /* Write the partition table if a member must be made active. */
If an entry is changed in the partition table in memory, then the change must be written back to disk (see line 6098).
6109         if (dirty && (r= writesectors(mon2abs(master), masterpos, 1)) != 0)
6110                   return r;
6111
Expand/Collapse Item6112            bootstrap(device, active);
bootstrap() jumps to the bootstrap that was loaded into memory (see line 6103).  This function does not return.
6113   }
6114
Expand/Collapse Item6115    void boot_device(char *devname)
Expand/Collapse Item6116    /* Boot the device named by devname. */
boot_device() is invoked by the boot monitor boot command (see line 6513).  For the command:

hd2a>boot hd2a

devname is the string "hd2a".

boot_device() first calls name2dev() to fill in the global variable tmpdev (line 5418); exec_bootstrap() (see line 6131) then boots the device described by tmpdev.

boot_device() does not return unless something goes wrong.

6117   {
Expand/Collapse Item6118            dev_t dev= name2dev(devname);
name2dev() (line 6979) fills in the global variable tmpdev.  Here are some possible values for tmpdev:

device, primary and secondary for "fd1", "hd7", and "hd3a" are:

devname= "fd1"
device= 0x01
primary= -1
secondary= -1

devname= "hd7"
device= 0x81
primary= 1
secondary= -1

devname= "hd3a"
device= 0x80
primary= 2
secondary= 0

Note that dev is never used in boot_device().

Expand/Collapse Item6119            int save_dev= device;
If something goes wrong, the value of the global variable device (declared in boot.h) is restored on line 6137.  device is the hard drive or floppy from which this code originated.
6120           int r;
6121           char *err;
6122
Expand/Collapse Item6123            if (tmpdev.device < 0) {
If tmpdev.device < 0, name2dev() couldn't match up devname with a device.
6124                   if (dev != -1) printf("Can't boot from %s\n", devname);
6125                   return;
6126           }
6127
6128     /* Change current device and try to load and execute its bootstrap. */
Expand/Collapse Item6129            device= tmpdev.device;
dev_open() (see line 6131) opens the device specified by the global variable device (declared in boot.h ).
6130
Expand/Collapse Item6131            if ((r= dev_open()) == 0) r= exec_bootstrap();
dev_open() determines the number of sectors (the variable sectors) and the number of sectors per cylinder (the variable secspcyl) of device.

exec_bootstrap() (line 6070) loads the new bootstrap and jumps to it.  Unless something goes wrong, exec_bootstrap() does not return.  The rest of this function (boot_device()) reports the error that occurred and restores the system to the state previous to the call to boot_device().

6132
Expand/Collapse Item6133            err= r == B_NOSIG ? "Not bootable" : bios_err(r);
If device was bootable, the error must be a bios (I/O) error. bios_err() (line 5051) translates a bios error code to a readable string.
6134           printf("Can't boot %s: %s\n", devname, err);
6135
6136           /* Restore boot device setting. */
6137           device= save_dev;
6138           (void) dev_open();
6139   }
6140
Expand/Collapse Item6141    #else /* DOS */
I do not cover DOS-specific sections of code (lines 6141-6149).  I may cover these sections at a later time.
6142
6143   void boot_device(char *devname)
6144   /* No booting of other devices under DOS */
6145   {
6146           printf("Can't boot devices under MS-DOS\n");
6147   }
6148
6149   #endif /* DOS */
6150   #endif /* BIOS */
6151
6152   void ls(char *dir)
6153   /* List the contents of a directory. */
6154   {
Expand/Collapse Item6155            ino_t ino;
ino_t is declared in <sys/types.h>ino is the i-node number of a (normal or special) file or directory.  Read the I-nodes section on page 418 of Operating Systems and section 5.6.4 to better understand the function of i-nodes.
Expand/Collapse Item6156            struct stat st;
stat is declared in <sys/stat.h> (see line 02300 in the book).  r_stat() (see line 6048) returns information about an i-node in st.
Expand/Collapse Item6157            char name[NAME_MAX+1];
r_readdir() (see line 6162) reads the name of a file into name[].
6158
Expand/Collapse Item6159            if (!fsok) return;
fsok is set in boot() (see line 6615).  fsok is nonzero (TRUE) if a minix version 1 (SUPER_MAGIC) or minix version 2 (SUPER_V2) file system is on device (the floppy drive, hard drive, or scsi drive from which this code originated).
6160
Expand/Collapse Item6161            if ((ino= r_lookup(ROOT_INO, dir)) == 0
Expand/Collapse Item6162                    || (r_stat(ino, &st), r_readdir(name)) == -1
ino_t r_lookup(ino_t cwd, char *path) translates path to an i-node number.  r_lookup() begins its search in the directory whose i-node is cwd.

void r_stat(ino_t file, struct stat *stp) returns information in *stp about the file or directory whose i-node number is file (see comments for line 5989).  Note that, in this case, *stp is st. r_stat() is declared in "rawfs.h".   r_stat() also fills in a variable (curfil) with information about this file or directory.  r_readdir() reads a single directory entry into name[] from this directory (hopefully it will be a directory and not a file) and advances to the next directory entry in preparation for the next call to r_readdir().

r_readdir() returns -1 if curfil is a file and not a directory.

The first time r_readdir() is called (and curfil is a valid directory), it returns "." (dot - the current directory) and the second time r_readdir() is called (line 6167), it returns ".." (dot dot - the parent directory).  Neither of these listings are printed (see line 6169).

6163           ) {
6164                   printf("ls: %s: %s\n", dir, unix_err(errno));
6165                   return;
6166           }
6167           (void) r_readdir(name); /* Skip ".." too. */
6168
Expand/Collapse Item6169            while ((ino= r_readdir(name)) != 0) printf("%s/%s\n", dir, name);
All directory entries (except for "." and "..") are printed.

r_readdir() returns 0 if there are no more directory entries.

6170   }
6171
Expand/Collapse Item6172    u32_t milli_time(void)
milli_time() is called by milli_since() (line 6177) and schedule() (line 6197).  milli_time() returns the number of milliseconds since midnight.
6173   {
Expand/Collapse Item6174            return get_tick() * MSEC_PER_TICK;
get_tick() returns the number of ticks since midnight.  There are 18.2 ticks per second.
6175   }
6176
Expand/Collapse Item6177    u32_t milli_since(u32_t base)
base is a time (in milliseconds) returned by get_tick() (see lines 6219 and 6225).  milli_since() returns the number of milliseconds since base.

milli_since() is called by expired() (line 6207) and delay() (line 6213).

6178   {
6179           return (milli_time() + (TICKS_PER_DAY*MSEC_PER_TICK) - base)
6180                           % (TICKS_PER_DAY*MSEC_PER_TICK);
6181   }
6182
Expand/Collapse Item6183    char *Thandler;
Expand/Collapse Item6184    u32_t Tbase, Tcount;
Only one command can be scheduled at any given time.  If a command is scheduled, Thandler is the command that is executed Tcount milliseconds after Tbase (see lines6201-6203).
6185
6186   void unschedule(void)
6187   /* Invalidate a waiting command. */
6188   {
Expand/Collapse Item6189            alarm(0);
alarm(n) expands to 0 (see line 5138) for the BIOS.  In other words, it's a nop (no operation; it does nothing).
6190
Expand/Collapse Item6191            if (Thandler != nil) {
A value of nil for Thandler means that nothing is scheduled.
6192                   free(Thandler);
6193                   Thandler= nil;
6194           }
6195   }
6196
Expand/Collapse Item6197    void schedule(long msec, char *cmd)
Expand/Collapse Item6198    /* Schedule command at a certain time from now. */
schedule() is invoked by the boot monitor trap command (see line 6525).  For example,

hd2a>trap 5000 boot

schedules the boot command to be executed in 5 seconds.

6199   {
Expand/Collapse Item6200            unschedule();
Only one command can be scheduled at any given time.  schedule() unschedules a previously scheduled command before scheduling Thandler.
Expand/Collapse Item6201            Thandler= cmd;
Expand/Collapse Item6202            Tbase= milli_time();
Expand/Collapse Item6203            Tcount= msec;
Thandler is the command that is executed Tcount milliseconds after TbaseThandler is nil if no commands are scheduled.
Expand/Collapse Item6204            alarm(1);
alarm(n) expands to 0 (see line 5138) for the BIOS.
6205   }
6206
Expand/Collapse Item6207    int expired(void)
Expand/Collapse Item6208    /* Check if the timer expired for getch(). */
expired() is also called by delay() (see line 6225).

getch() returns the ascii code for the escape key (ESC) if expired() returns TRUE.

6209   {
6210           return (Thandler != nil && milli_since(Tbase) >= Tcount);
6211   }
6212
Expand/Collapse Item6213    void delay(char *msec)
Expand/Collapse Item6214    /* Delay for a given time. */
delay() is invoked by the boot monitor delay command (see line 6512).

hd2a>delay 5000

waits 5 seconds before returning.  The delay command is handy in scripts - delay gives users time to see screen output before scrolling continues.

6215   {
6216           u32_t base, count;
6217
Expand/Collapse Item6218            if ((count= a2l(msec)) == 0) return;
a2l() converts a string (like "1023") to a long.  (a2l() also converts negative values, although msec should not be negative)
6219           base= milli_time();
6220
6221           alarm(1);
6222
6223           do {
Expand/Collapse Item6224                 pause();
pause() expands to 0 (see line 5139).  The delay is caused by the conditional statement on line 6225.

The boot monitor delay command returns when:

1)  the escape key (interrupt() (line 5403)) is pressed.

2)  another command is scheduled (expired() (line 6207)) to be executed.

3)  the delay is complete (milli_since(base) < count).

6225           } while (!interrupt() && !expired() && milli_since(base) < count);
6226   }
6227
Expand/Collapse Item6228    enum whatfun { NOFUN, SELECT, DEFFUN, USERFUN }menufun(environment *e)
enum whatfun {NOFUN, SELECT, DEFFUN, USERFUN} declares NOFUN to be equal to 0, SELECT to be equal to 1, DEFFUN to be equal to 2 and USERFUN to be equal to 3.  Therefore menufun() returns a value of 0, 1, 2, or 3.

struct environment is declared in boot.h .

6229   {
Expand/Collapse Item6230            if (!(e->flags & E_FUNCTION) || e->arg[0] == 0) return NOFUN;
If e is an environment variable or a simple function (a function with arg equal to null), menufun() returns NOFUN.
Expand/Collapse Item6231            if (e->arg[1] != ',') return SELECT;
Expand/Collapse Item6232            return e->flags & E_RESERVED ? DEFFUN : USERFUN;
Environment functions can be defined in two ways:

hd2a>image1(a) {image=image_file_1; boot}

hd2a>image2(b,boot image_file_2) {image=image_file_2; boot}

The first command defines a SELECT function since arg[1] != ','.

The second command defines a USERFUN function since arg[1] == ','.  Since the user has defined this function, it is not a DEFFUN (default) function.  The only DEFFUN function is defined on line 5834 with setenv().

The term NOFUN is misleading since an environment function with arg equal to null (a simple function) is NOFUN and the term SELECT is misleading since a SELECT function is also a user-defined function.

6233   }
6234
Expand/Collapse Item6235    void menu(void)
menu() is invoked by the boot monitor menu command (see line 6543).  By default, the only option the menu command shows is:

     =  Start Minix

If the user types "=", the command boot is executed (see line 5834).

If there are several images and the user wishes to be given a choice, he can define his own functions:

hd2a>image1(a) {image=image_file_1; boot}

hd2a>image2(b,Boot image_file_2) {image=image_file_2; boot}

The menu command would show the following choices:

     a  Select image1 kernel
     b  Boot image_file_2

Note that the "= Start Minix" command is not shown.  If the user defines any functions of the form:

hd2a>image2(b,Boot image_file_2) {image=image_file_2; boot}

then no default functions are shown (see comment for line 6231).  Also note that the menu command shows no simple functions (simple functions are functions whose arg is null).  The following function would not be shown as an option with the menu command:

hd2a>image3() {image=image_file_3; boot}

6236   /* By default:  Show a simple menu.
6237    * Multiple kernels/images:  Show extra selection options.
6238    * User defined function:  Kill the defaults and show these.
6239    * Wait for a keypress and execute the given function.
6240    */
6241   {
6242           int c, def= 1;
6243           char *choice= nil;
Expand/Collapse Item6244           environment *e;
struct environment is declared in boot.h .
6245
6246           /* Just a default menu? */
Expand/Collapse Item6247         for (e= env; e != nil; e= e->next) if (menufun(e) == USERFUN) def= 0;
If the user defines any functions of the form:

hd2a>image2(b,Boot image_file_2) {image=image_file_2; boot}

no default functions are shown (see comments for lines 6231, 6235, and 6255).  def is set to 0 (FALSE) if there are any USERFUN functions.

6248
6249           printf("\nHit a key as follows:\n\n");
6250
6251           /* Show the choices. */
6252           for (e= env; e != nil; e= e->next) {
Expand/Collapse Item6253                    switch (menufun(e)) {
menufun() (line 6228) returns NOFUN, SELECT, DEFFUN, or USERFUN, depending on e->arg.
6254                   case DEFFUN:
6255                           if (!def) break;
6256                           /*FALL THROUGH*/
Expand/Collapse Item6257                    case USERFUN:
If the user defined the following two commands:

hd2a>image1(a) {image=image_file_1; boot}

hd2a>image2(b,Boot image_file_2) {image=image_file_2; boot}

then the menu command would show the following choices:

     a  Select image1 kernel
     b  Boot image_file_2

6258                           printf("    %c  %s\n", e->arg[0], e->arg+2);
6259                           break;
6260                   case SELECT:
6261                  printf("    %c  Select %s kernel\n", e->arg[0],e->name);
6262                           break;
6263                   default:;
6264                }
6265           }
6266
6267           /* Wait for a keypress. */
6268           do {
Expand/Collapse Item6269                    c= getch();
Expand/Collapse Item6270                    if (interrupt() || expired()) return;
getch() waits for a character to be typed.  If a command has been scheduled, get_char() returns before a character is typed and menu() returns (see expired() on line 6207).  If the character typed is the escape key (ESC), menu() also returns (see interrupt() on line 5403).
6271
Expand/Collapse Item6272                 unschedule();
If the user types in a choice before a scheduled command is ready to execute, the command is unscheduled.
6273
Expand/Collapse Item6274                    for (e= env; e != nil; e= e->next) {
All the environment variables are searched to find a match with the character (c) typed.
6275                           switch (menufun(e)) {
6276                           case DEFFUN:
6277                                   if (!def) break;
6278                           case USERFUN:
6279                           case SELECT:
6280                                   if (c == e->arg[0]) choice= e->value;
6281                           }
6282                }
Expand/Collapse Item6283            } while (choice == nil);
If the user types in a character that is not given as an menu option, the choice is ignored and the wait continues for a valid choice.
6284
6285           /* Execute the chosen function. */
6286           printf("%c\n", c);
Expand/Collapse Item6287            (void) tokenize(&cmds, choice);
tokenize() (line 5361) breaks a string into "tokens" so that execute() (see lines 6330 and 6620) can call the appropriate function.  See the comments for line 5307 for an explanation of tokens.
6288   }
6289
Expand/Collapse Item6290    void help(void)
Expand/Collapse Item6291    /* Not everyone is a rocket scientist. */
help() is invoked by the boot monitor help command (see line 6546).  help() lists a few environment variables and their descriptions and also shows the available boot monitor commands.  (A good description of these environment variables and boot monitor commands can be found in the man page for monitor.)
6292   {
6293           struct help {
6294                   char    *thing;
6295                   char    *help;
Expand/Collapse Item6296            } *pi;
pi is used to move from one element of info[] to the next (see lines 6324-6327).
Expand/Collapse Item6297            static struct help info[] = {
The static declaration specifies that the variable (in this case, the array info[]) remains in existence after the function returns.  Since the contents of info[] don't change from one invocation of help() to the next, it is preferable that the array remain in existence.  It saves the time for reinitialization that would otherwise be necessary.
6298             { nil,  "Names:" },
6299             { "rootdev",            "Root device" },
6300             { "ramimagedev",        "RAM disk image if root is RAM" },
6301             { "ramsize",            "RAM disk size if root is not RAM" },
6302             { "bootdev",            "Special name for the boot device" },
6303             { "fd0, hd3, hd2a",     "Devices (as in /dev)" },
Expand/Collapse Item6304              { "image",              "Name of the kernel image" },
The environment variable image is initialized to "minix".  Note that this is the directory /minix and not a file.  If image is a directory and not a file, the boot monitor searches through the the directory and boots the image file with the most recent modification time.  To boot a specific image, reset image to a filename:

hd2a> image = /minix/minix_386_09282000

6305             { "main",               "Startup function" },
6306             { nil,  "Commands:" },
6307             { "name = [device] value",  "Set environment variable" },
6308             { "name() { ... }",         "Define function" },
6309             { "name(key,text) { ... }",
6310                        "A menu function like: minix(=,Start Minix) {boot}" },
6311             { "name",               "Call function" },
6312             { "boot [device]",      "Boot Minix or another O.S." },
6313             { "delay [msec]",       "Delay (500 msec default)" },
6314             { "echo word ...",      "Print the words" },
6315             { "ls [directory]",     "List contents of directory" },
6316             { "menu",               "Choose a menu function" },
6317             { "save",               "Save environment" },
6318             { "set",                "Show environment" },
6319             { "trap msec command",  "Schedule command" },
6320             { "unset name ...",     "Unset variable or set to default" },
6321             { "exit",               "Exit the Monitor" },
6322           };
6323
Expand/Collapse Item6324            for (pi= info; pi < arraylimit(info); pi++) {
arraylimit(info) (line 5047) returns the address of the first byte after the last element of array info.
6325                   if (pi->thing != nil) printf("    %-24s- ", pi->thing);
6326                   printf("%s\n", pi->help);
6327           }
6328   }
6329
Expand/Collapse Item6330    void execute(void)
Expand/Collapse Item6331    /* Get one command from the command chain and execute it. */
execute() is called in run_trailer() (see line 6582) and boot() (see line 6620).  execute() processes commands from the bootparams sector (see line 5848) and commands entered at the command prompt (see monitor() on line 6587).

Before execute() is called, commands are tokenized (see line 5307) and the tokens are linked together in a command chain.  cmds (line 5382) points to the first token in the command chain.  execute() translates the first command in the command chain to an action.  For example, the command:

hd2a>help

invokes help() (see line 6546).  The command:

hd2a>echo \v

prints the version number of the boot monitor to the screen (see line 6478).

execute() can determine the nature of the first command in the command chain from:

1)  the first four tokens (if there are that many).

2)  the number of tokens until the first separator or the end of the command chain.

For example, if the second token is a command separator (either a semicolon or a newline), the first command must be a simple command like boot or menu (see line 6535).  If the first character of the second token is the left parenthesis ('(') and the first token is not a "sugar" (a sugar is a single character special token - see line 5307), then the command defines a function:

hd2a>name1(args) {command1; command2; ... }

and appropriate action is taken (see line 6383).

6332   {
Expand/Collapse Item6333            token *second, *third, *fourth, *sep;
token is declared on line 5356:

typedef struct token {
       struct token    *next;  /* Next in a command chain. */
       char            *token;
} token;

sep is used on line 6349 to find the number of tokens in the first command.  second, third, and fourth are set on lines 6351-6353.

6334           char *name= cmds->token;
6335           size_t n= 0;
6336
Expand/Collapse Item6337            if (err) {
The global variable err is declared on line 5383.  err can be set for a number of reasons; one reason is if the escape key (ESC) has been pressed (see lines 6448 and 6600).
6338                   /* An error occured, stop interpreting. */
Expand/Collapse Item6339                    while (cmds != nil) voidtoken();
Clear out the command chain.
6340                   return;
6341           }
6342
Expand/Collapse Item6343            if (expired()) {        /* Timer expired? */
Expand/Collapse Item6344                    (void) tokenize(tokenize(&cmds, Thandler), ";");
Expand/Collapse Item6345                unschedule();
Expand/Collapse Item6346            }
expired() returns TRUE if a command (Thandler) is scheduled to be executed at this time.

The global variable cmds points to the first token in the command chain.  Since we wish to change the value of a pointer (cmds), we pass in a reference to cmds.  The command Thandler is inserted into the beginning of the command chain and a semicolon (separator) is inserted directly after Thandler.

Thandler can be unscheduled since it has been placed in the command chain.

6347
Expand/Collapse Item6348            /* There must be a separator lurking somewhere. */
Expand/Collapse Item6349 for (sep= cmds; sep != nil && sep->token[0] != ';'; sep= sep->next) n++;
Expand/Collapse Item6350
Expand/Collapse Item6351            if ((second= cmds->next) != nil
Expand/Collapse Item6352                    && (third= second->next) != nil)
Expand/Collapse Item6353                            fourth= third->next;
The command chain for the command:

hd2a>name1=value1

is shown below.  n=3 since there are 3 tokens in the command.


6354
6355                   /* Null command? */
Expand/Collapse Item6356            if (n == 0) {
From this line until line 6566 (near the end of execute()) is a giant if - else if - else if - ... statement.  Each condition looks at a subset of name, first, second, third, fourth and n (see comments for lines 6348-6353) to determine what action to take.

If n=0, the first token is a semicolon or the command chain is empty.

6357                   voidtoken();
6358                   return;
6359           } else
Expand/Collapse Item6360                    /* name = [device] value? */
token1    token2    token3    token4
name1     =         dev       value1

or

token1    token2    token3
name1     =         value1

Expand/Collapse Item6361            if ((n == 3 || n == 4)
The following 2 if blocks (until line 6429) define environment variables and functions.
Expand/Collapse Item6362                    && !sugar(name)
A sugar is a single character special token (see line 5307).
6363                   && second->token[0] == '='
6364                   && !sugar(third->token)
6365                   && (n == 3 || (n == 4 && third->token[0] == 'd'
6366                                           && !sugar(fourth->token)
6367           ))) {
6368                   char *value= third->token;
6369                   int flags= E_VAR;
6370
6371                   if (n == 4) { value= fourth->token; flags|= E_DEV; }
6372
Expand/Collapse Item6373                    if ((flags= b_setvar(flags, name, value)) != 0) {
b_setvar() (line 5690) sets an environment variable or a simple function (a simple function is a function without an argument).  b_setvar() can fail (return a nonzero value) if:

1)  an attempt is made to change the value of an E_RESERVED variable or function.

2)  an attempt is made to change an E_SPECIAL function to a variable (or vice versa).

6374                           printf("%s is a %s\n", name,
6375                                   flags & E_RESERVED ? "reserved word" :
6376                                                   "special function");
6377                           err= 1;
6378                   }
Expand/Collapse Item6379                    while (cmds != sep) voidtoken();
Clear out the command chain.
6380                   return;
6381          } else
Expand/Collapse Item6382                    /* name '(arg)' ... ? */
The following if block defines a function.  After a function is defined, it can be used as a boot monitor command.  For example, if a function is defined:

hd2a>name1() { echo hello }

it can be called:

hd2a>name1
hello

If a function has one or two arguments:

hd2a>image1(a) {image=image_file_1; boot}
hd2a>image2(b,Boot image_file_2) {image=image_file_2; boot}

the function must be invoked by the boot monitor menu command (see line 6235).

token1    token2    token3    ...    tokenN
name1     (arg)     {         ...    }

Note that arg can be the empty string; in other words, token2 can be "()".

6383           if (n >= 3
6384                   && !sugar(name)
6385                   && second->token[0] == '('
6386           ) {
6387                   token *fun;
6388                   int c, flags, depth;
6389                   char *body;
6390                   size_t len;
6391
6392                   sep= fun= third;
6393                   depth= 0;
6394                   len= 1;
Expand/Collapse Item6395                    while (sep != nil) {
The use of curly braces is flexible.  For example, the following function definitions are the same:

hd2a>name1() boot;
hd2a>name1() { boot }
hd2a>name1() { { boot } }

Expand/Collapse Item6396                           if ((c= sep->token[0]) == ';' && depth == 0) break;
This if statement is for the case of a function definition without curly braces:

hd2a>name1() boot;

Expand/Collapse Item6397                            len+= strlen(sep->token) + 1;
The third token through the last token are concatenated to form the string body.  The tokens are separated by spaces (which accounts for the "+1" - see line 6410).

size_t strlen(char *s) returns the length of the string s. strlen() is declared in <string.h>.

6398                           sep= sep->next;
Expand/Collapse Item6399                            if (c == '{') depth++;
Expand/Collapse Item6400                            if (c == '}' && --depth == 0) break;
depth allows for the use of nested curly braces.
6401                   }
6402
Expand/Collapse Item6403                    body= malloc(len * sizeof(char));
void *malloc(size_t size) allocates size bytes from the heap and returns a pointer to the first byte of the allocated memory. malloc() is declared in stdlib.h.  (The heap is the region between the stack and the bss.)

body is used as an argument for b_setenv() (see lines 6419-6420).

6404                   *body= 0;
6405
6406                   while (fun != sep) {
6407                           strcat(body, fun->token);
6408                           if (!sugar(fun->token)
6409                                   && !sugar(fun->next->token)
6410                           ) strcat(body, " ");
6411                           fun= fun->next;
6412                   }
Expand/Collapse Item6413                    second->token[strlen(second->token)-1]= 0;
token1    token2    token3    ...    tokenN
name1     (arg)     {         ...    }

token2 is (arg).  Note that arg can be the empty string; in other words, token2 can be "()".

Let's look at an example.  If second->token is "(+)", this line replaces the right parenthesis with '\0' (a terminating 0).  The third argument to b_setenv() on line 6420 is second->token+1 (which skips the left parenthesis).  The string "+" is therefore the second argument to b_setenv().

6414
6415                   if (depth != 0) {
6416                           printf("Missing '}'\n");
6417                           err= 1;
6418                   } else
Expand/Collapse Item6419                    if ((flags= b_setenv(E_FUNCTION, name,
b_setenv() (line 5690) sets an environment variable or a function. b_setvar() can fail (return a nonzero value) if:

1)  an attempt is made to change the value of an E_RESERVED variable or function.

2)  an attempt is made to change an E_SPECIAL variable to a function (or vice versa).

6420                                           second->token+1, body)) != 0) {
6421                           printf("%s is a %s\n", name,
6422                                   flags & E_RESERVED ? "reserved word" :
6423                                                   "special variable");
6424                           err= 1;
6425                   }
Expand/Collapse Item6426                    while (cmds != sep) voidtoken();
Clear out the command chain.
6427                   free(body);
6428                   return;
6429           } else
Expand/Collapse Item6430                    /* Grouping? */
token1    ...       tokenN
{         ...       }

Curly braces can be used to string commands together.  The following command:

hd2a>{{echo hello\n} echo hello\n}
hello
hello

is the same as:

hd2a>echo hello\n; echo hello\n;
hello
hello

(See line 6475 for an explanation of "\n".)

This if block removes the first left curly brace (with voidtoken() on line 6444), replaces the last right curly brace with a semicolon (on line 6441) and then returns.  Note that the commands are not executed; only the curly braces are eliminated.  Other than the 2 curly braces, the command chain is left unchanged (in other words, cmds != nil).  After execute() returns on line 6620, execute() is immediately called again.

hd2a>{{echo hello\n} echo hello\n}

is broken up into the following tokens:

token1    token2    token3    token4
{         {         echo      hello\n

token5    token6    token7    token8
}         echo      hello\n    }

The first time execute() is called, it removes the first curly brace and replaces the second curly brace with a semicolon:

token1    token2    token3
{         echo      hello\n

token4    token5    token6    token7
}         echo      hello\n    ;

The second time execute() is called, it again removes the first curly brace and replaces the second curly brace with a semicolon:

token1    token2
echo      hello\n

token3    token4    token5    token6
;         echo      hello\n    ;

The next time execute() is called, it processes the echo hello\n command and the last time execute() is called, it processes the echo hello\n command again.

Note that the two while loops on lines 6617 and 6619 are very important.  Their significance is discussed in boot() (line 6605).

6431           if (name[0] == '{') {
Expand/Collapse Item6432                 token **acmds= &cmds->next;
token is declared on line 5356:

typedef struct token {
       struct token    *next;  /* Next in a command chain. */
       char            *token;
} token;


6433                   char *t;
6434                   int depth= 1;
6435
6436                   /* Find and remove matching '}' */
6437                   depth= 1;
6438                   while (*acmds != nil) {
6439                           t= (*acmds)->token;
6440                           if (t[0] == '{') depth++;
6441                       if (t[0] == '}' && --depth == 0) { t[0]= ';'; break; }
Expand/Collapse Item6442                            acmds= &(*acmds)->next;
token is declared on line 5356:

typedef struct token {
       struct token    *next;  /* Next in a command chain. */
       char            *token;
} token;


6443                }
Expand/Collapse Item6444                 voidtoken();
voidtoken() (line 5397) removes the first token from the command chain.
6445                   return;
6446           } else
6447                   /* Command coming up, check if ESC typed. */
Expand/Collapse Item6448            if (interrupt()) {
interrupt() returns 1 (TRUE) if the ESC key has been pressed. interrupt() sets the variable err (line 5383).
6449                   return;
6450           } else
6451                   /* unset name ..., echo word ...? */
Expand/Collapse Item6452            if (n >= 1 && (
Expand/Collapse Item6453                    strcmp(name, "unset") == 0
Expand/Collapse Item6454                    || strcmp(name, "echo") == 0
int strcmp(char *s1, char *s2) compares string s1 to string s2 and returns 0 if the two strings are the same.
6455           )) {
6456                   int cmd= name[0];
Expand/Collapse Item6457                    char *arg= poptoken(), *p;
poptoken() (line 5385) pops the first token off the command chain.  In this case, the token is either the string "unset" or "echo".  Subsequent calls to poptoken()on line 6462 pop the arguments to the boot monitor unset or echo commands.
6458
Expand/Collapse Item6459                    for (;;) {
for(;;) loops are endless loops.  while(1) loops are also endless loops (see line 6617).
6460                           free(arg);
6461                           if (cmds == sep) break;
6462                           arg= poptoken();
6463                           if (cmd == 'u') {       /* unset arg */
6464                               b_unset(arg);
6465                           } else {                /* echo arg */
6466                                   p= arg;
Expand/Collapse Item6467                                    while (*p != 0) {
One character at a time is printed to the screen.
Expand/Collapse Item6468                                            if (*p != '\\') {
'\\' is a single backslash character.
6469                                               putch(*p);
6470                                           } else
6471                                           switch (*++p) {
Expand/Collapse Item6472                                            case 0:
Expand/Collapse Item6473                                                  if (cmds == sep) return;
Expand/Collapse Item6474                                                    continue;
If an argument ends in a backslash, the backslash is ignored:

hd2a>echo hello\
hello

Expand/Collapse Item6475                                            case 'n':
If you wish to print two lines with echo, insert newlines:

hd2a>hello\n world
hello
world

6476                                                   putch('\n');
6477                                                   break;
Expand/Collapse Item6478                                            case 'v':
hd2a>echo version \v
version 2.11
6479                                                   printf(version);
6480                                                   break;
Expand/Collapse Item6481                                            case 'c':
The boot monitor leader command (see lines 5827-5829) clears the screen.
6482                                                   clear_screen();
6483                                                   break;
Expand/Collapse Item6484                                            case 'w':
The command:

hd2a>echo hello \w world
hello

waits until the user hits the Return (or Enter) key or the escape key (ESC).  All other keys are ignored.  The Return key prints "world" before returning to the prompt; the escape key returns to the prompt without printing "world".

6485                                                   for (;;) {
6486                                                           if (interrupt())
6487                                                                   return;
6488                                                         if (getch() == '\n')
6489                                                                   break;
6490                                                   }
6491                                                   break;
Expand/Collapse Item6492                                            default:
To print a backslash:

hd2a>echo hello\\
hello\

6493                                                   putch(*p);
6494                                           }
6495                                           p++;
6496                                   }
Expand/Collapse Item6497                                    putch(cmds != sep ? ' ' : '\n');
A newline is printed before echo finishes.  If newlines weren't appended, the following would occur:

hd2a>echo hello
hellohd2a>echo hello
hellohd2a>

6498                           }
6499                   }
6500                   return;
6501        } else
6502                   /* boot -opts? */
Expand/Collapse Item6503         if (n == 2 && strcmp(name, "boot") == 0 && second->token[0] == '-') {
The command:

hd2a>boot -option1

sets the environment variable bootopts to "option1" and then boots the OS image specified by the environment variable image.

6504                   static char optsvar[]= "bootopts";
Expand/Collapse Item6505                    (void) b_setvar(E_VAR, optsvar, second->token);
b_setvar() (line 5690) sets an environment variable or a simple function (a simple function is a function without an argument).
Expand/Collapse Item6506                    bootminix();
bootminix() boots the OS image specified by the environment variable imagebootminix() returns when the OS returns to the boot monitor.
Expand/Collapse Item6507                    b_unset(optsvar);
The environment variable bootopts is in existence only until the OS returns to the boot monitor.
Expand/Collapse Item6508                    voidtoken();
Expand/Collapse Item6509                    voidtoken();
Remove the two tokens from the command chain.
6510                   return;
6511           } else
6512                   /* boot device, ls dir, delay msec? */
6513           if (n == 2 && (
6514                   strcmp(name, "boot") == 0
6515                   || strcmp(name, "delay") == 0
6516                   || strcmp(name, "ls") == 0
6517           )) {
Expand/Collapse Item6518                    if (name[0] == 'b') boot_device(second->token);
void boot_device(char *devname) (line 6115) boots the partition specified by devnameboot_device() does not return unless the partition cannot be booted.
Expand/Collapse Item6519                    if (name[0] == 'd') delay(second->token);
void delay(char *msec) (line 6213) delays for msec milliseconds.
Expand/Collapse Item6520                    if (name[0] == 'l') ls(second->token);
void ls(char *dir) (line 6152) lists the contents of directory dir.
Expand/Collapse Item6521                    voidtoken();
Expand/Collapse Item6522                    voidtoken();
Remove the two tokens from the command chain.
6523                   return;
6524           } else
Expand/Collapse Item6525                    /* trap msec command? */
The command:

hd2a>trap 5000 boot

schedules the boot monitor boot command to be executed in 5 seconds.

6526         if (n == 3 && strcmp(name, "trap") == 0 && numeric(second->token)) {
6527                   long msec= a2l(second->token);
6528
6529                   voidtoken();
6530                   voidtoken();
Expand/Collapse Item6531                    schedule(msec, poptoken());
void schedule(long msec, char *cmd) (line 6197) schedules the boot monitor cmd command to be executed in msec milliseconds.

poptoken() returns the name of the command to be executed.

6532                   return;
6533           } else
6534                   /* Simple command. */
6535           if (n == 1) {
Expand/Collapse Item6536                    char *cmd= poptoken();
Expand/Collapse Item6537                    char *body;
If cmd is a user-defined function, body is set to the corresponding command string on line 6553 and the command string is tokenized on line 6554.
Expand/Collapse Item6538                    int ok= 0;
If ok isn't set by line 6557, something's wrong.
6539
Expand/Collapse Item6540                    if (strcmp(cmd, "boot") == 0) { bootminix(); ok= 1; }
bootminix() boots the OS image specified by the environment variable imagebootminix() returns when the OS returns to the boot monitor.
Expand/Collapse Item6541                    if (strcmp(cmd, "delay") == 0) { delay("500"); ok= 1; }
The default for the boot monitor delay command is 500 milliseconds (1/2 second).  void delay(char *msec) (line 6213) delays for msec milliseconds.
Expand/Collapse Item6542                    if (strcmp(cmd, "ls") == 0) { ls(null); ok= 1; }
The default for the boot monitor ls delay is to list the contents of the root directory ('/').  void ls(char *dir) (line 6152) lists the contents of directory dir.
Expand/Collapse Item6543                    if (strcmp(cmd, "menu") == 0) { menu(); ok= 1; }
menu() (line 6235) (typically) shows a list of different OS images the user can boot.
Expand/Collapse Item6544                    if (strcmp(cmd, "save") == 0) { save_parameters(); ok= 1; }
save_parameters() (line 5889) saves non-default environment variables to the bootparams sector.
Expand/Collapse Item6545                    if (strcmp(cmd, "set") == 0) { show_env(); ok= 1; }
show_env() (line 5930) prints all environment variables and functions whose flag field doesn't have the E_RESERVED bit set.
Expand/Collapse Item6546                    if (strcmp(cmd, "help") == 0) { help(); ok= 1; }
help() (line 6290) lists a few environment variables and their descriptions and also shows the available boot monitor commands.
Expand/Collapse Item6547                    if (strcmp(cmd, "exit") == 0) { exit(0); }
exit() reboots the system.
6548
Expand/Collapse Item6549                    /* Command to check bootparams: */
There was an error! 2002: php_network_getaddresses: getaddrinfo failed: Name or service not known
It is unclear to me what this function does.  It does not appear to check anything.  If you feel otherwise, please submit a comment to the site.
6550                   if (strcmp(cmd, ":") == 0) ok= 1;
6551
Expand/Collapse Item6552                    /* User defined function. */
If cmd doesn't match any of the built-in commands above, it must be a user-defined function.  body is tokenized and successive calls to execute() process the commands within body.

char *b_body(char *name) (line 5645) returns the value of the environment function namevalue contains the function's commands.  If name doesn't exist or is not a function, b_body() returns nil.

6553                   if (!ok && (body= b_body(cmd)) != nil) {
6554                           (void) tokenize(&cmds, body);
6555                           ok= 1;
6556               }
6557                   if (!ok) printf("%s: unknown function", cmd);
6558                   free(cmd);
6559                   if (ok) return;
6560              } else {
6561                   /* Syntax error. */
6562                   printf("Can't parse:");
Expand/Collapse Item6563                    while (cmds != sep) {
Expand/Collapse Item6564                            printf(" %s", cmds->token); voidtoken();
The attempted command is printed and voided.
6565                   }
6566              }
6567
6568          /* Getting here means that the command is not understood. */
6569           printf("\nTry 'help'\n");
6570           err= 1;
6571   }
6572
Expand/Collapse Item6573    int run_trailer(void)
run_trailer() is called from exec_image() in bootimage.c right after the OS image has been loaded into memory and before jumping to the minix kernel.

run_trailer() executes the boot monitor trailer command (see line 5831), which clears the screen.

run_trailer() temporarily replaces the current command chain with a command chain containing a single command - trailer.  After trailer is processed, the current command chain is reactivated.

6574 /* Run the trailer function between loading Minix and handing control to it.