2005-09-03 Yoshinori K. Okuji <okuji@enbug.org>

* normal/completion.c (complete_arguments): Add the qualifier
        const into OPTIONS.

        From Omniflux <omniflux+lists@omniflux.com>:
        * include/grub/terminfo.h: New file.
        * include/grub/tparm.h: Likewise.
        * include/grub/i386/pc/serial.h: Likewise.
        * term/terminfo.c: Likewise.
        * term/tparm.c: Likewise.
        * term/i386/pc/serial.c: Likewise.
        * conf/i386-pc.rmk (pkgdata_MODULES): Added terminfo.mod and
        serial.mod.
        (terminfo_mod_SOURCES): New variable.
        (terminfo_mod_CFLAGS): Likewise.
        (serial_mod_SOURCES): Likewise.
        (serial_mod_CFLAGS): Likewise.
This commit is contained in:
okuji 2005-09-03 16:54:27 +00:00
parent 48b671ff70
commit 47d2d65e33
12 changed files with 1892 additions and 4 deletions

637
term/i386/pc/serial.c Normal file
View file

@ -0,0 +1,637 @@
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2000,2001,2002,2003,2004,2005 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 <grub/machine/serial.h>
#include <grub/machine/console.h>
#include <grub/term.h>
#include <grub/types.h>
#include <grub/dl.h>
#include <grub/misc.h>
#include <grub/normal.h>
#include <grub/arg.h>
#include <grub/terminfo.h>
#define TEXT_WIDTH 80
#define TEXT_HEIGHT 25
static unsigned int xpos, ypos;
static unsigned int keep_track = 1;
static unsigned int registered = 0;
/* An input buffer. */
static char input_buf[8];
static unsigned int npending = 0;
/* Argument options. */
static const struct grub_arg_option options[] =
{
{"unit", 'u', 0, "Set the serial unit", 0, ARG_TYPE_INT},
{"port", 'p', 0, "Set the serial port address", 0, ARG_TYPE_STRING},
{"speed", 's', 0, "Set the serial port speed", 0, ARG_TYPE_INT},
{"word", 'w', 0, "Set the serial port word length", 0, ARG_TYPE_INT},
{"parity", 'r', 0, "Set the serial port parity", 0, ARG_TYPE_STRING},
{"stop", 't', 0, "Set the serial port stop bits", 0, ARG_TYPE_INT},
{0, 0, 0, 0, 0, 0}
};
/* Serial port settings. */
struct serial_port
{
unsigned short port;
unsigned short divisor;
unsigned short word_len;
unsigned int parity;
unsigned short stop_bits;
};
/* Serial port settings. */
static struct serial_port serial_settings;
/* Read a byte from a port. */
static inline unsigned char
inb (const unsigned short port)
{
unsigned char value;
asm volatile ("inb %w1, %0" : "=a" (value) : "Nd" (port));
asm volatile ("outb %%al, $0x80" : : );
return value;
}
/* Write a byte to a port. */
static inline void
outb (const unsigned short port, const unsigned char value)
{
asm volatile ("outb %b0, %w1" : : "a" (value), "Nd" (port));
asm volatile ("outb %%al, $0x80" : : );
}
/* Return the port number for the UNITth serial device. */
static inline unsigned short
serial_hw_get_port (const unsigned short unit)
{
/* The BIOS data area. */
const unsigned short *addr = (const unsigned short *) 0x0400;
return addr[unit];
}
/* Fetch a key. */
static int
serial_hw_fetch (void)
{
if (inb (serial_settings.port + UART_LSR) & UART_DATA_READY)
return inb (serial_settings.port + UART_RX);
return -1;
}
/* Put a chararacter. */
static void
serial_hw_put (const int c)
{
unsigned int timeout = 100000;
/* Wait until the transmitter holding register is empty. */
while ((inb (serial_settings.port + UART_LSR) & UART_EMPTY_TRANSMITTER) == 0)
{
if (--timeout == 0)
/* There is something wrong. But what can I do? */
return;
}
outb (serial_settings.port + UART_TX, c);
}
static void
serial_translate_key_sequence (void)
{
static struct
{
char key;
char ascii;
}
three_code_table[] =
{
{'A', 16},
{'B', 14},
{'C', 6},
{'D', 2},
{'F', 5},
{'H', 1},
{'4', 4}
};
static struct
{
short key;
char ascii;
}
four_code_table[] =
{
{('1' | ('~' << 8)), 1},
{('3' | ('~' << 8)), 4},
{('5' | ('~' << 8)), 7},
{('6' | ('~' << 8)), 3}
};
/* The buffer must start with "ESC [". */
if (*((unsigned short *) input_buf) != ('\e' | ('[' << 8)))
return;
if (npending >= 3)
{
unsigned int i;
for (i = 0;
i < sizeof (three_code_table) / sizeof (three_code_table[0]);
i++)
if (three_code_table[i].key == input_buf[2])
{
input_buf[0] = three_code_table[i].ascii;
npending -= 2;
grub_memmove (input_buf + 1, input_buf + 3, npending - 1);
return;
}
}
if (npending >= 4)
{
unsigned int i;
short key = *((short *) (input_buf + 2));
for (i = 0;
i < sizeof (four_code_table) / sizeof (four_code_table[0]);
i++)
if (four_code_table[i].key == key)
{
input_buf[0] = four_code_table[i].ascii;
npending -= 3;
grub_memmove (input_buf + 1, input_buf + 4, npending - 1);
return;
}
}
}
static int
fill_input_buf (const int nowait)
{
int i;
for (i = 0; i < 10000 && npending < sizeof (input_buf); i++)
{
int c;
c = serial_hw_fetch ();
if (c >= 0)
{
input_buf[npending++] = c;
/* Reset the counter to zero, to wait for the same interval. */
i = 0;
}
if (nowait)
break;
}
/* Translate some key sequences. */
serial_translate_key_sequence ();
return npending;
}
/* Convert speed to divisor. */
static unsigned short
serial_get_divisor (unsigned int speed)
{
unsigned int i;
/* The structure for speed vs. divisor. */
struct divisor
{
unsigned int speed;
unsigned short div;
};
/* The table which lists common configurations. */
/* 1843200 / (speed * 16) */
static struct divisor divisor_tab[] =
{
{ 2400, 0x0030 },
{ 4800, 0x0018 },
{ 9600, 0x000C },
{ 19200, 0x0006 },
{ 38400, 0x0003 },
{ 57600, 0x0002 },
{ 115200, 0x0001 }
};
/* Set the baud rate. */
for (i = 0; i < sizeof (divisor_tab) / sizeof (divisor_tab[0]); i++)
if (divisor_tab[i].speed == speed)
return divisor_tab[i].div;
return 0;
}
/* The serial version of checkkey. */
static int
grub_serial_checkkey (void)
{
if (fill_input_buf (1))
return input_buf[0];
else
return -1;
}
/* The serial version of getkey. */
static int
grub_serial_getkey (void)
{
int c;
while (! fill_input_buf (0))
;
c = input_buf[0];
grub_memmove (input_buf, input_buf + 1, --npending);
return c;
}
/* Initialize a serial device. PORT is the port number for a serial device.
SPEED is a DTE-DTE speed which must be one of these: 2400, 4800, 9600,
19200, 38400, 57600 and 115200. WORD_LEN is the word length to be used
for the device. Likewise, PARITY is the type of the parity and
STOP_BIT_LEN is the length of the stop bit. The possible values for
WORD_LEN, PARITY and STOP_BIT_LEN are defined in the header file as
macros. */
static grub_err_t
serial_hw_init (void)
{
unsigned char status = 0;
/* Turn off the interupt. */
outb (serial_settings.port + UART_IER, 0);
/* Set DLAB. */
outb (serial_settings.port + UART_LCR, UART_DLAB);
/* Set the baud rate. */
outb (serial_settings.port + UART_DLL, serial_settings.divisor & 0xFF);
outb (serial_settings.port + UART_DLH, serial_settings.divisor >> 8 );
/* Set the line status. */
status |= (serial_settings.parity
| serial_settings.word_len
| serial_settings.stop_bits);
outb (serial_settings.port + UART_LCR, status);
/* Enable the FIFO. */
outb (serial_settings.port + UART_FCR, UART_ENABLE_FIFO);
/* Turn on DTR, RTS, and OUT2. */
outb (serial_settings.port + UART_MCR, UART_ENABLE_MODEM);
/* Drain the input buffer. */
while (grub_serial_checkkey () != -1)
(void) grub_serial_getkey ();
/* FIXME: should check if the serial terminal was found. */
return GRUB_ERR_NONE;
}
/* The serial version of putchar. */
static void
grub_serial_putchar (grub_uint32_t c)
{
/* Keep track of the cursor. */
if (keep_track)
{
/* The serial terminal does not have VGA fonts. */
if (c > 0x7F)
{
/* Better than nothing. */
switch (c)
{
case GRUB_TERM_DISP_LEFT:
c = '<';
break;
case GRUB_TERM_DISP_UP:
c = '^';
break;
case GRUB_TERM_DISP_RIGHT:
c = '>';
break;
case GRUB_TERM_DISP_DOWN:
c = 'v';
break;
case GRUB_TERM_DISP_HLINE:
c = '-';
break;
case GRUB_TERM_DISP_VLINE:
c = '|';
break;
case GRUB_TERM_DISP_UL:
case GRUB_TERM_DISP_UR:
case GRUB_TERM_DISP_LL:
case GRUB_TERM_DISP_LR:
c = '+';
break;
default:
c = '?';
break;
}
}
switch (c)
{
case '\a':
break;
case '\b':
case 127:
if (xpos > 0)
xpos--;
break;
case '\n':
if (ypos < TEXT_HEIGHT)
ypos++;
break;
case '\r':
xpos = 0;
break;
default:
if (xpos >= TEXT_WIDTH)
{
grub_putchar ('\r');
grub_putchar ('\n');
}
xpos++;
break;
}
}
serial_hw_put (c);
}
static grub_ssize_t
grub_serial_getcharwidth (grub_uint32_t c __attribute__ ((unused)))
{
return 1;
}
static grub_uint16_t
grub_serial_getwh (void)
{
return (TEXT_WIDTH << 8) | TEXT_HEIGHT;
}
static grub_uint16_t
grub_serial_getxy (void)
{
return ((xpos << 8) | ypos);
}
static void
grub_serial_gotoxy (grub_uint8_t x, grub_uint8_t y)
{
if (x > TEXT_WIDTH || y > TEXT_HEIGHT)
{
grub_error (GRUB_ERR_OUT_OF_RANGE, "invalid point (%u,%u)", x, y);
}
else
{
keep_track = 0;
grub_terminfo_gotoxy (x, y);
keep_track = 1;
xpos = x;
ypos = y;
}
}
static void
grub_serial_cls (void)
{
keep_track = 0;
grub_terminfo_cls ();
keep_track = 1;
xpos = ypos = 0;
}
static void
grub_serial_setcolorstate (const grub_term_color_state state)
{
keep_track = 0;
switch (state)
{
case GRUB_TERM_COLOR_STANDARD:
case GRUB_TERM_COLOR_NORMAL:
grub_terminfo_reverse_video_off ();
break;
case GRUB_TERM_COLOR_HIGHLIGHT:
grub_terminfo_reverse_video_on ();
break;
default:
break;
}
keep_track = 1;
}
static void
grub_serial_setcolor (grub_uint8_t normal_color __attribute__ ((unused)),
grub_uint8_t highlight_color __attribute__ ((unused)))
{
/* FIXME */
}
static void
grub_serial_setcursor (const int on)
{
if (on)
grub_terminfo_cursor_on ();
else
grub_terminfo_cursor_off ();
}
static struct grub_term grub_serial_term =
{
.name = "serial",
.init = 0,
.fini = 0,
.putchar = grub_serial_putchar,
.getcharwidth = grub_serial_getcharwidth,
.checkkey = grub_serial_checkkey,
.getkey = grub_serial_getkey,
.getwh = grub_serial_getwh,
.getxy = grub_serial_getxy,
.gotoxy = grub_serial_gotoxy,
.cls = grub_serial_cls,
.setcolorstate = grub_serial_setcolorstate,
.setcolor = grub_serial_setcolor,
.setcursor = grub_serial_setcursor,
.flags = 0,
.next = 0
};
static grub_err_t
grub_cmd_serial (struct grub_arg_list *state,
int argc __attribute__ ((unused)),
char **args __attribute__ ((unused)))
{
struct serial_port backup_settings = serial_settings;
grub_err_t hwiniterr;
int arg;
if (state[0].set)
{
arg = grub_strtoul (state[0].arg, 0, 0);
if (arg >= 0 && arg < 4)
serial_settings.port = serial_hw_get_port ((int) arg);
else
return grub_error (GRUB_ERR_BAD_ARGUMENT, "bad unit number.");
}
if (state[1].set)
serial_settings.port = (unsigned short) grub_strtoul (state[1].arg, 0, 0);
if (state[2].set)
{
unsigned long speed;
speed = grub_strtoul (state[2].arg, 0, 0);
serial_settings.divisor = serial_get_divisor ((unsigned int) speed);
if (serial_settings.divisor == 0)
{
serial_settings = backup_settings;
return grub_error (GRUB_ERR_BAD_ARGUMENT, "bad speed");
}
}
if (state[3].set)
{
if (! grub_strcmp (state[3].arg, "5"))
serial_settings.word_len = UART_5BITS_WORD;
else if (! grub_strcmp (state[3].arg, "6"))
serial_settings.word_len = UART_6BITS_WORD;
else if (! grub_strcmp (state[3].arg, "7"))
serial_settings.word_len = UART_7BITS_WORD;
else if (! grub_strcmp (state[3].arg, "8"))
serial_settings.word_len = UART_8BITS_WORD;
else
{
serial_settings = backup_settings;
return grub_error (GRUB_ERR_BAD_ARGUMENT, "bad word length");
}
}
if (state[4].set)
{
if (! grub_strcmp (state[4].arg, "no"))
serial_settings.parity = UART_NO_PARITY;
else if (! grub_strcmp (state[4].arg, "odd"))
serial_settings.parity = UART_ODD_PARITY;
else if (! grub_strcmp (state[4].arg, "even"))
serial_settings.parity = UART_EVEN_PARITY;
else
{
serial_settings = backup_settings;
return grub_error (GRUB_ERR_BAD_ARGUMENT, "bad parity");
}
}
if (state[5].set)
{
if (! grub_strcmp (state[5].arg, "1"))
serial_settings.stop_bits = UART_1_STOP_BIT;
else if (! grub_strcmp (state[5].arg, "2"))
serial_settings.stop_bits = UART_2_STOP_BITS;
else
{
serial_settings = backup_settings;
return grub_error (GRUB_ERR_BAD_ARGUMENT, "bad number of stop bits");
}
}
/* Initialize with new settings. */
hwiniterr = serial_hw_init ();
if (hwiniterr == GRUB_ERR_NONE)
{
/* Register terminal if not yet registered. */
if (registered == 0)
{
grub_term_register (&grub_serial_term);
registered = 1;
}
}
else
{
/* Initialization with new settings failed. */
if (registered == 1)
{
/* If the terminal is registered, attempt to restore previous
settings. */
serial_settings = backup_settings;
if (serial_hw_init () != GRUB_ERR_NONE)
{
/* If unable to restore settings, unregister terminal. */
grub_term_unregister (&grub_serial_term);
registered = 0;
}
}
}
return hwiniterr;
}
GRUB_MOD_INIT
{
(void) mod; /* To stop warning. */
grub_register_command ("serial", grub_cmd_serial, GRUB_COMMAND_FLAG_BOTH,
"serial [OPTIONS...]", "Configure serial port.", options);
/* Set default settings. */
serial_settings.port = serial_hw_get_port (0);
serial_settings.divisor = serial_get_divisor (9600);
serial_settings.word_len = UART_8BITS_WORD;
serial_settings.parity = UART_NO_PARITY;
serial_settings.stop_bits = UART_1_STOP_BIT;
}
GRUB_MOD_FINI
{
grub_unregister_command ("serial");
if (registered == 1) /* Unregister terminal only if registered. */
grub_term_unregister (&grub_serial_term);
}

