diff mbox series

[v3,4/8] libnet: Add functions for downloading and parsing pxelinux.cfg files

Message ID 1527314768-4797-5-git-send-email-thuth@redhat.com
State Accepted
Headers show
Series Support network booting with pxelinux.cfg files | expand

Commit Message

Thomas Huth May 26, 2018, 6:06 a.m. UTC
Booting a kernel via pxelinux.cfg files is common on x86 and also with
ppc64 bootloaders like petitboot, so it would be nice to support this
in SLOF, too. This patch adds functions for downloading and parsing
such pxelinux.cfg files. See this URL for more details on pxelinux.cfg:
https://www.syslinux.org/wiki/index.php?title=PXELINUX

Signed-off-by: Thomas Huth <thuth@redhat.com>
---
 lib/libnet/Makefile   |   2 +-
 lib/libnet/pxelinux.c | 212 ++++++++++++++++++++++++++++++++++++++++++++++++++
 lib/libnet/pxelinux.h |  33 ++++++++
 3 files changed, 246 insertions(+), 1 deletion(-)
 create mode 100644 lib/libnet/pxelinux.c
 create mode 100644 lib/libnet/pxelinux.h
diff mbox series

Patch

diff --git a/lib/libnet/Makefile b/lib/libnet/Makefile
index dfefea9..a2a6570 100644
--- a/lib/libnet/Makefile
+++ b/lib/libnet/Makefile
@@ -19,7 +19,7 @@  include $(TOP)/make.rules
 CFLAGS += -I. -I.. -I../libc/include -I$(TOP)/include $(FLAG)
 
 SRCS =	ethernet.c ipv4.c udp.c tcp.c dns.c bootp.c dhcp.c tftp.c \
-	ipv6.c dhcpv6.c icmpv6.c ndp.c netload.c ping.c args.c
+	ipv6.c dhcpv6.c icmpv6.c ndp.c netload.c ping.c args.c pxelinux.c
 
 OBJS = $(SRCS:%.c=%.o)
 
