grub2/normal/cmdline.c
marco_g db1771cfbe 2004-03-14 Marco Gerards <metgerards@student.han.nl>
* commands/boot.c: New file.
	* commands/cat.c: Likewise.
	* commands/cmp.c: Likewise.
	* commands/ls.c: Likewise.
	* commands/terminal.c: Likewise.
	* normal/command.c: Include <pupa/env.h> and <pupa/dl.h>.
	(pupa_register_command): Changed interface to match the new
	argument parser.
	(pupa_command_execute): Changed (almost rewritten) so it uses
	pupa_split_command.  Added support for setting variables using the
	syntax `foo=bar'.
	(rescue_command): Changed to work with the new argument parser.
	(terminal_command): Moved from here to commands/terminal.c.
	(set_command): New function.
	(unset_command): New function.
	(insmod_command): New function.
	(rmmod_command): New function.
	(lsmod_command): New function.
	(pupa_command_init): Don't initialize the command terminal
	anymore.  Initialize the commands set, unset, insmod, rmmod and
	lsmod.
	* conf/i386-pc.rmk (kernel_img_SOURCES): Add kern/env.c.
	(kernel_img_HEADERS): Add arg.h and env.h.
	(pupa_mkimage_LDFLAGS): Add kern/env.c.
	(pupa_emu_SOURCES): Add kern/env.c, commands/ls.c,
	commands/terminal.c commands/boot.c commands/cmp.c commands/cat.c,
	normal/arg.c.
	(pkgdata_MODULES): Add ls.mod, boot.mod, cmp.mod, cat.mod and
	terminal.mod.
	(normal_mod_SOURCES): Add normal/arg.c and normal/arg.c.
	(boot_mod_SOURCES): New variable.
	(terminal_mod_SOURCES): Likewise.
	(ls_mod_SOURCES): Likewise.
	(cmp_mod_SOURCES): Likewise.
	(cat_mod_SOURCES): Likewise.

	* normal/arg.c: New file.
	* kern/env.c: Likewise.
	* include/pupa/arg.h: Likewise.
	* include/pupa/env.h: Likewise.
	* font/manager.c (font_command): Changed to match argument parsing
	interface changes.
	(PUPA_MOD_INIT): Likewise.
	* hello/hello.c (pupa_cmd_hello): Likewise.
	(PUPA_MOD_INIT): Likewise.
	* include/pupa/disk.h: Include <pupa/device.h>.
	(pupa_print_partinfo): New prototype.
	* include/pupa/dl.h (pupa_dl_set_prefix): Prototype removed.
	(pupa_dl_get_prefix): Likewise.
	* include/pupa/misc.h: Include <pupa/err.h>.
	(pupa_isgraph): New prototype.
	(pupa_isdigit): Likewise.
	(pupa_split_cmdline): Likewise.
	* include/pupa/normal.h: Include <pupa/arg.h>.
	(pupa_command): Changed the prototype of the member `func' to
	match the argument parsing interface.  Added member `options'.
	(pupa_register_command): Updated to match function.
	(pupa_arg_parse): New prototype.
	(pupa_hello_init) [PUPA_UTIL]: New prototype.
	(pupa_hello_fini) [PUPA_UTIL]: Likewise.
	(pupa_ls_init) [PUPA_UTIL]: Likewise.
	(pupa_ls_fini) [PUPA_UTIL]: Likewise.
	(pupa_cat_init) [PUPA_UTIL]: Likewise.
	(pupa_cat_fini) [PUPA_UTIL]: Likewise.
	(pupa_boot_init) [PUPA_UTIL]: Likewise.
	(pupa_boot_fini) [PUPA_UTIL]: Likewise.
	(pupa_cmp_init) [PUPA_UTIL]: Likewise.
	(pupa_cmp_fini) [PUPA_UTIL]: Likewise.
	(pupa_terminal_init) [PUPA_UTIL]: Likewise.
	(pupa_terminal_fini) [PUPA_UTIL]: Likewise.
	* kern/disk.c: Include <pupa/file.h>.
	(pupa_print_partinfo): New function.
	* kern/dl.c: Include <pupa/env.h>.
	(pupa_dl_dir): Variable removed.
	(pupa_dl_load): Use the environment variable `prefix' instead of
	the variable pupa_dl_dir.
	(pupa_dl_set_prefix): Function removed.
	(pupa_dl_get_prefix): Likewise.
	* kern/i386/pc/init.c: Include <pupa/env.h>.
	(pupa_machine_init): Use the environment variable `prefix' instead of
	using pupa_dl_set_prefix to set the prefix.
	* kern/main.c: Include <pupa/env.h>.
	(pupa_set_root_dev): Use the environment variable `prefix' instead of
	using pupa_dl_get_prefix to get the prefix.
	* kern/misc.c: Include <pupa/env.h>.
	(pupa_isdigit): New function.
	(pupa_isgraph): Likewise.
	(pupa_ftoa): Likewise.
	(pupa_vsprintf): Added support for printing values of the type
	`double'.  Make it possible to format variable output when using
	formatting like `%1.2%f'.
	(pupa_split_cmdline): New function.
	* kern/rescue.c: Include <pupa/env.h>.
	(next_word): Removed function.
	(pupa_rescue_cmd_prefix): Likewise.
	(pupa_rescue_cmd_set): New function.
	(pupa_rescue_cmd_unset): New function.
	(pupa_enter_rescue_mode): Use the `pupa_split_cmdline' function to
	split the command line instead of splitting it here.  Added
	support for setting variables using the syntax `foo=bar'.  Don't
	initialize the prefix command anymore.  Initialized the set and
	unset commands.
	* normal/cmdline.c: Include <pupa/env.h>.
	(pupa_tab_complete): Added prototypes for print_simple_completion,
	print_partition_completion, add_completion, iterate_commands,
	iterate_dev, iterate_part and iterate_dir. Moved code to print
	partition information from here to kern/disk.c.
	(pupa_cmdline_run): Don't check if the funtion exists anymore.
	* normal/main.c: Include <pupa/env.h>.
	(pupa_rescue_cmd_normal): Use the environment variable `prefix'
	instead of using pupa_dl_get_prefix to get the prefix.
	* term/i386/pc/vga.c: Include <pupa/arg.h>.
	(check_vga_mem): Cast pointers to `void *' to silence a gcc
	warning.
	(pupa_vga_putchar) [! DEBUG_VGA]: Removed for this case.
	(pupa_vga_setcolor): Declare unused variables with `__attribute__
	((unused))' to silence a gcc warning.
	(pupa_vga_setcolor): Likewise.
	(debug_command): Changed to match argument parsing
	interface changes.
	* util/pupa-emu.c: Include <pupa/env.h>.
	(options): Added 0's for unused fields to silence a gcc warning.
	(argp): Likewise.
	(main): Use the environment variable `prefix' instead of using
	pupa_dl_set_prefix to set the prefix.  Initialize the commands ls,
	boot, cmp, cat and terminal.  Finish the commands boot, cmp, cat
	and terminal.

	* util/i386/pc/getroot.c: Include <pupa/i386/pc/util/biosdisk.h>.
	* util/misc.c: Include <malloc.h>.
	(pupa_malloc): Rewritten so errors are correctly reported.
	(pupa_realloc): Likewise.
	(pupa_memalign): Likewise.
	(pupa_mm_init_region): Declare unused variables with
	`__attribute__ ((unused))' to silence a gcc warning.
	* normal/i386/setjmp.S: Remove tab at the end of the file to
	silence a gcc warning.
	* loader/i386/pc/linux.c (pupa_rescue_cmd_initrd): Declare unused
	variables with `__attribute__ ((unused))' to silence a gcc
	warning.
	* loader/i386/pc/multiboot.c (pupa_multiboot_unload): Make the
	local variable i unsigned to silence a gcc warning.

	* kern/term.c: Include <pupa/misc.h>.
	(pupa_more_lines): New variable.
	(pupa_more): Likewise.
	(pupa_putcode): When the pager is active pause at the end of every
	screen.
	(pupa_set_more): New function.
	* include/pupa/term.h (pupa_set_more): New prototype.