188
term/terminfo.c Normal file
View file

@ -0,0 +1,188 @@
/* terminfo.c - simple terminfo module */
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2003,2004,2005 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.
*/
/*
* This file contains various functions dealing with different
* terminal capabilities. For example, vt52 and vt100.
*/
#include <grub/types.h>
#include <grub/misc.h>
#include <grub/mm.h>
#include <grub/err.h>
#include <grub/dl.h>
#include <grub/normal.h>
#include <grub/term.h>
#include <grub/terminfo.h>
#include <grub/tparm.h>
struct terminfo
{
char *name;
char *gotoxy;
char *cls;
char *reverse_video_on;
char *reverse_video_off;
char *cursor_on;
char *cursor_off;
};
static struct terminfo term;
/* Get current terminfo name. */
char *
grub_terminfo_get_current (void)
{
return term.name;
}
/* Free *PTR and set *PTR to NULL, to prevent double-free. */
static void
grub_terminfo_free (char **ptr)
{
grub_free (*ptr);
*ptr = 0;
}
/* Set current terminfo type. */
grub_err_t
grub_terminfo_set_current (const char *str)
{
/* TODO
* Lookup user specified terminfo type. If found, set term variables
* as appropriate. Otherwise return an error.
*
* How should this be done?
* a. A static table included in this module.
* - I do not like this idea.
* b. A table stored in the configuration directory.
* - Users must convert their terminfo settings if we have not already.
* c. Look for terminfo files in the configuration directory.
* - /usr/share/terminfo is 6.3M on my system.
* - /usr/share/terminfo is not on most users boot partition.
* + Copying the terminfo files you want to use to the grub
* configuration directory is easier then (b).
* d. Your idea here.
*/
/* Free previously allocated memory. */
grub_terminfo_free (&term.name);
grub_terminfo_free (&term.gotoxy);
grub_terminfo_free (&term.cls);
grub_terminfo_free (&term.reverse_video_on);
grub_terminfo_free (&term.reverse_video_off);
grub_terminfo_free (&term.cursor_on);
grub_terminfo_free (&term.cursor_off);
if (grub_strcmp ("vt100", str) == 0)
{
term.name = grub_strdup ("vt100");
term.gotoxy = grub_strdup ("\e[%i%p1%d;%p2%dH");
term.cls = grub_strdup ("\e[H\e[J");
term.reverse_video_on = grub_strdup ("\e[7m");
term.reverse_video_off = grub_strdup ("\e[m");
term.cursor_on = grub_strdup ("\e[?25l");
term.cursor_off = grub_strdup ("\e[?25h");
return grub_errno;
}
return grub_error (GRUB_ERR_BAD_ARGUMENT, "unknown terminfo type.");
}
/* Wrapper for grub_putchar to write strings. */
static void
putstr (const char *str)
{
while (*str)
grub_putchar (*str++);
}
/* Move the cursor to the given position starting with "0". */
void
grub_terminfo_gotoxy (grub_uint8_t x, grub_uint8_t y)
{
putstr (grub_terminfo_tparm (term.gotoxy, y, x));
}
/* Clear the screen. */
void
grub_terminfo_cls (void)
{
putstr (grub_terminfo_tparm (term.cls));
}
/* Set reverse video mode on. */
void
grub_terminfo_reverse_video_on (void)
{
putstr (grub_terminfo_tparm (term.reverse_video_on));
}
/* Set reverse video mode off. */
void
grub_terminfo_reverse_video_off (void)
{
putstr (grub_terminfo_tparm (term.reverse_video_off));
}
/* Show cursor. */
void
grub_terminfo_cursor_on (void)
{
putstr (grub_terminfo_tparm (term.cursor_on));
}
/* Hide cursor. */
void
grub_terminfo_cursor_off (void)
{
putstr (grub_terminfo_tparm (term.cursor_off));
}
/* GRUB Command. */
static grub_err_t
grub_cmd_terminfo (struct grub_arg_list *state __attribute__ ((unused)),
int argc, char **args)
{
if (argc == 0)
{
grub_printf ("Current terminfo type: %s\n", grub_terminfo_get_current());
return GRUB_ERR_NONE;
}
else if (argc != 1)
return grub_error (GRUB_ERR_BAD_ARGUMENT, "too many parameters.");
else
return grub_terminfo_set_current (args[0]);
}
GRUB_MOD_INIT
{
(void) mod; /* To stop warning. */
grub_register_command ("terminfo", grub_cmd_terminfo, GRUB_COMMAND_FLAG_BOTH,
"terminfo [TERM]", "Set terminfo type.", 0);
grub_terminfo_set_current ("vt100");
}
GRUB_MOD_FINI
{
grub_unregister_command ("terminfo");
}

