[BACK]Return to rdsetroot.c CVS log [TXT][DIR] Up to [local] / src / usr.sbin / rdsetroot

File: [local] / src / usr.sbin / rdsetroot / rdsetroot.c (download)

Revision 1.5, Mon Apr 24 14:06:01 2023 UTC (13 months, 2 weeks ago) by krw
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4, HEAD
Changes since 1.4: +17 -4 lines

Add '-s' option to simply display the number of bytes available for the rdroot
filesystem in the specified kernel.

No behaviour change to existing uses of rdsetroot.

Improved option handling & ok kn@

/* $OpenBSD: rdsetroot.c,v 1.5 2023/04/24 14:06:01 krw Exp $ */

/*
 * Copyright (c) 2019 Sunil Nimmagadda <sunil@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/mman.h>
#include <sys/stat.h>

#include <err.h>
#include <fcntl.h>
#include <gelf.h>
#include <libelf.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int find_rd_root_image(uint64_t *, uint64_t *, off_t *, size_t *);
int symbol_get_u64(const char *, uint64_t *);
__dead void usage(void);

Elf	*e;
Elf_Scn	*symtab;
size_t	 nsymb, strtabndx, strtabsz;

int
main(int argc, char **argv)
{
	GElf_Shdr	 shdr;
	Elf_Scn		*scn;
	char		*dataseg, *kernel = NULL, *fs = NULL, *name;
	off_t		 mmap_off, rd_root_size_val;
	size_t		 shstrndx, mmap_size;
	uint64_t	 rd_root_size_off, rd_root_image_off;
	uint32_t	*ip;
	int		 ch, debug = 0, fsfd, kfd, n, sflag = 0, xflag = 0;

	while ((ch = getopt(argc, argv, "dsx")) != -1) {
		switch (ch) {
		case 'd':
			debug = 1;
			break;
		case 's':
			sflag = 1;
			break;
		case 'x':
			xflag = 1;
			break;
		default:
			usage();
		}
	}
	argc -= optind;
	argv += optind;

	if (sflag && (debug || xflag || argc > 1))
		usage();

	if (argc == 1)
		kernel = argv[0];
	else if (argc == 2) {
		kernel = argv[0];
		fs = argv[1];
	} else
		usage();

	if ((kfd = open(kernel, xflag ? O_RDONLY : O_RDWR)) < 0)
		err(1, "%s", kernel);

	if (fs) {
		if (xflag)
			fsfd = open(fs, O_RDWR | O_CREAT | O_TRUNC, 0644);
		else
			fsfd = open(fs, O_RDONLY);
	} else {
		if (xflag)
			fsfd = dup(STDOUT_FILENO);
		else
			fsfd = dup(STDIN_FILENO);
	}
	if (fsfd < 0)
		err(1, "%s", fs);

	if (pledge("stdio", NULL) == -1)
		err(1, "pledge");

	if (elf_version(EV_CURRENT) == EV_NONE)
		errx(1, "elf_version: %s", elf_errmsg(-1));

	if ((e = elf_begin(kfd, xflag ? ELF_C_READ : ELF_C_RDWR, NULL)) == NULL)
		errx(1, "elf_begin: %s", elf_errmsg(-1));

	if (elf_kind(e) != ELF_K_ELF)
		errx(1, "%s: not an elf", kernel);

	if (gelf_getclass(e) == ELFCLASSNONE)
		errx(1, "%s: invalid elf, not 32 or 64 bit", kernel);

	/* Retrieve index of section name string table. */
	if (elf_getshdrstrndx(e, &shstrndx) != 0)
		errx(1, "elf_getshdrstrndx: %s", elf_errmsg(-1));

	/* Find symbol table, string table. */
	scn = symtab = NULL;
	while ((scn = elf_nextscn(e, scn)) != NULL) {
		if (gelf_getshdr(scn, &shdr) != &shdr)
			errx(1, "elf_getshdr: %s", elf_errmsg(-1));

		if ((name = elf_strptr(e, shstrndx, shdr.sh_name)) == NULL)
			errx(1, "elf_strptr: %s", elf_errmsg(-1));

		if (strcmp(name, ELF_SYMTAB) == 0 &&
		    shdr.sh_type == SHT_SYMTAB && shdr.sh_entsize != 0) {
			symtab = scn;
			nsymb = shdr.sh_size / shdr.sh_entsize;
		}

		if (strcmp(name, ELF_STRTAB) == 0 &&
		    shdr.sh_type == SHT_STRTAB) {
			strtabndx = elf_ndxscn(scn);
			strtabsz = shdr.sh_size;
		}
	}

	if (symtab == NULL)
		errx(1, "symbol table not found");

	if (strtabndx == 0)
		errx(1, "string table not found");

	if (find_rd_root_image(&rd_root_size_off, &rd_root_image_off,
	    &mmap_off, &mmap_size) != 0)
		errx(1, "can't locate space for rd_root_image!");

	if (debug) {
		fprintf(stderr, "rd_root_size_off: 0x%llx\n", rd_root_size_off);
		fprintf(stderr, "rd_root_image_off: 0x%llx\n",
		    rd_root_image_off);
	}

	/*
	 * Map in the whole data segment.
	 * The file offset needs to be page aligned.
	 */
	dataseg = mmap(NULL, mmap_size,
	    xflag ? PROT_READ : PROT_READ | PROT_WRITE,
	    MAP_SHARED, kfd, mmap_off);
	if (dataseg == MAP_FAILED)
		err(1, "%s: cannot map data seg", kernel);

	/*
	 * Find value in the location: rd_root_size
	 */
	ip = (uint32_t *) (dataseg + rd_root_size_off);
	rd_root_size_val = *ip;
	if (sflag) {
		fprintf(stdout, "%llu\n", (unsigned long long)rd_root_size_val);
		goto done;
	}

	if (debug) {
		fprintf(stderr, "rd_root_size  val: 0x%llx (%lld blocks)\n",
		    (unsigned long long)rd_root_size_val,
		    (unsigned long long)rd_root_size_val >> 9);
		fprintf(stderr, "copying root image...\n");
	}

	if (xflag) {
		n = write(fsfd, dataseg + rd_root_image_off,
		    (size_t)rd_root_size_val);
		if (n != rd_root_size_val)
			err(1, "write");
	} else {
		struct stat sstat;

		if (fstat(fsfd, &sstat) == -1)
			err(1, "fstat");
		if (S_ISREG(sstat.st_mode) &&
		    sstat.st_size > rd_root_size_val) {
			fprintf(stderr, "ramdisk too small 0x%llx 0x%llx\n",
			    (unsigned long long)sstat.st_size,
			    (unsigned long long)rd_root_size_val);
			exit(1);
		}
		n = read(fsfd, dataseg + rd_root_image_off,
		    (size_t)rd_root_size_val);
		if (n < 0)
			err(1, "read");

		msync(dataseg, mmap_size, 0);
	}

	if (debug)
		fprintf(stderr, "...copied %d bytes\n", n);

 done:
	elf_end(e);
	return 0;
}

