diff --git a/AUTHORS b/AUTHORS index adfb7ce57..8de5c4d30 100644 --- a/AUTHORS +++ b/AUTHORS @@ -8,3 +8,16 @@ in ext2fs. Marco Gerards added ext2fs support, grub-emu, a new command-line engine, and fixed many bugs. + +Omniflux added terminfo and serial support. + +Vincent Pelletier added Sparc64 support. + +Hollis Blanchard implemented many parts of PowerPC support. + +Tomas Ebenlendr added the command chainloader into the normal mode, +fixed some bugs. + +Guillem Jover merged architecture-independent ELF support code. + +Vesa Jaaskelainen added VBE support. diff --git a/ChangeLog b/ChangeLog index f063e7f32..51fb63a63 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,22 @@ +2005-09-03 Yoshinori K. Okuji + + * normal/completion.c (complete_arguments): Add the qualifier + const into OPTIONS. + + From Omniflux : + * 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. + 2005-08-31 Yoshinori K. Okuji * DISTLIST: Replaced boot/powerpc/ieee1275/crt0.S and diff --git a/THANKS b/THANKS index 42d6f6598..cb239c15c 100644 --- a/THANKS +++ b/THANKS @@ -12,11 +12,13 @@ Johan Rydberg Hollis Blanchard Marco Gerards NIIBE Yutaka +Omniflux Robert Bihlmeyer Ruslan Nikolaev Timothy Baldwin Tomas Ebenlendr Tsuneyoshi Yasuo +Vesa Jaaskelainen Vincent Guffens Vincent Pelletier Vladimir Serbinenko diff --git a/conf/i386-pc.mk b/conf/i386-pc.mk index 9b899da5e..42c3a37ed 100644 --- a/conf/i386-pc.mk +++ b/conf/i386-pc.mk @@ -1168,7 +1168,8 @@ pkgdata_MODULES = _chain.mod _linux.mod linux.mod fat.mod ufs.mod \ terminal.mod fshelp.mod chain.mod multiboot.mod amiga.mod \ apple.mod pc.mod sun.mod loopback.mod reboot.mod halt.mod \ help.mod default.mod timeout.mod configfile.mod vbe.mod \ - vesafb.mod vbetest.mod vbeinfo.mod search.mod gzio.mod + vesafb.mod vbetest.mod vbeinfo.mod search.mod gzio.mod \ + terminfo.mod serial.mod # For _chain.mod. _chain_mod_SOURCES = loader/i386/pc/chainloader.c @@ -2499,6 +2500,125 @@ fs-manager.lst: font/manager.c genfslist.sh font_mod_CFLAGS = $(COMMON_CFLAGS) +# For terminfo.mod. +terminfo_mod_SOURCES = term/terminfo.c term/tparm.c +CLEANFILES += terminfo.mod mod-terminfo.o mod-terminfo.c pre-terminfo.o terminfo_mod-term_terminfo.o terminfo_mod-term_tparm.o def-terminfo.lst und-terminfo.lst +MOSTLYCLEANFILES += terminfo_mod-term_terminfo.d terminfo_mod-term_tparm.d +DEFSYMFILES += def-terminfo.lst +UNDSYMFILES += und-terminfo.lst + +terminfo.mod: pre-terminfo.o mod-terminfo.o + -rm -f $@ + $(LD) -r -d -o $@ $^ + $(STRIP) --strip-unneeded -K grub_mod_init -K grub_mod_fini -R .note -R .comment $@ + +pre-terminfo.o: terminfo_mod-term_terminfo.o terminfo_mod-term_tparm.o + -rm -f $@ + $(LD) -r -d -o $@ $^ + +mod-terminfo.o: mod-terminfo.c + $(CC) $(CPPFLAGS) $(CFLAGS) $(terminfo_mod_CFLAGS) -c -o $@ $< + +mod-terminfo.c: moddep.lst genmodsrc.sh + sh $(srcdir)/genmodsrc.sh 'terminfo' $< > $@ || (rm -f $@; exit 1) + +def-terminfo.lst: pre-terminfo.o + $(NM) -g --defined-only -P -p $< | sed 's/^\([^ ]*\).*/\1 terminfo/' > $@ + +und-terminfo.lst: pre-terminfo.o + echo 'terminfo' > $@ + $(NM) -u -P -p $< | cut -f1 -d' ' >> $@ + +terminfo_mod-term_terminfo.o: term/terminfo.c + $(CC) -Iterm -I$(srcdir)/term $(CPPFLAGS) $(CFLAGS) $(terminfo_mod_CFLAGS) -c -o $@ $< + +terminfo_mod-term_terminfo.d: term/terminfo.c + set -e; $(CC) -Iterm -I$(srcdir)/term $(CPPFLAGS) $(CFLAGS) $(terminfo_mod_CFLAGS) -M $< | sed 's,terminfo\.o[ :]*,terminfo_mod-term_terminfo.o $@ : ,g' > $@; [ -s $@ ] || rm -f $@ + +-include terminfo_mod-term_terminfo.d + +CLEANFILES += cmd-terminfo.lst fs-terminfo.lst +COMMANDFILES += cmd-terminfo.lst +FSFILES += fs-terminfo.lst + +cmd-terminfo.lst: term/terminfo.c gencmdlist.sh + set -e; $(CC) -Iterm -I$(srcdir)/term $(CPPFLAGS) $(CFLAGS) $(terminfo_mod_CFLAGS) -E $< | sh $(srcdir)/gencmdlist.sh terminfo > $@ || (rm -f $@; exit 1) + +fs-terminfo.lst: term/terminfo.c genfslist.sh + set -e; $(CC) -Iterm -I$(srcdir)/term $(CPPFLAGS) $(CFLAGS) $(terminfo_mod_CFLAGS) -E $< | sh $(srcdir)/genfslist.sh terminfo > $@ || (rm -f $@; exit 1) + + +terminfo_mod-term_tparm.o: term/tparm.c + $(CC) -Iterm -I$(srcdir)/term $(CPPFLAGS) $(CFLAGS) $(terminfo_mod_CFLAGS) -c -o $@ $< + +terminfo_mod-term_tparm.d: term/tparm.c + set -e; $(CC) -Iterm -I$(srcdir)/term $(CPPFLAGS) $(CFLAGS) $(terminfo_mod_CFLAGS) -M $< | sed 's,tparm\.o[ :]*,terminfo_mod-term_tparm.o $@ : ,g' > $@; [ -s $@ ] || rm -f $@ + +-include terminfo_mod-term_tparm.d + +CLEANFILES += cmd-tparm.lst fs-tparm.lst +COMMANDFILES += cmd-tparm.lst +FSFILES += fs-tparm.lst + +cmd-tparm.lst: term/tparm.c gencmdlist.sh + set -e; $(CC) -Iterm -I$(srcdir)/term $(CPPFLAGS) $(CFLAGS) $(terminfo_mod_CFLAGS) -E $< | sh $(srcdir)/gencmdlist.sh terminfo > $@ || (rm -f $@; exit 1) + +fs-tparm.lst: term/tparm.c genfslist.sh + set -e; $(CC) -Iterm -I$(srcdir)/term $(CPPFLAGS) $(CFLAGS) $(terminfo_mod_CFLAGS) -E $< | sh $(srcdir)/genfslist.sh terminfo > $@ || (rm -f $@; exit 1) + + +terminfo_mod_CFLAGS = $(COMMON_CFLAGS) + +# For serial.mod. +serial_mod_SOURCES = term/i386/pc/serial.c +CLEANFILES += serial.mod mod-serial.o mod-serial.c pre-serial.o serial_mod-term_i386_pc_serial.o def-serial.lst und-serial.lst +MOSTLYCLEANFILES += serial_mod-term_i386_pc_serial.d +DEFSYMFILES += def-serial.lst +UNDSYMFILES += und-serial.lst + +serial.mod: pre-serial.o mod-serial.o + -rm -f $@ + $(LD) -r -d -o $@ $^ + $(STRIP) --strip-unneeded -K grub_mod_init -K grub_mod_fini -R .note -R .comment $@ + +pre-serial.o: serial_mod-term_i386_pc_serial.o + -rm -f $@ + $(LD) -r -d -o $@ $^ + +mod-serial.o: mod-serial.c + $(CC) $(CPPFLAGS) $(CFLAGS) $(serial_mod_CFLAGS) -c -o $@ $< + +mod-serial.c: moddep.lst genmodsrc.sh + sh $(srcdir)/genmodsrc.sh 'serial' $< > $@ || (rm -f $@; exit 1) + +def-serial.lst: pre-serial.o + $(NM) -g --defined-only -P -p $< | sed 's/^\([^ ]*\).*/\1 serial/' > $@ + +und-serial.lst: pre-serial.o + echo 'serial' > $@ + $(NM) -u -P -p $< | cut -f1 -d' ' >> $@ + +serial_mod-term_i386_pc_serial.o: term/i386/pc/serial.c + $(CC) -Iterm/i386/pc -I$(srcdir)/term/i386/pc $(CPPFLAGS) $(CFLAGS) $(serial_mod_CFLAGS) -c -o $@ $< + +serial_mod-term_i386_pc_serial.d: term/i386/pc/serial.c + set -e; $(CC) -Iterm/i386/pc -I$(srcdir)/term/i386/pc $(CPPFLAGS) $(CFLAGS) $(serial_mod_CFLAGS) -M $< | sed 's,serial\.o[ :]*,serial_mod-term_i386_pc_serial.o $@ : ,g' > $@; [ -s $@ ] || rm -f $@ + +-include serial_mod-term_i386_pc_serial.d + +CLEANFILES += cmd-serial.lst fs-serial.lst +COMMANDFILES += cmd-serial.lst +FSFILES += fs-serial.lst + +cmd-serial.lst: term/i386/pc/serial.c gencmdlist.sh + set -e; $(CC) -Iterm/i386/pc -I$(srcdir)/term/i386/pc $(CPPFLAGS) $(CFLAGS) $(serial_mod_CFLAGS) -E $< | sh $(srcdir)/gencmdlist.sh serial > $@ || (rm -f $@; exit 1) + +fs-serial.lst: term/i386/pc/serial.c genfslist.sh + set -e; $(CC) -Iterm/i386/pc -I$(srcdir)/term/i386/pc $(CPPFLAGS) $(CFLAGS) $(serial_mod_CFLAGS) -E $< | sh $(srcdir)/genfslist.sh serial > $@ || (rm -f $@; exit 1) + + +serial_mod_CFLAGS = $(COMMON_CFLAGS) + # For _multiboot.mod. _multiboot_mod_SOURCES = loader/i386/pc/multiboot.c CLEANFILES += _multiboot.mod mod-_multiboot.o mod-_multiboot.c pre-_multiboot.o _multiboot_mod-loader_i386_pc_multiboot.o def-_multiboot.lst und-_multiboot.lst diff --git a/conf/i386-pc.rmk b/conf/i386-pc.rmk index 16d6f5acc..1236a5fda 100644 --- a/conf/i386-pc.rmk +++ b/conf/i386-pc.rmk @@ -114,7 +114,8 @@ pkgdata_MODULES = _chain.mod _linux.mod linux.mod fat.mod ufs.mod \ terminal.mod fshelp.mod chain.mod multiboot.mod amiga.mod \ apple.mod pc.mod sun.mod loopback.mod reboot.mod halt.mod \ help.mod default.mod timeout.mod configfile.mod vbe.mod \ - vesafb.mod vbetest.mod vbeinfo.mod search.mod gzio.mod + vesafb.mod vbetest.mod vbeinfo.mod search.mod gzio.mod \ + terminfo.mod serial.mod # For _chain.mod. _chain_mod_SOURCES = loader/i386/pc/chainloader.c @@ -216,6 +217,14 @@ vga_mod_CFLAGS = $(COMMON_CFLAGS) font_mod_SOURCES = font/manager.c font_mod_CFLAGS = $(COMMON_CFLAGS) +# For terminfo.mod. +terminfo_mod_SOURCES = term/terminfo.c term/tparm.c +terminfo_mod_CFLAGS = $(COMMON_CFLAGS) + +# For serial.mod. +serial_mod_SOURCES = term/i386/pc/serial.c +serial_mod_CFLAGS = $(COMMON_CFLAGS) + # For _multiboot.mod. _multiboot_mod_SOURCES = loader/i386/pc/multiboot.c _multiboot_mod_CFLAGS = $(COMMON_CFLAGS) diff --git a/include/grub/i386/pc/serial.h b/include/grub/i386/pc/serial.h new file mode 100644 index 000000000..7d243010c --- /dev/null +++ b/include/grub/i386/pc/serial.h @@ -0,0 +1,68 @@ +/* serial.h - serial device interface */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2000,2001,2002,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. + */ + +#ifndef GRUB_SERIAL_MACHINE_HEADER +#define GRUB_SERIAL_MACHINE_HEADER 1 + +/* Macros. */ + +/* The offsets of UART registers. */ +#define UART_TX 0 +#define UART_RX 0 +#define UART_DLL 0 +#define UART_IER 1 +#define UART_DLH 1 +#define UART_IIR 2 +#define UART_FCR 2 +#define UART_LCR 3 +#define UART_MCR 4 +#define UART_LSR 5 +#define UART_MSR 6 +#define UART_SR 7 + +/* For LSR bits. */ +#define UART_DATA_READY 0x01 +#define UART_EMPTY_TRANSMITTER 0x20 + +/* The type of parity. */ +#define UART_NO_PARITY 0x00 +#define UART_ODD_PARITY 0x08 +#define UART_EVEN_PARITY 0x18 + +/* The type of word length. */ +#define UART_5BITS_WORD 0x00 +#define UART_6BITS_WORD 0x01 +#define UART_7BITS_WORD 0x02 +#define UART_8BITS_WORD 0x03 + +/* The type of the length of stop bit. */ +#define UART_1_STOP_BIT 0x00 +#define UART_2_STOP_BITS 0x04 + +/* the switch of DLAB. */ +#define UART_DLAB 0x80 + +/* Enable the FIFO. */ +#define UART_ENABLE_FIFO 0xC7 + +/* Turn on DTR, RTS, and OUT2. */ +#define UART_ENABLE_MODEM 0x0B + +#endif /* ! GRUB_SERIAL_MACHINE_HEADER */ diff --git a/include/grub/terminfo.h b/include/grub/terminfo.h new file mode 100644 index 000000000..98b5a09db --- /dev/null +++ b/include/grub/terminfo.h @@ -0,0 +1,36 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2002,2003,2005 Free Software Foundation, Inc. + * + * GRUB 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 GRUB; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef GRUB_TERMINFO_HEADER +#define GRUB_TERMINFO_HEADER 1 + +#include +#include + +char *grub_terminfo_get_current (void); +grub_err_t grub_terminfo_set_current (const char *); + +void grub_terminfo_gotoxy (grub_uint8_t x, grub_uint8_t y); +void grub_terminfo_cls (void); +void grub_terminfo_reverse_video_on (void); +void grub_terminfo_reverse_video_off (void); +void grub_terminfo_cursor_on (void); +void grub_terminfo_cursor_off (void); + +#endif /* ! GRUB_TERMINFO_HEADER */ diff --git a/include/grub/tparm.h b/include/grub/tparm.h new file mode 100644 index 000000000..0a52d7a67 --- /dev/null +++ b/include/grub/tparm.h @@ -0,0 +1,27 @@ +/* tparm.h - parameter formatting of terminfo */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2002,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. + */ + +#ifndef GRUB_TPARM_HEADER +#define GRUB_TPARM_HEADER 1 + +/* Function prototypes. */ +char *grub_terminfo_tparm (const char *string, ...); + +#endif /* ! GRUB_TPARM_HEADER */ diff --git a/normal/completion.c b/normal/completion.c index b587a36ac..aa89a02c8 100644 --- a/normal/completion.c +++ b/normal/completion.c @@ -308,7 +308,7 @@ static int complete_arguments (char *command) { grub_command_t cmd; - struct grub_arg_option *option; + const struct grub_arg_option *option; char shortarg[] = "- "; cmd = grub_command_find (command); @@ -322,7 +322,7 @@ complete_arguments (char *command) /* Add the short arguments. */ for (option = cmd->options; option->doc; option++) { - if (!option->shortarg) + if (! option->shortarg) continue; shortarg[1] = option->shortarg; diff --git a/term/i386/pc/serial.c b/term/i386/pc/serial.c new file mode 100644 index 000000000..0f3f3dc28 --- /dev/null +++ b/term/i386/pc/serial.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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); +} diff --git a/term/terminfo.c b/term/terminfo.c new file mode 100644 index 000000000..c66a38d21 --- /dev/null +++ b/term/terminfo.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +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"); +} diff --git a/term/tparm.c b/term/tparm.c new file mode 100644 index 000000000..8272d66fe --- /dev/null +++ b/term/tparm.c @@ -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 2002 + * + * Resync with ncurses-5.4 by Omniflux 2005 + **********************************************************************/ + +/**************************************************************************** + * Author: Zeyd M. Ben-Halim 1992,1995 * + * and: Eric S. Raymond * + * and: Thomas E. Dickey, 1996 on * + ****************************************************************************/ + +/* + * tparm.c + * + */ + +#include +#include +#include +#include + +/* + * 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; +}