2004-03-13 13:59:25 +00:00

744 lines
16 KiB
C

/*
* PUPA -- Preliminary Universal Programming Architecture for GRUB
* Copyright (C) 1999,2000,2001,2002,2003 Free Software Foundation, Inc.
*
* 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, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <pupa/normal.h>
#include <pupa/misc.h>
#include <pupa/term.h>
#include <pupa/err.h>
#include <pupa/types.h>
#include <pupa/mm.h>
#include <pupa/machine/partition.h>
#include <pupa/disk.h>
#include <pupa/file.h>
#include <pupa/env.h>
static char *kill_buf;
static int hist_size;
static char **hist_lines = 0;
static int hist_pos = 0;
static int hist_end = 0;
static int hist_used = 0;
pupa_err_t
pupa_set_history (int newsize)
{
char **old_hist_lines = hist_lines;
hist_lines = pupa_malloc (sizeof (char *) * newsize);
/* Copy the old lines into the new buffer. */
if (old_hist_lines)
{
/* Remove the lines that don't fit in the new buffer. */
if (newsize < hist_used)
{
int i;
int delsize = hist_used - newsize;
hist_used = newsize;
for (i = 0; i < delsize; i++)
{
int pos = hist_end - i;
if (pos > hist_size)
pos -= hist_size;
pupa_free (old_hist_lines[pos]);
}
hist_end -= delsize;
if (hist_end < 0)
hist_end = hist_size - hist_end;
}
if (hist_pos < hist_end)
pupa_memmove (hist_lines, old_hist_lines + hist_pos,
(hist_end - hist_pos) * sizeof (char *));
else
{
/* Copy the first part. */
pupa_memmove (hist_lines, old_hist_lines,
hist_pos * sizeof (char *));
/* Copy the last part. */
pupa_memmove (hist_lines + hist_pos, old_hist_lines + hist_pos,
(hist_size - hist_pos) * sizeof (char *));
}
}
pupa_free (old_hist_lines);
hist_size = newsize;
hist_pos = 0;
hist_end = hist_used;
return 0;
}
/* Get the entry POS from the history where `0' is the newest
entry. */
static char *
pupa_history_get (int pos)
{
pos = (hist_pos + pos) % hist_size;
return hist_lines[pos];
}
/* Insert a new history line S on the top of the history. */
static void
pupa_history_add (char *s)
{
/* Remove the oldest entry in the history to make room for a new
entry. */
if (hist_used + 1 > hist_size)
{
hist_end--;
if (hist_end < 0)
hist_end = hist_size + hist_end;
pupa_free (hist_lines[hist_end]);
}
else
hist_used++;
/* Move to the next position. */
hist_pos--;
if (hist_pos < 0)
hist_pos = hist_size + hist_pos;
/* Insert into history. */
hist_lines[hist_pos] = pupa_strdup (s);
}
/* Replace the history entry on position POS with the string S. */
static void
pupa_history_replace (int pos, char *s)
{
pos = (hist_pos + pos) % hist_size;
pupa_free (hist_lines[pos]);
hist_lines[pos] = pupa_strdup (s);
}
/* Try to complete the string in BUF, return the characters that
should be added to the string. This command outputs the possible
completions, in that case set RESTORE to 1 so the caller can
restore the prompt. */
static char *
pupa_tab_complete (char *buf, int *restore)
{
char *pos = buf;
char *path;
char *found = 0;
int begin;
int end;
int len;
int numfound = 0;
/* The disk that is used for pupa_partition_iterate. */
pupa_device_t partdev;
/* String that is added when matched. */
char *matchstr;
auto void print_simple_completion (char *comp);
auto void print_partition_completion (char *comp);
auto int NESTED_FUNC_ATTR add_completion (const char *comp, const char *match,
const char *what,
void (*print_completion) (char *));
auto int iterate_commands (pupa_command_t cmd);
auto int iterate_dev (const char *devname);
auto int iterate_part (const pupa_partition_t p);
auto int iterate_dir (const char *filename, int dir);
void print_simple_completion (char *comp)
{
pupa_printf (" %s", comp);
}
void print_partition_completion (char *comp)
{
pupa_print_partinfo (partdev, comp);
pupa_errno = 0;
}
/* Add a string to the list of possible completions. COMP is the
string that should be added. If this string completely matches
add the string MATCH to the input after adding COMP. The string
WHAT contains a discription of the kind of data that is added.
Use PRINT_COMPLETION to show the completions if there are
multiple matches. XXX: Because of a bug in gcc it is required to
use __regparm__ in some cases. */
int NESTED_FUNC_ATTR add_completion (const char *comp, const char *match,
const char *what,
void (*print_completion) (char *))
{
/* Bug in strncmp then len ==0. */
if (!len || pupa_strncmp (pos, comp, len) == 0)
{
numfound++;
if (numfound == 1)
{
begin = len;
found = pupa_strdup (comp);
end = pupa_strlen (found);
matchstr = (char *) match;
}
/* Multiple matches found, print the first instead of completing. */
else if (numfound == 2)
{
pupa_printf ("\nPossible %s are: ", what);
print_completion (found);
}
if (numfound > 1)
{
char *s1 = found;
const char *s2 = comp;
int cnt = 0;
print_completion ((char *) comp);
/* Find out how many characters match. */
while ((cnt < end) && *s1 && *s2 && (*s1 == *s2))
{
s1++;
s2++;
cnt++;
}
end = cnt;
}
}
return 0;
}
int iterate_part (const pupa_partition_t p)
{
add_completion (pupa_partition_get_name (p), ")", "partitions",
print_partition_completion);
return 0;
}
int iterate_dir (const char *filename, int dir)
{
if (!dir)
add_completion (filename, " ", "files", print_simple_completion);
else
{
char fname[pupa_strlen (filename) + 2];
pupa_strcpy (fname, filename);
pupa_sprintf (fname, "%s/", filename);
add_completion (fname, "", "files", print_simple_completion);
}
return 0;
}
int iterate_dev (const char *devname)
{
pupa_device_t dev;
/* Complete the partition part. */
dev = pupa_device_open (devname);
if (dev)
{
if (dev->disk && dev->disk->has_partitions)
add_completion (devname, ",", "disks", print_simple_completion);
else
add_completion (devname, ")", "disks", print_simple_completion);
}
return 0;
}
int iterate_commands (pupa_command_t cmd)
{
if (cmd->flags & PUPA_COMMAND_FLAG_CMDLINE)
add_completion (cmd->name, " ", "commands", print_simple_completion);
return 0;
}
/* Remove blank space on the beginning of the line. */
while (*pos == ' ')
pos++;
/* Check if the string is a command or path. */
path = pupa_strchr (pos, ' ');
if (!path)
{
/* Tab complete a command. */
len = pupa_strlen (pos);
pupa_iterate_commands (iterate_commands);
}
else
{
pos = path;
/* Remove blank space on the beginning of the line. */
while (*pos == ' ')
pos++;
/* Check if this is a completion for a device name. */
if (*pos == '(' && !pupa_strchr (pos, ')'))
{
/* Check if this is a device or partition. */
char *partition = pupa_strchr (++pos, ',');
if (!partition)
{
/* Complete the disk part. */
len = pupa_strlen (pos);
pupa_disk_dev_iterate (iterate_dev);
if (pupa_errno)
goto fail;
}
else
{
*partition = '\0';
/* Complete the partition part. */
partdev = pupa_device_open (pos);
*partition = ',';
pupa_errno = PUPA_ERR_NONE;
if (partdev)
{
if (partdev->disk && partdev->disk->has_partitions)
{
pos = partition + 1;
len = pupa_strlen (pos);
pupa_partition_iterate (partdev->disk, iterate_part);
if (pupa_errno)
pupa_errno = 0;
}
pupa_device_close (partdev);
}
else
goto fail;
}
}
else
{
char *device = pupa_file_get_device_name (pos);
pupa_device_t dev;
pupa_fs_t fs;
dev = pupa_device_open (device);
if (!dev)
goto fail;
fs = pupa_fs_probe (dev);
if (pupa_errno)
goto fail;
pos = pupa_strrchr (pos, '/');
if (pos)
{
char *dir;
char *dirfile;
pos++;
len = pupa_strlen (pos);
dir = pupa_strchr (path, '/');
if (!dir)
{
*restore = 0;
return 0;
}
dir = pupa_strdup (dir);
/* Cut away the filename part. */
dirfile = pupa_strrchr (dir, '/');
dirfile[1] = '\0';
/* Tab complete a file. */
(fs->dir) (dev, dir, iterate_dir);
if (dev)
pupa_device_close (dev);
pupa_free (device);
pupa_free (dir);
if (pupa_errno)
goto fail;
}
else
{
found = pupa_strdup ("/");
matchstr = "";
numfound = 1;
begin = 0;
end = 1;
}
}
}
/* If more than one match is found those matches will be printed and
the prompt should be restored. */
if (numfound > 1)
*restore = 1;
else
*restore = 0;
/* Return the part that matches. */
if (end && found)
{
char *insert;
insert = pupa_malloc (end - begin + 1 + sizeof (matchstr));
pupa_strncpy (insert, found + begin, end - begin);
insert[end - begin] = '\0';
if (numfound == 1)
pupa_strcat (insert, matchstr);
pupa_free (found);
return insert;
}
fail:
pupa_free (found);
pupa_errno = PUPA_ERR_NONE;
return 0;
}
void
pupa_cmdline_run (int nested)
{
pupa_normal_init_page ();
pupa_printf ("\
[ Minimal BASH-like line editing is supported. For the first word, TAB\n\
lists possible command completions. Anywhere else TAB lists possible\n\
device/file completions.%s ]\n\n",
nested ? " ESC at any time exits." : "");
while (1)
{
static char cmdline[PUPA_MAX_CMDLINE];
pupa_print_error ();
pupa_errno = PUPA_ERR_NONE;
cmdline[0] = '\0';
if (! pupa_cmdline_get ("pupa> ", cmdline, sizeof (cmdline), 0, 1)
&& nested)
return;
if (! *cmdline)
continue;
pupa_command_execute (cmdline);
}
}
/* Get a command-line. If ECHO_CHAR is not zero, echo it instead of input
characters. If READLINE is non-zero, readline-like key bindings are
available. If ESC is pushed, return non-zero, otherwise return zero. */
/* FIXME: The dumb interface is not supported yet. */
int
pupa_cmdline_get (const char *prompt, char cmdline[], unsigned max_len,
int echo_char, int readline)
{
unsigned xpos, ypos, ystart;
pupa_size_t lpos, llen;
pupa_size_t plen;
char buf[max_len];
int key;
int histpos = 0;
auto void cl_insert (const char *str);
auto void cl_delete (unsigned len);
auto void cl_print (int pos, int c);
auto void cl_set_pos (void);
void cl_set_pos (void)
{
xpos = (plen + lpos) % 79;
ypos = ystart + (plen + lpos) / 79;
pupa_gotoxy (xpos, ypos);
}
void cl_print (int pos, int c)
{
char *p;
for (p = buf + pos; *p; p++)
{
if (xpos++ > 78)
{
pupa_putchar ('\n');
xpos = 1;
if (ypos == (unsigned) (pupa_getxy () & 0xFF))
ystart--;
else
ypos++;
}
if (c)
pupa_putchar (c);
else
pupa_putchar (*p);
}
}
void cl_insert (const char *str)
{
pupa_size_t len = pupa_strlen (str);
if (len + llen < max_len)
{
pupa_memmove (buf + lpos + len, buf + lpos, llen - lpos + 1);
pupa_memmove (buf + lpos, str, len);
llen += len;
lpos += len;
cl_print (lpos - len, echo_char);
cl_set_pos ();
}
}
void cl_delete (unsigned len)
{
if (lpos + len <= llen)
{
pupa_size_t saved_lpos = lpos;
lpos = llen - len;
cl_set_pos ();
cl_print (lpos, ' ');
lpos = saved_lpos;
cl_set_pos ();
pupa_memmove (buf + lpos, buf + lpos + len, llen - lpos + 1);
llen -= len;
cl_print (lpos, echo_char);
cl_set_pos ();
}
}
plen = pupa_strlen (prompt);
lpos = llen = 0;
buf[0] = '\0';
if ((pupa_getxy () >> 8) != 0)
pupa_putchar ('\n');
pupa_printf (prompt);
xpos = plen;
ystart = ypos = (pupa_getxy () & 0xFF);
cl_insert (cmdline);
pupa_history_add (buf);
while ((key = PUPA_TERM_ASCII_CHAR (pupa_getkey ())) != '\n' && key != '\r')
{
if (readline)
{
switch (key)
{
case 1: /* Ctrl-a */
lpos = 0;
cl_set_pos ();
break;
case 2: /* Ctrl-b */
if (lpos > 0)
{
lpos--;
cl_set_pos ();
}
break;
case 5: /* Ctrl-e */
lpos = llen;
cl_set_pos ();
break;
case 6: /* Ctrl-f */
if (lpos < llen)
{
lpos++;
cl_set_pos ();
}
break;
case 9: /* Ctrl-i or TAB */
{
char *insert;
int restore;
/* Backup the next character and make it 0 so it will
be easy to use string functions. */
char backup = buf[lpos];
buf[lpos] = '\0';
insert = pupa_tab_complete (buf, &restore);
/* Restore the original string. */
buf[lpos] = backup;
if (restore)
{
/* Restore the prompt. */
pupa_printf ("\n%s%s", prompt, buf);
xpos = plen;
ystart = ypos = (pupa_getxy () & 0xFF);
}
if (insert)
{
cl_insert (insert);
pupa_free (insert);
}
}
break;
case 11: /* Ctrl-k */
if (lpos < llen)
{
if (kill_buf)
pupa_free (kill_buf);
kill_buf = pupa_strdup (buf + lpos);
pupa_errno = PUPA_ERR_NONE;
cl_delete (llen - lpos);
}
break;
case 14: /* Ctrl-n */
{
char *hist;
lpos = 0;
if (histpos > 0)
histpos--;
cl_delete (llen);
hist = pupa_history_get (histpos);
cl_insert (hist);
break;
}
case 16: /* Ctrl-p */
{
char *hist;
lpos = 0;
if (histpos < hist_used - 1)
histpos++;
cl_delete (llen);
hist = pupa_history_get (histpos);
cl_insert (hist);
}
break;
case 21: /* Ctrl-u */
if (lpos > 0)
{
pupa_size_t n = lpos;
if (kill_buf)
pupa_free (kill_buf);
kill_buf = pupa_malloc (n + 1);
pupa_errno = PUPA_ERR_NONE;
if (kill_buf)
{
pupa_memcpy (kill_buf, buf, n);
kill_buf[n] = '\0';
}
lpos = 0;
cl_set_pos ();
cl_delete (n);
}
break;
case 25: /* Ctrl-y */
if (kill_buf)
cl_insert (kill_buf);
break;
}
}
switch (key)
{
case '\e':
return 0;
case '\b':
if (lpos > 0)
{
lpos--;
cl_set_pos ();
}
/* fall through */
case 4: /* Ctrl-d */
if (lpos < llen)
cl_delete (1);
break;
default:
if (pupa_isprint (key))
{
char str[2];
str[0] = key;
str[1] = '\0';
cl_insert (str);
}
break;
}
pupa_history_replace (histpos, buf);
}
pupa_putchar ('\n');
pupa_refresh ();
/* If ECHO_CHAR is NUL, remove leading spaces. */
lpos = 0;
if (! echo_char)
while (buf[lpos] == ' ')
lpos++;
pupa_memcpy (cmdline, buf + lpos, llen - lpos + 1);
return 1;
}