From: Jeremy Kerr Date: Mon, 2 Apr 2007 00:31:10 +0000 (+1000) Subject: Initial gitification of petitboot X-Git-Tag: v0.0.1~39 X-Git-Url: https://git.ozlabs.org/?p=petitboot;a=commitdiff_plain;h=f60d0b2e7dbd9d85980866c68d0f87b6bc823663 Initial gitification of petitboot Start a git repo for petitboot, sources from Ben Herrenschmidt and Jeremy Kerr. Signed-off-by: Jeremy Kerr Signed-off-by: Benjamin Herrenschmidt --- f60d0b2e7dbd9d85980866c68d0f87b6bc823663 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b0a92ea --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +CC=gcc +TWIN_CFLAGS=$(shell pkg-config --cflags libtwin) +TWIN_LDFLAGS=$(shell pkg-config --libs libtwin) + +LDFLAGS = $(TWIN_LDFLAGS) +CFLAGS = -O0 -ggdb -Wall $(TWIN_CFLAGS) + +OBJFILES = petitboot.o devices.o + +petitboot: $(OBJFILES) + $(CC) $(LDFLAGS) -o $@ $^ + +clean: + rm -f petitboot + rm -f *.o diff --git a/artwork/background.png b/artwork/background.png new file mode 100644 index 0000000..f6ea9da Binary files /dev/null and b/artwork/background.png differ diff --git a/artwork/cdrom.png b/artwork/cdrom.png new file mode 100644 index 0000000..8b3eeb6 Binary files /dev/null and b/artwork/cdrom.png differ diff --git a/artwork/cursor b/artwork/cursor new file mode 100644 index 0000000..fac3b0c Binary files /dev/null and b/artwork/cursor differ diff --git a/artwork/hdd.png b/artwork/hdd.png new file mode 100644 index 0000000..2b1594c Binary files /dev/null and b/artwork/hdd.png differ diff --git a/artwork/usbpen.png b/artwork/usbpen.png new file mode 100644 index 0000000..53a365e Binary files /dev/null and b/artwork/usbpen.png differ diff --git a/devices.c b/devices.c new file mode 100644 index 0000000..ac93287 --- /dev/null +++ b/devices.c @@ -0,0 +1,272 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "petitboot.h" +#include "devices/message.h" + +#define PBOOT_DEVICE_SOCKET "/var/tmp/petitboot-dev" +#define PBOOT_DEFAULT_ICON "artwork/usbpen.png" + +static const char *default_icon = PBOOT_DEFAULT_ICON; + +struct discovery_context { + /* nothing at present */ + int pad; +} _ctx; + +struct device_context { + struct discovery_context *discovery_ctx; + uint8_t action; + int device_idx; +}; + +static twin_pixmap_t *get_icon(const char *filename) +{ + /* todo: cache */ + twin_pixmap_t *icon; + + if (!filename) + filename = default_icon; + +retry: + LOG("loading icon %s ... ", filename); + icon = twin_png_to_pixmap(filename, TWIN_ARGB32); + LOG("%s\n", icon ? "ok" : "failed"); + + if (!icon && filename != default_icon) { + filename = default_icon; + LOG("reverting to default icon %s\n", filename); + goto retry; + } + + return icon; +} + +#define MAX_LEN 4096 +static char *read_string(int fd) +{ + int len; + uint32_t len_buf; + char *str, *pos; + + if (read(fd, &len_buf, sizeof(len_buf)) != sizeof(len_buf)) { + perror("read"); + return NULL; + } + + len = __be32_to_cpu(len_buf); + if (len + 1 > MAX_LEN) { + /* todo: truncate instead */ + return NULL; + } + + pos = str = malloc(len + 1); + + while (len) { + int rc = read(fd, pos, len); + if (rc <= 0) { + free(str); + LOG("read failed: %s\n", strerror(errno)); + return NULL; + } + pos += rc; + len -= rc; + } + *pos = '\0'; + + return str; +} + +static int _read_strings(int fd, void *ptr, int n_strings) +{ + char **strings = ptr; + int i, ret = TWIN_TRUE; + + for (i = 0; i < n_strings; i++) { + strings[i] = read_string(fd); + if (!strings[i]) { + ret = TWIN_FALSE; + break; + } + } + + if (!ret) + while (i-- > 0) { + free(strings[i]); + strings[i] = NULL; + } + + return ret; +} + +static void _free_strings(void *ptr, int n_strings) +{ + char **strings = ptr; + int i; + + for (i = 0; i < n_strings; i++) + free(strings[i]); + +} + +#define n_strings(x) (sizeof((x)) / sizeof(char *)) +#define read_strings(f,x) _read_strings((f), &(x), n_strings(x)) +#define free_strings(x) _free_strings(&(x), n_strings(x)) + +static int read_action(int fd, uint8_t *action) +{ + return read(fd, action, sizeof(*action)) != sizeof(*action); +} + +static int read_device(int fd, struct device_context *dev_ctx) +{ + /* name, description, icon_file */ + struct device dev; + twin_pixmap_t *icon; + int index = -1; + + if (!read_strings(fd, dev)) + return TWIN_FALSE; + + LOG("got device: '%s'\n", dev.name); + + icon = get_icon(dev.icon_file); + + if (!icon) + goto out; + + index = dev_ctx->device_idx = pboot_add_device(dev.id, dev.name, icon); + +out: + free_strings(dev); + + return index != -1; +} + +static int read_option(int fd, struct device_context *dev_ctx) +{ + struct boot_option opt; + twin_pixmap_t *icon; + int index = -1; + + if (!read_strings(fd, opt)) + return TWIN_FALSE; + + LOG("got option: '%s'\n", opt.name); + icon = get_icon(opt.icon_file); + + if (icon) + index = pboot_add_option(dev_ctx->device_idx, opt.name, + opt.description, icon); + + free_strings(opt); + + return index != -1; +} + +static twin_bool_t pboot_proc_client_sock(int sock, twin_file_op_t ops, + void *closure) +{ + struct device_context *dev_ctx = closure; + uint8_t action; + + if (read_action(sock, &action)) + goto out_err; + + if (action == DEV_ACTION_ADD_DEVICE) { + if (!read_device(sock, dev_ctx)) + goto out_err; + + } else if (action == DEV_ACTION_ADD_OPTION) { + if (dev_ctx->device_idx == -1) { + LOG("option, but no device has been sent?\n"); + goto out_err; + } + + if (!read_option(sock, dev_ctx)) + goto out_err; + + } else if (action == DEV_ACTION_REMOVE_DEVICE) { + char *dev_id = read_string(sock); + if (!dev_id) + goto out_err; + + LOG("remove device %s\n", dev_id); + pboot_remove_device(dev_id); + + } else { + LOG("unsupported action %d\n", action); + goto out_err; + } + + return TWIN_TRUE; + +out_err: + close(sock); + return TWIN_FALSE; +} + +static twin_bool_t pboot_proc_server_sock(int sock, twin_file_op_t ops, + void *closure) +{ + int fd; + struct discovery_context *disc_ctx = closure; + struct device_context *dev_ctx; + + fd = accept(sock, NULL, 0); + if (fd < 0) { + LOG("accept failed: %s", strerror(errno)); + return TWIN_FALSE; + } + + dev_ctx = malloc(sizeof(*dev_ctx)); + dev_ctx->discovery_ctx = disc_ctx; + dev_ctx->device_idx = -1; + dev_ctx->action = 0xff; + + twin_set_file(pboot_proc_client_sock, fd, TWIN_READ, dev_ctx); + + return TWIN_TRUE; +} + +int pboot_start_device_discovery(void) +{ + int sock; + struct sockaddr_un addr; + + unlink(PBOOT_DEVICE_SOCKET); + + sock = socket(PF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + perror("socket"); + return TWIN_FALSE; + } + + addr.sun_family = AF_UNIX; + strcpy(addr.sun_path, PBOOT_DEVICE_SOCKET); + + if (bind(sock, (struct sockaddr *)&addr, sizeof(addr))) { + LOG("can't bind to %s: %s", addr.sun_path, strerror(errno)); + return TWIN_FALSE; + } + + if (listen(sock, 1)) { + LOG("can't listen on socket %s: %s", + addr.sun_path, strerror(errno)); + return TWIN_FALSE; + } + + LOG("listening on %s\n", addr.sun_path); + + twin_set_file(pboot_proc_server_sock, sock, TWIN_READ, &_ctx); + + return TWIN_TRUE; +} + diff --git a/devices/Makefile b/devices/Makefile new file mode 100644 index 0000000..ec9d87c --- /dev/null +++ b/devices/Makefile @@ -0,0 +1,9 @@ + +CC=gcc +CFLAGS=-Wall -g -O2 + +parsers = native-parser.o + +all: udev-helper + +udev-helper: udev-helper.o params.o $(parsers) diff --git a/devices/message.h b/devices/message.h new file mode 100644 index 0000000..2e8bbba --- /dev/null +++ b/devices/message.h @@ -0,0 +1,26 @@ + +enum device_action { + DEV_ACTION_ADD_DEVICE = 0, + DEV_ACTION_ADD_OPTION = 1, + DEV_ACTION_REMOVE_DEVICE = 2, + DEV_ACTION_REMOVE_OPTION = 3 +}; + +struct device { + char *id; + char *name; + char *description; + char *icon_file; +}; + +struct boot_option { + char *id; + char *name; + char *description; + char *icon_file; + char *boot_image_file; + char *initrd_file; + char *boot_args; +}; + + diff --git a/devices/native-parser.c b/devices/native-parser.c new file mode 100644 index 0000000..f47fdca --- /dev/null +++ b/devices/native-parser.c @@ -0,0 +1,136 @@ + +#include "udev-helper.h" +#include "params.h" + +#include +#include +#include + +const char *conf_filename = "/boot/petitboot.conf"; + +static struct boot_option *cur_opt; +static struct device *dev; +static const char *mountpoint; +int device_added; + +static char *prepend_mountpoint(const char *path) +{ + char *full_path; + + full_path = malloc(strlen(path) + strlen(mountpoint) + 2); + + strcpy(full_path, mountpoint); + if (path[0] != '/') + strcat(full_path, "/"); + strcat(full_path, path); + + return full_path; +} + +int check_and_add_device(struct device *dev) +{ + if (!dev->icon_file) + dev->icon_file = strdup(generic_icon_file(guess_device_type())); + + return !add_device(dev); +} + +static int section(char *section_name) +{ + if (!device_added++ && !check_and_add_device(dev)) + return 0; + + if (cur_opt) { + add_boot_option(cur_opt); + free_boot_option(cur_opt); + } + + cur_opt = malloc(sizeof(*cur_opt)); + memset(cur_opt, 0, sizeof(*cur_opt)); + return 1; +} + + +static void set_boot_option_parameter(struct boot_option *opt, + const char *name, const char *value) +{ + if (streq(name, "name")) + opt->name = strdup(value); + + else if (streq(name, "description")) + opt->description = strdup(value); + + else if (streq(name, "image")) + opt->boot_image_file = prepend_mountpoint(value); + + else if (streq(name, "icon")) + opt->icon_file = prepend_mountpoint(value); + + else if (streq(name, "initrd")) + opt->initrd_file = prepend_mountpoint(value); + + else if (streq(name, "args")) + opt->boot_args = strdup(value); + + else + fprintf(stderr, "Unknown parameter %s\n", name); +} + +static void set_device_parameter(struct device *dev, + const char *name, const char *value) +{ + if (streq(name, "name")) + dev->name = strdup(value); + + else if (streq(name, "description")) + dev->description = strdup(value); + + else if (streq(name, "icon")) + dev->icon_file = prepend_mountpoint(value); +} + +static int parameter(char *param_name, char *param_value) +{ + if (cur_opt) + set_boot_option_parameter(cur_opt, param_name, param_value); + else + set_device_parameter(dev, param_name, param_value); + return 1; +} + + +int parse(const char *devicepath, const char *_mountpoint) +{ + char *filepath; + + mountpoint = _mountpoint; + + filepath = prepend_mountpoint(conf_filename); + + cur_opt = NULL; + dev = malloc(sizeof(*dev)); + memset(dev, 0, sizeof(*dev)); + dev->id = strdup(devicepath); + + pm_process(filepath, section, parameter); + + if (cur_opt) { + add_boot_option(cur_opt); + free_boot_option(cur_opt); + } + + cur_opt = NULL; + + free(filepath); + + return 1; +} + +struct parser native_parser = { + .name = "native petitboot parser", + .priority = 100, + .parse = parse +}; + + + diff --git a/devices/params.c b/devices/params.c new file mode 100644 index 0000000..76a1451 --- /dev/null +++ b/devices/params.c @@ -0,0 +1,595 @@ +/* This modules is based on the params.c module from Samba, written by Karl Auer + and much modifed by Christopher Hertel. */ + +/* + * 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., + * 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include + +#include "params.h" + +#define new_array(type, num) ((type *)_new_array(sizeof(type), (num))) +#define realloc_array(ptr, type, num) \ + ((type *)_realloc_array((ptr), sizeof(type), (num))) + +#define rprintf(x, ...) do { fprintf(stderr, ##__VA_ARGS__); \ + fprintf(stderr, "\n"); } while (0) +#define rsyserr(x, y, ...) do { fprintf(stderr, ##__VA_ARGS__); \ + fprintf(stderr, "\n"); } while (0) + +#define MALLOC_MAX 0x40000000 +#define False 0 +#define True 1 + +void *_new_array(unsigned int size, unsigned long num) +{ + if (num >= MALLOC_MAX/size) + return NULL; + return malloc(size * num); +} + +void *_realloc_array(void *ptr, unsigned int size, unsigned long num) +{ + if (num >= MALLOC_MAX/size) + return NULL; + /* No realloc should need this, but just in case... */ + if (!ptr) + return malloc(size * num); + return realloc(ptr, size * num); +} + + +/* -------------------------------------------------------------------------- ** + * + * Module name: params + * + * -------------------------------------------------------------------------- ** + * + * This module performs lexical analysis and initial parsing of a + * Windows-like parameter file. It recognizes and handles four token + * types: section-name, parameter-name, parameter-value, and + * end-of-file. Comments and line continuation are handled + * internally. + * + * The entry point to the module is function pm_process(). This + * function opens the source file, calls the Parse() function to parse + * the input, and then closes the file when either the EOF is reached + * or a fatal error is encountered. + * + * A sample parameter file might look like this: + * + * [section one] + * parameter one = value string + * parameter two = another value + * [section two] + * new parameter = some value or t'other + * + * The parameter file is divided into sections by section headers: + * section names enclosed in square brackets (eg. [section one]). + * Each section contains parameter lines, each of which consist of a + * parameter name and value delimited by an equal sign. Roughly, the + * syntax is: + * + * :== {
} EOF + * + *
:==
{ } + * + *
:== '[' NAME ']' + * + * :== NAME '=' VALUE '\n' + * + * Blank lines and comment lines are ignored. Comment lines are lines + * beginning with either a semicolon (';') or a pound sign ('#'). + * + * All whitespace in section names and parameter names is compressed + * to single spaces. Leading and trailing whitespace is stipped from + * both names and values. + * + * Only the first equals sign in a parameter line is significant. + * Parameter values may contain equals signs, square brackets and + * semicolons. Internal whitespace is retained in parameter values, + * with the exception of the '\r' character, which is stripped for + * historic reasons. Parameter names may not start with a left square + * bracket, an equal sign, a pound sign, or a semicolon, because these + * are used to identify other tokens. + * + * -------------------------------------------------------------------------- ** + */ + +/* -------------------------------------------------------------------------- ** + * Constants... + */ + +#define BUFR_INC 1024 + + +/* -------------------------------------------------------------------------- ** + * Variables... + * + * bufr - pointer to a global buffer. This is probably a kludge, + * but it was the nicest kludge I could think of (for now). + * bSize - The size of the global buffer . + */ + +static char *bufr = NULL; +static int bSize = 0; + +/* -------------------------------------------------------------------------- ** + * Functions... + */ + +static int EatWhitespace( FILE *InFile ) + /* ------------------------------------------------------------------------ ** + * Scan past whitespace (see ctype(3C)) and return the first non-whitespace + * character, or newline, or EOF. + * + * Input: InFile - Input source. + * + * Output: The next non-whitespace character in the input stream. + * + * Notes: Because the config files use a line-oriented grammar, we + * explicitly exclude the newline character from the list of + * whitespace characters. + * - Note that both EOF (-1) and the nul character ('\0') are + * considered end-of-file markers. + * + * ------------------------------------------------------------------------ ** + */ + { + int c; + + for( c = getc( InFile ); isspace( c ) && ('\n' != c); c = getc( InFile ) ) + ; + return( c ); + } /* EatWhitespace */ + +static int EatComment( FILE *InFile ) + /* ------------------------------------------------------------------------ ** + * Scan to the end of a comment. + * + * Input: InFile - Input source. + * + * Output: The character that marks the end of the comment. Normally, + * this will be a newline, but it *might* be an EOF. + * + * Notes: Because the config files use a line-oriented grammar, we + * explicitly exclude the newline character from the list of + * whitespace characters. + * - Note that both EOF (-1) and the nul character ('\0') are + * considered end-of-file markers. + * + * ------------------------------------------------------------------------ ** + */ + { + int c; + + for( c = getc( InFile ); ('\n'!=c) && (EOF!=c) && (c>0); c = getc( InFile ) ) + ; + return( c ); + } /* EatComment */ + +static int Continuation( char *line, int pos ) + /* ------------------------------------------------------------------------ ** + * Scan backards within a string to discover if the last non-whitespace + * character is a line-continuation character ('\\'). + * + * Input: line - A pointer to a buffer containing the string to be + * scanned. + * pos - This is taken to be the offset of the end of the + * string. This position is *not* scanned. + * + * Output: The offset of the '\\' character if it was found, or -1 to + * indicate that it was not. + * + * ------------------------------------------------------------------------ ** + */ + { + pos--; + while( (pos >= 0) && isspace(((unsigned char *)line)[pos]) ) + pos--; + + return( ((pos >= 0) && ('\\' == line[pos])) ? pos : -1 ); + } /* Continuation */ + + +static BOOL Section( FILE *InFile, BOOL (*sfunc)(char *) ) + /* ------------------------------------------------------------------------ ** + * Scan a section name, and pass the name to function sfunc(). + * + * Input: InFile - Input source. + * sfunc - Pointer to the function to be called if the section + * name is successfully read. + * + * Output: True if the section name was read and True was returned from + * . False if failed or if a lexical error was + * encountered. + * + * ------------------------------------------------------------------------ ** + */ + { + int c; + int i; + int end; + char *func = "params.c:Section() -"; + + i = 0; /* is the offset of the next free byte in bufr[] and */ + end = 0; /* is the current "end of string" offset. In most */ + /* cases these will be the same, but if the last */ + /* character written to bufr[] is a space, then */ + /* will be one less than . */ + + c = EatWhitespace( InFile ); /* We've already got the '['. Scan */ + /* past initial white space. */ + + while( (EOF != c) && (c > 0) ) + { + + /* Check that the buffer is big enough for the next character. */ + if( i > (bSize - 2) ) + { + bSize += BUFR_INC; + bufr = realloc_array( bufr, char, bSize ); + if( NULL == bufr ) + { + rprintf(FERROR, "%s Memory re-allocation failure.", func); + return( False ); + } + } + + /* Handle a single character. */ + switch( c ) + { + case ']': /* Found the closing bracket. */ + bufr[end] = '\0'; + if( 0 == end ) /* Don't allow an empty name. */ + { + rprintf(FERROR, "%s Empty section name in configuration file.\n", func ); + return( False ); + } + if( !sfunc( bufr ) ) /* Got a valid name. Deal with it. */ + return( False ); + (void)EatComment( InFile ); /* Finish off the line. */ + return( True ); + + case '\n': /* Got newline before closing ']'. */ + i = Continuation( bufr, i ); /* Check for line continuation. */ + if( i < 0 ) + { + bufr[end] = '\0'; + rprintf(FERROR, "%s Badly formed line in configuration file: %s\n", + func, bufr ); + return( False ); + } + end = ( (i > 0) && (' ' == bufr[i - 1]) ) ? (i - 1) : (i); + c = getc( InFile ); /* Continue with next line. */ + break; + + default: /* All else are a valid name chars. */ + if( isspace( c ) ) /* One space per whitespace region. */ + { + bufr[end] = ' '; + i = end + 1; + c = EatWhitespace( InFile ); + } + else /* All others copy verbatim. */ + { + bufr[i++] = c; + end = i; + c = getc( InFile ); + } + } + } + + /* We arrive here if we've met the EOF before the closing bracket. */ + rprintf(FERROR, "%s Unexpected EOF in the configuration file: %s\n", func, bufr ); + return( False ); + } /* Section */ + +static BOOL Parameter( FILE *InFile, BOOL (*pfunc)(char *, char *), int c ) + /* ------------------------------------------------------------------------ ** + * Scan a parameter name and value, and pass these two fields to pfunc(). + * + * Input: InFile - The input source. + * pfunc - A pointer to the function that will be called to + * process the parameter, once it has been scanned. + * c - The first character of the parameter name, which + * would have been read by Parse(). Unlike a comment + * line or a section header, there is no lead-in + * character that can be discarded. + * + * Output: True if the parameter name and value were scanned and processed + * successfully, else False. + * + * Notes: This function is in two parts. The first loop scans the + * parameter name. Internal whitespace is compressed, and an + * equal sign (=) terminates the token. Leading and trailing + * whitespace is discarded. The second loop scans the parameter + * value. When both have been successfully identified, they are + * passed to pfunc() for processing. + * + * ------------------------------------------------------------------------ ** + */ + { + int i = 0; /* Position within bufr. */ + int end = 0; /* bufr[end] is current end-of-string. */ + int vstart = 0; /* Starting position of the parameter value. */ + char *func = "params.c:Parameter() -"; + + /* Read the parameter name. */ + while( 0 == vstart ) /* Loop until we've found the start of the value. */ + { + + if( i > (bSize - 2) ) /* Ensure there's space for next char. */ + { + bSize += BUFR_INC; + bufr = realloc_array( bufr, char, bSize ); + if( NULL == bufr ) + { + rprintf(FERROR, "%s Memory re-allocation failure.", func) ; + return( False ); + } + } + + switch( c ) + { + case '=': /* Equal sign marks end of param name. */ + if( 0 == end ) /* Don't allow an empty name. */ + { + rprintf(FERROR, "%s Invalid parameter name in config. file.\n", func ); + return( False ); + } + bufr[end++] = '\0'; /* Mark end of string & advance. */ + i = end; /* New string starts here. */ + vstart = end; /* New string is parameter value. */ + bufr[i] = '\0'; /* New string is nul, for now. */ + break; + + case '\n': /* Find continuation char, else error. */ + i = Continuation( bufr, i ); + if( i < 0 ) + { + bufr[end] = '\0'; + rprintf(FERROR, "%s Ignoring badly formed line in configuration file: %s\n", + func, bufr ); + return( True ); + } + end = ( (i > 0) && (' ' == bufr[i - 1]) ) ? (i - 1) : (i); + c = getc( InFile ); /* Read past eoln. */ + break; + + case '\0': /* Shouldn't have EOF within param name. */ + case EOF: + bufr[i] = '\0'; + rprintf(FERROR, "%s Unexpected end-of-file at: %s\n", func, bufr ); + return( True ); + + default: + if( isspace( c ) ) /* One ' ' per whitespace region. */ + { + bufr[end] = ' '; + i = end + 1; + c = EatWhitespace( InFile ); + } + else /* All others verbatim. */ + { + bufr[i++] = c; + end = i; + c = getc( InFile ); + } + } + } + + /* Now parse the value. */ + c = EatWhitespace( InFile ); /* Again, trim leading whitespace. */ + while( (EOF !=c) && (c > 0) ) + { + + if( i > (bSize - 2) ) /* Make sure there's enough room. */ + { + bSize += BUFR_INC; + bufr = realloc_array( bufr, char, bSize ); + if( NULL == bufr ) + { + rprintf(FERROR, "%s Memory re-allocation failure.", func) ; + return( False ); + } + } + + switch( c ) + { + case '\r': /* Explicitly remove '\r' because the older */ + c = getc( InFile ); /* version called fgets_slash() which also */ + break; /* removes them. */ + + case '\n': /* Marks end of value unless there's a '\'. */ + i = Continuation( bufr, i ); + if( i < 0 ) + c = 0; + else + { + for( end = i; (end >= 0) && isspace(((unsigned char *) bufr)[end]); end-- ) + ; + c = getc( InFile ); + } + break; + + default: /* All others verbatim. Note that spaces do */ + bufr[i++] = c; /* not advance . This allows trimming */ + if( !isspace( c ) ) /* of whitespace at the end of the line. */ + end = i; + c = getc( InFile ); + break; + } + } + bufr[end] = '\0'; /* End of value. */ + + return( pfunc( bufr, &bufr[vstart] ) ); /* Pass name & value to pfunc(). */ + } /* Parameter */ + +static BOOL Parse( FILE *InFile, + BOOL (*sfunc)(char *), + BOOL (*pfunc)(char *, char *) ) + /* ------------------------------------------------------------------------ ** + * Scan & parse the input. + * + * Input: InFile - Input source. + * sfunc - Function to be called when a section name is scanned. + * See Section(). + * pfunc - Function to be called when a parameter is scanned. + * See Parameter(). + * + * Output: True if the file was successfully scanned, else False. + * + * Notes: The input can be viewed in terms of 'lines'. There are four + * types of lines: + * Blank - May contain whitespace, otherwise empty. + * Comment - First non-whitespace character is a ';' or '#'. + * The remainder of the line is ignored. + * Section - First non-whitespace character is a '['. + * Parameter - The default case. + * + * ------------------------------------------------------------------------ ** + */ + { + int c; + + c = EatWhitespace( InFile ); + while( (EOF != c) && (c > 0) ) + { + switch( c ) + { + case '\n': /* Blank line. */ + c = EatWhitespace( InFile ); + break; + + case ';': /* Comment line. */ + case '#': + c = EatComment( InFile ); + break; + + case '[': /* Section Header. */ + if (!sfunc) return True; + if( !Section( InFile, sfunc ) ) + return( False ); + c = EatWhitespace( InFile ); + break; + + case '\\': /* Bogus backslash. */ + c = EatWhitespace( InFile ); + break; + + default: /* Parameter line. */ + if( !Parameter( InFile, pfunc, c ) ) + return( False ); + c = EatWhitespace( InFile ); + break; + } + } + return( True ); + } /* Parse */ + +static FILE *OpenConfFile( char *FileName ) + /* ------------------------------------------------------------------------ ** + * Open a configuration file. + * + * Input: FileName - The pathname of the config file to be opened. + * + * Output: A pointer of type (FILE *) to the opened file, or NULL if the + * file could not be opened. + * + * ------------------------------------------------------------------------ ** + */ + { + FILE *OpenedFile; + char *func = "params.c:OpenConfFile() -"; + + if( NULL == FileName || 0 == *FileName ) + { + rprintf(FERROR,"%s No configuration filename specified.\n", func); + return( NULL ); + } + + OpenedFile = fopen( FileName, "r" ); + if( NULL == OpenedFile ) + { + rsyserr(FERROR, errno, "unable to open configuration file \"%s\"", + FileName); + } + + return( OpenedFile ); + } /* OpenConfFile */ + +BOOL pm_process( char *FileName, + BOOL (*sfunc)(char *), + BOOL (*pfunc)(char *, char *) ) + /* ------------------------------------------------------------------------ ** + * Process the named parameter file. + * + * Input: FileName - The pathname of the parameter file to be opened. + * sfunc - A pointer to a function that will be called when + * a section name is discovered. + * pfunc - A pointer to a function that will be called when + * a parameter name and value are discovered. + * + * Output: TRUE if the file was successfully parsed, else FALSE. + * + * ------------------------------------------------------------------------ ** + */ + { + int result; + FILE *InFile; + char *func = "params.c:pm_process() -"; + + InFile = OpenConfFile( FileName ); /* Open the config file. */ + if( NULL == InFile ) + return( False ); + + if( NULL != bufr ) /* If we already have a buffer */ + result = Parse( InFile, sfunc, pfunc ); /* (recursive call), then just */ + /* use it. */ + + else /* If we don't have a buffer */ + { /* allocate one, then parse, */ + bSize = BUFR_INC; /* then free. */ + bufr = new_array( char, bSize ); + if( NULL == bufr ) + { + rprintf(FERROR,"%s memory allocation failure.\n", func); + fclose(InFile); + return( False ); + } + result = Parse( InFile, sfunc, pfunc ); + free( bufr ); + bufr = NULL; + bSize = 0; + } + + fclose(InFile); + + if( !result ) /* Generic failure. */ + { + rprintf(FERROR,"%s Failed. Error returned from params.c:parse().\n", func); + return( False ); + } + + return( True ); /* Generic success. */ + } /* pm_process */ + +/* -------------------------------------------------------------------------- */ + diff --git a/devices/params.h b/devices/params.h new file mode 100644 index 0000000..02a39c9 --- /dev/null +++ b/devices/params.h @@ -0,0 +1,6 @@ + +#define BOOL int + +BOOL pm_process( char *FileName, + BOOL (*sfunc)(char *), + BOOL (*pfunc)(char *, char *) ); diff --git a/devices/udev-helper.c b/devices/udev-helper.c new file mode 100644 index 0000000..f936086 --- /dev/null +++ b/devices/udev-helper.c @@ -0,0 +1,430 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "udev-helper.h" + +#define parser_dir "." + +#define tmp_dir "/var/tmp/petitboot" +#define socket_file "/var/tmp/petitboot-dev" +#define mount_bin "/bin/mount" +#define umount_bin "/bin/umount" + +extern struct parser native_parser; +static FILE *logf; +static int sock; + +/* array of parsers, ordered by priority */ +static struct parser *parsers[] = { + &native_parser, + NULL +}; + +#define log(...) fprintf(logf, __VA_ARGS__) + +static void iterate_parsers(const char *devpath, const char *mountpoint) +{ + int i; + + log("trying parsers for %s@%s\n", devpath, mountpoint); + + for (i = 0; parsers[i]; i++) { + log("\ttrying parser '%s'\n", parsers[i]->name); + /* just use a dummy device path for now */ + if (parsers[i]->parse(devpath, mountpoint)) + return; + } + log("\tno boot_options found\n"); +} + +static void print_boot_option(const struct boot_option *opt) +{ + log("\tname: %s\n", opt->name); + log("\tdescription: %s\n", opt->description); + log("\tboot_image: %s\n", opt->boot_image_file); + log("\tboot_args: %s\n", opt->boot_args); + +} + +static void print_device(const struct device *dev) +{ + log("\tid: %s\n", dev->name); + log("\tname: %s\n", dev->name); + log("\tdescription: %s\n", dev->description); + log("\tboot_image: %s\n", dev->icon_file); +} + + +void free_device(struct device *dev) +{ + if (!dev) + return; + if (dev->id) + free(dev->id); + if (dev->name) + free(dev->name); + if (dev->description) + free(dev->description); + if (dev->icon_file) + free(dev->icon_file); + free(dev); +} + +void free_boot_option(struct boot_option *opt) +{ + if (!opt) + return; + if (opt->name) + free(opt->name); + if (opt->description) + free(opt->description); + if (opt->icon_file) + free(opt->icon_file); + if (opt->boot_image_file) + free(opt->boot_image_file); + if (opt->initrd_file) + free(opt->initrd_file); + if (opt->boot_args) + free(opt->boot_args); + free(opt); +} + +static int write_action(int fd, enum device_action action) +{ + uint8_t action_buf = action; + return write(fd, &action_buf, sizeof(action_buf)) != sizeof(action_buf); +} + +static int write_string(int fd, const char *str) +{ + int len, pos = 0; + uint32_t len_buf; + + if (!str) { + len_buf = 0; + if (write(fd, &len_buf, sizeof(len_buf)) != sizeof(len_buf)) { + log("write failed: %s\n", strerror(errno)); + return -1; + } + return 0; + } + + len = strlen(str); + if (len > (1ull << (sizeof(len_buf) * 8 - 1))) { + log("string too large\n"); + return -1; + } + + len_buf = __cpu_to_be32(len); + if (write(fd, &len_buf, sizeof(len_buf)) != sizeof(len_buf)) { + log("write failed: %s\n", strerror(errno)); + return -1; + } + + while (pos < len) { + int rc = write(fd, str, len - pos); + if (rc <= 0) { + log("write failed: %s\n", strerror(errno)); + return -1; + } + pos += rc; + str += rc; + } + + return 0; +} + +int add_device(const struct device *dev) +{ + int rc; + + log("device added:\n"); + print_device(dev); + rc = write_action(sock, DEV_ACTION_ADD_DEVICE) || + write_string(sock, dev->id) || + write_string(sock, dev->name) || + write_string(sock, dev->description) || + write_string(sock, dev->icon_file); + + if (rc) + log("error writing device %s to socket\n", dev->name); + + return rc; +} + +int add_boot_option(const struct boot_option *opt) +{ + int rc; + + log("boot option added:\n"); + print_boot_option(opt); + + rc = write_action(sock, DEV_ACTION_ADD_OPTION) || + write_string(sock, opt->id) || + write_string(sock, opt->name) || + write_string(sock, opt->description) || + write_string(sock, opt->icon_file) || + write_string(sock, opt->boot_image_file) || + write_string(sock, opt->initrd_file) || + write_string(sock, opt->boot_args); + + if (rc) + log("error writing boot option %s to socket\n", opt->name); + + return rc; +} + +int remove_device(const char *dev_path) +{ + return write_action(sock, DEV_ACTION_REMOVE_DEVICE) || + write_string(sock, dev_path); +} + +int connect_to_socket() +{ +#if 1 + int fd; + struct sockaddr_un addr; + + fd = socket(PF_UNIX, SOCK_STREAM, 0); + if (fd == -1) { + log("can't create socket: %s\n", strerror(errno)); + return -1; + } + + addr.sun_family = AF_UNIX; + strcpy(addr.sun_path, socket_file); + + if (connect(fd, (struct sockaddr *)&addr, sizeof(addr))) { + log("can't connect to %s: %s\n", + addr.sun_path, strerror(errno)); + return -1; + } + sock = fd; + + return 0; +#else + int fd; + fd = open("./debug_socket", O_WRONLY | O_CREAT, 0640); + if (fd < 0) { + log("can't create output file: %s\n", strerror(errno)); + return -1; + } + sock = fd; + return 0; +#endif +} + +#define template "mnt-XXXXXX" + +static int mount_device(const char *dev_path, char *mount_path) +{ + char *dir; + int pid, status, rc = -1; + + /* create a unique mountpoint */ + dir = malloc(strlen(tmp_dir) + 2 + strlen(template)); + sprintf(dir, "%s/%s", tmp_dir, template); + + if (!mkdtemp(dir)) { + log("failed to create temporary directory in %s: %s", + tmp_dir, strerror(errno)); + goto out; + } + + pid = fork(); + if (pid == -1) { + log("%s: fork failed: %s\n", __FUNCTION__, strerror(errno)); + goto out; + } + + if (pid == 0) { + execl(mount_bin, mount_bin, dev_path, dir, "-o", "ro", NULL); + exit(EXIT_FAILURE); + } + + if (waitpid(pid, &status, 0) == -1) { + log("%s: waitpid failed: %s\n", __FUNCTION__, strerror(errno)); + goto out; + } + + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { + strcpy(mount_path, dir); + rc = 0; + } + +out: + free(dir); + return rc; +} + +static int unmount_device(const char *dev_path) +{ + int pid, status, rc; + + pid = fork(); + + if (pid == -1) { + log("%s: fork failed: %s\n", __FUNCTION__, strerror(errno)); + return -1; + } + + if (pid == 0) { + execl(umount_bin, umount_bin, dev_path, NULL); + exit(EXIT_FAILURE); + } + + if (waitpid(pid, &status, 0) == -1) { + log("%s: waitpid failed: %s\n", __FUNCTION__, strerror(errno)); + return -1; + } + + rc = !WIFEXITED(status) || WEXITSTATUS(status) != 0; + + return rc; +} + +const char *generic_icon_file(enum generic_icon_type type) +{ + switch (type) { + case ICON_TYPE_DISK: + return "artwork/hdd.png"; + case ICON_TYPE_USB: + return "artwork/usbpen.png"; + case ICON_TYPE_OPTICAL: + return "artwork/cdrom.png"; + case ICON_TYPE_NETWORK: + case ICON_TYPE_UNKNOWN: + break; + } + return "artwork/hdd.png"; +} + +static const struct device fake_boot_devices[] = +{ + { + .id = "fakeDisk0", + .name = "Hard Disk", + .icon_file = "artwork/hdd.png", + }, + { + .id = "fakeDisk1", + .name = "PinkCat Linux CD", + .icon_file = "artwork/cdrom.png", + } +}; + +static const struct boot_option fake_boot_options[] = +{ + { + .id = "fakeBoot0", + .name = "Bloobuntu Linux", + .description = "Boot Bloobuntu Linux", + .icon_file = "artwork/hdd.png", + }, + { + .id = "fakeBoot1", + .name = "Pendora Gore 6", + .description = "Boot Pendora Gora 6", + .icon_file = "artwork/hdd.png", + }, + { + .id = "fakeBoot2", + .name = "Genfoo Minux", + .description = "Boot Genfoo Minux", + .icon_file = "artwork/hdd.png", + }, + { + .id = "fakeBoot3", + .name = "PinkCat Linux", + .description = "Install PinkCat Linux - Graphical install", + .icon_file = "artwork/cdrom.png", + }, +}; + +enum generic_icon_type guess_device_type(void) +{ + const char *bus = getenv("ID_BUS"); + if (streq(bus, "usb")) + return ICON_TYPE_USB; + if (streq(bus, "ata") || streq(bus, "scsi")) + return ICON_TYPE_DISK; + return ICON_TYPE_UNKNOWN; +} + +int main(int argc, char **argv) +{ + char mountpoint[PATH_MAX]; + char *dev_path, *action; + int rc; + + /*if (fork()) + return EXIT_SUCCESS; + */ + action = getenv("ACTION"); + + logf = stdout; + rc = EXIT_SUCCESS; + + if (!action) { + log("missing environment?\n"); + return EXIT_FAILURE; + } + + if (connect_to_socket()) + return EXIT_FAILURE; + + if (streq(action, "fake")) { + log("fake mode"); + + add_device(&fake_boot_devices[0]); + add_boot_option(&fake_boot_options[0]); + add_boot_option(&fake_boot_options[1]); + add_boot_option(&fake_boot_options[2]); + add_device(&fake_boot_devices[1]); + add_boot_option(&fake_boot_options[3]); + + return EXIT_SUCCESS; + } + + dev_path = getenv("DEVNAME"); + if (!dev_path) { + log("missing environment?\n"); + return EXIT_FAILURE; + } + + if (streq(action, "add")) { + if (mount_device(dev_path, mountpoint)) { + log("failed to mount %s\n", dev_path); + return EXIT_FAILURE; + } + + log("mounted %s at %s\n", dev_path, mountpoint); + + iterate_parsers(dev_path, mountpoint); + + } else if (streq(action, "remove")) { + log("%s removed\n", dev_path); + + remove_device(dev_path); + + unmount_device(dev_path); + + } else { + log("invalid action '%s'\n", action); + rc = EXIT_FAILURE; + } + return rc; +} diff --git a/devices/udev-helper.h b/devices/udev-helper.h new file mode 100644 index 0000000..ea5400c --- /dev/null +++ b/devices/udev-helper.h @@ -0,0 +1,27 @@ + +#include "message.h" + +int add_device(const struct device *dev); + +int add_boot_option(const struct boot_option *opt); +void free_boot_option(struct boot_option *opt); + +struct parser { + char *name; + int priority; + int (*parse)(const char *devicepath, const char *mountpoint); + struct parser *next; +}; + +enum generic_icon_type { + ICON_TYPE_DISK, + ICON_TYPE_USB, + ICON_TYPE_OPTICAL, + ICON_TYPE_NETWORK, + ICON_TYPE_UNKNOWN +}; + +enum generic_icon_type guess_device_type(void); +const char *generic_icon_file(enum generic_icon_type type); + +#define streq(a,b) (!strcasecmp((a),(b))) diff --git a/petitboot.c b/petitboot.c new file mode 100644 index 0000000..e69353e --- /dev/null +++ b/petitboot.c @@ -0,0 +1,909 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include "petitboot.h" + +#define _USE_X11 + +static twin_fbdev_t *pboot_fbdev; +static twin_x11_t *pboot_x11; +static twin_screen_t *pboot_screen; + +#define PBOOT_LEFT_PANE_SIZE 200 +#define PBOOT_LEFT_PANE_COLOR 0x80000000 +#define PBOOT_LEFT_LINE_COLOR 0xff000000 + +#define PBOOT_LEFT_FOCUS_WIDTH 80 +#define PBOOT_LEFT_FOCUS_HEIGHT 80 +#define PBOOT_LEFT_FOCUS_XOFF 60 +#define PBOOT_LEFT_FOCUS_YOFF 60 +#define PBOOT_LEFT_FOCUS_XRAD (6 * TWIN_FIXED_ONE) +#define PBOOT_LEFT_FOCUS_YRAD (6 * TWIN_FIXED_ONE) + +#define PBOOT_RIGHT_FOCUS_XOFF 20 +#define PBOOT_RIGHT_FOCUS_YOFF 60 +#define PBOOT_RIGHT_FOCUS_HEIGHT 80 +#define PBOOT_RIGHT_FOCUS_XRAD (6 * TWIN_FIXED_ONE) +#define PBOOT_RIGHT_FOCUS_YRAD (6 * TWIN_FIXED_ONE) + +#define PBOOT_LEFT_ICON_WIDTH 64 +#define PBOOT_LEFT_ICON_HEIGHT 64 +#define PBOOT_LEFT_ICON_XOFF 70 +#define PBOOT_LEFT_ICON_YOFF 70 +#define PBOOT_LEFT_ICON_STRIDE 100 + +#define PBOOT_RIGHT_OPTION_LMARGIN 30 +#define PBOOT_RIGHT_OPTION_RMARGIN 30 +#define PBOOT_RIGHT_OPTION_TMARGIN 70 +#define PBOOT_RIGHT_OPTION_HEIGHT 64 +#define PBOOT_RIGHT_OPTION_STRIDE 100 +#define PBOOT_RIGHT_TITLE_TEXT_SIZE (30 * TWIN_FIXED_ONE) +#define PBOOT_RIGHT_SUBTITLE_TEXT_SIZE (18 * TWIN_FIXED_ONE) +#define PBOOT_RIGHT_TITLE_XOFFSET 80 +#define PBOOT_RIGHT_TITLE_YOFFSET 30 +#define PBOOT_RIGHT_SUBTITLE_XOFFSET 200 +#define PBOOT_RIGHT_SUBTITLE_YOFFSET 50 +#define PBOOT_RIGHT_BADGE_XOFFSET 2 +#define PBOOT_RIGHT_BADGE_YOFFSET 0 + + +#define PBOOT_RIGHT_TITLE_COLOR 0xff000000 +#define PBOOT_RIGHT_SUBTITLE_COLOR 0xff400000 + +#define PBOOT_FOCUS_COLOR 0x10404040 + + +typedef struct _pboot_option pboot_option_t; +typedef struct _pboot_device pboot_device_t; + +struct _pboot_option +{ + char *title; + char *subtitle; + twin_pixmap_t *badge; + twin_pixmap_t *cache; + twin_rect_t box; +}; + +struct _pboot_device +{ + char *id; + twin_pixmap_t *badge; + twin_rect_t box; + int option_count; + pboot_option_t options[PBOOT_MAX_OPTION]; +}; + +static twin_pixmap_t *pboot_cursor; +static int pboot_cursor_hx; +static int pboot_cursor_hy; + +static pboot_device_t *pboot_devices[PBOOT_MAX_DEV]; +static int pboot_dev_count; +static int pboot_dev_sel = -1; +static int pboot_focus_lpane = 1; + +typedef struct _pboot_lpane { + twin_window_t *window; + twin_rect_t focus_box; + int focus_start; + int focus_target; + int focus_curindex; + int mouse_target; +} pboot_lpane_t; + +typedef struct _pboot_rpane { + twin_window_t *window; + twin_rect_t focus_box; + int focus_start; + int focus_target; + int focus_curindex; + int mouse_target; +} pboot_rpane_t; + +static pboot_lpane_t *pboot_lpane; +static pboot_rpane_t *pboot_rpane; + +/* XXX move to twin */ +static inline twin_bool_t twin_rect_intersect(twin_rect_t r1, + twin_rect_t r2) +{ + return !(r1.left > r2.right || + r1.right < r2.left || + r1.top > r2.bottom || + r1.bottom < r2.top); +} + +static void pboot_draw_option_cache(pboot_device_t *dev, pboot_option_t *opt, + int index) +{ + twin_pixmap_t *px; + twin_path_t *path; + twin_fixed_t tx, ty; + + /* Create pixmap */ + px = twin_pixmap_create(TWIN_ARGB32, opt->box.right - opt->box.left, + opt->box.bottom - opt->box.top); + assert(px); + opt->cache = px; + + /* Fill background */ + twin_fill(px, 0x00000000, TWIN_SOURCE, 0, 0, px->width, px->height); + + /* Allocate a path for drawing */ + path = twin_path_create(); + assert(path); + +#if 0 + /* TEST - Bounding rectangle */ + twin_path_rectangle(path, 0, 0, + twin_int_to_fixed(px->width), + twin_int_to_fixed(px->height)); + twin_paint_path(px, PBOOT_RIGHT_TITLE_COLOR, path); + twin_path_empty(path); + twin_fill(px, 0x00000000, TWIN_SOURCE, 2, 2, + px->width - 3, px->height - 3); +#endif + + /* Draw texts */ + twin_path_set_font_size(path, PBOOT_RIGHT_TITLE_TEXT_SIZE); + twin_path_set_font_style(path, TWIN_TEXT_UNHINTED); + tx = twin_int_to_fixed(PBOOT_RIGHT_TITLE_XOFFSET); + ty = twin_int_to_fixed(PBOOT_RIGHT_TITLE_YOFFSET); + twin_path_move (path, tx, ty); + twin_path_utf8 (path, opt->title); + twin_paint_path (px, PBOOT_RIGHT_TITLE_COLOR, path); + twin_path_empty (path); + + if (opt->subtitle) { + twin_path_set_font_size(path, PBOOT_RIGHT_SUBTITLE_TEXT_SIZE); + twin_path_set_font_style(path, TWIN_TEXT_UNHINTED); + tx = twin_int_to_fixed(PBOOT_RIGHT_SUBTITLE_XOFFSET); + ty = twin_int_to_fixed(PBOOT_RIGHT_SUBTITLE_YOFFSET); + twin_path_move (path, tx, ty); + twin_path_utf8 (path, opt->subtitle); + twin_paint_path (px, PBOOT_RIGHT_SUBTITLE_COLOR, path); + twin_path_empty (path); + } + + if (opt->badge) { + twin_operand_t src; + + src.source_kind = TWIN_PIXMAP; + src.u.pixmap = opt->badge; + + twin_composite(px, PBOOT_RIGHT_BADGE_XOFFSET, + PBOOT_RIGHT_BADGE_YOFFSET, + &src, 0, 0, NULL, 0, 0, TWIN_OVER, + opt->badge->width, opt->badge->height); + } + + + /* Destroy path */ + twin_path_destroy(path); +} + +static void pboot_rpane_draw(twin_window_t *window) +{ + twin_pixmap_t *px = window->pixmap; + pboot_rpane_t *rpane = window->client_data; + pboot_device_t *dev; + twin_path_t *path; + twin_fixed_t x, y, w, h; + int i; + + /* Fill background */ + twin_fill(px, 0x00000000, TWIN_SOURCE, 0, 0, px->width, px->height); + + /* Nothing to draw, return */ + if (pboot_dev_sel < 0) + return; + + /* Create a path for use later */ + path = twin_path_create(); + assert(path); + + /* Draw focus box */ + if (rpane->focus_curindex >= 0 && + twin_rect_intersect(rpane->focus_box, px->clip)) { + x = twin_int_to_fixed(rpane->focus_box.left + 2); + y = twin_int_to_fixed(rpane->focus_box.top + 2); + w = twin_int_to_fixed(rpane->focus_box.right - + rpane->focus_box.left - 4); + h = twin_int_to_fixed(rpane->focus_box.bottom - + rpane->focus_box.top - 4); + twin_path_rounded_rectangle(path, x, y, w, h, + PBOOT_RIGHT_FOCUS_XRAD, + PBOOT_RIGHT_FOCUS_YRAD); + if (!pboot_focus_lpane) + twin_paint_path(px, PBOOT_FOCUS_COLOR, path); + else + twin_paint_stroke(px, PBOOT_FOCUS_COLOR, path, + 4 * TWIN_FIXED_ONE); + } + + /* Get device and iterate through options */ + dev = pboot_devices[pboot_dev_sel]; + for (i = 0; i < dev->option_count; i++) { + pboot_option_t *opt = &dev->options[i]; + twin_operand_t src; + + if (opt->title == NULL) + continue; + if (!twin_rect_intersect(opt->box, px->clip)) + continue; + if (opt->cache == NULL) + pboot_draw_option_cache(dev, opt, i); + + src.source_kind = TWIN_PIXMAP; + src.u.pixmap = opt->cache; + + twin_composite(px, opt->box.left, opt->box.top, + &src, 0, 0, NULL, 0, 0, TWIN_OVER, + opt->box.right - opt->box.left, + opt->box.bottom - opt->box.top); + } + + /* Destroy path */ + twin_path_destroy(path); +} + +static twin_time_t pboot_rfocus_timeout (twin_time_t now, void *closure) +{ + int dir = 1, dist, pos; + const int accel[11] = { 7, 4, 2, 1, 1, 1, 1, 2, 3, 4, 5 }; + + dist = abs(pboot_rpane->focus_target - pboot_rpane->focus_start); + dir = dist > 5 ? 5 : dist; + pos = pboot_rpane->focus_target - (int)pboot_rpane->focus_box.top; + if (pos == 0) { + return -1; + } + if (pos < 0) { + dir = -dir; + pos = -pos; + } + twin_window_damage(pboot_rpane->window, + pboot_rpane->focus_box.left, + pboot_rpane->focus_box.top, + pboot_rpane->focus_box.right, + pboot_rpane->focus_box.bottom); + + pboot_rpane->focus_box.top += dir; + pboot_rpane->focus_box.bottom += dir; + + twin_window_damage(pboot_rpane->window, + pboot_rpane->focus_box.left, + pboot_rpane->focus_box.top, + pboot_rpane->focus_box.right, + pboot_rpane->focus_box.bottom); + + twin_window_queue_paint(pboot_rpane->window); + + return accel[(pos * 10) / dist]; +} + +static void pboot_set_rfocus(int index) +{ + pboot_device_t *dev; + + if (pboot_dev_sel < 0 || pboot_dev_sel >= pboot_dev_count) + return; + dev = pboot_devices[pboot_dev_sel]; + if (index < 0 || index >= dev->option_count) + return; + + pboot_rpane->focus_start = pboot_rpane->focus_box.top; + pboot_rpane->focus_target = PBOOT_RIGHT_FOCUS_YOFF + + PBOOT_RIGHT_OPTION_STRIDE * index; + pboot_rpane->focus_curindex = index; + + twin_set_timeout(pboot_rfocus_timeout, 0, NULL); +} + +static void pboot_select_rpane(void) +{ + if (pboot_focus_lpane == 0) + return; + pboot_focus_lpane = 0; + + twin_screen_set_active(pboot_screen, pboot_rpane->window->pixmap); + + twin_window_damage(pboot_lpane->window, + pboot_lpane->focus_box.left, + pboot_lpane->focus_box.top, + pboot_lpane->focus_box.right, + pboot_lpane->focus_box.bottom); + + twin_window_damage(pboot_rpane->window, + pboot_rpane->focus_box.left, + pboot_rpane->focus_box.top, + pboot_rpane->focus_box.right, + pboot_rpane->focus_box.bottom); + + twin_window_queue_paint(pboot_lpane->window); + twin_window_queue_paint(pboot_rpane->window); + + pboot_set_rfocus(0); +} + +static void pboot_select_lpane(void) +{ + if (pboot_focus_lpane == 1) + return; + pboot_focus_lpane = 1; + + twin_screen_set_active(pboot_screen, pboot_lpane->window->pixmap); + + twin_window_damage(pboot_lpane->window, + pboot_lpane->focus_box.left, + pboot_lpane->focus_box.top, + pboot_lpane->focus_box.right, + pboot_lpane->focus_box.bottom); + + twin_window_damage(pboot_rpane->window, + pboot_rpane->focus_box.left, + pboot_rpane->focus_box.top, + pboot_rpane->focus_box.right, + pboot_rpane->focus_box.bottom); + + twin_window_queue_paint(pboot_lpane->window); + twin_window_queue_paint(pboot_rpane->window); +} + +static void pboot_rpane_mousetrack(twin_coord_t x, twin_coord_t y) +{ + pboot_device_t *dev; + pboot_option_t *opt; + int candidate = -1; + + if (pboot_dev_sel < 0 || pboot_dev_sel >= pboot_dev_count) + return; + dev = pboot_devices[pboot_dev_sel]; + + if (y < PBOOT_RIGHT_OPTION_TMARGIN) + goto miss; + candidate = (y - PBOOT_RIGHT_OPTION_TMARGIN) / + PBOOT_RIGHT_OPTION_STRIDE; + if (candidate >= dev->option_count) { + candidate = -1; + goto miss; + } + if (candidate == pboot_rpane->mouse_target) + return; + opt = &dev->options[candidate]; + if (x < opt->box.left || x > opt->box.right || + y < opt->box.top || y > opt->box.bottom) { + candidate = -1; + goto miss; + } + + /* Ok, so now, we know the mouse hit an icon that wasn't the same + * as the previous one, we trigger a focus change + */ + pboot_set_rfocus(candidate); + + miss: + pboot_rpane->mouse_target = candidate; +} + +static twin_bool_t pboot_rpane_event (twin_window_t *window, + twin_event_t *event) +{ + /* filter out all mouse events */ + switch(event->kind) { + case TwinEventEnter: + case TwinEventMotion: + case TwinEventLeave: + pboot_select_rpane(); + pboot_rpane_mousetrack(event->u.pointer.x, event->u.pointer.y); + return TWIN_TRUE; + case TwinEventButtonDown: + case TwinEventButtonUp: + return TWIN_TRUE; + case TwinEventKeyDown: + switch(event->u.key.key) { + case KEY_UP: + pboot_set_rfocus(pboot_rpane->focus_curindex - 1); + return TWIN_TRUE; + case KEY_DOWN: + pboot_set_rfocus(pboot_rpane->focus_curindex + 1); + return TWIN_TRUE; + case KEY_LEFT: + pboot_select_lpane(); + return TWIN_TRUE; + default: + break; + } + break; + default: + break; + } + return TWIN_FALSE; +} + + +int pboot_add_option(int devindex, const char *title, + const char *subtitle, twin_pixmap_t *badge) +{ + pboot_device_t *dev; + pboot_option_t *opt; + twin_coord_t width; + int index; + + if (devindex < 0 || devindex >= pboot_dev_count) + return -1; + dev = pboot_devices[devindex]; + + if (dev->option_count >= PBOOT_MAX_OPTION) + return -1; + index = dev->option_count++; + opt = &dev->options[index]; + + opt->title = malloc(strlen(title) + 1); + strcpy(opt->title, title); + + if (subtitle) { + opt->subtitle = malloc(strlen(subtitle) + 1); + strcpy(opt->subtitle, subtitle); + } else + opt->subtitle = NULL; + + opt->badge = badge; + opt->cache = NULL; + + width = pboot_rpane->window->pixmap->width - + (PBOOT_RIGHT_OPTION_LMARGIN + PBOOT_RIGHT_OPTION_RMARGIN); + + opt->box.left = PBOOT_RIGHT_OPTION_LMARGIN; + opt->box.right = opt->box.left + width; + opt->box.top = PBOOT_RIGHT_OPTION_TMARGIN + + index * PBOOT_RIGHT_OPTION_STRIDE; + opt->box.bottom = opt->box.top + PBOOT_RIGHT_OPTION_HEIGHT; + + return index; +} + + +static void pboot_set_device_select(int sel) +{ + LOG("%s: %d -> %d\n", __FUNCTION__, pboot_dev_sel, sel); + if (sel == pboot_dev_sel || sel >= pboot_dev_count) + return; + pboot_dev_sel = sel; + pboot_rpane->focus_curindex = -1; + pboot_rpane->mouse_target = -1; + pboot_rpane->focus_box.top = -2*PBOOT_RIGHT_FOCUS_HEIGHT; + pboot_rpane->focus_box.bottom = pboot_rpane->focus_box.top + + PBOOT_RIGHT_FOCUS_HEIGHT; + twin_window_damage(pboot_rpane->window, 0, 0, + pboot_rpane->window->pixmap->width, + pboot_rpane->window->pixmap->height); + twin_window_queue_paint(pboot_rpane->window); +} + +static twin_time_t pboot_lfocus_timeout (twin_time_t now, void *closure) +{ + int dir = 1, dist, pos; + const int accel[11] = { 7, 4, 2, 1, 1, 1, 1, 2, 3, 4, 5 }; + + dist = abs(pboot_lpane->focus_target - pboot_lpane->focus_start); + pos = pboot_lpane->focus_target - (int)pboot_lpane->focus_box.top; + if (pos == 0) { + pboot_set_device_select(pboot_lpane->focus_curindex); + return -1; + } + if (pos < 0) { + dir = -1; + pos = -pos; + } + twin_window_damage(pboot_lpane->window, + pboot_lpane->focus_box.left, + pboot_lpane->focus_box.top, + pboot_lpane->focus_box.right, + pboot_lpane->focus_box.bottom); + + pboot_lpane->focus_box.top += dir; + pboot_lpane->focus_box.bottom += dir; + + twin_window_damage(pboot_lpane->window, + pboot_lpane->focus_box.left, + pboot_lpane->focus_box.top, + pboot_lpane->focus_box.right, + pboot_lpane->focus_box.bottom); + + twin_window_queue_paint(pboot_lpane->window); + + return accel[(pos * 10) / dist]; +} + +static void pboot_set_lfocus(int index) +{ + if (index >= pboot_dev_count) + return; + + pboot_lpane->focus_start = pboot_lpane->focus_box.top; + + if (index < 0) + pboot_lpane->focus_target = 0 - PBOOT_LEFT_FOCUS_HEIGHT; + else + pboot_lpane->focus_target = PBOOT_LEFT_FOCUS_YOFF + + PBOOT_LEFT_ICON_STRIDE * index; + + pboot_lpane->focus_curindex = index; + + twin_set_timeout(pboot_lfocus_timeout, 0, NULL); +} + +static void pboot_lpane_mousetrack(twin_coord_t x, twin_coord_t y) +{ + int candidate = -1; + twin_coord_t icon_top; + + if (x < PBOOT_LEFT_ICON_XOFF || + x > (PBOOT_LEFT_ICON_XOFF + PBOOT_LEFT_ICON_WIDTH)) + goto miss; + if (y < PBOOT_LEFT_ICON_YOFF) + goto miss; + candidate = (y - PBOOT_LEFT_ICON_YOFF) / PBOOT_LEFT_ICON_STRIDE; + if (candidate >= pboot_dev_count) { + candidate = -1; + goto miss; + } + if (candidate == pboot_lpane->mouse_target) + return; + icon_top = PBOOT_LEFT_ICON_YOFF + + candidate * PBOOT_LEFT_ICON_STRIDE; + if (y > (icon_top + PBOOT_LEFT_ICON_HEIGHT)) { + candidate = -1; + goto miss; + } + + /* Ok, so now, we know the mouse hit an icon that wasn't the same + * as the previous one, we trigger a focus change + */ + pboot_set_lfocus(candidate); + + miss: + pboot_lpane->mouse_target = candidate; +} + +static twin_bool_t pboot_lpane_event (twin_window_t *window, + twin_event_t *event) +{ + /* filter out all mouse events */ + switch(event->kind) { + case TwinEventEnter: + case TwinEventMotion: + case TwinEventLeave: + pboot_select_lpane(); + pboot_lpane_mousetrack(event->u.pointer.x, event->u.pointer.y); + return TWIN_TRUE; + case TwinEventButtonDown: + case TwinEventButtonUp: + return TWIN_TRUE; + case TwinEventKeyDown: + switch(event->u.key.key) { + case KEY_UP: + if (pboot_lpane->focus_curindex > 0) + pboot_set_lfocus( + pboot_lpane->focus_curindex - 1); + return TWIN_TRUE; + case KEY_DOWN: + pboot_set_lfocus(pboot_lpane->focus_curindex + 1); + return TWIN_TRUE; + case KEY_RIGHT: + pboot_select_rpane(); + return TWIN_TRUE; + default: + break; + } + break; + default: + break; + } + return TWIN_FALSE; +} + +twin_bool_t pboot_event_filter(twin_screen_t *screen, + twin_event_t *event) +{ + switch(event->kind) { + case TwinEventEnter: + case TwinEventMotion: + case TwinEventLeave: + case TwinEventButtonDown: + case TwinEventButtonUp: + if (pboot_cursor != NULL) + twin_screen_set_cursor(pboot_screen, pboot_cursor, + pboot_cursor_hx, + pboot_cursor_hy); + break; + case TwinEventKeyDown: + case TwinEventKeyUp: + twin_screen_set_cursor(pboot_screen, NULL, 0, 0); + break; + default: + break; + } + return TWIN_FALSE; +} + +static void pboot_lpane_draw(twin_window_t *window) +{ + twin_pixmap_t *px = window->pixmap; + pboot_lpane_t *lpane = window->client_data; + twin_path_t *path; + twin_fixed_t x, y, w, h; + int i; + + /* Fill background */ + twin_fill(px, PBOOT_LEFT_PANE_COLOR, TWIN_SOURCE, + 0, 0, px->width, px->height); + + /* Create a path for use later */ + path = twin_path_create(); + assert(path); + + /* Draw right line if needed */ + if (px->clip.right > (PBOOT_LEFT_PANE_SIZE - 4)) { + x = twin_int_to_fixed(PBOOT_LEFT_PANE_SIZE - 4); + y = twin_int_to_fixed(px->height); + twin_path_rectangle(path, x, 0, 0x40000, y); + twin_paint_path(px, PBOOT_LEFT_LINE_COLOR, path); + twin_path_empty(path); + } + + /* Draw focus box */ + if (lpane->focus_curindex >= 0 && + twin_rect_intersect(lpane->focus_box, px->clip)) { + x = twin_int_to_fixed(lpane->focus_box.left + 2); + y = twin_int_to_fixed(lpane->focus_box.top + 2); + w = twin_int_to_fixed(lpane->focus_box.right - + lpane->focus_box.left - 4); + h = twin_int_to_fixed(lpane->focus_box.bottom - + lpane->focus_box.top - 4); + twin_path_rounded_rectangle(path, x, y, w, h, + PBOOT_LEFT_FOCUS_XRAD, + PBOOT_LEFT_FOCUS_YRAD); + if (pboot_focus_lpane) + twin_paint_path(px, PBOOT_FOCUS_COLOR, path); + else + twin_paint_stroke(px, PBOOT_FOCUS_COLOR, path, + 4 * TWIN_FIXED_ONE); + } + + /* Draw icons */ + for (i = 0; i < pboot_dev_count; i++) { + pboot_device_t *dev = pboot_devices[i]; + twin_operand_t src; + + if (!twin_rect_intersect(dev->box, px->clip)) + continue; + + src.source_kind = TWIN_PIXMAP; + src.u.pixmap = dev->badge; + + twin_composite(px, dev->box.left, dev->box.top, + &src, 0, 0, NULL, 0, 0, TWIN_OVER, + dev->box.right - dev->box.left, + dev->box.bottom - dev->box.top); + + } + + /* Destroy path */ + twin_path_destroy(path); +} + +static void pboot_create_panels(void) +{ + /* left pane */ + pboot_lpane = calloc(1, sizeof(pboot_lpane_t)); + assert(pboot_lpane); + + pboot_lpane->window = twin_window_create(pboot_screen, TWIN_ARGB32, + TwinWindowPlain, + 0, 0, PBOOT_LEFT_PANE_SIZE, + pboot_screen->height); + assert(pboot_lpane->window); + + pboot_lpane->window->draw = pboot_lpane_draw; + pboot_lpane->window->event = pboot_lpane_event; + pboot_lpane->window->client_data = pboot_lpane; + pboot_lpane->focus_curindex = -1; + pboot_lpane->focus_box.left = PBOOT_LEFT_FOCUS_XOFF; + pboot_lpane->focus_box.top = -2*PBOOT_LEFT_FOCUS_HEIGHT; + pboot_lpane->focus_box.right = pboot_lpane->focus_box.left + + PBOOT_LEFT_FOCUS_WIDTH; + pboot_lpane->focus_box.bottom = pboot_lpane->focus_box.top + + PBOOT_LEFT_FOCUS_HEIGHT; + pboot_lpane->mouse_target = -1; + twin_window_show(pboot_lpane->window); + + /* right pane */ + pboot_rpane = calloc(1, sizeof(pboot_rpane_t)); + assert(pboot_rpane); + + pboot_rpane->window = twin_window_create(pboot_screen, TWIN_ARGB32, + TwinWindowPlain, + PBOOT_LEFT_PANE_SIZE, 0, + pboot_screen->width - + PBOOT_LEFT_PANE_SIZE, + pboot_screen->height); + assert(pboot_rpane->window); + + pboot_rpane->window->draw = pboot_rpane_draw; + pboot_rpane->window->event = pboot_rpane_event; + pboot_rpane->window->client_data = pboot_rpane; + + pboot_rpane->focus_curindex = -1; + pboot_rpane->focus_box.left = PBOOT_RIGHT_FOCUS_XOFF; + pboot_rpane->focus_box.top = -2*PBOOT_RIGHT_FOCUS_HEIGHT; + pboot_rpane->focus_box.right = pboot_rpane->window->pixmap->width - + 2 * PBOOT_RIGHT_FOCUS_XOFF; + pboot_rpane->focus_box.bottom = pboot_rpane->focus_box.top + + PBOOT_RIGHT_FOCUS_HEIGHT; + pboot_rpane->mouse_target = -1; + twin_window_show(pboot_rpane->window); +} + +int pboot_add_device(const char *dev_id, const char *name, + twin_pixmap_t *pixmap) +{ + int index; + pboot_device_t *dev; + + if (pboot_dev_count >= PBOOT_MAX_DEV) + return -1; + + index = pboot_dev_count++; + + dev = malloc(sizeof(*dev)); + memset(dev, 0, sizeof(*dev)); + dev->id = malloc(strlen(dev_id) + 1); + strcpy(dev->id, dev_id); + dev->badge = pixmap; + dev->box.left = PBOOT_LEFT_ICON_XOFF; + dev->box.right = dev->box.left + PBOOT_LEFT_ICON_WIDTH; + dev->box.top = PBOOT_LEFT_ICON_YOFF + + PBOOT_LEFT_ICON_STRIDE * index; + dev->box.bottom = dev->box.top + PBOOT_LEFT_ICON_HEIGHT; + + pboot_devices[index] = dev; + + twin_window_damage(pboot_lpane->window, + dev->box.left, dev->box.top, + dev->box.right, dev->box.bottom); + twin_window_queue_paint(pboot_lpane->window); + + return index; +} + +int pboot_remove_device(const char *dev_id) +{ + int i, new_dev_index; + pboot_device_t *dev = NULL; + + /* find the matching device */ + for (i = 0; i < pboot_dev_count; i++) { + if (!strcmp(pboot_devices[i]->id, dev_id)) { + dev = pboot_devices[i]; + break; + } + } + + if (!dev) + return TWIN_FALSE; + + /* select the newly-focussed device */ + if (i == pboot_dev_count - 1) + new_dev_index = i - 1; + else + new_dev_index = i + 1; + + memmove(pboot_devices + i, pboot_devices + i + 1, + sizeof(*pboot_devices) * (pboot_dev_count + i - 1)); + + pboot_devices[--pboot_dev_count] = NULL; + + pboot_set_device_select(new_dev_index); + twin_window_damage(pboot_lpane->window, + dev->box.left, dev->box.top, + dev->box.right, dev->box.bottom); + twin_window_queue_paint(pboot_lpane->window); + + /* todo: free device & options */ + + return TWIN_TRUE; +} + +static void exitfunc(void) +{ + if (pboot_fbdev) + twin_fbdev_destroy(pboot_fbdev); + pboot_fbdev = NULL; +} + +static void sigint(int sig) +{ + exitfunc(); + syscall(__NR_exit); +} + +int main(int argc, char **argv) +{ + twin_pixmap_t *pic; + + atexit(exitfunc); + signal(SIGINT, sigint); + +#ifdef _USE_X11 + pboot_x11 = twin_x11_create(XOpenDisplay(0), 1024, 768); + if (pboot_x11 == NULL) { + perror("failed to create x11 screen !\n"); + return 1; + } + pboot_screen = pboot_x11->screen; +#else + /* Create screen and mouse drivers */ + pboot_fbdev = twin_fbdev_create(-1, SIGUSR1); + if (pboot_fbdev == NULL) { + perror("failed to create fbdev screen !\n"); + return 1; + } + pboot_screen = pboot_fbdev->screen; + twin_linux_mouse_create(NULL, pboot_screen); +#endif + + if (pboot_fbdev != NULL) { + pboot_cursor = twin_load_X_cursor("artwork/cursor", 2, + &pboot_cursor_hx, + &pboot_cursor_hy); + if (pboot_cursor == NULL) + pboot_cursor = + twin_get_default_cursor(&pboot_cursor_hx, + &pboot_cursor_hy); + } + + /* Set background pixmap */ + LOG("loading background..."); + pic = twin_png_to_pixmap("artwork/background.png", TWIN_ARGB32); + LOG("%s\n", pic ? "ok" : "failed"); + if (pic) + twin_screen_set_background(pboot_screen, pic); + + /* Init more stuffs */ + pboot_create_panels(); + twin_window_queue_paint(pboot_lpane->window); + twin_window_queue_paint(pboot_rpane->window); + + if (!pboot_start_device_discovery()) { + LOG("Couldn't start device discovery!\n"); + return 1; + } + + pboot_set_lfocus(0); + twin_screen_set_active(pboot_screen, pboot_lpane->window->pixmap); + pboot_screen->event_filter = pboot_event_filter; + + /* Console switch */ + if (pboot_fbdev) + twin_fbdev_activate(pboot_fbdev); + + /* Process events */ + twin_dispatch (); + + return 0; +} diff --git a/petitboot.h b/petitboot.h new file mode 100644 index 0000000..aeac73b --- /dev/null +++ b/petitboot.h @@ -0,0 +1,15 @@ + +#include + +#define LOG(fmt...) printf(fmt) + +#define PBOOT_MAX_DEV 16 +#define PBOOT_MAX_OPTION 16 + +int pboot_add_device(const char *dev_id, const char *name, + twin_pixmap_t *pixmap); +int pboot_add_option(int devindex, const char *title, + const char *subtitle, twin_pixmap_t *badge); +int pboot_remove_device(const char *dev_id); + +int pboot_start_device_discovery(void);