diff options
-rw-r--r-- | boot/boot_script.c | 861 | ||||
-rw-r--r-- | boot/boot_script.h | 91 |
2 files changed, 952 insertions, 0 deletions
diff --git a/boot/boot_script.c b/boot/boot_script.c new file mode 100644 index 00000000..ca71ccc3 --- /dev/null +++ b/boot/boot_script.c @@ -0,0 +1,861 @@ +/* Script parser for Mach. */ + +#include <mach/mach_types.h> +#include <mach/message.h> +#include "boot_script.h" + +/* This structure describes a command. */ +struct cmd +{ + /* Path of executable. */ + char *path; + + /* Task port. */ + mach_port_t task; + + /* Argument list. */ + struct arg **args; + + /* Amount allocated for `args'. */ + int args_alloc; + + /* Next available slot in `args'. */ + int args_index; + + /* List of functions that want to be run on command execution. */ + struct sym **exec_funcs; + + /* Amount allocated for `exec_funcs'. */ + int exec_funcs_alloc; + + /* Next available slot in `exec_funcs'. */ + int exec_funcs_index; +}; + +/* This structure describes a symbol. */ +struct sym +{ + /* Symbol name. */ + char *name; + + /* Pointer to function that returns symbol value. + The function should return 0 on success, non-zero otherwise. */ + int (*func) (struct cmd *cmd, int *val); + + /* Type of value returned by function. */ + int type; + + /* Symbol value. */ + int val; + + /* If set, don't create an argument if the symbol + appears by itself in an expression. */ + int no_arg; + + /* If set, execute function at the time of command execution, + not during parsing. A function with this field set must + also have `no_arg' set. Also, the function's `val' argument + will always be NULL. */ + int run_on_exec; +}; + +/* Type of values symbols can have. */ +#define VAL_NONE 0 /* none */ +#define VAL_STR 1 /* string */ +#define VAL_PORT 2 /* port */ +#define VAL_SYM 3 /* symbol table entry */ + +/* This structure describes an argument. */ +struct arg +{ + /* Argument text copied verbatim. 0 if none. */ + char *text; + + /* Type of value assigned. 0 if none. */ + int type; + + /* Argument value. */ + int val; +}; + +/* List of commands. */ +static struct cmd **cmds = 0; + +/* Amount allocated for `cmds'. */ +static int cmds_alloc = 0; + +/* Next available slot in `cmds'. */ +static int cmds_index = 0; + +/* Symbol table. */ +static struct sym **symtab = 0; + +/* Amount allocated for `symtab'. */ +static int symtab_alloc = 0; + +/* Next available slot in `symtab'. */ +static int symtab_index = 0; + +void memset (void *str, char c, int size); + +/* Create a task. */ +static int +create_task (struct cmd *cmd, int *val) +{ + cmd->task = boot_script_task_create (); + if (cmd->task == 0) + return BOOT_SCRIPT_MACH_ERROR; + boot_script_task_suspend (cmd->task); + *val = (int) cmd->task; + return 0; +} + +/* Resume a task. */ +static int +resume_task (struct cmd *cmd, int *val) +{ + if (boot_script_task_resume (cmd->task)) + return BOOT_SCRIPT_MACH_ERROR; + return 0; +} + +/* Return a send right to the host port. */ +static int +get_host_port (struct cmd *cmd, int *val) +{ + *val = (int) boot_script_host_port; + return 0; +} + +/* Return a send right to the master device port. */ +static int +get_device_port (struct cmd *cmd, int *val) +{ + *val = (int) boot_script_device_port; + return 0; +} + +/* Return the root device name. */ +static int +get_root_device (struct cmd *cmd, int *val) +{ + *val = (int) boot_script_root_device; + return 0; +} + +/* Set the bootstrap port for a task. */ +static int +set_bootstrap_port (struct cmd *cmd, int *val) +{ + boot_script_set_bootstrap_port (cmd->task, boot_script_bootstrap_port); + return 0; +} + +/* List of builtin symbols. */ +static struct sym builtin_symbols[] = +{ + { "task-create", create_task, VAL_PORT, 0, 1, 0 }, + { "task-resume", resume_task, VAL_NONE, 0, 1, 1 }, + { "host-port", get_host_port, VAL_PORT, 0, 0, 0 }, + { "device-port", get_device_port, VAL_PORT, 0, 0, 0 }, + { "bootstrap-port", set_bootstrap_port, VAL_NONE, 0, 1, 0 }, + { "root-device", get_root_device, VAL_STR, 0, 0, 0 } +}; +#define NUM_BUILTIN (sizeof (builtin_symbols) / sizeof (builtin_symbols[0])) + +/* Free CMD and all storage associated with it. + If ABORTING is set, terminate the task associated with CMD, + otherwise just deallocate the send right. */ +static void +free_cmd (struct cmd *cmd, int aborting) +{ + if (cmd->task) + { + if (aborting) + boot_script_task_terminate (cmd->task); + else + boot_script_port_deallocate (boot_script_task_port, cmd->task); + } + if (cmd->args) + { + int i; + + for (i = 0; i < cmd->args_index; i++) + boot_script_free (cmd->args[i], sizeof (struct arg)); + boot_script_free (cmd->args, cmd->args_alloc * sizeof (struct arg *)); + } + if (cmd->exec_funcs) + boot_script_free (cmd->exec_funcs, + cmd->exec_funcs_alloc * sizeof (struct sym *)); + boot_script_free (cmd, sizeof (struct cmd)); +} + +/* Free all storage allocated by the parser. + If ABORTING is set, terminate all tasks. */ +static void +cleanup (int aborting) +{ + int i; + + for (i = 0; i < cmds_index; i++) + free_cmd (cmds[i], aborting); + boot_script_free (cmds, cmds_alloc * sizeof (struct cmd *)); + cmds = 0; + cmds_index = cmds_alloc = 0; + + for (i = 0; i < symtab_index; i++) + boot_script_free (symtab[i], sizeof (struct sym)); + boot_script_free (symtab, symtab_alloc * sizeof (struct sym *)); + symtab = 0; + symtab_index = symtab_alloc = 0; +} + +/* Add PTR to the list of pointers PTR_LIST, which + currently has ALLOC amount of space allocated to it, and + whose next available slot is INDEX. If more space + needs to to allocated, INCR is the amount by which + to increase it. Return 0 on success, non-zero otherwise. */ +static int +add_list (void *ptr, void ***ptr_list, int *alloc, int *index, int incr) +{ + if (*index == *alloc) + { + void **p; + + *alloc += incr; + p = boot_script_malloc (*alloc * sizeof (void *)); + if (! p) + { + *alloc -= incr; + return 1; + } + if (*ptr_list) + { + memcpy (p, *ptr_list, *index * sizeof (void *)); + boot_script_free (*ptr_list, *index * sizeof (void *)); + } + *ptr_list = p; + } + *(*ptr_list + *index) = ptr; + *index += 1; + return 0; +} + +/* Create an argument with TEXT, value type TYPE, and value VAL. + Add the argument to the argument list of CMD. */ +static struct arg * +add_arg (struct cmd *cmd, char *text, int type, int val) +{ + struct arg *arg; + + arg = boot_script_malloc (sizeof (struct arg)); + if (arg) + { + arg->text = text; + arg->type = type; + arg->val = val; + if (add_list (arg, (void ***) &cmd->args, + &cmd->args_alloc, &cmd->args_index, 5)) + { + boot_script_free (arg, sizeof (struct arg)); + return 0; + } + } + return arg; +} + +/* Search for the symbol NAME in the symbol table. */ +static struct sym * +sym_lookup (char *name) +{ + int i; + + for (i = 0; i < symtab_index; i++) + if (! strcmp (name, symtab[i]->name)) + return symtab[i]; + return 0; +} + +/* Create an entry for symbol NAME in the symbol table. */ +static struct sym * +sym_enter (char *name) +{ + struct sym *sym; + + sym = boot_script_malloc (sizeof (struct sym)); + if (sym) + { + memset (sym, 0, sizeof (struct sym)); + sym->name = name; + if (add_list (sym, (void ***) &symtab, &symtab_alloc, &symtab_index, 20)) + { + boot_script_free (sym, sizeof (struct sym)); + return 0; + } + } + return sym; +} + +/* Parse the command line CMDLINE. */ +int +boot_script_parse_line (char *cmdline) +{ + char *p, *q; + int error; + struct cmd *cmd; + struct arg *arg; + + /* Extract command name. Ignore line if it lacks a command. */ + for (p = cmdline; *p == ' ' || *p == '\t'; p++) + ; + for (q = p; *q && *q != ' ' && *q != '\t' && *q != '\n'; q++) + ; + if (p == q) + return 0; + *q = '\0'; + + /* Allocate a command structure. */ + cmd = boot_script_malloc (sizeof (struct cmd)); + if (! cmd) + return BOOT_SCRIPT_NOMEM; + memset (cmd, 0, sizeof (struct cmd)); + cmd->path = p; + p = q + 1; + + for (arg = 0;;) + { + if (! arg) + { + /* Skip whitespace. */ + while (*p == ' ' || *p == '\t') + p++; + + /* End of command line. */ + if (! *p || *p == '\n') + { + /* Add command to list. */ + if (add_list (cmd, (void ***) &cmds, + &cmds_alloc, &cmds_index, 10)) + { + error = BOOT_SCRIPT_NOMEM; + goto bad; + } + return 0; + } + } + + /* Look for a symbol. */ + if (arg || (*p == '$' && *(p + 1) == '{')) + { + int saw_assignment = 0; + struct sym *sym = 0; + + for (p += 2;;) + { + char c; + int i, val, type, is_builtin = 0; + struct sym *s; + + /* Parse symbol name. */ + for (q = p; *q && *q != '\n' && *q != '}' && *q != '='; q++) + ; + if (p == q || ! *q || *q == '\n') + { + error = BOOT_SCRIPT_SYNTAX_ERROR; + goto bad; + } + c = *q; + *q = '\0'; + + /* See if this is a builtin symbol. */ + for (i = 0; i < NUM_BUILTIN; i++) + if (! strcmp (p, builtin_symbols[i].name)) + break; + + if (i < NUM_BUILTIN) + { + is_builtin = 1; + s = &builtin_symbols[i]; + + /* Check that assignment is valid. */ + if (c != '}' || ((arg || sym) && s->type == VAL_NONE)) + { + error = BOOT_SCRIPT_INVALID_ASG; + goto bad; + } + + if (! s->run_on_exec) + { + error = (*s->func) (cmd, &val); + if (error) + goto bad; + type = s->type; + } + else + { + if (add_list (s, (void ***) &cmd->exec_funcs, + &cmd->exec_funcs_alloc, + &cmd->exec_funcs_index, 5)) + { + error = BOOT_SCRIPT_NOMEM; + goto bad; + } + type = VAL_NONE; + goto out; + } + } + else + { + /* Look up symbol in symbol table. + If no entry exists, create one. */ + s = sym_lookup (p); + if (! s) + { + s = sym_enter (p); + if (! s) + { + error = BOOT_SCRIPT_NOMEM; + goto bad; + } + } + type = VAL_SYM; + val = (int) s; + } + + if (sym) + { + sym->type = type; + sym->val = val; + } + else if (arg) + { + arg->type = type; + arg->val = val; + } + + out: + p = q + 1; + if (c == '}') + { + /* Create an argument if necessary. + We create an argument if the symbol appears + in the expression by itself, and it's `no_arg' + field is 0. + + NOTE: This is temporary till the boot filesystem + servers support arguments. When that happens, + symbol values will only be printed if they're + associated with an argument. */ + if (! arg && ! saw_assignment && ! s->no_arg) + { + struct arg *a; + + if (is_builtin) + a = add_arg (cmd, 0, type, val); + else + a = add_arg (cmd, 0, VAL_SYM, (int) s); + if (! a) + { + error = BOOT_SCRIPT_NOMEM; + goto bad; + } + } + arg = 0; + break; + } + else + saw_assignment = 1; + if (! is_builtin) + sym = s; + } + } + else + { + char c; + + /* Command argument. Just copy the text. */ + for (q = p;; q++) + { + if (! *q || *q == ' ' || *q == '\t' || *q == '\n') + break; + if (*q == '$' && *(q + 1) == '{') + break; + } + c = *q; + *q = '\0'; + + /* Add argument to list. */ + arg = add_arg (cmd, p, VAL_NONE, 0); + if (! arg) + { + error = BOOT_SCRIPT_NOMEM; + goto bad; + } + if (c == '$') + p = q; + else + { + if (c) + p = q + 1; + else + p = q; + arg = 0; + } + } + } + + bad: + free_cmd (cmd, 1); + cleanup (1); + return error; +} + +/* Ensure that the command line buffer can accommodate LEN bytes of space. */ +#define CHECK_CMDLINE_LEN(len) \ +{ \ + if (cmdline_alloc - cmdline_index < len) \ + { \ + char *ptr; \ + int alloc; \ + alloc = cmdline_alloc + len - (cmdline_alloc - cmdline_index) + 100; \ + ptr = boot_script_malloc (alloc); \ + if (! ptr) \ + { \ + error = BOOT_SCRIPT_NOMEM; \ + goto done; \ + } \ + memcpy (ptr, cmdline, cmdline_index); \ + boot_script_free (cmdline, cmdline_alloc); \ + cmdline = ptr; \ + cmdline_alloc = alloc; \ + } \ +} + +/* Execute commands previously parsed. */ +int +boot_script_exec () +{ + int cmd_index; + + for (cmd_index = 0; cmd_index < cmds_index; cmd_index++) + { + char **argv, *cmdline; + int i, argc, cmdline_alloc; + int cmdline_index, error, arg_index; + struct cmd *cmd = cmds[cmd_index]; + + /* Skip command if it doesn't have an associated task. */ + if (cmd->task == 0) + continue; + + /* Allocate a command line and copy command name. */ + cmdline_index = strlen (cmd->path) + 1; + cmdline_alloc = cmdline_index + 100; + cmdline = boot_script_malloc (cmdline_alloc); + if (! cmdline) + { + cleanup (1); + return BOOT_SCRIPT_NOMEM; + } + memcpy (cmdline, cmd->path, cmdline_index); + + /* Allocate argument vector. */ + argv = boot_script_malloc (sizeof (char *) * (cmd->args_index + 1)); + if (! argv) + { + boot_script_free (cmdline, cmdline_alloc); + cleanup (1); + return BOOT_SCRIPT_NOMEM; + } + argv[0] = cmdline; + argc = 1; + + /* Build arguments. */ + for (arg_index = 0; arg_index < cmd->args_index; arg_index++) + { + struct arg *arg = cmd->args[arg_index]; + + /* Copy argument text. */ + if (arg->text) + { + int len = strlen (arg->text); + + if (arg->type == VAL_NONE) + len++; + CHECK_CMDLINE_LEN (len); + memcpy (cmdline + cmdline_index, arg->text, len); + argv[argc++] = &cmdline[cmdline_index]; + cmdline_index += len; + } + + /* Add value of any symbol associated with this argument. */ + if (arg->type != VAL_NONE) + { + char *p = 0, buf[50]; + int len = 0; + + if (arg->type == VAL_SYM) + { + struct sym *sym = (struct sym *) arg->val; + + /* Resolve symbol value. */ + while (sym->type == VAL_SYM) + sym = (struct sym *) sym->val; + if (sym->type == VAL_NONE) + { + error = BOOT_SCRIPT_UNDEF_SYM; + goto done; + } + arg->type = sym->type; + arg->val = sym->val; + } + + /* Print argument value. */ + switch (arg->type) + { + case VAL_STR: + p = (char *) arg->val; + len = strlen (p); + break; + + case VAL_PORT: + /* Insert send right. */ + if ((boot_script_port_insert_right + (cmd->task, (mach_port_t) arg->val, + (mach_port_t) arg->val, MACH_MSG_TYPE_COPY_SEND))) + { + error = BOOT_SCRIPT_MACH_ERROR; + goto done; + } + + i = arg->val; + p = buf + sizeof (buf); + len = 0; + do + { + *--p = i % 10 + '0'; + len++; + } + while (i /= 10); + break; + } + len++; + CHECK_CMDLINE_LEN (len); + memcpy (cmdline + cmdline_index, p, len - 1); + *(cmdline + cmdline_index + len - 1) = '\0'; + if (! arg->text) + argv[argc++] = &cmdline[cmdline_index]; + cmdline_index += len; + } + } + + /* Terminate argument vector. */ + argv[argc] = 0; + + /* Execute the command. */ + if (boot_script_exec_cmd (cmd->task, cmd->path, + argc, argv, cmdline, cmdline_index)) + { + error = BOOT_SCRIPT_EXEC_ERROR; + goto done; + } + + error = 0; + + done: + boot_script_free (cmdline, cmdline_alloc); + boot_script_free (argv, (cmd->args_index + 1) * sizeof (char *)); + if (error) + { + cleanup (1); + return error; + } + } + + for (cmd_index = 0; cmd_index < cmds_index; cmd_index++) + { + int i; + struct cmd *cmd = cmds[cmd_index]; + + if (cmd->task) + /* Execute functions that want to be run on exec. */ + for (i = 0; i < cmd->exec_funcs_index; i++) + { + struct sym *sym = cmd->exec_funcs[i]; + int error = (*sym->func) (cmd, 0); + if (error) + { + cleanup (1); + return error; + } + } + } + + cleanup (0); + return 0; +} + +/* Return a string describing ERR. */ +char * +boot_script_error_string (int err) +{ + switch (err) + { + case BOOT_SCRIPT_NOMEM: + return "no memory"; + + case BOOT_SCRIPT_SYNTAX_ERROR: + return "syntax error"; + + case BOOT_SCRIPT_INVALID_ASG: + return "invalid variable in assignment"; + + case BOOT_SCRIPT_MACH_ERROR: + return "mach error"; + + case BOOT_SCRIPT_UNDEF_SYM: + return "undefined symbol"; + + case BOOT_SCRIPT_EXEC_ERROR: + return "exec error"; + } + return 0; +} + +#ifdef BOOT_SCRIPT_TEST +#include <mach.h> +#include <stdio.h> + +extern void *malloc (int size); +extern void free (void *ptr); + +char *boot_script_root_device = "hd0a"; +mach_port_t boot_script_task_port; +mach_port_t boot_script_host_port; +mach_port_t boot_script_device_port; +mach_port_t boot_script_bootstrap_port = MACH_PORT_NULL; + +void * +boot_script_malloc (int size) +{ + return malloc (size); +} + +void +boot_script_free (void *ptr, int size) +{ + free (ptr); +} + +mach_port_t +boot_script_task_create () +{ + mach_port_t task; + + if (task_create (mach_task_self (), FALSE, &task)) + return 0; + return task; +} + +void +boot_script_task_terminate (mach_port_t task) +{ + task_terminate (task); +} + +void +boot_script_task_suspend (mach_port_t task) +{ + task_suspend (task); +} + +int +boot_script_task_resume (mach_port_t task) +{ + mach_port_t bootport; + + task_get_bootstrap_port (task, &bootport); + printf ("bootstrap port %d\n", bootport); + printf ("boot_script_task_resume()\n"); + return 0; +} + +void +boot_script_port_deallocate (mach_port_t task, mach_port_t port) +{ + mach_port_deallocate (task, port); +} + +int +boot_script_port_insert_right (mach_port_t task, mach_port_t name, + mach_port_t port, mach_msg_type_name_t right) +{ + if (mach_port_insert_right (task, name, port, right)) + return 1; + return 0; +} + +void +boot_script_set_bootstrap_port (mach_port_t task, mach_port_t port) +{ + task_set_bootstrap_port (task, port); +} + +int +boot_script_exec_cmd (mach_port_t task, char *path, int argc, + char **argv, char *strings, int stringlen) +{ + int i; + + for (i = 0; i < argc; i++) + printf ("%s ", argv[i]); + printf ("\n"); + return 0; +} + +void +main (int argc, char **argv) +{ + char buf[500], *p; + int len; + FILE *fp; + + if (argc < 2) + { + fprintf (stderr, "Usage: %s <script>\n", argv[0]); + exit (1); + } + fp = fopen (argv[1], "r"); + if (! fp) + { + fprintf (stderr, "Can't open %s\n", argv[1]); + exit (1); + } + boot_script_task_port = mach_task_self (); + boot_script_host_port = task_by_pid (-1); + boot_script_device_port = task_by_pid (-2); + if (! boot_script_host_port || ! boot_script_device_port) + { + fprintf (stderr, "Unable to get privileged ports\n"); + exit (1); + } + p = buf; + len = sizeof (buf); + while (fgets (p, len, fp)) + { + int i, err; + + i = strlen (p) + 1; + err = boot_script_parse_line (p); + if (err) + { + fprintf (stderr, "error %s\n", boot_script_error_string (err)); + exit (1); + } + p += i; + len -= i; + } + boot_script_exec (); + exit (0); +} +#endif /* BOOT_SCRIPT_TEST */ diff --git a/boot/boot_script.h b/boot/boot_script.h new file mode 100644 index 00000000..88a6bf49 --- /dev/null +++ b/boot/boot_script.h @@ -0,0 +1,91 @@ +/* Error codes returned by boot_script_parse_line() + and boot_script_exec_cmd(). */ +#define BOOT_SCRIPT_NOMEM 1 +#define BOOT_SCRIPT_SYNTAX_ERROR 2 +#define BOOT_SCRIPT_INVALID_ASG 3 +#define BOOT_SCRIPT_MACH_ERROR 4 +#define BOOT_SCRIPT_UNDEF_SYM 5 +#define BOOT_SCRIPT_EXEC_ERROR 6 + +/* The user must define this variable. The root device name. + This must be initialized prior to calling the parser. */ +extern char *boot_script_root_device; + +/* The user must define this variable. The task port of the program. */ +extern mach_port_t boot_script_task_port; + +/* The user must define this variable. Send right to the host port. */ +extern mach_port_t boot_script_host_port; + +/* The user must define this variable. Send right to the + master device port. */ +extern mach_port_t boot_script_device_port; + +/* The user must define this variable. Send right to the + the bootstrap port. */ +extern mach_port_t boot_script_bootstrap_port; + +/* The user must define this function. Allocate SIZE bytes of memory + and return a pointer to it. */ +void *boot_script_malloc (int size); + +/* The user must define this function. Free SIZE bytes of memory + named by PTR that was previously allocated by boot_script_malloc(). */ +void boot_script_free (void *ptr, int size); + +/* The user must define this function. Create a new task and + return a send right to it presumably using task_create(). + Return 0 on error. */ +mach_port_t boot_script_task_create (void); + +/* The user must define this function. Terminate the task whose + send right is TASK presumably using task_terminate(). */ +void boot_script_task_terminate (mach_port_t task); + +/* The user must define this function. Suspend TASK presumably + using task_suspend(). */ +void boot_script_task_suspend (mach_port_t task); + +/* The user must define this function. Resume TASK presumably + using task_resume(). Return 0 on success, non-zero otherwise. */ +int boot_script_task_resume (mach_port_t task); + +/* The user must define this function. Deallocate a send right to PORT + in the TASK presumably using mach_port_deallocate(). */ +void boot_script_port_deallocate (mach_port_t task, mach_port_t port); + +/* The user must define this function. Insert the specified RIGHT to + the PORT in the current task into TASK with NAME, presumably using + mach_port_insert_right(). RIGHT can take any value allowed by + mach_port_insert_right(). Return 0 for success, non-zero otherwise. */ +int boot_script_port_insert_right (mach_port_t task, + mach_port_t name, mach_port_t port, + mach_msg_type_name_t right); + +/* The user must define this function. Set the bootstrap port for TASK. */ +void boot_script_set_bootstrap_port (mach_port_t task, mach_port_t port); + +/* The user must define this function. Load the image of the + executable specified by PATH in TASK. Create a thread + in TASK and point it at the executable's entry point. Initialize + TASK's stack with argument vector ARGV of length ARGC whose + strings are STRINGS. STRINGS has length STRINGLEN. + Return 0 for success, non-zero otherwise. */ +int boot_script_exec_cmd (mach_port_t task, char *path, int argc, + char **argv, char *strings, int stringlen); + +/* Parse the command line LINE. This causes the command line to be + converted into an internal format. Returns 0 for success, non-zero + otherwise. + + NOTE: The parser writes into the line so it must not be a string constant. + It is also the responsibility of the caller not to deallocate the line + across calls to the parser. */ +int boot_script_parse_line (char *cmdline); + +/* Execute the command lines prevously parsed. + Returns 0 for success, non-zero otherwise. */ +int boot_script_exec (void); + +/* Returns a string describing the error ERR. */ +char *boot_script_error_string (int err); |