All pastes #487176 Raw Edit

Someone

public c v1 · immutable
·published 2007-05-14 09:38 UTC
rendered paste body
/* Copyright (C) 2006 Free Software Foundation.   Written by Johan Rydberg <jrydberg@gnu.org>   This program is free software; you can redistribute it and/or   modify it under the terms of the GNU General Public License as   published by the Free Software Foundation; either version 2 of the   License, or (at your option) any later version.   This program is distributed in the hope that it will be useful, but   WITHOUT ANY WARRANTY; without even the implied warranty of   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU   General Public License for more details.   You should have received a copy of the GNU General Public License   along with this program; if not, write to the Free Software   Foundation. 51 Franklin Street, Fifth Floor Boston, MA 02110-1301,   USA. */#include <gnu/efi/api.h>#include <gnu/efi/protocol/simple-file-system.h>#include "boot/readline.h"/* Definitions.  */typedef struct command command_t;/* Evaluate to nonzero if C is between '0' and '9'.  */#define ISNUM(c)   (c >= '0' && c <= '9')/* Evaluate to nonzero if C is between 'a' and 'z'.  */#define ISALPHA(c) (c >= 'a' && c <= 'z')/* Structures.  */struct command{  command_t *next;  /* Name of the command, that will be used when the command is     invoked.  */  efi_char16_t *name;  /* Documentation string of the command.  First line should be     self-explaining.  */  efi_char16_t *doc;  /* List of child commands.  */  command_t **prefix_list;  /* Commandline complete generator.  */  int (*completer) (efi_char16_t **text, efi_char16_t ***matches);  /* Function that will be called when the command is invoked.  */  void (*func) (efi_char16_t *);};/* List of global commands.  */static command_t *cmdlist = 0;/* Create a new command and put it on command list LIST.  The command   will be invoked by typing in NAME.  FN will be called with the   argument string.  DOC is string that documents the effects of the   command.  First line of the document string should be self   explaining.  PREFIX_LIST can point to a sub-list of commands.  */static command_t *command_add (efi_char16_t *name, void (*fn) (efi_char16_t *),             command_t **prefix_list, command_t **list,             efi_char16_t *doc){  command_t *c;  c = efi_xmalloc (sizeof *c);  c->next = 0;  c->name = name;  c->doc = doc;  c->prefix_list = prefix_list;  c->func = fn;  c->completer = 0;  if (*list)    {      do        list = &(*list)->next;      while (*list);    }  *list = c;  return c;}static efi_char16_t *skip_whitespaces (efi_char16_t *s){  while (*s == ' ' || *s == '\t')    s++;  return s;}static efi_char16_t *eat_whitespaces (efi_char16_t *s){  s = skip_whitespaces (s);  while (s[efi_wstrlen (s) - 1] == ' ')    s[efi_wstrlen (s) - 1] = '\0';  return s;}static voidsplit_cmdline (efi_char16_t **s, efi_char16_t **e){  *s = skip_whitespaces (*s);  for (*e = *s; **e && (**e != ' ' && **e != '\t');)    (*e)++;}/* Lookup command in list LIST.  Found command will be returned or -1   if more than one command matched the command line.  *SP will be   updated on success to point to the next command or argument.  */static command_t *command_lookup_1 (command_t *list, efi_char16_t **sp){  efi_char16_t *p, *s;  s = skip_whitespaces (*sp);  for (p = s; *p && (*p != ' ' && *p != '\t');)    p++;  if (p == s)    return 0;  else    {      efi_char16_t name[p - s + 1];      command_t *c;      command_t *found = 0;      efi_wstrncpy0 (name, s, p - s);      for (c = list; c; c = c->next)        {          if (!efi_wstrncmp (c->name, name, p - s))            {              if (found)                return (command_t *) -1;              found = c;            }        }      *sp = p;      return found;    }}/* Lookup command *SP in list LIST.  Returns pointer to matching   command, and *SP will point to the first argument.  -1 is returned   if more than one command matches.  */static command_t *command_lookup (command_t *list, efi_char16_t **sp){  command_t *found = 0;  while (1)    {      command_t *c = command_lookup_1 (list, sp);      if (c == (command_t *) -1)        /* Several commands matching.  */        return (command_t *) -1;      else if (c)        {          if (!c->prefix_list)            return c;          found = c;          list = *c->prefix_list;        }      else        {          return found;        }    }}/* Completion generator function for the command line interface.  On   call, *TEXT points to the command line to complete.  This function   will locate commands matching the text, and return an array of   matching names in *MATCHES.  The number of elements in the array is   returned, or zero if there were no matches.  On return, *TEXT will   be updated so that it is aligned with the matches.  */static intcommand_generator (efi_char16_t **text, efi_char16_t ***matches){  command_t *list = cmdlist;  auto int complete (command_t *l, efi_char16_t *t,                     efi_char16_t ***vect);  int complete (command_t *l, efi_char16_t *t,                efi_char16_t ***vect)    {      int n = 0, i;      efi_char16_t *e;      efi_char16_t *v;      command_t *c, *f;      split_cmdline (&t, &e);      v = efi_xwstrndup (t, e - t);      for (c = l; c; c = c->next)        {          if (!efi_wstrncmp (c->name, v, e - t))            n++;        }      if (!vect)        {          efi_free_pool (v);          return n;        }      *vect = efi_xmalloc (sizeof (efi_char16_t *) * n);      for (c = l, i = 0; c; c = c->next)        {          if (!efi_wstrncmp (c->name, v, e - t))            {              /* Add a space after the matched command name, so that                 the user can start completing the next argument                 directly without having to press space.  */              (*vect)[i++] = efi_wasprintf (L"%s ", c->name);            }        }      efi_free_pool (v);      return n;    }  *text = skip_whitespaces (*text);  while (1)    {      efi_char16_t *sp = *text;      command_t *c;      c = command_lookup_1 (list, &sp);      if (c == (command_t *) -1)        /* More than one command matched; gather them.  */        return complete (list, *text, matches);      else if (c)        {          /* At this point SP should point at the character directly             after the matching command name.  Keep completing until             there is a space after the command.  */          if (efi_wstrlen (c->name) != (efi_uint_t) (sp - *text)              || *sp != ' ')            /* The text did not match the command name completely, so               return a complete with just one matche.  That will fill               the command line to the next argument/command.  */            return complete (list, *text, matches);          if (c->prefix_list)            /* This is a prefix command, try to complete the next               command on the command line.  */            list = *c->prefix_list;          else            {              if (c->completer)                {                  *text = skip_whitespaces (sp);                  return (*c->completer) (text, matches);                }              return 0;            }        }      else        {          if (!**text)            /* Special case: If no command matched, and there were not               even an effort to match anything; give a list of all               available commands.  */            return complete (list, *text, matches);          /* No matching nothing.  */          return 0;        }      /* Eat whitespaces till the next command.  */      *text = skip_whitespaces (sp);    }}/* Execute command line specified by P, picking commands from list   LIST.  Will print out errors messages.  */static voidcommand_execute (command_t *list, efi_char16_t *p){  command_t *c;  c = command_lookup (list, &p);  if (c == (command_t *) -1)    efi_wprintf (L"More than one command matching.\n");  else if (!c || !c->func)    efi_wprintf (L"No such command.\n");  else    {      /* Skip whitespaces up to the first argument.  Pass NULL instead         of an empty string.  */      p = skip_whitespaces (p);      if (!*p)        p = NULL;      (*c->func) (p);    }}/* Return nonzero if N is a valid disk name.  */static intvalid_name (efi_char16_t *n){  return ((n[0] == 'u' || n[0] == 'h' || n[0] == 's')          && n[1] == 'd'          && ISNUM (n[2]) && ISALPHA (n[3])          && n[4] == '\0');}/* The reason for not keeping disks in a list is that they might come   and go dynamicly (think CD's and USB's).  Instead we probe for them   every time they are needed.  *//* Iterate through available disks that implement protocol specified    by GUID and invoke HOOK for each one of them.  */static efi_status_tdisk_iterate (efi_guid_t *guid,              int (*hook) (efi_device_path_t *p, efi_char16_t *name)){  int usb_name = 0;  efi_uint_t num_handles, i;  efi_handle_t *handles;  efi_status_t err;  efi_char16_t name[5];  err = efi_locate_handle_buffer (EFI_LOCATE_SEARCH_TYPE_BY_PROTOCOL,                                  guid, 0, &num_handles, &handles);  if (err)    return err;  for (i = 0; i < num_handles; i++)    {      efi_device_path_t *path, *p, *s;      path = efi_device_path_get_from_handle (handles[i]);      if (!path)        continue;      efi_memset (name, 0, sizeof name);      p = path;      while ((s = efi_device_path_iterate (&p)))        {          switch (EFI_DEVICE_PATH_TYPE (s))            {            case EFI_MESSAGING_DEVICE_PATH_TYPE:              switch (EFI_DEVICE_PATH_SUBTYPE (s))                {                case EFI_ATAPI_DEVICE_PATH_SUBTYPE:                  {                    efi_atapi_device_path_t *atapi                      = (efi_atapi_device_path_t *) s;                    efi_wstrcpy (name, L"hd");                    name[2] = ('0'                               + atapi->primary_secondary * 2                               + atapi->slave_master);                    break;                  }                case EFI_SCSI_DEVICE_PATH_SUBTYPE:                  /* FIXME: implement */                  break;                }              break;            case EFI_MEDIA_DEVICE_PATH_TYPE:              switch (EFI_DEVICE_PATH_SUBTYPE (s))                {                case EFI_HARD_DRIVE_DEVICE_PATH_SUBTYPE:                  {                    efi_hard_drive_device_path_t *hard_drive                      = (efi_hard_drive_device_path_t *) s;                    name[3] = ('a' + hard_drive->partition_number);                    break;                  }                }              break;            }        }      if (valid_name (name))        {          if ((*hook) (path, name))            goto done;        }    } done:  efi_free_pool (handles);  return EFI_SUCCESS;}static efi_char16_t *efi_wstrchr (efi_char16_t *s, efi_char16_t c){  while (*s)    {      if (*s == c)        return s;      s++;    }  return NULL;}/* Open file system that is attached to the handle that can be found   at the device path specified by PATH.  The root node of the file   system will be returned.  */static efi_file_t *open_file_system (efi_device_path_t *path){  efi_simple_file_system_t *fs;  efi_handle_t h;  efi_status_t err;  efi_file_t *file = 0;  err = efi_locate_device_path (&EFI_SIMPLE_FILE_SYSTEM_GUID, &path, &h);  if (err)    goto done;  err = efi_handle_protocol (h, &EFI_SIMPLE_FILE_SYSTEM_GUID, (void **) &fs);  if (err)    goto done;  err = (*fs->open_volume) (fs, &file);  if (err)    /* Can not be sure that the file system handler leaves the pointer       alone.  */    file = 0; done:  return file;}/* Split filepath *FILEPATH into filename and device path of the disk   in the filepath.  The device path will be returned in *PATH.  On   success, *FILEPATH will point to the first character in the   filename.  */static efi_status_tsplit_filepath (efi_char16_t **filepath, efi_device_path_t **path){  efi_char16_t *fp = *filepath;  efi_char16_t *sp;  sp = efi_wstrchr (*filepath, ':');  if (!sp)    /* FIXME: A better solution for this case might be to just return       the filename and set *PATH to NULL.  */    return EFI_NOT_FOUND;  else    {      efi_char16_t disk_name[sp - *filepath + 1];      auto int match_disk (efi_device_path_t *p, efi_char16_t *name);      int match_disk (efi_device_path_t *p, efi_char16_t *name)        {          if (efi_wstrcmp (name, disk_name) == 0)            *path = efi_device_path_duplicate (p);          return 0;        }      efi_wstrncpy0 (disk_name, *filepath, sp - *filepath);      /* Check for a match by setting *PATH to the matched path.  */      *path = NULL;      disk_iterate (&EFI_SIMPLE_FILE_SYSTEM_GUID, match_disk);      if (*path == NULL)        return EFI_NOT_FOUND;      *filepath = (sp + 1);      return EFI_SUCCESS;    }}static efi_device_path_t *make_filepath_device_path (efi_device_path_t *p, efi_char16_t *fn){  efi_file_path_device_path_t *fp;  efi_uint_t len;  len = (efi_wstrlen (fn) + 1) * sizeof (efi_char16_t);  fp = (efi_file_path_device_path_t *)    efi_device_path_create_node (EFI_MEDIA_DEVICE_PATH_TYPE,                                 EFI_FILE_PATH_DEVICE_PATH_SUBTYPE,                                 sizeof *fp + len);  efi_memcpy (fp->path_name, fn, len);  return efi_device_path_append_node (p, (efi_device_path_t *) fp);}static efi_device_path_t *make_filepath (efi_char16_t *filepath){  efi_device_path_t *path;  if (split_filepath (&filepath, &path))    return NULL;  return make_filepath_device_path (path, filepath);}/* On the final completion, add a type character to ease further   completions.  For directories, add a '\\' and a space for files (so   that the user can continue with the next argument.)  */#define TYPE_CHAR(fi) (EFI_FILE_INFO_DIRECTORY_P (fi) ? '\\' : ' ')/* Filepath completer.  This completer completes paths in the form of   DISK:\PATH.  */static intfilepath_completer (efi_char16_t **text, efi_char16_t ***matches){  int num_matches = 0;  efi_char16_t *t, *s;  efi_status_t err;  auto void add_match (efi_char16_t *match);  void add_match (efi_char16_t *match)    {      int last_size = num_matches++;      efi_char16_t **new_m;      new_m = efi_xmalloc (num_matches * sizeof (efi_char16_t *));      efi_memcpy (new_m, *matches, last_size * sizeof (efi_char16_t *));      new_m[last_size] = match;      *matches = new_m;    }  t = skip_whitespaces (*text);  s = efi_wstrchr (t, ':');  if (!s)    /* Disk name completion.  */    {      int match_disk (efi_device_path_t *dp, efi_char16_t *name)        {          if (!efi_wstrncmp (name, t, efi_wstrlen (t)))            add_match (efi_wasprintf (L"%s:\\", name));          return 0;        }      disk_iterate (&EFI_SIMPLE_FILE_SYSTEM_GUID, match_disk);    }  else    {      efi_device_path_t *path;      efi_file_t *curr_dir, *next_dir;      err = split_filepath (&t, &path);      if (err)        goto done;      curr_dir = open_file_system (path);      if (!curr_dir)        goto done;      while (1)        {          efi_file_info_t *info;          /* Make sure that completion is done in a directory and             nothing else.  */          info = efi_file_get_file_info (curr_dir);          if (! EFI_FILE_INFO_DIRECTORY_P (info))            {              (*curr_dir->close) (curr_dir);              efi_free_pool (info);              goto done;            }          efi_free_pool (info);          while (*t == '\\')            t++;          s = efi_wstrchr (t, '\\');          if (!s)            {              /* Final component; read directory and try to complete                 against matching entries.  */              while ((info = efi_file_readdir (curr_dir)))                {                  if (!efi_wstrncmp (info->file_name,                                     t, efi_wstrlen (t)))                    {                      /* Add a type character after the file name so                         that user can continue to complete with                         having to add a space or a backslash.  */                      add_match                        (efi_wasprintf (L"%s%c", info->file_name,                                        TYPE_CHAR (info)));                    }                  efi_free_pool (info);                }              *text = t;              (*curr_dir->close) (curr_dir);              goto done;            }          else            {              efi_char16_t comp[s - t + 1];              efi_wstrncpy0 (comp, t, s - t);              err = (*curr_dir->open) (curr_dir, &next_dir, comp,                                       EFI_FILE_MODE_READ, 0);              if (err)                {                  (*curr_dir->close) (curr_dir);                  goto done;                }              t = s;              (*curr_dir->close) (curr_dir);              curr_dir = next_dir;            }        }    } done:  return num_matches;}/* Load and start application or driver.  */voidload_command (efi_char16_t *arg){  efi_device_path_t *dp;  efi_status_t err;  efi_handle_t h;  efi_char16_t *reason;  efi_uint_t size;  if (!arg)    {      efi_wprintf (L"load: No path specified.\n");      return;    }  arg = eat_whitespaces (arg);  dp = make_filepath (arg);  if (!dp)    {      err = EFI_NOT_FOUND;      goto error;    }  err = efi_load_image (0, _efi_image_handle, dp, 0, 0, &h);  if (err)    goto error;  err = efi_start_image (h, &size, &reason);  if (err)    {      if (size)        {          efi_wprintf (L"(exit reason; %s)\n", reason);          efi_free_pool (reason);        }      goto error;    } error:  if (err)    efi_wprintf (L"load: %s: %s\n", arg, efi_wstrerror (err));}/* Display version information.  */static voidshow_version (void){  efi_puts (L"\gnufi " VERSION "\n\Copyright 2006 Free Software Foundation, Inc.\n\gnufi is free software, covered by the GNU General Public License, and you are\n\welcome to change it and/or distribute copies of it under certain conditions.\n\n");}voidshell (void){  efi_char16_t cmdline[128];  command_t *c;  c = command_add    (L"load", load_command, 0, &cmdlist,     L"Load and start program or driver.");  c->completer = filepath_completer;  show_version ();  while (1)    {      cmdline[0] = '\0';      efi_readline (L"(gnufi) ", cmdline, 128, command_generator);      command_execute (cmdlist, cmdline);    }}efi_status_tefi_main (efi_handle_t image){  shell ();  return 0; /* make GCC happy.  */}