diff options
author | Jeremy Kerr <jk@ozlabs.org> | 2008-12-15 15:22:34 +1100 |
---|---|---|
committer | Jeremy Kerr <jk@ozlabs.org> | 2008-12-15 15:22:34 +1100 |
commit | 32e6a41f33e5576716b351bd473a27939fe94fa1 (patch) | |
tree | 0d6b75ac0a02d2496416095405cb9498777c3beb /discover | |
parent | 000a92b4fa909c432732ac3ed8f28eeeaeac70ee (diff) | |
download | petitboot-32e6a41f33e5576716b351bd473a27939fe94fa1.zip petitboot-32e6a41f33e5576716b351bd473a27939fe94fa1.tar.gz |
Initial support for multiple UIs
Move the device discovery code from separate udev helpers to a single
process to listen on two sockets: one SOCK_DGRAM for incoming udev
events, and one SOCK_STREAM for UIs to connect.
Initial support for client/server infrastructure, still need to wire-up
the udev messages.
Signed-off-by: Jeremy Kerr <jk@ozlabs.org>
Diffstat (limited to 'discover')
-rw-r--r-- | discover/discover-server.c | 227 | ||||
-rw-r--r-- | discover/discover-server.h | 10 | ||||
-rw-r--r-- | discover/kboot-parser.c | 288 | ||||
-rw-r--r-- | discover/log.c | 24 | ||||
-rw-r--r-- | discover/log.h | 6 | ||||
-rw-r--r-- | discover/message.h | 31 | ||||
-rw-r--r-- | discover/native-parser.c | 124 | ||||
-rw-r--r-- | discover/params.c | 595 | ||||
-rw-r--r-- | discover/params.h | 6 | ||||
-rw-r--r-- | discover/parser.c | 85 | ||||
-rw-r--r-- | discover/parser.h | 46 | ||||
-rw-r--r-- | discover/paths.c | 141 | ||||
-rw-r--r-- | discover/paths.h | 53 | ||||
-rw-r--r-- | discover/pb-discover.c | 34 | ||||
-rw-r--r-- | discover/pb-discover.h | 6 | ||||
-rw-r--r-- | discover/udev.c | 208 | ||||
-rw-r--r-- | discover/udev.h | 26 | ||||
-rw-r--r-- | discover/waiter.c | 83 | ||||
-rw-r--r-- | discover/waiter.h | 23 | ||||
-rw-r--r-- | discover/yaboot-cfg.c | 491 | ||||
-rw-r--r-- | discover/yaboot-cfg.h | 30 | ||||
-rw-r--r-- | discover/yaboot-parser.c | 235 |
22 files changed, 2772 insertions, 0 deletions
diff --git a/discover/discover-server.c b/discover/discover-server.c new file mode 100644 index 0000000..8358f06 --- /dev/null +++ b/discover/discover-server.c @@ -0,0 +1,227 @@ + +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdint.h> +#include <errno.h> +#include <assert.h> + +#include <sys/socket.h> +#include <sys/un.h> +#include <asm/byteorder.h> + +#include <talloc/talloc.h> + +#include "ui/common/device.h" +#include "pb-protocol/pb-protocol.h" +#include "list/list.h" + +#include "log.h" +#include "waiter.h" + +struct discover_server { + int socket; + struct waiter *waiter; + struct list clients; +}; + +struct client { + struct list_item list; + int fd; +}; + + +static int server_destructor(void *arg) +{ + struct discover_server *server = arg; + + if (server->waiter) + waiter_unregister(server->waiter); + + if (server->socket >= 0) + close(server->socket); + + return 0; +} + +static int client_destructor(void *arg) +{ + struct client *client = arg; + + if (client->fd >= 0) + close(client->fd); + + list_remove(&client->list); + + return 0; + +} + +static void print_clients(struct discover_server *server) + __attribute__((unused)); + +static void print_clients(struct discover_server *server) +{ + struct client *client; + + printf("current clients [%p,%p,%p]:\n", + &server->clients.head, + server->clients.head.prev, + server->clients.head.next); + list_for_each_entry(&server->clients, client, list) + printf("\t[%p,%p,%p] client: %d\n", &client->list, + client->list.prev, client->list.next, + client->fd); +} + +static struct boot_option options[] = { + { + .id = "1.1", + .name = "meep one", + .description = "meep description one", + .icon_file = "meep.one.png", + .boot_args = "root=/dev/sda1", + }, +}; + +static struct device device = { + .id = "1", + .name = "meep", + .description = "meep description", + .icon_file = "meep.png", + .n_options = 1, + .options = options, +}; + +static int client_write_message(struct discover_server *server, + struct client *client, struct pb_protocol_message *message) +{ + int rc; + + rc = pb_protocol_write_message(client->fd, message); + if (rc) + talloc_free(client); + + return rc; +} + +static int write_add_message(struct discover_server *server, + struct client *client, struct device *dev) +{ + struct pb_protocol_message *message; + int len; + + len = pb_protocol_device_len(dev); + + message = pb_protocol_create_message(client, + PB_PROTOCOL_ACTION_ADD, len); + if (!message) + return -1; + + pb_protocol_serialise_device(dev, message->payload, len); + + return client_write_message(server, client, message); +} + +static int write_remove_message(struct discover_server *server, + struct client *client, char *dev_id) +{ + struct pb_protocol_message *message; + int len; + + len = strlen(dev_id) + sizeof(uint32_t); + + message = pb_protocol_create_message(client, + PB_PROTOCOL_ACTION_REMOVE, len); + if (!message) + return -1; + + pb_protocol_serialise_string(message->payload, dev_id); + + return client_write_message(server, client, message); +} + +static int discover_server_process(void *arg) +{ + struct discover_server *server = arg; + struct client *client; + int fd; + + + len = sizeof(addr); + + /* accept the incoming connection */ + fd = accept(server->socket, NULL, 0); + if (!fd) { + pb_log("accept: %s\n", strerror(errno)); + return 0; + } + + /* add to our list of clients */ + client = talloc(server, struct client); + list_add(&server->clients, &client->list); + + talloc_set_destructor(client, client_destructor); + + client->fd = fd; + + /* send existing devices to client */ + write_add_message(server, client, &device); + + sleep(2); + + write_remove_message(server, client, "1"); + + return 0; +} + +struct discover_server *discover_server_init(void) +{ + struct discover_server *server; + struct sockaddr_un addr; + + server = talloc(NULL, struct discover_server); + if (!server) + return NULL; + + server->waiter = NULL; + list_init(&server->clients); + + unlink(PB_SOCKET_PATH); + + server->socket = socket(AF_UNIX, SOCK_STREAM, 0); + if (server->socket < 0) { + pb_log("error creating server socket: %s\n", strerror(errno)); + goto out_err; + } + + talloc_set_destructor(server, server_destructor); + + addr.sun_family = AF_UNIX; + strcpy(addr.sun_path, PB_SOCKET_PATH); + + if (bind(server->socket, (struct sockaddr *)&addr, sizeof(addr))) { + pb_log("error binding server socket: %s\n", strerror(errno)); + goto out_err; + } + + if (listen(server->socket, 8)) { + pb_log("server socket listen: %s\n", strerror(errno)); + goto out_err; + } + + server->waiter = waiter_register(server->socket, WAIT_IN, + discover_server_process, server); + + return server; + +out_err: + talloc_free(server); + return NULL; +} + +void discover_server_destroy(struct discover_server *server) +{ + talloc_free(server); +} + diff --git a/discover/discover-server.h b/discover/discover-server.h new file mode 100644 index 0000000..ff27f15 --- /dev/null +++ b/discover/discover-server.h @@ -0,0 +1,10 @@ +#ifndef _DISCOVER_SERVER_H +#define _DISCOVER_SERVER_H + +struct discover_server; + +struct discover_server *discover_server_init(void); + +void discover_server_destroy(struct discover_server *server); + +#endif /* _DISCOVER_SERVER_H */ diff --git a/discover/kboot-parser.c b/discover/kboot-parser.c new file mode 100644 index 0000000..df2e762 --- /dev/null +++ b/discover/kboot-parser.c @@ -0,0 +1,288 @@ +#define _GNU_SOURCE + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <unistd.h> + +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include "parser.h" +#include "params.h" + +#define buf_size 1024 + +static const char *devpath; + +static int param_is_ignored(const char *param) +{ + static const char *ignored_options[] = + { "message", "timeout", "default", NULL }; + const char **str; + + for (str = ignored_options; *str; str++) + if (streq(*str, param)) + return 1; + return 0; +} + +/** + * Splits a name=value pair, with value terminated by @term (or nul). if there + * is no '=', then only the value is populated, and *name is set to NULL. The + * string is modified in place. + * + * Returns the next byte to process, or null if we've hit the end of the + * string. + * + * */ +static char *get_param_pair(char *str, char **name_out, char **value_out, + char terminator) +{ + char *sep, *tmp, *name, *value; + + /* terminate the value */ + tmp = strchr(str, terminator); + if (tmp) + *tmp = 0; + else + tmp = NULL; + + sep = strchr(str, '='); + if (!sep) { + *name_out = NULL; + *value_out = str; + return tmp ? tmp + 1 : NULL; + } + + /* terminate the name */ + *sep = 0; + + /* remove leading spaces */ + for (name = str; isspace(*name); name++); + for (value = sep + 1; isspace(*value); value++); + + /* .. and trailing ones.. */ + for (sep--; isspace(*sep); sep--) + *sep = 0; + for (sep = value + strlen(value) - 1; isspace(*sep); sep--) + *sep = 0; + + *name_out = name; + *value_out = value; + + return tmp ? tmp + 1 : NULL; +} + +struct global_option { + char *name; + char *value; +}; + + +static struct global_option global_options[] = { + { .name = "root" }, + { .name = "initrd" }, + { .name = "video" }, + { .name = NULL } +}; + +/* + * Check if an option (name=value) is a global option. If so, store it in + * the global options table, and return 1. Otherwise, return 0. + */ +static int check_for_global_option(const char *name, const char *value) +{ + int i; + + for (i = 0; global_options[i].name ;i++) { + if (!strcmp(name, global_options[i].name)) { + global_options[i].value = strdup(value); + return 1; + } + } + return 0; +} + +static char *get_global_option(const char *name) +{ + int i; + + for (i = 0; global_options[i].name ;i++) + if (!strcmp(name, global_options[i].name)) + return global_options[i].value; + + return NULL; +} + +static int parse_option(struct boot_option *opt, char *config) +{ + char *pos, *name, *value, *root, *initrd, *cmdline, *tmp; + + root = initrd = cmdline = NULL; + + /* remove quotes around the value */ + while (*config == '"' || *config == '\'') + config++; + + pos = config + strlen(config) - 1; + while (*pos == '"' || *pos == '\'') + *(pos--) = 0; + + if (!strlen(pos)) + return 0; + + pos = strchr(config, ' '); + + /* if there's no space, it's only a kernel image with no params */ + if (!pos) { + opt->boot_image_file = resolve_path(config, devpath); + opt->description = strdup(config); + return 1; + } + + *pos = 0; + opt->boot_image_file = resolve_path(config, devpath); + + cmdline = malloc(buf_size); + *cmdline = 0; + + for (pos++; pos;) { + pos = get_param_pair(pos, &name, &value, ' '); + + if (!name) { + strcat(cmdline, " "); + strcat(cmdline, value); + + } else if (streq(name, "initrd")) { + initrd = value; + + } else if (streq(name, "root")) { + root = value; + + } else { + strcat(cmdline, " "); + *(value - 1) = '='; + strcat(cmdline, name); + } + } + + if (!root) + root = get_global_option("root"); + if (!initrd) + initrd = get_global_option("initrd"); + + if (initrd) { + asprintf(&tmp, "initrd=%s %s", initrd, cmdline); + free(cmdline); + cmdline = tmp; + + opt->initrd_file = resolve_path(initrd, devpath); + } + + if (root) { + asprintf(&tmp, "root=%s %s", root, cmdline); + free(cmdline); + cmdline = tmp; + + } else if (initrd) { + /* if there's an initrd but no root, fake up /dev/ram0 */ + asprintf(&tmp, "root=/dev/ram0 %s", cmdline); + free(cmdline); + cmdline = tmp; + } + + pb_log("kboot cmdline: %s\n", cmdline); + opt->boot_args = cmdline; + + asprintf(&opt->description, "%s %s", + config, opt->boot_args); + + return 1; +} + +static void parse_buf(struct device *dev, char *buf) +{ + char *pos, *name, *value; + int sent_device = 0; + + for (pos = buf; pos;) { + struct boot_option opt; + + pos = get_param_pair(pos, &name, &value, '\n'); + + pb_log("kboot param: '%s' = '%s'\n", name, value); + + if (name == NULL || param_is_ignored(name)) + continue; + + if (*name == '#') + continue; + + if (check_for_global_option(name, value)) + continue; + + memset(&opt, 0, sizeof(opt)); + opt.name = strdup(name); + + if (parse_option(&opt, value)) + if (!sent_device++) + add_device(dev); + add_boot_option(&opt); + + free(opt.name); + } +} + +static int parse(const char *device) +{ + char *filepath, *buf; + int fd, len, rc = 0; + struct stat stat; + struct device *dev; + + devpath = device; + + filepath = resolve_path("/etc/kboot.conf", devpath); + + fd = open(filepath, O_RDONLY); + if (fd < 0) + goto out_free_path; + + if (fstat(fd, &stat)) + goto out_close; + + buf = malloc(stat.st_size + 1); + if (!buf) + goto out_close;; + + len = read(fd, buf, stat.st_size); + if (len < 0) + goto out_free_buf; + buf[len] = 0; + + dev = malloc(sizeof(*dev)); + memset(dev, 0, sizeof(*dev)); + dev->id = strdup(device); + dev->icon_file = strdup(generic_icon_file(guess_device_type())); + + parse_buf(dev, buf); + + rc = 1; + +out_free_buf: + free(buf); +out_close: + close(fd); +out_free_path: + free(filepath); + return rc; +} + +struct parser kboot_parser = { + .name = "kboot.conf parser", + .priority = 98, + .parse = parse +}; diff --git a/discover/log.c b/discover/log.c new file mode 100644 index 0000000..189f31e --- /dev/null +++ b/discover/log.c @@ -0,0 +1,24 @@ + +#include <stdarg.h> +#include <stdio.h> + +#include "log.h" + +static FILE *logf; + +void pb_log(const char *fmt, ...) +{ + va_list ap; + FILE *stream; + + stream = logf ? logf : stdout; + + va_start(ap, fmt); + vfprintf(stream, fmt, ap); + va_end(ap); +} + +void pb_log_set_stream(FILE *stream) +{ + logf = stream; +} diff --git a/discover/log.h b/discover/log.h new file mode 100644 index 0000000..7f9b01f --- /dev/null +++ b/discover/log.h @@ -0,0 +1,6 @@ +#ifndef _LOG_H +#define _LOG_H + +void pb_log(const char *fmt, ...); + +#endif /* _LOG_H */ diff --git a/discover/message.h b/discover/message.h new file mode 100644 index 0000000..d0b0e34 --- /dev/null +++ b/discover/message.h @@ -0,0 +1,31 @@ + +#ifndef _MESSAGE_H +#define _MESSAGE_H + +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; + } *options; + int n_options; +}; + + +#endif /* _MESSAGE_H */ diff --git a/discover/native-parser.c b/discover/native-parser.c new file mode 100644 index 0000000..24713b1 --- /dev/null +++ b/discover/native-parser.c @@ -0,0 +1,124 @@ + +#include "parser.h" +#include "params.h" +#include "paths.h" + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +const char *conf_filename = "/boot/petitboot.conf"; + +static struct boot_option *cur_opt; +static struct device *dev; +static const char *devpath; +int device_added; + +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 = resolve_path(value, devpath); + + else if (streq(name, "icon")) + opt->icon_file = resolve_path(value, devpath); + + else if (streq(name, "initrd")) + opt->initrd_file =resolve_path(value, devpath); + + 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 = resolve_path(value, devpath); +} + +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 *device) +{ + char *filepath; + int rc; + + filepath = resolve_path(conf_filename, device); + + cur_opt = NULL; + dev = malloc(sizeof(*dev)); + memset(dev, 0, sizeof(*dev)); + dev->id = strdup(device); + + rc = pm_process(filepath, section, parameter); + if (!rc) + return 0; + + 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/discover/params.c b/discover/params.c new file mode 100644 index 0000000..76a1451 --- /dev/null +++ b/discover/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 <stdlib.h> +#include <stdio.h> +#include <ctype.h> +#include <errno.h> + +#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: + * + * <file> :== { <section> } EOF + * + * <section> :== <section header> { <parameter line> } + * + * <section header> :== '[' NAME ']' + * + * <parameter line> :== 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 <bufr>. + */ + +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 + * <sfunc>. False if <sfunc> failed or if a lexical error was + * encountered. + * + * ------------------------------------------------------------------------ ** + */ + { + int c; + int i; + int end; + char *func = "params.c:Section() -"; + + i = 0; /* <i> is the offset of the next free byte in bufr[] and */ + end = 0; /* <end> 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 <end> */ + /* will be one less than <i>. */ + + 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 <end>. 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/discover/params.h b/discover/params.h new file mode 100644 index 0000000..02a39c9 --- /dev/null +++ b/discover/params.h @@ -0,0 +1,6 @@ + +#define BOOL int + +BOOL pm_process( char *FileName, + BOOL (*sfunc)(char *), + BOOL (*pfunc)(char *, char *) ); diff --git a/discover/parser.c b/discover/parser.c new file mode 100644 index 0000000..5e50dcb --- /dev/null +++ b/discover/parser.c @@ -0,0 +1,85 @@ + +#include <petitboot-paths.h> +#include <stdlib.h> +#include <string.h> + +#include "parser.h" + +extern struct parser native_parser; +extern struct parser yaboot_parser; +extern struct parser kboot_parser; + +/* array of parsers, ordered by priority */ +static struct parser *parsers[] = { + &native_parser, + &yaboot_parser, + &kboot_parser, + NULL +}; + +void iterate_parsers(const char *devpath, const char *mountpoint) +{ + int i; + + pb_log("trying parsers for %s\n", devpath); + + for (i = 0; parsers[i]; i++) { + pb_log("\ttrying parser '%s'\n", parsers[i]->name); + /* just use a dummy device path for now */ + if (parsers[i]->parse(devpath)) + return; + } + pb_log("\tno boot_options found\n"); +} + +/* convenience functions for parsers */ +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); +} + +const char *generic_icon_file(enum generic_icon_type type) +{ + switch (type) { + case ICON_TYPE_DISK: + return artwork_pathname("hdd.png"); + case ICON_TYPE_USB: + return artwork_pathname("usbpen.png"); + case ICON_TYPE_OPTICAL: + return artwork_pathname("cdrom.png"); + case ICON_TYPE_NETWORK: + case ICON_TYPE_UNKNOWN: + break; + } + return artwork_pathname("hdd.png"); +} + diff --git a/discover/parser.h b/discover/parser.h new file mode 100644 index 0000000..9c6fb35 --- /dev/null +++ b/discover/parser.h @@ -0,0 +1,46 @@ + +#ifndef _PARSERS_H +#define _PARSERS_H + +#include <stdarg.h> +#include "message.h" + +struct parser { + char *name; + int priority; + int (*parse)(const char *device); + struct parser *next; +}; + +enum generic_icon_type { + ICON_TYPE_DISK, + ICON_TYPE_USB, + ICON_TYPE_OPTICAL, + ICON_TYPE_NETWORK, + ICON_TYPE_UNKNOWN +}; + +#define streq(a,b) (!strcasecmp((a),(b))) + +/* general functions provided by parsers.c */ +void iterate_parsers(const char *devpath, const char *mountpoint); + +void free_device(struct device *dev); +void free_boot_option(struct boot_option *opt); + +const char *generic_icon_file(enum generic_icon_type type); + +/* functions provided by udev-helper or the test wrapper */ +void pb_log(const char *fmt, ...); + +int mount_device(const char *dev_path); + +char *resolve_path(const char *path, const char *current_dev); +const char *mountpoint_for_device(const char *dev_path); + +enum generic_icon_type guess_device_type(void); + +int add_device(const struct device *dev); +int add_boot_option(const struct boot_option *opt); + +#endif /* _PARSERS_H */ diff --git a/discover/paths.c b/discover/paths.c new file mode 100644 index 0000000..2373c28 --- /dev/null +++ b/discover/paths.c @@ -0,0 +1,141 @@ +#define _GNU_SOURCE + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#include "paths.h" + +static char *mount_base; + +struct device_map { + char *dev, *mnt; +}; + +#define DEVICE_MAP_SIZE 32 +static struct device_map device_map[DEVICE_MAP_SIZE]; + +char *encode_label(const char *label) +{ + char *str, *c; + int i; + + /* the label can be expanded by up to four times */ + str = malloc(strlen(label) * 4 + 1); + c = str; + + for (i = 0; i < strlen(label); i++) { + + if (label[i] == '/' || label[i] == '\\') { + sprintf(c, "\\x%02x", label[i]); + c += 4; + continue; + } + + *(c++) = label[i]; + } + + *c = '\0'; + + return str; +} + +char *parse_device_path(const char *dev_str, const char *cur_dev) +{ + char *dev, tmp[256], *enc; + + if (!strncasecmp(dev_str, "uuid=", 5)) { + asprintf(&dev, "/dev/disk/by-uuid/%s", dev_str + 5); + return dev; + } + + if (!strncasecmp(dev_str, "label=", 6)) { + enc = encode_label(dev_str + 6); + asprintf(&dev, "/dev/disk/by-label/%s", enc); + free(enc); + return dev; + } + + /* normalise '/dev/foo' to 'foo' for easy comparisons, we'll expand + * back before returning. + */ + if (!strncmp(dev_str, "/dev/", 5)) + dev_str += 5; + + /* PS3 hack: if we're reading from a ps3dx device, and we refer to + * a sdx device, remap to ps3dx */ + if (cur_dev && !strncmp(cur_dev, "/dev/ps3d", 9) + && !strncmp(dev_str, "sd", 2)) { + snprintf(tmp, 255, "ps3d%s", dev_str + 2); + dev_str = tmp; + } + + return join_paths("/dev", dev_str); +} + +const char *mountpoint_for_device(const char *dev) +{ + int i; + + if (!strncmp(dev, "/dev/", 5)) + dev += 5; + + /* check existing entries in the map */ + for (i = 0; (i < DEVICE_MAP_SIZE) && device_map[i].dev; i++) + if (!strcmp(device_map[i].dev, dev)) + return device_map[i].mnt; + + if (i == DEVICE_MAP_SIZE) + return NULL; + + device_map[i].dev = strdup(dev); + device_map[i].mnt = join_paths(mount_base, dev); + return device_map[i].mnt; +} + +char *resolve_path(const char *path, const char *current_dev) +{ + char *ret; + const char *devpath, *sep; + + sep = strchr(path, ':'); + if (!sep) { + devpath = mountpoint_for_device(current_dev); + ret = join_paths(devpath, path); + } else { + /* parse just the device name into dev */ + char *tmp, *dev; + tmp = strndup(path, sep - path); + dev = parse_device_path(tmp, current_dev); + + devpath = mountpoint_for_device(dev); + ret = join_paths(devpath, sep + 1); + + free(dev); + free(tmp); + } + + return ret; +} + +void set_mount_base(const char *path) +{ + if (mount_base) + free(mount_base); + mount_base = strdup(path); +} + +char *join_paths(const char *a, const char *b) +{ + char *full_path; + + full_path = malloc(strlen(a) + strlen(b) + 2); + + strcpy(full_path, a); + if (b[0] != '/' && a[strlen(a) - 1] != '/') + strcat(full_path, "/"); + strcat(full_path, b); + + return full_path; +} + diff --git a/discover/paths.h b/discover/paths.h new file mode 100644 index 0000000..26d4ce4 --- /dev/null +++ b/discover/paths.h @@ -0,0 +1,53 @@ +#ifndef PATHS_H +#define PATHS_H + +/** + * Given a string (eg /dev/sda1, sda1 or UUID=B8E53381CA9EA0E3), parse the + * device path (eg /dev/sda1). Any device descriptions read from config files + * should be parsed into the path first. + * + * The cur_dev is provided for some remapping situations. If NULL is provided, + * no remapping will be done. + * + * Returns a newly-allocated string. + */ +char *parse_device_path(const char *dev_str, const char *current_device); + +/** + * Get the mountpoint for a device. + */ +const char *mountpoint_for_device(const char *dev); + +/** + * Resolve a path given in a config file, to a path in the local filesystem. + * Paths may be of the form: + * device:path (eg /dev/sda:/boot/vmlinux) + * + * or just a path: + * /boot/vmlinux + * - in this case, the current mountpoint is used. + * + * Returns a newly-allocated string containing a full path to the file in path + */ +char *resolve_path(const char *path, const char *current_device); + + +/** + * Set the base directory for newly-created mountpoints + */ +void set_mount_base(const char *path); + +/** + * Utility function for joining two paths. Adds a / between a and b if + * required. + * + * Returns a newly-allocated string. + */ +char *join_paths(const char *a, const char *b); + +/** + * encode a disk label (or uuid) for use in a symlink. + */ +char *encode_label(const char *label); + +#endif /* PATHS_H */ diff --git a/discover/pb-discover.c b/discover/pb-discover.c new file mode 100644 index 0000000..45b6ba1 --- /dev/null +++ b/discover/pb-discover.c @@ -0,0 +1,34 @@ + +#include <stdlib.h> +#include <signal.h> + +#include "udev.h" +#include "discover-server.h" +#include "waiter.h" +#include "log.h" + + +int main(void) +{ + struct discover_server *server; + struct udev *udev; + + /* we look for closed sockets when we write, so ignore SIGPIPE */ + signal(SIGPIPE, SIG_IGN); + + udev = udev_init(); + if (!udev) + return EXIT_FAILURE; + + server = discover_server_init(); + if (!server) + return EXIT_FAILURE; + + for (;;) { + if (waiter_poll()) + return EXIT_FAILURE; + } + + + return EXIT_SUCCESS; +} diff --git a/discover/pb-discover.h b/discover/pb-discover.h new file mode 100644 index 0000000..a48557c --- /dev/null +++ b/discover/pb-discover.h @@ -0,0 +1,6 @@ +#ifndef _PB_DISCOVER_H +#define _PB_DISCOVER_H + +int register_waiter(int fd, int *callback(void *), void *arg); + +#endif /* _PB_DISCOVER_H */ diff --git a/discover/udev.c b/discover/udev.c new file mode 100644 index 0000000..9c1b399 --- /dev/null +++ b/discover/udev.c @@ -0,0 +1,208 @@ + +#define _GNU_SOURCE + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> + +#include <talloc/talloc.h> + +#include "udev.h" +#include "log.h" +#include "waiter.h" +#include "pb-discover.h" + +#define PBOOT_DEVICE_SOCKET "/tmp/petitboot.udev" + +#define max(a, b) ((a) > (b) ? (a) : (b)) + +struct udev { + int socket; +}; + +static void parse_event_params(struct udev_event *event, char *buf, int len) +{ + int param_len, name_len, value_len; + struct param *param; + char *sep; + + for (; len > 0; len -= param_len + 1, buf += param_len + 1) { + + /* find the length of the whole parameter */ + param_len = strnlen(buf, len); + if (!param_len) { + /* multiple NULs? skip over */ + param_len = 1; + continue; + } + + /* find the separator */ + sep = memchr(buf, '=', param_len); + if (!sep) + continue; + + name_len = sep - buf; + value_len = param_len - name_len - 1; + + /* update the params array */ + event->params = talloc_realloc(event, event->params, + struct param, ++event->n_params); + param = &event->params[event->n_params - 1]; + + param->name = talloc_strndup(event, buf, name_len); + param->value = talloc_strndup(event, sep + 1, value_len); + } +} + +static const char *event_param(struct udev_event *event, const char *name) +{ + int i; + + for (i = 0; i < event->n_params; i++) + if (!strcasecmp(event->params[i].name, name)) + return event->params[i].value; + + return NULL; +} + +static void print_event(struct udev_event *event) +{ + const char *action, *params[] = { + "DEVNAME", "ID_TYPE", "ID_BUS", "ID_FS_UUID", "ID_FS_LABEL", + NULL, + }; + int i; + + action = event->action == UDEV_ACTION_ADD ? "add" : "remove"; + + pb_log("udev %s event:\n", action); + printf("\tdevice: %s\n", event->device); + + for (i = 0; params[i]; i++) + printf("\t%-12s => %s\n", + params[i], event_param(event, params[i])); + +} + +static void handle_udev_message(struct udev *udev, char *buf, int len) +{ + char *sep, *device; + enum udev_action action; + struct udev_event *event; + int device_len; + + /* we should see an <action>@<device>\0 at the head of the buffer */ + sep = strchr(buf, '@'); + if (!sep) + return; + + /* terminate the action string */ + *sep = '\0'; + len -= sep - buf + 1; + + if (!strcmp(buf, "add")) { + action = UDEV_ACTION_ADD; + + } else if (!strcmp(buf, "remove")) { + action = UDEV_ACTION_REMOVE; + + } else { + return; + } + + /* initialise the device string */ + device = sep + 1; + device_len = strnlen(device, len); + if (!device_len) + return; + + /* now we have an action and a device, we can construct an event */ + event = talloc(udev, struct udev_event); + event->action = action; + event->device = talloc_strndup(event, device, device_len); + event->n_params = 0; + event->params = NULL; + + len -= device_len + 1; + parse_event_params(event, device + device_len + 1, len); + + print_event(event); + + talloc_free(event); + + return; +} + +static int udev_process(void *arg) +{ + struct udev *udev = arg; + char buf[4096]; + int len; + + len = recvfrom(udev->socket, buf, sizeof(buf), 0, NULL, NULL); + + if (len < 0) { + pb_log("udev socket read failed: %s", strerror(errno)); + return -1; + } + + if (len == 0) + return 0; + + handle_udev_message(udev, buf, len); + + return 0; +} + +static int udev_destructor(void *p) +{ + struct udev *udev = p; + + if (udev->socket >= 0) + close(udev->socket); + + return 0; +} + +struct udev *udev_init(void) +{ + struct sockaddr_un addr; + struct udev *udev; + + unlink(PBOOT_DEVICE_SOCKET); + + udev = talloc(NULL, struct udev); + + udev->socket = socket(PF_UNIX, SOCK_DGRAM, 0); + if (udev->socket < 0) { + pb_log("Error creating udev socket: %s\n", strerror(errno)); + goto out_err; + } + + talloc_set_destructor(udev, udev_destructor); + + addr.sun_family = AF_UNIX; + strcpy(addr.sun_path, PBOOT_DEVICE_SOCKET); + + if (bind(udev->socket, (struct sockaddr *)&addr, sizeof(addr))) { + pb_log("Error binding udev socket: %s\n", strerror(errno)); + goto out_err; + } + + waiter_register(udev->socket, WAIT_IN, udev_process, udev); + + return udev; + +out_err: + talloc_free(udev); + return NULL; +} + +void udev_destroy(struct udev *udev) +{ + talloc_free(udev); +} diff --git a/discover/udev.h b/discover/udev.h new file mode 100644 index 0000000..c30adc9 --- /dev/null +++ b/discover/udev.h @@ -0,0 +1,26 @@ +#ifndef _UDEV_H +#define _UDEV_H + +enum udev_action { + UDEV_ACTION_ADD, + UDEV_ACTION_REMOVE, +}; + +struct udev_event { + enum udev_action action; + char *device; + + struct param { + char *name; + char *value; + } *params; + int n_params; +}; + +struct udev; + +struct udev *udev_init(void); + +void udev_destroy(struct udev *udev); + +#endif /* _UDEV_H */ diff --git a/discover/waiter.c b/discover/waiter.c new file mode 100644 index 0000000..21dd4a5 --- /dev/null +++ b/discover/waiter.c @@ -0,0 +1,83 @@ + +#include <poll.h> +#include <string.h> +#include <assert.h> + +#include <talloc/talloc.h> + +#include "waiter.h" + +struct waiter { + int fd; + int events; + waiter_cb callback; + void *arg; +}; + +static struct waiter *waiters; +static int n_waiters; + +struct waiter *waiter_register(int fd, int events, + waiter_cb callback, void *arg) +{ + struct waiter *waiter; + + n_waiters++; + + waiters = talloc_realloc(NULL, waiters, struct waiter, n_waiters); + waiter = &waiters[n_waiters - 1]; + + waiter->fd = fd; + waiter->events = events; + waiter->callback = callback; + waiter->arg = arg; + + return 0; +} + +void waiter_remove(struct waiter *waiter) +{ + int i; + + i = waiter - waiters; + assert(i >= 0 && i < n_waiters); + + n_waiters--; + memmove(&waiters[i], &waiters[i+1], n_waiters - i); + + waiters = talloc_realloc(NULL, waiters, struct waiter, n_waiters); +} + +int waiter_poll(void) +{ + static struct pollfd *pollfds; + static int n_pollfds; + int i, rc; + + if (n_waiters > n_pollfds) { + pollfds = talloc_realloc(NULL, pollfds, + struct pollfd, n_waiters); + } + + for (i = 0; i < n_waiters; i++) { + pollfds[i].fd = waiters[i].fd; + pollfds[i].events = waiters[i].events; + pollfds[i].revents = 0; + } + + rc = poll(pollfds, n_waiters, -1); + + if (rc <= 0) + return rc; + + for (i = 0; i < n_waiters; i++) { + if (pollfds[i].revents) { + rc = waiters[i].callback(waiters[i].arg); + + if (rc) + waiter_remove(&waiters[i]); + } + } + + return 0; +} diff --git a/discover/waiter.h b/discover/waiter.h new file mode 100644 index 0000000..ff8a5ff --- /dev/null +++ b/discover/waiter.h @@ -0,0 +1,23 @@ +#ifndef _WAITER_H +#define _WAITER_H + +#include <poll.h> + +struct waiter; + +enum events { + WAIT_IN = POLLIN, + WAIT_OUT = POLLOUT, +}; + +typedef int (*waiter_cb)(void *); + +struct waiter *waiter_register(int fd, int events, + waiter_cb callback, void *arg); + +void waiter_remove(struct waiter *waiter); + +int waiter_poll(void); +#endif /* _WAITER_H */ + + diff --git a/discover/yaboot-cfg.c b/discover/yaboot-cfg.c new file mode 100644 index 0000000..6b007e4 --- /dev/null +++ b/discover/yaboot-cfg.c @@ -0,0 +1,491 @@ +/* + * cfg.c - Handling and parsing of yaboot.conf + * + * Copyright (C) 1995 Werner Almesberger + * 1996 Jakub Jelinek + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <setjmp.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <stdio.h> + +#define prom_printf printf +#define prom_putchar putchar +#define prom_vprintf vprintf + +/* Imported functions */ +extern int strcasecmp(const char *s1, const char *s2); + +typedef enum { + cft_strg, cft_flag, cft_end +} CONFIG_TYPE; + +typedef struct { + CONFIG_TYPE type; + char *name; + void *data; +} CONFIG; + +#define MAX_TOKEN 200 +#define MAX_VAR_NAME MAX_TOKEN +char *cfg_get_default (void); + +CONFIG cf_options[] = +{ + {cft_strg, "device", NULL}, + {cft_strg, "partition", NULL}, + {cft_strg, "default", NULL}, + {cft_strg, "timeout", NULL}, + {cft_strg, "password", NULL}, + {cft_flag, "restricted", NULL}, + {cft_strg, "message", NULL}, + {cft_strg, "root", NULL}, + {cft_strg, "ramdisk", NULL}, + {cft_flag, "read-only", NULL}, + {cft_flag, "read-write", NULL}, + {cft_strg, "append", NULL}, + {cft_strg, "initrd", NULL}, + {cft_flag, "initrd-prompt", NULL}, + {cft_strg, "initrd-size", NULL}, + {cft_flag, "pause-after", NULL}, + {cft_strg, "pause-message", NULL}, + {cft_strg, "init-code", NULL}, + {cft_strg, "init-message", NULL}, + {cft_strg, "fgcolor", NULL}, + {cft_strg, "bgcolor", NULL}, + {cft_strg, "ptypewarning", NULL}, + {cft_end, NULL, NULL}}; + +CONFIG cf_image[] = +{ + {cft_strg, "image", NULL}, + {cft_strg, "label", NULL}, + {cft_strg, "alias", NULL}, + {cft_flag, "single-key", NULL}, + {cft_flag, "restricted", NULL}, + {cft_strg, "device", NULL}, + {cft_strg, "partition", NULL}, + {cft_strg, "root", NULL}, + {cft_strg, "ramdisk", NULL}, + {cft_flag, "read-only", NULL}, + {cft_flag, "read-write", NULL}, + {cft_strg, "append", NULL}, + {cft_strg, "literal", NULL}, + {cft_strg, "initrd", NULL}, + {cft_flag, "initrd-prompt", NULL}, + {cft_strg, "initrd-size", NULL}, + {cft_flag, "pause-after", NULL}, + {cft_strg, "pause-message", NULL}, + {cft_flag, "novideo", NULL}, + {cft_strg, "sysmap", NULL}, + {cft_end, NULL, NULL}}; + +static char flag_set; +static char *last_token = NULL, *last_item = NULL, *last_value = NULL; +static int line_num; +static int back = 0; /* can go back by one char */ +static char *currp = NULL; +static char *endp = NULL; +static char *file_name = NULL; +static CONFIG *curr_table = cf_options; +static jmp_buf env; + +static struct IMAGES { + CONFIG table[sizeof (cf_image) / sizeof (cf_image[0])]; + struct IMAGES *next; +} *images = NULL; + +void cfg_error (char *msg,...) +{ + va_list ap; + + va_start (ap, msg); + prom_printf ("Config file error: "); + prom_vprintf (msg, ap); + va_end (ap); + prom_printf (" near line %d in file %s\n", line_num, file_name); + longjmp (env, 1); +} + +void cfg_warn (char *msg,...) +{ + va_list ap; + + va_start (ap, msg); + prom_printf ("Config file warning: "); + prom_vprintf (msg, ap); + va_end (ap); + prom_printf (" near line %d in file %s\n", line_num, file_name); +} + +int cfg_getc () +{ + if (currp == endp) + return EOF; + return *currp++; +} + +#define next_raw next +static int next (void) +{ + int ch; + + if (!back) + return cfg_getc (); + ch = back; + back = 0; + return ch; +} + +static void again (int ch) +{ + back = ch; +} + +static char *cfg_get_token (void) +{ + char buf[MAX_TOKEN + 1]; + char *here; + int ch, escaped; + + if (last_token) { + here = last_token; + last_token = NULL; + return here; + } + while (1) { + while (ch = next (), ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r') + if (ch == '\n' || ch == '\r') + line_num++; + if (ch == EOF || ch == (int)NULL) + return NULL; + if (ch != '#') + break; + while (ch = next_raw (), (ch != '\n' && ch != '\r')) + if (ch == EOF) + return NULL; + line_num++; + } + if (ch == '=') + return strdup ("="); + if (ch == '"') { + here = buf; + while (here - buf < MAX_TOKEN) { + if ((ch = next ()) == EOF) + cfg_error ("EOF in quoted string"); + if (ch == '"') { + *here = 0; + return strdup (buf); + } + if (ch == '\\') { + ch = next (); + switch (ch) { + case '"': + case '\\': + break; + case '\n': + case '\r': + while ((ch = next ()), ch == ' ' || ch == '\t'); + if (!ch) + continue; + again (ch); + ch = ' '; + break; + case 'n': + ch = '\n'; + break; + default: + cfg_error ("Bad use of \\ in quoted string"); + } + } else if ((ch == '\n') || (ch == '\r')) + cfg_error ("newline is not allowed in quoted strings"); + *here++ = ch; + } + cfg_error ("Quoted string is too long"); + return 0; /* not reached */ + } + here = buf; + escaped = 0; + while (here - buf < MAX_TOKEN) { + if (escaped) { + if (ch == EOF) + cfg_error ("\\ precedes EOF"); + if (ch == '\n') + line_num++; + else + *here++ = ch == '\t' ? ' ' : ch; + escaped = 0; + } else { + if (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r' || ch == '#' || + ch == '=' || ch == EOF) { + again (ch); + *here = 0; + return strdup (buf); + } + if (!(escaped = (ch == '\\'))) + *here++ = ch; + } + ch = next (); + } + cfg_error ("Token is too long"); + return 0; /* not reached */ +} + +static void cfg_return_token (char *token) +{ + last_token = token; +} + +static int cfg_next (char **item, char **value) +{ + char *this; + + if (last_item) { + *item = last_item; + *value = last_value; + last_item = NULL; + return 1; + } + *value = NULL; + if (!(*item = cfg_get_token ())) + return 0; + if (!strcmp (*item, "=")) + cfg_error ("Syntax error"); + if (!(this = cfg_get_token ())) + return 1; + if (strcmp (this, "=")) { + cfg_return_token (this); + return 1; + } + if (!(*value = cfg_get_token ())) + cfg_error ("Value expected at EOF"); + if (!strcmp (*value, "=")) + cfg_error ("Syntax error after %s", *item); + return 1; +} + +#if 0 +// The one and only call to this procedure is commented out +// below, so we don't need this unless we decide to use it again. +static void cfg_return (char *item, char *value) +{ + last_item = item; + last_value = value; +} +#endif + +static int cfg_set (char *item, char *value) +{ + CONFIG *walk; + + if (!strcasecmp (item, "image")) { + struct IMAGES **p = &images; + + while (*p) + p = &((*p)->next); + *p = (struct IMAGES *)malloc (sizeof (struct IMAGES)); + if (*p == NULL) { + prom_printf("malloc error in cfg_set\n"); + return -1; + } + (*p)->next = 0; + curr_table = ((*p)->table); + memcpy (curr_table, cf_image, sizeof (cf_image)); + } + for (walk = curr_table; walk->type != cft_end; walk++) { + if (walk->name && !strcasecmp (walk->name, item)) { + if (value && walk->type != cft_strg) + cfg_warn ("'%s' doesn't have a value", walk->name); + else if (!value && walk->type == cft_strg) + cfg_warn ("Value expected for '%s'", walk->name); + else { + if (walk->data) + cfg_warn ("Duplicate entry '%s'", walk->name); + if (walk->type == cft_flag) + walk->data = &flag_set; + else if (walk->type == cft_strg) + walk->data = value; + } + break; + } + } + if (walk->type != cft_end) + return 1; +// cfg_return (item, value); + return 0; +} + +int cfg_parse (char *cfg_file, char *buff, int len) +{ + char *item, *value; + + file_name = cfg_file; + currp = buff; + endp = currp + len; + + if (setjmp (env)) + return -1; + while (1) { + if (!cfg_next (&item, &value)) + return 0; + if (!cfg_set (item, value)) { +#if DEBUG + prom_printf("Can't set item %s to value %s\n", item, value); +#endif + } + free (item); + } +} + +static char *cfg_get_strg_i (CONFIG * table, char *item) +{ + CONFIG *walk; + + for (walk = table; walk->type != cft_end; walk++) + if (walk->name && !strcasecmp (walk->name, item)) + return walk->data; + return 0; +} + +char *cfg_get_strg (char *image, char *item) +{ + struct IMAGES *p; + char *label, *alias; + char *ret; + + if (!image) + return cfg_get_strg_i (cf_options, item); + for (p = images; p; p = p->next) { + label = cfg_get_strg_i (p->table, "label"); + if (!label) { + label = cfg_get_strg_i (p->table, "image"); + alias = strrchr (label, '/'); + if (alias) + label = alias + 1; + } + alias = cfg_get_strg_i (p->table, "alias"); + if (!strcmp (label, image) || (alias && !strcmp (alias, image))) { + ret = cfg_get_strg_i (p->table, item); + if (!ret) + ret = cfg_get_strg_i (cf_options, item); + return ret; + } + } + return 0; +} + +int cfg_get_flag (char *image, char *item) +{ + return !!cfg_get_strg (image, item); +} + +static int printl_count = 0; +static void printlabel (char *label, int defflag) +{ + int len = strlen (label); + + if (!printl_count) + prom_printf ("\n"); + prom_printf ("%s %s",defflag?"*":" ", label); + while (len++ < 25) + prom_putchar (' '); + printl_count++; + if (printl_count == 3) + printl_count = 0; +} + +void cfg_print_images (void) +{ + struct IMAGES *p; + char *label, *alias; + + char *ret = cfg_get_default();//strg_i (cf_options, "default"); + int defflag=0; + + printl_count = 0; + for (p = images; p; p = p->next) { + label = cfg_get_strg_i (p->table, "label"); + if (!label) { + label = cfg_get_strg_i (p->table, "image"); + alias = strrchr (label, '/'); + if (alias) + label = alias + 1; + } + if(!strcmp(ret,label)) + defflag=1; + else + defflag=0; + alias = cfg_get_strg_i (p->table, "alias"); + printlabel (label, defflag); + if (alias) + printlabel (alias, 0); + } + prom_printf("\n"); +} + +char *cfg_get_default (void) +{ + char *label; + char *ret = cfg_get_strg_i (cf_options, "default"); + + if (ret) + return ret; + if (!images) + return 0; + ret = cfg_get_strg_i (images->table, "label"); + if (!ret) { + ret = cfg_get_strg_i (images->table, "image"); + label = strrchr (ret, '/'); + if (label) + ret = label + 1; + } + return ret; +} + +char *cfg_next_image(char *prev) +{ + struct IMAGES *p; + char *label, *alias; + int wantnext = 0; + + if (!prev) + wantnext = 1; + + for (p = images; p; p = p->next) { + label = cfg_get_strg_i (p->table, "label"); + if (!label) { + label = cfg_get_strg_i (p->table, "image"); + alias = strrchr (label, '/'); + if (alias) + label = alias + 1; + } + if (wantnext) + return label; + if (!strcmp(prev, label)) + wantnext = 1; + } + return NULL; +} +/* + * Local variables: + * c-file-style: "k&r" + * c-basic-offset: 5 + * End: + */ diff --git a/discover/yaboot-cfg.h b/discover/yaboot-cfg.h new file mode 100644 index 0000000..2ab4fec --- /dev/null +++ b/discover/yaboot-cfg.h @@ -0,0 +1,30 @@ +/* + * cfg.h - config file parsing definitions + * + * Copyright (C) 1999 Benjamin Herrenschmidt + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef CFG_H +#define CFG_H + +extern int cfg_parse(char *cfg_file, char *buff, int len); +extern char* cfg_get_strg(char *image, char *item); +extern int cfg_get_flag(char *image, char *item); +extern void cfg_print_images(void); +extern char* cfg_get_default(void); +extern char* cfg_next_image(char *); +#endif diff --git a/discover/yaboot-parser.c b/discover/yaboot-parser.c new file mode 100644 index 0000000..27b4b78 --- /dev/null +++ b/discover/yaboot-parser.c @@ -0,0 +1,235 @@ + +#include "parser.h" +#include "params.h" +#include "paths.h" +#include "yaboot-cfg.h" + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <ctype.h> +#include <sys/param.h> + +static struct device *dev; +static char *devpath; +static char *defimage; + +char * +make_params(char *label, char *params) +{ + char *p, *q; + static char buffer[2048]; + + q = buffer; + *q = 0; + + p = cfg_get_strg(label, "literal"); + if (p) { + strcpy(q, p); + q = strchr(q, 0); + if (params) { + if (*p) + *q++ = ' '; + strcpy(q, params); + } + return buffer; + } + + p = cfg_get_strg(label, "root"); + if (p) { + strcpy (q, "root="); + strcpy (q + 5, p); + q = strchr (q, 0); + *q++ = ' '; + } + if (cfg_get_flag(label, "read-only")) { + strcpy (q, "ro "); + q += 3; + } + if (cfg_get_flag(label, "read-write")) { + strcpy (q, "rw "); + q += 3; + } + p = cfg_get_strg(label, "ramdisk"); + if (p) { + strcpy (q, "ramdisk="); + strcpy (q + 8, p); + q = strchr (q, 0); + *q++ = ' '; + } + p = cfg_get_strg(label, "initrd-size"); + if (p) { + strcpy (q, "ramdisk_size="); + strcpy (q + 13, p); + q = strchr (q, 0); + *q++ = ' '; + } + if (cfg_get_flag(label, "novideo")) { + strcpy (q, "video=ofonly"); + q = strchr (q, 0); + *q++ = ' '; + } + p = cfg_get_strg (label, "append"); + if (p) { + strcpy (q, p); + q = strchr (q, 0); + *q++ = ' '; + } + *q = 0; + if (params) + strcpy(q, params); + + return buffer; +} + +static 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); +} + +void process_image(char *label) +{ + struct boot_option opt; + char *cfgopt; + + memset(&opt, 0, sizeof(opt)); + + opt.name = label; + cfgopt = cfg_get_strg(label, "image"); + opt.boot_image_file = resolve_path(cfgopt, devpath); + if (cfgopt == defimage) + pb_log("This one is default. What do we do about it?\n"); + + cfgopt = cfg_get_strg(label, "initrd"); + if (cfgopt) + opt.initrd_file = resolve_path(cfgopt, devpath); + + opt.boot_args = make_params(label, NULL); + + add_boot_option(&opt); + + if (opt.initrd_file) + free(opt.initrd_file); +} + +static int yaboot_parse(const char *device) +{ + char *filepath; + char *conf_file; + char *tmpstr; + ssize_t conf_len; + int fd; + struct stat st; + char *label; + + devpath = strdup(device); + + filepath = resolve_path("/etc/yaboot.conf", devpath); + + fd = open(filepath, O_RDONLY); + if (fd < 0) { + free(filepath); + filepath = resolve_path("/yaboot.conf", devpath); + fd = open(filepath, O_RDONLY); + + if (fd < 0) + return 0; + } + + if (fstat(fd, &st)) { + close(fd); + return 0; + } + + conf_file = malloc(st.st_size+1); + if (!conf_file) { + close(fd); + return 0; + } + + conf_len = read(fd, conf_file, st.st_size); + if (conf_len < 0) { + close(fd); + return 0; + } + conf_file[conf_len] = 0; + + close(fd); + + if (cfg_parse(filepath, conf_file, conf_len)) { + pb_log("Error parsing yaboot.conf\n"); + return 0; + } + + free(filepath); + + dev = malloc(sizeof(*dev)); + memset(dev, 0, sizeof(*dev)); + dev->id = strdup(devpath); + if (cfg_get_strg(0, "init-message")) { + char *newline; + dev->description = strdup(cfg_get_strg(0, "init-message")); + newline = strchr(dev->description, '\n'); + if (newline) + *newline = 0; + } + dev->icon_file = strdup(generic_icon_file(guess_device_type())); + + /* If we have a 'partiton=' directive, update the default devpath + * to use that instead of the current device */ + tmpstr = cfg_get_strg(0, "partition"); + if (tmpstr) { + char *endp; + int partnr = strtol(tmpstr, &endp, 10); + if (endp != tmpstr && !*endp) { + char *new_dev, *tmp; + + new_dev = malloc(strlen(devpath) + strlen(tmpstr) + 1); + if (!new_dev) + return 0; + + strcpy(new_dev, devpath); + + /* Strip digits (partition number) from string */ + endp = new_dev + strlen(devpath) - 1; + while (isdigit(*endp)) + *(endp--) = 0; + + /* and add our own... */ + sprintf(endp + 1, "%d", partnr); + + tmp = devpath; + devpath = parse_device_path(new_dev, devpath); + free(tmp); + free(new_dev); + } + } + + defimage = cfg_get_default(); + if (!defimage) + return 0; + defimage = cfg_get_strg(defimage, "image"); + + label = cfg_next_image(NULL); + if (!label || !check_and_add_device(dev)) + return 0; + + do { + process_image(label); + } while ((label = cfg_next_image(label))); + + return 1; +} + +struct parser yaboot_parser = { + .name = "yaboot.conf parser", + .priority = 99, + .parse = yaboot_parse +}; |