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 <ip> <netmask>

  Example:
    ip 192.168.1.42 255.255.255.0

Set the IP adress (IPv6)
  ip <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.


QR Code
QR Code soc:fredrikhultin (generated for current page)