Patchwork [16/18] petitboot: Add system helper routines

login
register
mail settings
Submitter Geoff Levand
Date March 25, 2009, 10:36 p.m.
Message ID <20090325223547.833552887@am.sony.com>>
Download mbox | patch
Permalink /patch/25121/
State Accepted
Delegated to: Jeremy Kerr
Headers show

Comments

Geoff Levand - March 25, 2009, 10:36 p.m.
Add some system helper routines for UI convenience:

  pb_run_system()
  pb_run_kexec()
  pb_elf_hash()
  pb_cat_hash()
  pb_opt_hash()

Signed-off-by: Geoff Levand <geoffrey.levand@am.sony.com>
---

This still needs some work to finish, but it is mostly working and
is needed to kexec anything.

-Geoff

 rules.mk              |    2 
 ui/common/pb-system.c |  535 ++++++++++++++++++++++++++++++++++++++++++++++++++
 ui/common/pb-system.h |   53 ++++
 3 files changed, 589 insertions(+), 1 deletion(-)
Jeremy Kerr - March 30, 2009, 10:37 a.m.
> -ui_common_objs = ui/common/discover-client.o
> +ui_common_objs = ui/common/discover-client.o ui/common/pb-system.o

I've kinda moved away from the pb- prefix on the object files, how about 
just 'system' or 'system-helpers' ?

> +int pb_run_system(const char *cmd)

I'm a bit weary of this method of executing external programs, as we can 
end up in trouble with shell metacharachters. Can we do this with an 
array of char * instead?

> +	if (result) {
> +		cmd = talloc_asprintf(NULL, "%s -f%s%s%s%s%s%s %s",
> +		pb_system_apps.kexec,
> +		(l_initrd ? " --initrd='" : ""),
> +		(l_initrd ? l_initrd : ""),
> +		(l_initrd ? "'" : ""),
> +		(args ? " --append='" : ""),
> +		(args ? args : ""),
> +		(args ? "'" : ""),
> +		l_image);
> +
> +		pb_log("%s: '%s'\n", __func__, cmd);
> +
> +		result = pb_run_system(cmd);
> +		talloc_free(cmd);
> +	}

.. and it means we don't have to do this kind of sprintf to construct a 
command line.


> +	static const char s_file[] = "file://";
> +	static const char s_ftp[] = "ftp://";
> +	static const char s_http[] = "http://";
> +	static const char s_https[] = "https://";
> +	static const char s_nfs[] = "nfs://";
> +	static const char s_scp[] = "scp://";
> +	static const char s_tftp[] = "tftp://";

[snip]

> +
> +	if (!strncasecmp(s_url, s_file, sizeof(s_file) - 1)) {
> +		url->scheme = pb_url_file;
> +		p = s_url + sizeof(s_file) - 1;
> +		goto got_scheme;
> +	}
> +
> +	if (!strncasecmp(s_url, s_ftp, sizeof(s_ftp) - 1)) {
> +		url->scheme = pb_url_ftp;
> +		p = s_url + sizeof(s_ftp) - 1;
> +		goto got_scheme;
> +	}


static const struct {
	const char *scheme_str;
	enum pb_url_scheme scheme;
} schemes[] = {
	{ "tftp", pb_url_tftp },
	{ "file", pb_url_file },
    .... 
}