769
term/tparm.c Normal file
View file

@ -0,0 +1,769 @@
/****************************************************************************
* Copyright (c) 1998-2003,2004,2005 Free Software Foundation, Inc. *
* *
* Permission is hereby granted, free of charge, to any person obtaining a *
* copy of this software and associated documentation files (the *
* "Software"), to deal in the Software without restriction, including *
* without limitation the rights to use, copy, modify, merge, publish, *
* distribute, distribute with modifications, sublicense, and/or sell *
* copies of the Software, and to permit persons to whom the Software is *
* furnished to do so, subject to the following conditions: *
* *
* The above copyright notice and this permission notice shall be included *
* in all copies or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS *
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF *
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. *
* IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, *
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR *
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR *
* THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
* *
* Except as contained in this notice, the name(s) of the above copyright *
* holders shall not be used in advertising or otherwise to promote the *
* sale, use or other dealings in this Software without prior written *
* authorization. *
****************************************************************************/
/**********************************************************************
* This code is a modification of lib_tparm.c found in ncurses-5.2. The
* modification are for use in grub by replacing all libc function through
* special grub functions. This also meant to delete all dynamic memory
* allocation and replace it by a number of fixed buffers.
*
* Modifications by Tilmann Bubeck <t.bubeck@reinform.de> 2002
*
* Resync with ncurses-5.4 by Omniflux <omniflux+devel@omniflux.com> 2005
**********************************************************************/
/****************************************************************************
* Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995 *
* and: Eric S. Raymond <esr@snark.thyrsus.com> *
* and: Thomas E. Dickey, 1996 on *
****************************************************************************/
/*
* tparm.c
*
*/
#include <grub/misc.h>
#include <grub/mm.h>
#include <grub/types.h>
#include <grub/tparm.h>
/*
* Common/troublesome character definitions
*/
typedef char grub_bool_t;
#ifndef FALSE
# define FALSE (0)
#endif
#ifndef TRUE
# define TRUE (!FALSE)
#endif
#define NUM_PARM 9
#define NUM_VARS 26
#define STACKSIZE 20
#define MAX_FORMAT_LEN 256
#define max(a,b) ((a) > (b) ? (a) : (b))
#define isdigit(c) ((c) >= '0' && (c) <= '9')
#define isUPPER(c) ((c) >= 'A' && (c) <= 'Z')
#define isLOWER(c) ((c) >= 'a' && (c) <= 'z')
#define UChar(c) ((unsigned char)(c))
//MODULE_ID("$Id$")
/*
* char *
* tparm(string, ...)
*
* Substitute the given parameters into the given string by the following
* rules (taken from terminfo(5)):
*
* Cursor addressing and other strings requiring parame-
* ters in the terminal are described by a parameterized string
* capability, with like escapes %x in it. For example, to
* address the cursor, the cup capability is given, using two
* parameters: the row and column to address to. (Rows and
* columns are numbered from zero and refer to the physical
* screen visible to the user, not to any unseen memory.) If
* the terminal has memory relative cursor addressing, that can
* be indicated by
*
* The parameter mechanism uses a stack and special %
* codes to manipulate it. Typically a sequence will push one
* of the parameters onto the stack and then print it in some
* format. Often more complex operations are necessary.
*
* The % encodings have the following meanings:
*
* %% outputs `%'
* %c print pop() like %c in printf()
* %s print pop() like %s in printf()
* %[[:]flags][width[.precision]][doxXs]
* as in printf, flags are [-+#] and space
* The ':' is used to avoid making %+ or %-
* patterns (see below).
*
* %p[1-9] push ith parm
* %P[a-z] set dynamic variable [a-z] to pop()
* %g[a-z] get dynamic variable [a-z] and push it
* %P[A-Z] set static variable [A-Z] to pop()
* %g[A-Z] get static variable [A-Z] and push it
* %l push strlen(pop)
* %'c' push char constant c
* %{nn} push integer constant nn
*
* %+ %- %* %/ %m
* arithmetic (%m is mod): push(pop() op pop())
* %& %| %^ bit operations: push(pop() op pop())
* %= %> %< logical operations: push(pop() op pop())
* %A %O logical and & or operations for conditionals
* %! %~ unary operations push(op pop())
* %i add 1 to first two parms (for ANSI terminals)
*
* %? expr %t thenpart %e elsepart %;
* if-then-else, %e elsepart is optional.
* else-if's are possible ala Algol 68:
* %? c1 %t b1 %e c2 %t b2 %e c3 %t b3 %e c4 %t b4 %e b5 %;
*
* For those of the above operators which are binary and not commutative,
* the stack works in the usual way, with
* %gx %gy %m
* resulting in x mod y, not the reverse.
*/
typedef struct {
union {
int num;
char *str;
} data;
grub_bool_t num_type;
} stack_frame;
static stack_frame stack[STACKSIZE];
static int stack_ptr;
static const char *tparam_base = "";
static char *out_buff;
static grub_size_t out_size;
static grub_size_t out_used;
static char *fmt_buff;
static grub_size_t fmt_size;
static inline void
get_space(grub_size_t need)
{
need += out_used;
if (need > out_size) {
out_size = need * 2;
out_buff = grub_realloc(out_buff, out_size*sizeof(char));
if (out_buff == 0)
// FIX ME! OOM, what now?
;
}
}
static inline void
save_text(const char *fmt, const char *s, int len)
{
grub_size_t s_len = grub_strlen(s);
if (len > (int) s_len)
s_len = len;
get_space(s_len + 1);
(void) grub_sprintf(out_buff + out_used, fmt, s);
out_used += grub_strlen(out_buff + out_used);
}
static inline void
save_number(const char *fmt, int number, int len)
{
if (len < 30)
len = 30; /* actually log10(MAX_INT)+1 */
get_space((unsigned) len + 1);
(void) grub_sprintf(out_buff + out_used, fmt, number);
out_used += grub_strlen(out_buff + out_used);
}
static inline void
save_char(int c)
{
if (c == 0)
c = 0200;
get_space(1);
out_buff[out_used++] = c;
}
static inline void
npush(int x)
{
if (stack_ptr < STACKSIZE) {
stack[stack_ptr].num_type = TRUE;
stack[stack_ptr].data.num = x;
stack_ptr++;
}
}
static inline int
npop(void)
{
int result = 0;
if (stack_ptr > 0) {
stack_ptr--;
if (stack[stack_ptr].num_type)
result = stack[stack_ptr].data.num;
}
return result;
}
static inline void
spush(char *x)
{
if (stack_ptr < STACKSIZE) {
stack[stack_ptr].num_type = FALSE;
stack[stack_ptr].data.str = x;
stack_ptr++;
}
}
static inline char *
spop(void)
{
static char dummy[] = ""; /* avoid const-cast */
char *result = dummy;
if (stack_ptr > 0) {
stack_ptr--;
if (!stack[stack_ptr].num_type && stack[stack_ptr].data.str != 0)
result = stack[stack_ptr].data.str;
}
return result;
}
static inline const char *
parse_format(const char *s, char *format, int *len)
{
*len = 0;
if (format != 0) {
grub_bool_t done = FALSE;
grub_bool_t allowminus = FALSE;
grub_bool_t dot = FALSE;
grub_bool_t err = FALSE;
char *fmt = format;
int my_width = 0;
int my_prec = 0;
int value = 0;
*len = 0;
*format++ = '%';
while (*s != '\0' && !done) {
switch (*s) {
case 'c': /* FALLTHRU */
case 'd': /* FALLTHRU */
case 'o': /* FALLTHRU */
case 'x': /* FALLTHRU */
case 'X': /* FALLTHRU */
case 's':
*format++ = *s;
done = TRUE;
break;
case '.':
*format++ = *s++;
if (dot) {
err = TRUE;
} else { /* value before '.' is the width */
dot = TRUE;
my_width = value;
}
value = 0;
break;
case '#':
*format++ = *s++;
break;
case ' ':
*format++ = *s++;
break;
case ':':
s++;
allowminus = TRUE;
break;
case '-':
if (allowminus) {
*format++ = *s++;
} else {
done = TRUE;
}
break;
default:
if (isdigit(UChar(*s))) {
value = (value * 10) + (*s - '0');
if (value > 10000)
err = TRUE;
*format++ = *s++;
} else {
done = TRUE;
}
}
}
/*
* If we found an error, ignore (and remove) the flags.
*/
if (err) {
my_width = my_prec = value = 0;
format = fmt;
*format++ = '%';
*format++ = *s;
}
/*
* Any value after '.' is the precision. If we did not see '.', then
* the value is the width.
*/
if (dot)
my_prec = value;
else
my_width = value;
*format = '\0';
/* return maximum string length in print */
*len = (my_width > my_prec) ? my_width : my_prec;
}
return s;
}
/*
* Analyze the string to see how many parameters we need from the varargs list,
* and what their types are. We will only accept string parameters if they
* appear as a %l or %s format following an explicit parameter reference (e.g.,
* %p2%s). All other parameters are numbers.
*
* 'number' counts coarsely the number of pop's we see in the string, and
* 'popcount' shows the highest parameter number in the string. We would like
* to simply use the latter count, but if we are reading termcap strings, there
* may be cases that we cannot see the explicit parameter numbers.
*/
static inline int
analyze(const char *string, char *p_is_s[NUM_PARM], int *popcount)
{
grub_size_t len2;
int i;
int lastpop = -1;
int len;
int number = 0;
const char *cp = string;
static char dummy[] = "";
if (cp == 0)
return 0;
if ((len2 = grub_strlen(cp)) > fmt_size) {
fmt_size = len2 + fmt_size + 2;
if ((fmt_buff = grub_realloc(fmt_buff, fmt_size*sizeof(char))) == 0)
return 0;
}
grub_memset(p_is_s, 0, sizeof(p_is_s[0]) * NUM_PARM);
*popcount = 0;
while ((cp - string) < (int) len2) {
if (*cp == '%') {
cp++;
cp = parse_format(cp, fmt_buff, &len);
switch (*cp) {
default:
break;
case 'd': /* FALLTHRU */
case 'o': /* FALLTHRU */
case 'x': /* FALLTHRU */
case 'X': /* FALLTHRU */
case 'c': /* FALLTHRU */
if (lastpop <= 0)
number++;
lastpop = -1;
break;
case 'l':
case 's':
if (lastpop > 0)
p_is_s[lastpop - 1] = dummy;
++number;
break;
case 'p':
cp++;
i = (UChar(*cp) - '0');
if (i >= 0 && i <= NUM_PARM) {
lastpop = i;
if (lastpop > *popcount)
*popcount = lastpop;
}
break;
case 'P':
++number;
++cp;
break;
case 'g':
cp++;
break;
case '\'':
cp += 2;
lastpop = -1;
break;
case '{':
cp++;
while (isdigit(UChar(*cp))) {
cp++;
}
break;
case '+':
case '-':
case '*':
case '/':
case 'm':
case 'A':
case 'O':
case '&':
case '|':
case '^':
case '=':
case '<':
case '>':
lastpop = -1;
number += 2;
break;
case '!':
case '~':
lastpop = -1;
++number;
break;
case 'i':
/* will add 1 to first (usually two) parameters */
break;
}
}
if (*cp != '\0')
cp++;
}
if (number > NUM_PARM)
number = NUM_PARM;
return number;
}
static inline char *
tparam_internal(const char *string, va_list ap)
{
char *p_is_s[NUM_PARM];
long param[NUM_PARM];
int popcount;
int number;
int len;
int level;
int x, y;
int i;
const char *cp = string;
grub_size_t len2;
static int dynamic_var[NUM_VARS];
static int static_vars[NUM_VARS];
if (cp == 0)
return 0;
out_used = out_size = fmt_size = 0;
len2 = (int) grub_strlen(cp);
/*
* Find the highest parameter-number referred to in the format string.
* Use this value to limit the number of arguments copied from the
* variable-length argument list.
*/
number = analyze(cp, p_is_s, &popcount);
if (fmt_buff == 0)
return 0;
for (i = 0; i < max(popcount, number); i++) {
/*
* A few caps (such as plab_norm) have string-valued parms.
* We'll have to assume that the caller knows the difference, since
* a char* and an int may not be the same size on the stack.
*/
if (p_is_s[i] != 0) {
p_is_s[i] = va_arg(ap, char *);
} else {
param[i] = va_arg(ap, long int);
}
}
/*
* This is a termcap compatibility hack. If there are no explicit pop
* operations in the string, load the stack in such a way that
* successive pops will grab successive parameters. That will make
* the expansion of (for example) \E[%d;%dH work correctly in termcap
* style, which means tparam() will expand termcap strings OK.
*/
stack_ptr = 0;
if (popcount == 0) {
popcount = number;
for (i = number - 1; i >= 0; i--)
npush(param[i]);
}
while ((cp - string) < (int) len2) {
if (*cp != '%') {
save_char(UChar(*cp));
} else {
tparam_base = cp++;
cp = parse_format(cp, fmt_buff, &len);
switch (*cp) {
default:
break;
case '%':
save_char('%');
break;
case 'd': /* FALLTHRU */
case 'o': /* FALLTHRU */
case 'x': /* FALLTHRU */
case 'X': /* FALLTHRU */
save_number(fmt_buff, npop(), len);
break;
case 'c': /* FALLTHRU */
save_char(npop());
break;
case 'l':
save_number("%d", (int) grub_strlen(spop()), 0);
break;
case 's':
save_text(fmt_buff, spop(), len);
break;
case 'p':
cp++;
i = (UChar(*cp) - '1');
if (i >= 0 && i < NUM_PARM) {
if (p_is_s[i])
spush(p_is_s[i]);
else
npush(param[i]);
}
break;
case 'P':
cp++;
if (isUPPER(*cp)) {
i = (UChar(*cp) - 'A');
static_vars[i] = npop();
} else if (isLOWER(*cp)) {
i = (UChar(*cp) - 'a');
dynamic_var[i] = npop();
}
break;
case 'g':
cp++;
if (isUPPER(*cp)) {
i = (UChar(*cp) - 'A');
npush(static_vars[i]);
} else if (isLOWER(*cp)) {
i = (UChar(*cp) - 'a');
npush(dynamic_var[i]);
}
break;
case '\'':
cp++;
npush(UChar(*cp));
cp++;
break;
case '{':
number = 0;
cp++;
while (isdigit(UChar(*cp))) {
number = (number * 10) + (UChar(*cp) - '0');
cp++;
}
npush(number);
break;
case '+':
npush(npop() + npop());
break;
case '-':
y = npop();
x = npop();
npush(x - y);
break;
case '*':
npush(npop() * npop());
break;
case '/':
y = npop();
x = npop();
npush(y ? (x / y) : 0);
break;
case 'm':
y = npop();
x = npop();
npush(y ? (x % y) : 0);
break;
case 'A':
npush(npop() && npop());
break;
case 'O':
npush(npop() || npop());
break;
case '&':
npush(npop() & npop());
break;
case '|':
npush(npop() | npop());
break;
case '^':
npush(npop() ^ npop());
break;
case '=':
y = npop();
x = npop();
npush(x == y);
break;
case '<':
y = npop();
x = npop();
npush(x < y);
break;
case '>':
y = npop();
x = npop();
npush(x > y);
break;
case '!':
npush(!npop());
break;
case '~':
npush(~npop());
break;
case 'i':
if (p_is_s[0] == 0)
param[0]++;
if (p_is_s[1] == 0)
param[1]++;
break;
case '?':
break;
case 't':
x = npop();
if (!x) {
/* scan forward for %e or %; at level zero */
cp++;
level = 0;
while (*cp) {
if (*cp == '%') {
cp++;
if (*cp == '?')
level++;
else if (*cp == ';') {
if (level > 0)
level--;
else
break;
} else if (*cp == 'e' && level == 0)
break;
}
if (*cp)
cp++;
}
}
break;
case 'e':
/* scan forward for a %; at level zero */
cp++;
level = 0;
while (*cp) {
if (*cp == '%') {
cp++;
if (*cp == '?')
level++;
else if (*cp == ';') {
if (level > 0)
level--;
else
break;
}
}
if (*cp)
cp++;
}
break;
case ';':
break;
} /* endswitch (*cp) */
} /* endelse (*cp == '%') */
if (*cp == '\0')
break;
cp++;
} /* endwhile (*cp) */
get_space(1);
out_buff[out_used] = '\0';
return (out_buff);
}
char *
grub_terminfo_tparm (const char *string, ...)
{
va_list ap;
char *result;
va_start (ap, string);
result = tparam_internal (string, ap);
va_end (ap);
return result;
}