====== Fredrik Hultin, gPXE Command Line Interface ======
===== The Project =====
I'm going to write a command line for gPXE which will let the user do some basic tasks interactively. Things like setting and getting an IP, pinging, specifying tftp-servers etc. I'm going to do this as a separate, custom made, but implementation independent command line library which will handle all the internals of the command line. For this lib a small driver in the underlying application (ie. gPXE) will send key pressings to the lib. Code in gPXE will be able to add any command in run time or link time to the lib using linker tables or doubly-linked lists. The command line lib will then call the registered service when a command successfully has been entered.
===== Design goals =====
Small, efficient and extensible code. It will be easy to add new commands and the command line will do as much data-processing and error checking it can before passing the presumably correct arguments to the underlying functions.
===== Design =====
==== User interface ====
The plans for the user interface are so that it will look something like
Welcome to Etherboot
?>help
Built in commands:
help Command help, use "help help" for more info.
exit, quit, boot Exits the command line and boots
Compiled in commands:
ip Views or sets current IP adress and netmask
?>help ip
ip - Views or sets current IP adress and netmask
Used for both IPv6 and IPv4.
Set the IP adress and netmask (IPv4)
ip
Example:
ip 192.168.1.42 255.255.255.0
Set the IP adress (IPv6)
ip
Example:
ip 2001:0db8::1428:57ab
View the IP/netmask
ip
Example:
ip
?>ip 10.0.0.4 255.0.0.0
?>ip
Protocol version: 4
Adress: 10.0.0.4
Netmask: 255.0.0.0
?>boot
Booting...
==== Command implementation ====
I've tried to minimize the code and work in/on the custom commands; the command line code will do most of the work. The ip command implementation could look something like this (without the parts actually doing anything)
#include "command.h"
/* Command description, table entry*/
struct command test_command __command = {
.name = "ip",
.extra_help = "Used for both IPv6 and IPv4.",
.desc = "Views or sets current IP adress and netmask",
.exec = cmd_test_exec,
.get_param_list = cmd_ip_get_param_list;
};
/* Get the parameter specifications */
static cmdl_param_list *cmd_ip_get_param_list (void) {
/* List set 1: set ipv4 adress and netmask */
/* Create the list set */
cmdl_param_list_set* ipv4;
ipv4 = cmdl_param_list_set_create();
/* Name and describe the list set */
cmdl_param_list_set_name(ipv4, "ipv4");
cmdl_param_list_set_desc(ipv4, "Set the IP adress and netmask (IPv4)");
/* Add the parameters with name, type and example value */
cmdl_param_list_set_add(ipv4, "ip", CMDL_IPV4, "192.168.1.42");
cmdl_param_list_set_add(ipv4, "netmask", CMDL_IPV4, "255.255.255.0");
/* List set 2: set ipv6 adress */
/* Create the list set */
cmdl_param_list_set* ipv6;
ipv6 = cmdl_param_list_set_create();
/* Name and describe the list set */
cmdl_param_list_set_name(ipv6, "ipv6");
cmdl_param_list_set_desc(ipv6, "Set the IP adress (IPv6)");
cmdl_param_list_set_add(ipv6, "ip", CMDL_IPV6, "2001:0db8::1428:57ab");
/* List set 3: view the IP adress and netmask */
/* Create the list set */
cmdl_param_list_set* view;
view = cmdl_param_list_set_create();
/* Name and describe the list set */
cmdl_param_list_set_name(view, "view");
cmdl_param_list_set_desc(view, "View the IP/netmask");
/* (No parameters = view) */
/* Create the parameter list */
cmdl_param_list* param_list;
param_list = cmdl_param_list_create();
/* Add the three list sets to the parameter list */
cmdl_param_list_add(param_list, ipv4);
cmdl_param_list_add(param_list, ipv6);
cmdl_param_list_add(param_list, view);
/* Return the parameter list to the command line */
return param_list;
}
static int cmd_ip_exec ( cmd_line* cmd, cmdl_params* params ) {
/* View */
if(strcmp("view", params->set_name) == 0){
int protocolv;
cmdl_ipv4 ip4, netmask;
cmdl_ipv6 ip6;
protocolv = ...();
cmdl_printf(cmd, "Protocol version: %i\n");
if(protocol != 4){
}else{
ip4 = ...();
netmask = ...();
cmdl_printf(cmd, "Adress: %i.%i.%i.%i\nNetmask: %i.%i.%i.%i\n",
ip4.octet[0], ip4.octet[1], ip4.octet[2], ip4.octet[3],
netmask.octet[0], netmask.octet[1], netmask.octet[2], netmask.octet[3] );
}else{
...
}
}
/* ipv4 */
if(strcmp("ipv4", params->set_name) == 0){
if( !set_gpxe_ip_something( cmdl_param_get_ipv4(params->param[0]) ){
return -1;
}
if( !set_gpxe_netmask_something( cmdl_param_get_ipv4(params, 1) ){
return -1;
}
}
/* ipv6 */
if(strcmp("ipv6", params->set_name) == 0){
if( !set_gpxe_ip6_something( cmdl_param_get_ipv6(params, 0) ){
return -1;
}
}
return 0;
}
Observe that the output of "help ip" is generated from this implementation.
==== Structures ====
=== The main command structure ===
struct command {
/* The name of the command */
const char *name;
/* Additional help, or help that can't be automatically generated */
const char *extra_help;
/* Short description of the command, what it does. */
const char *desc;
/* The command function, returns an error code, takes command line pointer and params specified in param_list */
int ( *exec ) ( cmd_line*, cmdl_params*)
/* Returns a list describing parameters the command accepts */
cmdl_param_list *( *get_param_list )( void );
};
=== cmdl_param_list ===
cmdl_param_list will contain parameter list sets, since a command might accept many different sets of parameters.
typedef struct {
int num_sets; // The number of sets in the list
cmdl_param_list_set** param_list_set; // An array of list set pointers
} cmdl_param_list;
=== cmdl_param_list_set ===
A parameter list set will contain parameter descriptions
typedef struct {
char* name; // Name of the list set (used for identification)
char* desc; // Description of the list set (used for the automated help)
int num_params; // The number of parameters
cmdl_param_desc** param; // An array of parameter description pointers
} cmdl_param_list_set;
=== cmdl_param_desc ===
A parameter description contains information about a single parameter
typedef struct {
int type; // Parameter type
char* name; // Parameter name
char* example; // Example of possible value
} cmdl_param_desc;
* name is the name of the input field, it will be used for the standardized automatic help function
* example is an example value for the field, also for the help
type will be an enum with different types of inputs the command line will understand and parse, for example:
enum{
CMDL_INT=0,
CMDL_FP,
CMDL_STR,
CMDL_IPV4,
CMDL_IPV6
};
=== cmdl_params ===
Contains one named parameter set
typedef struct{
char* set_name; // Name of the set
int num_params; // The number of parameters the set contains
cmdl_param** param; // An array of param pointers
}cmdl_params;
=== cmdl_param ===
Container class for parameter values. Only one parameter type is valid per parameter.
typedef struct{
int valid_type; // Defines the param type (which pointer is valid)
// Pointers to available types
int* integer;
double* fp;
char* str;
cmdl_ipv4* ipv4;
cmdl_ipv6* ipv6;
// ...
}cmdl_param;
// with supporting structures like
typedef struct{
unsigned char octet[4];
}cmdl_ipv4;
typedef struct{
unsigned short int part[
8];
}cmdl_ipv6;
// ...etc
==== Functions ====
Functions for the command implementation interface
=== cmdl_param_list_set_create ===
Allocates a new list set and returns it.
cmdl_param_list_set* cmdl_param_list_set_create();
=== cmdl_param_list_set_name ===
Names a given list set with a given name.
void cmdl_param_list_set_name(cmdl_param_list_set* list_set, char* name);
=== cmdl_param_list_set_desc ===
Adds a description to a given list set.
void cmdl_param_list_set_desc(cmdl_param_list_set* list_set, char* desc);
=== cmdl_param_list_set_add ===
Adds a parameter description to a given list set.
void cmdl_param_list_set_add(cmdl_param_list_set* list_set, char* name, int type, char* example);
=== cmdl_param_list_create ===
Allocates and returns a parameter new parameter list.
cmdl_param_list* cmdl_param_list_create();
=== cmdl_param_list_add ===
Adds a parameter list set to a given parameter list.
void cmdl_param_list_add(cmdl_param_list* param_list, cmdl_param
=== cmdl_printf ===
Prints formated text to a given command line.
int cmdl_printf(cmd_line* cmd, const char *format, ...);
=== cmdl_param_get_TYPE ===
Returns the given parameter from a cmdl_params as TYPE.
Rturns NULL if the parameter isn't a TYPE.
int *cmdl_param_get_int(cmdl_params* params, int param_num);
cmdl_ipv4 *cmdl_param_get_ipv4(cmdl_params* params, int param_num);
...
==== Concerns ====
Perhaps this implementation would be a bit over the top. I got an email from Michael last week and he suggested, without reading this, that I should use ordinary C-style command structures (int argc, char** argv). That might not be as grandiose, and it would be harder to generate standardized automated help output, but it'd be much easier to implement. With some nice parse helper functions available to the command implementations, then perhaps it wouldn't be so bad. Hmm...
===== Status =====
The command line is working and accepts input from the user, which it parses and then, at the moment, disregards.