diff --git a/lib/libnet/pxelinux.c b/lib/libnet/pxelinux.c
new file mode 100644
index 0000000..d702811
--- /dev/null
+++ b/lib/libnet/pxelinux.c
@@ -0,0 +1,212 @@ 
+/*****************************************************************************
+ * pxelinux.cfg-style config file support.
+ *
+ * See https://www.syslinux.org/wiki/index.php?title=PXELINUX for information
+ * about the pxelinux config file layout.
+ *
+ * Copyright 2018 Red Hat, Inc.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the BSD License which accompanies this distribution, and is
+ * available at http://www.opensource.org/licenses/bsd-license.php
+ *
+ * Contributors:
+ *     Thomas Huth, Red Hat Inc. - initial implementation
+ *****************************************************************************/
+
+#include <stdio.h>
+#include <string.h>
+#include "tftp.h"
+#include "pxelinux.h"
+
+/**
+ * Call tftp() and report errors (excet "file-not-found" errors)
+ */
+static int pxelinux_tftp_load(filename_ip_t *fnip, void *buffer, int len,
+                              int retries)
+{
+	tftp_err_t tftp_err;
+	int rc, ecode;
+
+	rc = tftp(fnip, buffer, len, retries, &tftp_err);
+
+	if (rc > 0) {
+		printf("\r  TFTP: Received %s (%d bytes)\n",
+		       fnip->filename, rc);
+	} else if (rc == -3) {
+		/* Ignore file-not-found (since we are probing the files)
+		 * and simply erase the "Receiving data:  0 KBytes" string */
+		printf("\r                           \r");
+	} else {
+		const char *errstr = NULL;
+		rc = tftp_get_error_info(fnip, &tftp_err, rc, &errstr, &ecode);
+		if (errstr)
+			printf("\r  TFTP error: %s\n", errstr);
+	}
+
+	return rc;
+}
+
+/**
+ * Try to load a pxelinux.cfg file by probing the possible file names.
+ * Note that this function will overwrite filename_ip_t->filename.
+ */
+static int pxelinux_load_cfg(filename_ip_t *fn_ip, uint8_t *mac,
+                             int retries, char *cfgbuf, int cfgbufsize)
+{
+	int rc, idx;
+	char *baseptr;
+
+	/* Try to get a usable base directory from the DHCP bootfile name */
+	baseptr = strrchr(fn_ip->filename, '/');
+	if (!baseptr)
+		baseptr = fn_ip->filename;
+	else
+		++baseptr;
+	/* Check that we've got enough space to store "pxelinux.cfg/" and
+	 * the UUID (which is the longest file name) there */
+	if (baseptr - fn_ip->filename > sizeof(fn_ip->filename) - 50) {
+		puts("Error: The bootfile string is too long for deriving the "
+		     "pxelinux.cfg file name from it.");
+		return -1;
+	}
+	strcpy(baseptr, "pxelinux.cfg/");
+	baseptr += strlen(baseptr);
+
+	puts("Trying pxelinux.cfg files...");
+
+	/* Look for config file with MAC address in its name */
+	sprintf(baseptr, "01-%02x-%02x-%02x-%02x-%02x-%02x",
+		mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+	rc = pxelinux_tftp_load(fn_ip, cfgbuf, cfgbufsize - 1, retries);
+	if (rc > 0) {
+		return rc;
+	}
+
+	/* Look for config file with IP address in its name */
+	if (fn_ip->ip_version == 4) {
+		sprintf(baseptr, "%02X%02X%02X%02X",
+			(fn_ip->own_ip >> 24) & 0xff,
+			(fn_ip->own_ip >> 16) & 0xff,
+			(fn_ip->own_ip >> 8) & 0xff,
+			fn_ip->own_ip & 0xff);
+		for (idx = 0; idx <= 7; idx++) {
+			baseptr[8 - idx] = 0;
+			rc = pxelinux_tftp_load(fn_ip, cfgbuf, cfgbufsize - 1,
+			                        retries);
+			if (rc > 0) {
+				return rc;
+			}
+		}
+	}
+
+	/* Try "default" config file */
+	strcpy(baseptr, "default");
+	rc = pxelinux_tftp_load(fn_ip, cfgbuf, cfgbufsize - 1, retries);
+
+	return rc;
+}
+
+/**
+ * Parse a pxelinux-style configuration file.
+ * The discovered entries are filled into the "struct pl_cfg_entry entries[]"
+ * array. Note that the callers must keep the cfg buffer valid as long as
+ * they wish to access the "struct pl_cfg_entry" entries, since the pointers
+ * in entries point to the original location in the cfg buffer area. The cfg
+ * buffer is altered for this, too, e.g. terminating NUL-characters are put
+ * into the right locations.
+ * @param cfg          Pointer to the buffer with contents of the config file
+ * @param cfgsize      Size of the cfg buffer
+ * @param entries      Pointer to array where the results should be put into
+ * @param max_entries  Number of available slots in the entries array
+ * @param def_ent      Used to return the index of the default entry
+ * @return             Number of valid entries
+ */
+int pxelinux_parse_cfg(char *cfg, int cfgsize, struct pl_cfg_entry *entries,
+                       int max_entries, int *def_ent)
+{
+	int num_entries = 0;
+	char *ptr = cfg, *nextptr, *eol, *arg;
+	char *defaultlabel = NULL;
+
+	*def_ent = 0;
+
+	cfg[cfgsize - 1] = 0;  /* Make sure it is NUL-terminated */
+
+	while (ptr < cfg + cfgsize && num_entries < max_entries) {
+		eol = strchr(ptr, '\n');
+		if (!eol) {
+			eol = cfg + cfgsize;
+		}
+		nextptr = eol + 1;
+		do {
+			*eol-- = '\0';	/* Remove spaces, tabs and returns */
+		} while (eol >= ptr &&
+		         (*eol == '\r' || *eol == ' ' || *eol == '\t'));
+		while (*ptr == ' ' || *ptr == '\t') {
+			ptr++;
+		}
+		if (*ptr == 0 || *ptr == '#') {
+			goto nextline;	/* Ignore comments and empty lines */
+		}
+		arg = strchr(ptr, ' ');	/* Search space between cmnd and arg */
+		if (!arg) {
+			arg = strchr(ptr, '\t');
+		}
+		if (!arg) {
+			printf("Failed to parse this line:\n %s\n", ptr);
+			goto nextline;
+		}
+		*arg++ = 0;
+		while (*arg == ' ' || *arg == '\t') {
+			arg++;
+		}
+		if (!strcasecmp("default", ptr)) {
+			defaultlabel = arg;
+		} else if (!strcasecmp("label", ptr)) {
+			entries[num_entries].label = arg;
+			if (defaultlabel && !strcmp(arg, defaultlabel)) {
+				*def_ent = num_entries;
+			}
+			num_entries++;
+		} else if (!strcasecmp("kernel", ptr) && num_entries) {
+			entries[num_entries - 1].kernel = arg;
+		} else if (!strcasecmp("initrd", ptr) && num_entries) {
+			entries[num_entries - 1].initrd = arg;
+		} else if (!strcasecmp("append", ptr) && num_entries) {
+			entries[num_entries - 1].append = arg;
+		} else {
+			printf("Command '%s' is not supported.\n", ptr);
+		}
+nextline:
+		ptr = nextptr;
+	}
+
+	return num_entries;
+}
+
+/**
+ * Try to load and parse a pxelinux-style configuration file.
+ * @param fn_ip        must contain server and client IP information
+ * @param mac          MAC address which should be used for probing
+ * @param retries      Amount of TFTP retries before giving up
+ * @param cfgbuf       Pointer to the buffer where config file should be loaded
+ * @param cfgsize      Size of the cfgbuf buffer
+ * @param entries      Pointer to array where the results should be put into
+ * @param max_entries  Number of available slots in the entries array
+ * @param def_ent      Used to return the index of the default entry
+ * @return             Number of valid entries
+ */
+int pxelinux_load_parse_cfg(filename_ip_t *fn_ip, uint8_t *mac,
+                            int retries, char *cfgbuf, int cfgsize,
+                            struct pl_cfg_entry *entries, int max_entries,
+                            int *def_ent)
+{
+	int rc;
+
+	rc = pxelinux_load_cfg(fn_ip, mac, retries, cfgbuf, cfgsize);
+	if (rc < 0)
+		return rc;
+
+	return pxelinux_parse_cfg(cfgbuf, rc, entries, max_entries, def_ent);
+}
diff --git a/lib/libnet/pxelinux.h b/lib/libnet/pxelinux.h
new file mode 100644
index 0000000..98b3925
--- /dev/null
+++ b/lib/libnet/pxelinux.h
@@ -0,0 +1,33 @@ 
+/*****************************************************************************
+ * Definitions for pxelinux-style config file support
+ *
+ * Copyright 2018 Red Hat, Inc.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the BSD License
+ * which accompanies this distribution, and is available at
+ * http://www.opensource.org/licenses/bsd-license.php
+ *
+ * Contributors:
+ *     Thomas Huth, Red Hat Inc. - initial implementation
+ *****************************************************************************/
+
+#ifndef LIBNET_PXELINUX_H
+#define LIBNET_PXELINUX_H
+
+/* This structure holds the data from one pxelinux.cfg file entry */
+struct pl_cfg_entry {
+    const char *label;
+    const char *kernel;
+    const char *initrd;
+    const char *append;
+};
+
+int pxelinux_parse_cfg(char *cfg, int cfgsize, struct pl_cfg_entry *entries,
+                       int max_entries, int *def_ent);
+int pxelinux_load_parse_cfg(filename_ip_t *fn_ip, uint8_t *mac,
+                            int retries, char *cfgbuf, int cfgsize,
+                            struct pl_cfg_entry *entries,
+                            int max_entries, int *def_ent);
+
+#endif