int
find_rd_root_image(uint64_t *rd_root_size_off, uint64_t *rd_root_image_off,
    off_t *pmmap_off, size_t *pmmap_size)
{
	GElf_Phdr	phdr;
	size_t		i, phdrnum;
	unsigned long	kernel_start, kernel_size;
	uint64_t	adiff, rd_root_size, rd_root_image, size_off, image_off;
	int		error = 1;

	if (symbol_get_u64("rd_root_size", &rd_root_size) != 0)
		errx(1, "no rd_root_image symbols?");

	if (symbol_get_u64("rd_root_image", &rd_root_image) != 0)
		errx(1, "no rd_root_image symbols?");

	/* Retrieve number of program headers. */
	if (elf_getphdrnum(e, &phdrnum) != 0)
		errx(1, "elf_getphdrnum: %s", elf_errmsg(-1));

	/* Locate the data segment. */
	for (i = 0; i < phdrnum; i++) {
		if (gelf_getphdr(e, i, &phdr) != &phdr)
			errx(1, "gelf_getphdr: %s", elf_errmsg(-1));

		if (phdr.p_type != PT_LOAD)
			continue;

		kernel_start = phdr.p_paddr;
		kernel_size = phdr.p_filesz;
		adiff = phdr.p_vaddr - phdr.p_paddr;

		size_off = rd_root_size - kernel_start;
		image_off = rd_root_image - kernel_start;
		if (size_off < adiff || image_off < adiff)
			continue;

		size_off -= adiff;
		image_off -= adiff;
		if (image_off >= kernel_size)
			continue;
		if (size_off >= kernel_size)
			errx(1, "rd_root_size not in data segment");

		*pmmap_off = phdr.p_offset;
		*pmmap_size = kernel_size;
		*rd_root_size_off = size_off;
		*rd_root_image_off = image_off;
		error = 0;
		break;
	}

	return error;
}

int
symbol_get_u64(const char *symbol, uint64_t *result)
{
	GElf_Sym	 sym;
	Elf_Data	*data;
	const char	*name;
	size_t		 i;
	int		 error = 1;

	data = NULL;
	while ((data = elf_rawdata(symtab, data)) != NULL) {
		for (i = 0; i < nsymb; i++) {
			if (gelf_getsym(data, i, &sym) != &sym)
				continue;

			if (sym.st_name >= strtabsz)
				break;

			if ((name = elf_strptr(e, strtabndx,
			    sym.st_name)) == NULL)
				continue;

			if (strcmp(name, symbol) == 0) {
				if (result)
					*result = sym.st_value;
				error = 0;
				break;
			}
		}
	}

	return error;
}

__dead void
usage(void)
{
	extern char *__progname;

	fprintf(stderr, "usage: %s -s kernel\n", __progname);
	fprintf(stderr, "       %s [-dx] kernel [disk.fs]\n", __progname);
	exit(1);
}