for (i = 0; i < ARRAY_SIZE(schemes); i++) {
	int len = strlen(schemes[i].scheme_str);

	if (strncasecmp(s_url, schemes[i].scheme_str, len)
		continue;

	url->scheme = schemes[i].scheme
	p = s_url + len;
}

We could even hook the load_*() functions into that schemes[] array, 
which would simplify pb_load_file()...

(or maybe do this in a separate common/url.c object? up to you.)

Cheers,

Jeremy
Geoff Levand - April 12, 2009, 10:14 p.m.
On 03/30/2009 03:37 AM, Jeremy Kerr wrote:
>> +	static const char s_file[] = "file://";
>> +	static const char s_ftp[] = "ftp://";
>> +	static const char s_http[] = "http://";
>> +	static const char s_https[] = "https://";
>> +	static const char s_nfs[] = "nfs://";
>> +	static const char s_scp[] = "scp://";
>> +	static const char s_tftp[] = "tftp://";
> 
> [snip]
> 
>> +
>> +	if (!strncasecmp(s_url, s_file, sizeof(s_file) - 1)) {
>> +		url->scheme = pb_url_file;
>> +		p = s_url + sizeof(s_file) - 1;
>> +		goto got_scheme;
>> +	}
>> +
>> +	if (!strncasecmp(s_url, s_ftp, sizeof(s_ftp) - 1)) {
>> +		url->scheme = pb_url_ftp;
>> +		p = s_url + sizeof(s_ftp) - 1;
>> +		goto got_scheme;
>> +	}
> 
> 
> static const struct {
> 	const char *scheme_str;
> 	enum pb_url_scheme scheme;
> } schemes[] = {
> 	{ "tftp", pb_url_tftp },
> 	{ "file", pb_url_file },
>     .... 
> }
> 
> for (i = 0; i < ARRAY_SIZE(schemes); i++) {
> 	int len = strlen(schemes[i].scheme_str);
> 
> 	if (strncasecmp(s_url, schemes[i].scheme_str, len)
> 		continue;
> 
> 	url->scheme = schemes[i].scheme
> 	p = s_url + len;
> }

I had the same idea, but didn't get to it before posting.
It is now done.

> We could even hook the load_*() functions into that schemes[] array, 
> which would simplify pb_load_file()...

I decided against this, I thought it better to have the loader
know nothing about url parsing.  It also allows some extra logic
in pb_load_file(), based on the url type.

> (or maybe do this in a separate common/url.c object? up to you.)

OK, I set that up.  I also made a common/loader.c.

There were some user requests to load bootloader config files
from a server so the config files could be centrally maintained
in a big installation.  If we decide to support something like
that we can move loader.c and url.c to lib/ and use them in the
daemon.

-Geoff

Patch

--- a/rules.mk
+++ b/rules.mk
@@ -43,7 +43,7 @@  discover_objs =  discover/udev.o discove
 	discover/device-handler.o discover/paths.o discover/parser-utils.o
 
 # client objs
-ui_common_objs = ui/common/discover-client.o
+ui_common_objs = ui/common/discover-client.o ui/common/pb-system.o
 ncurses_objs =
 twin_objs = ui/twin/pb-twin.o
 
--- /dev/null
+++ b/ui/common/pb-system.c
@@ -0,0 +1,535 @@ 
+/*
+ *  Copyright (C) 2009 Sony Computer Entertainment Inc.
+ *  Copyright 2009 Sony Corp.
+ *
+ *  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; version 2 of the License.
+ *
+ *  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
+ */
+
+#define _GNU_SOURCE
+
+#include <assert.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "log/log.h"
+#include "talloc/talloc.h"
+#include "pb-system.h"
+
+static const struct pb_system_apps pb_system_apps = {
+	.cp = "/bin/cp",
+	.kexec = "/usr/sbin/kexec",
+	.mkdir = "/bin/mkdir",
+	.mount = "/bin/mount",
+	.scp = "/usr/bin/scp",
+	.tftp = "/usr/bin/tftp",
+	.umount = "/bin/umount",
+	.wget = "/usr/bin/wget",
+};
+
+/**
+ * pb_run_system - Run system with the supplied command.
+ * @cmd: A command string.
+ */
+
+int pb_run_system(const char *cmd)
+{
+	int result;
+
+	pb_log("%s: '%s'\n", __func__, cmd);
+
+	result = system(cmd);
+
+	if (result == -1)
+		pb_log("%s: result failed: %s (%s)\n", __func__, cmd,
+			strerror(errno));
+
+	if (WEXITSTATUS(result))
+		pb_log("%s: WEXITSTATUS %d\n", __func__, WEXITSTATUS(result));
+
+	if (WIFSIGNALED(result) && WTERMSIG(result) == SIGINT)
+		pb_log("%s: signaled\n", __func__);
+
+	return WEXITSTATUS(result);
+}
+
+/**
+ * run_kexec_local - Final kexec helper.
+ * @l_image: The local image file for kexec to execute.
+ * @l_initrd: Optional local initrd file for kexec --initrd, can be NULL.
+ * @args: Optional command line args for kexec --append, can be NULL.
+ */
+
+static int run_kexec_local(const char *l_image, const char *l_initrd,
+	const char *args)
+{
+	int result;
+	char *cmd;
+
+	/* First try by telling kexec to run shutdown */
+
+	cmd = talloc_asprintf(NULL, "%s%s%s%s%s%s%s %s", pb_system_apps.kexec,
+		(l_initrd ? " --initrd='" : ""),
+		(l_initrd ? l_initrd : ""),
+		(l_initrd ? "'" : ""),
+		(args ? " --append='" : ""),
+		(args ? args : ""),
+		(args ? "'" : ""),
+		l_image);
+
+	pb_log("%s: '%s'\n", __func__, cmd);
+
+	result = pb_run_system(cmd);
+	talloc_free(cmd);
+
+	/* kexec will return zero on success */
+	/* On error force a kexec with the -f option */
+
+	if (result) {
+		cmd = talloc_asprintf(NULL, "%s -f%s%s%s%s%s%s %s",
+		pb_system_apps.kexec,
+		(l_initrd ? " --initrd='" : ""),
+		(l_initrd ? l_initrd : ""),
+		(l_initrd ? "'" : ""),
+		(args ? " --append='" : ""),
+		(args ? args : ""),
+		(args ? "'" : ""),
+		l_image);
+
+		pb_log("%s: '%s'\n", __func__, cmd);
+
+		result = pb_run_system(cmd);
+		talloc_free(cmd);
+	}
+
+	if (result)
+		pb_log("%s: failed: (%d)\n", __func__, result);
+
+	return result;
+}
+
+enum pb_url_scheme {
+	pb_url_unknown = 0,
+	pb_url_file,
+	pb_url_ftp,
+	pb_url_http,
+	pb_url_https,
+	pb_url_nfs,
+	pb_url_scp,
+	pb_url_tftp,
+};
+
+/**
+ * struct pb_url
+ *
+ * scheme://host:port/path
+ * http://www.ietf.org/rfc/rfc3986.txt
+ */
+
+struct pb_url {
+	enum pb_url_scheme scheme;
+	char *full;
+	char *host;
+	char *port;
+	char *path;
+	char *dir;
+	char *file;
+};
+
+/**
+ * pb_url_parse - Parse a remote file URL.
+ */
+
+static struct pb_url *pb_url_parse(const char *s_url)
+{
+	static const char s_file[] = "file://";
+	static const char s_ftp[] = "ftp://";
+	static const char s_http[] = "http://";
+	static const char s_https[] = "https://";
+	static const char s_nfs[] = "nfs://";
+	static const char s_scp[] = "scp://";
+	static const char s_tftp[] = "tftp://";
+	struct pb_url *url;
+	const char *p;
+
+	pb_log("%s: '%s'\n", __func__, s_url);
+
+	assert(s_url && *s_url);
+
+	url = talloc_zero(NULL, struct pb_url);
+
+	if (!url || !s_url || !*s_url) {
+		pb_log("%s: talloc failed\n", __func__);
+		return NULL;
+	}
+
+	if (!strncasecmp(s_url, s_file, sizeof(s_file) - 1)) {
+		url->scheme = pb_url_file;
+		p = s_url + sizeof(s_file) - 1;
+		goto got_scheme;
+	}
+
+	if (!strncasecmp(s_url, s_ftp, sizeof(s_ftp) - 1)) {
+		url->scheme = pb_url_ftp;
+		p = s_url + sizeof(s_ftp) - 1;
+		goto got_scheme;
+	}
+
+	if (!strncasecmp(s_url, s_http, sizeof(s_http) - 1)) {
+		url->scheme = pb_url_http;
+		p = s_url + sizeof(s_http) - 1;
+		goto got_scheme;
+	}
+
+	if (!strncasecmp(s_url, s_https, sizeof(s_https) - 1)) {
+		url->scheme = pb_url_https;
+		p = s_url + sizeof(s_https) - 1;
+		goto got_scheme;
+	}
+
+	if (!strncasecmp(s_url, s_nfs, sizeof(s_nfs) - 1)) {
+		url->scheme = pb_url_nfs;
+		p = s_url + sizeof(s_nfs) - 1;
+		goto got_scheme;
+	}
+
+	if (!strncasecmp(s_url, s_scp, sizeof(s_scp) - 1)) {
+		url->scheme = pb_url_scp;
+		p = s_url + sizeof(s_scp) - 1;
+		goto got_scheme;
+	}
+
+	if (!strncasecmp(s_url, s_tftp, sizeof(s_tftp) - 1)) {
+		url->scheme = pb_url_tftp;
+		p = s_url + sizeof(s_tftp) - 1;
+		goto got_scheme;
+	}
+
+	url->scheme = pb_url_file;
+	p = s_url;
+
+got_scheme:
+
+	url->full = talloc_strdup(url, s_url);
+
+	if (url->scheme == pb_url_file) {
+		url->port = NULL;
+		url->host = NULL;
+		url->path = talloc_strdup(url, p);
+	} else {
+		int len;
+		const char *col;
+		const char *path;
+
+		path = strchr(p, '/');
+
+		if (!path) {
+			pb_log("%s: parse path failed '%s'\n", p);
+			goto fail;
+		}
+
+		col = strchr(p, ':');
+
+		if (col) {
+			len = path - col - 1;
+			url->port = len ? talloc_strndup(url, col + 1, len)
+				: NULL;
+			len = col - p;
+			url->host = len ? talloc_strndup(url, p, len) : NULL;
+		} else {
+			url->port = NULL;
+			url->host = talloc_strndup(url, p, path - p);
+		}
+		url->path = talloc_strdup(url, path);
+	}
+
+	p = strrchr(url->path, '/');
+
+	if (p) {
+		p++;
+		url->dir = talloc_strndup(url, url->path, p - url->path);
+		url->file = talloc_strdup(url, p);
+	} else {
+		url->dir = NULL;
+		url->file = talloc_strdup(url, url->path);
+	}
+
+	pb_log(" scheme %d\n", url->scheme);
+	pb_log(" host '%s'\n", url->host);
+	pb_log(" port '%s'\n", url->port);
+	pb_log(" path '%s'\n", url->path);
+	pb_log(" dir '%s'\n", url->dir);
+	pb_log(" file '%s'\n", url->file);
+
+	return url;
+
+fail:
+	talloc_free(url);
+	return NULL;
+}
+
+/**
+ * pb_load_nfs - Mounts the NFS export and returns the local file path.
+ *
+ * The caller must free the returned string.
+ */
+
+static char *pb_load_nfs(struct pb_url *url)
+{
+	int result;
+	char *cmd;
+	char *local;
+	const char *mnt;
+
+	mnt = tempnam(NULL, "pb-");
+
+	if (mnt)
+		return NULL;
+
+	cmd = talloc_asprintf(NULL, "%s -p %s", pb_system_apps.mkdir, mnt);
+
+	result = pb_run_system(cmd);
+	talloc_free(cmd);
+
+	if (result)
+		goto fail;
+
+	cmd = talloc_asprintf(NULL,
+		"%s -t nfs %s%s -o ro,nolock,nodiratime %s:%s %s",
+		pb_system_apps.mount,
+		(url->port ? "-o port=" : ""),
+		(url->port ? url->port : ""),
+		url->host, url->dir, mnt);
+
+	pb_run_system(cmd);
+	talloc_free(cmd);
+
+	if (result)
+		goto fail;
+
+	asprintf(&local, "%s/%s", mnt, url->path);
+	pb_log("%s: local '%s'\n", __func__, local);
+
+	return local;
+
+fail:
+	cmd = talloc_asprintf(NULL, "%s %s", pb_system_apps.umount, mnt);
+	pb_run_system(cmd);
+	talloc_free(cmd);
+
+	return NULL;
+}
+
+/**
+ * pb_load_scp - Loads a remote file via scp and returns the local file path.
+ *
+ * The caller must free the returned string.
+ */
+
+static char *pb_load_scp(struct pb_url __attribute__((unused)) *url)
+{
+#if 0
+	int result;
+	char *cmd;
+
+	cmd = talloc_asprintf(NULL, "%s %s %s", pb_system_apps.scp, remote,
+		local);
+
+	result = pb_run_system(cmd);
+	talloc_free(cmd);
+
+	return result ? NULL : local;
+#endif
+	return NULL;
+}
+
+/**
+ * pb_load_tftp - Loads a remote file via tftp and returns the local file path.
+ *
+ * The caller must free the returned string.
+ */
+
+static char *pb_load_tftp(struct pb_url *url)
+{
+	int result;
+	char *cmd;
+	char *local;
+
+	local = tempnam(NULL, "pb-");
+
+	if (!local)
+		return NULL;
+
+	/* first try busybox tftp args */
+
+	cmd = talloc_asprintf(NULL, "%s -g -l %s -r %s %s %s >&- 2>&-",
+		pb_system_apps.tftp, local, url->path, url->host,
+		(url->port ? url->port : ""));
+
+	result = pb_run_system(cmd);
+	talloc_free(cmd);
+
+	if (!result)
+		return local;
+
+	/* next try tftp-hpa args */
+
+	cmd = talloc_asprintf(NULL,
+		"%s -v -m binary %s %s -c get %s %s >&- 2>&-",
+		pb_system_apps.tftp, url->host, (url->port ? url->port : ""),
+		url->path, local);
+
+	result = pb_run_system(cmd);
+	talloc_free(cmd);
+
+	return result ? NULL : local;
+}
+
+/**
+ * pb_load_wget - Loads a remote file via wget and returns the local file path.
+ *
+ * The caller must free the returned string.
+ */
+
+static char *pb_load_wget(struct pb_url *url)
+{
+	int result;
+	char *cmd;
+	char *local;
+
+	local = tempnam(NULL, "pb-");
+
+	if (!local)
+		return NULL;
+
+	cmd = talloc_asprintf(NULL, "%s -O %s %s", pb_system_apps.wget, local,
+		url->full);
+
+	result = pb_run_system(cmd);
+	talloc_free(cmd);
+
+	return result ? NULL : local;
+}
+
+/**
+ * pb_load_file - Loads a remote file and returns the local file path.
+ *
+ * The caller must free the returned string.
+ */
+
+static char *pb_load_file(const char *remote)
+{
+	char *local;
+	struct pb_url *url = pb_url_parse(remote);
+
+	if (!url)
+		return NULL;
+
+	switch(url->scheme) {
+		case pb_url_ftp:
+		case pb_url_http:
+		case pb_url_https:
+			local = pb_load_wget(url);
+			break;
+		case pb_url_nfs:
+			local = pb_load_nfs(url);
+			break;
+		case pb_url_scp:
+			local = pb_load_scp(url);
+			break;
+		case pb_url_tftp:
+			local = pb_load_tftp(url);
+			break;
+		default:
+			local = strdup(remote);
+			break;
+	}
+
+	talloc_free(url);
+	return local;
+}
+
+/**
+ * pb_run_kexec - Run kexec with the supplied boot options.
+ *
+ * For the convenience of the user, tries to load both files before
+ * returning error.
+ */
+
+int pb_run_kexec(const struct pb_kexec_data *kd)
+{
+	int result;
+	char *l_image;
+	char *l_initrd;
+
+	pb_log("%s: image:  '%s'\n", __func__, kd->image);
+	pb_log("%s: initrd: '%s'\n", __func__, kd->initrd);
+	pb_log("%s: args:   '%s'\n", __func__, kd->args);
+
+	if (kd->image)
+		l_image = pb_load_file(kd->image);
+	else {
+		l_image = NULL;
+		pb_log("%s: error null image\n", __func__);
+	}
+
+	l_initrd = kd->initrd ? pb_load_file(kd->initrd) : NULL;
+
+	if (!l_image || (kd->initrd && !l_initrd))
+		result = -1;
+	else
+		result = run_kexec_local(l_image, l_initrd, kd->args);
+
+	//FIXME: bug here!!
+	//free(l_image);
+	//free(l_initrd);
+
+	return result;
+}
+
+/**
+ * pb_elf_hash - Standard elf hash routine.
+ */
+
+unsigned int pb_elf_hash(const char *str)
+{
+	unsigned int h = 0, g;
+
+	while (*str) {
+		h = (h << 4) + *str++;
+		g = h & 0xf0000000;
+		if (g)
+			h ^= g >> 24;
+		h &= ~g;
+	}
+	pb_log("%s: %u\n", __func__, h);
+	return h;
+}
+
+/**
+ * pb_cat_hash - Hashes concatenation of two strings.
+ */
+
+unsigned int pb_cat_hash(const char *a, const char *b)
+{
+	unsigned int h;
+	char *s;
+
+	s = talloc_asprintf(NULL, "%s%s", a, b);
+	h = pb_elf_hash(s);
+	talloc_free(s);
+
+	return h;;
+}
--- /dev/null
+++ b/ui/common/pb-system.h
@@ -0,0 +1,53 @@ 
+/*
+ *  Copyright (C) 2009 Sony Computer Entertainment Inc.
+ *  Copyright 2009 Sony Corp.
+ *
+ *  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; version 2 of the License.
+ *
+ *  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
+ */
+
+#if !defined(_PB_KEXEC_H)
+#define _PB_KEXEC_H
+
+#include "pb-protocol/pb-protocol.h"
+
+struct pb_system_apps {
+	const char *cp;
+	const char *kexec;
+	const char *mkdir;
+	const char *mount;
+	const char *scp;
+	const char *tftp;
+	const char *umount;
+	const char *wget;
+};
+
+struct pb_kexec_data {
+	char *image;
+	char *initrd;
+	char *args;
+};
+
+int pb_run_system(const char *cmd);
+int pb_run_kexec(const struct pb_kexec_data *kd);
+
+unsigned int pb_elf_hash(const char *str);
+unsigned int pb_cat_hash(const char *a, const char *b);
+
+static inline unsigned int pb_opt_hash(const struct device *dev,
+	const struct boot_option *opt)
+{
+	return pb_cat_hash(dev->name, opt->name);
+}
+
+#endif