diff options
Diffstat (limited to 'util')
| -rw-r--r-- | util/libreboot-utils/lottery.c | 3 | ||||
| -rw-r--r-- | util/libreboot-utils/mkhtemp.c | 3 | ||||
| -rw-r--r-- | util/libreboot-utils/nvmutil.c | 3 | ||||
| -rw-r--r-- | util/nvmutil/.gitignore | 2 | ||||
| -rw-r--r-- | util/nvmutil/COPYING | 2 | ||||
| -rw-r--r-- | util/nvmutil/ChangeLog.md | 8 | ||||
| -rw-r--r-- | util/nvmutil/Makefile | 122 | ||||
| -rw-r--r-- | util/nvmutil/README.md | 4 | ||||
| -rw-r--r-- | util/nvmutil/include/common.h | 522 | ||||
| -rw-r--r-- | util/nvmutil/lib/checksum.c | 108 | ||||
| -rw-r--r-- | util/nvmutil/lib/command.c | 546 | ||||
| -rw-r--r-- | util/nvmutil/lib/file.c | 890 | ||||
| -rw-r--r-- | util/nvmutil/lib/io.c | 649 | ||||
| -rw-r--r-- | util/nvmutil/lib/num.c | 349 | ||||
| -rw-r--r-- | util/nvmutil/lib/state.c | 280 | ||||
| -rw-r--r-- | util/nvmutil/lib/string.c | 75 | ||||
| -rw-r--r-- | util/nvmutil/lib/usage.c | 30 | ||||
| -rw-r--r-- | util/nvmutil/lib/word.c | 68 | ||||
| -rw-r--r-- | util/nvmutil/nvmutil.c | 727 | ||||
| -rw-r--r-- | util/spkmodem_decode/.gitignore | 2 | ||||
| -rw-r--r-- | util/spkmodem_decode/Makefile | 30 | ||||
| -rw-r--r-- | util/spkmodem_decode/spkmodem-decode.c | 725 | ||||
| -rw-r--r-- | util/spkmodem_recv/.gitignore | 1 | ||||
| -rw-r--r-- | util/spkmodem_recv/Makefile | 14 | ||||
| -rw-r--r-- | util/spkmodem_recv/spkmodem-recv.c | 124 |
25 files changed, 880 insertions, 4407 deletions
diff --git a/util/libreboot-utils/lottery.c b/util/libreboot-utils/lottery.c index 38407512..3ac4d135 100644 --- a/util/libreboot-utils/lottery.c +++ b/util/libreboot-utils/lottery.c @@ -16,6 +16,9 @@ exit_cleanup(void); int main(int argc, char **argv) { +#ifndef __linux__ +#error This code is currently buggy on BSD systems. Only use on Linux. +#endif int same = 0; char *buf; size_t size = BUFSIZ; diff --git a/util/libreboot-utils/mkhtemp.c b/util/libreboot-utils/mkhtemp.c index d6300f16..9ff70328 100644 --- a/util/libreboot-utils/mkhtemp.c +++ b/util/libreboot-utils/mkhtemp.c @@ -41,6 +41,9 @@ exit_cleanup(void); int main(int argc, char *argv[]) { +#ifndef __linux__ +#error This code is currently buggy on BSD systems. Only use on Linux. +#endif size_t len; size_t tlen; size_t xc = 0; diff --git a/util/libreboot-utils/nvmutil.c b/util/libreboot-utils/nvmutil.c index 09801585..67b01ae7 100644 --- a/util/libreboot-utils/nvmutil.c +++ b/util/libreboot-utils/nvmutil.c @@ -27,6 +27,9 @@ exit_cleanup(void); int main(int argc, char *argv[]) { +#ifndef __linux__ +#error This code is currently buggy on BSD systems. Only use on Linux. +#endif struct xstate *x; struct commands *cmd; struct xfile *f; diff --git a/util/nvmutil/.gitignore b/util/nvmutil/.gitignore index 9414c836..fbfbd130 100644 --- a/util/nvmutil/.gitignore +++ b/util/nvmutil/.gitignore @@ -1,5 +1,7 @@ /nvm /nvmutil +/mkhtemp +/lottery *.bin *.o *.d diff --git a/util/nvmutil/COPYING b/util/nvmutil/COPYING index 47c35a86..a6ecf266 100644 --- a/util/nvmutil/COPYING +++ b/util/nvmutil/COPYING @@ -1,4 +1,4 @@ -Copyright (C) 2022-2026 Leah Rowe <leah@libreboot.org> +Copyright (C) 2022-2025 Leah Rowe <leah@libreboot.org> Copyright (c) 2023 Riku Viitanen <riku.viitanen@protonmail.com> Permission is hereby granted, free of charge, to any person obtaining a diff --git a/util/nvmutil/ChangeLog.md b/util/nvmutil/ChangeLog.md new file mode 100644 index 00000000..e1ed5754 --- /dev/null +++ b/util/nvmutil/ChangeLog.md @@ -0,0 +1,8 @@ +This change log has moved. Please refer here for historical pre-osboot-merge +changes: + +<https://libreboot.org/docs/install/nvmutilimport.html> + +Osboot merged with Libreboot on November 17th, 2022. For nvmutil changes after +this date, please check regular Libreboot release announcements which shall +now specify any such changes. diff --git a/util/nvmutil/Makefile b/util/nvmutil/Makefile index 9d8548b9..7c7411f9 100644 --- a/util/nvmutil/Makefile +++ b/util/nvmutil/Makefile @@ -1,114 +1,24 @@ # SPDX-License-Identifier: MIT -# Copyright (c) 2022,2026 Leah Rowe <leah@libreboot.org> -# Copyright (c) 2023 Riku Viitanen <riku.viitanen@protonmail.com> +# SPDX-FileCopyrightText: 2022,2025 Leah Rowe <leah@libreboot.org> +# SPDX-FileCopyrightText: 2023 Riku Viitanen <riku.viitanen@protonmail.com> -# Makefile for nvmutil, which is an application -# that modifies Intel GbE NVM configurations. +CC?=cc +CFLAGS?=-Os -Wall -Wextra -Werror -pedantic +DESTDIR?= +PREFIX?=/usr/local +INSTALL?=install -CC = cc -HELLCC = clang +nvmutil: nvmutil.c + $(CC) $(CFLAGS) nvmutil.c -o nvmutil -CFLAGS = -LDFLAGS = -DESTDIR = -PREFIX = /usr/local -INSTALL = install - -.SUFFIXES: .c .o - -LDIR = - -PORTABLE = $(LDIR) $(CFLAGS) -WARN = $(PORTABLE) -Wall -Wextra -STRICT = $(WARN) -std=c90 -pedantic -Werror -HELLFLAGS = $(STRICT) -Weverything - -PROG = nvmutil - -OBJS = \ - obj/nvmutil.o \ - obj/lib/state.o \ - obj/lib/file.o \ - obj/lib/string.o \ - obj/lib/usage.o \ - obj/lib/command.o \ - obj/lib/num.o \ - obj/lib/io.o \ - obj/lib/checksum.o \ - obj/lib/word.o - -# default mode -CFLAGS_MODE = $(PORTABLE) -CC_MODE = $(CC) - -all: $(PROG) - -$(PROG): $(OBJS) - $(CC_MODE) $(OBJS) -o $(PROG) $(LDFLAGS) - -# ensure obj directory exists -$(OBJS): obj - -obj: - mkdir obj || true - mkdir obj/lib || true - -# main program object - -obj/nvmutil.o: nvmutil.c - $(CC_MODE) $(CFLAGS_MODE) -c nvmutil.c -o obj/nvmutil.o - -# library/helper objects - -obj/lib/state.o: lib/state.c - $(CC_MODE) $(CFLAGS_MODE) -c lib/state.c -o obj/lib/state.o - -obj/lib/file.o: lib/file.c - $(CC_MODE) $(CFLAGS_MODE) -c lib/file.c -o obj/lib/file.o - -obj/lib/string.o: lib/string.c - $(CC_MODE) $(CFLAGS_MODE) -c lib/string.c -o obj/lib/string.o - -obj/lib/usage.o: lib/usage.c - $(CC_MODE) $(CFLAGS_MODE) -c lib/usage.c -o obj/lib/usage.o - -obj/lib/command.o: lib/command.c - $(CC_MODE) $(CFLAGS_MODE) -c lib/command.c -o obj/lib/command.o - -obj/lib/num.o: lib/num.c - $(CC_MODE) $(CFLAGS_MODE) -c lib/num.c -o obj/lib/num.o - -obj/lib/io.o: lib/io.c - $(CC_MODE) $(CFLAGS_MODE) -c lib/io.c -o obj/lib/io.o - -obj/lib/checksum.o: lib/checksum.c - $(CC_MODE) $(CFLAGS_MODE) -c lib/checksum.c -o obj/lib/checksum.o - -obj/lib/word.o: lib/word.c - $(CC_MODE) $(CFLAGS_MODE) -c lib/word.c -o obj/lib/word.o - -# install - -install: $(PROG) - $(INSTALL) -d $(DESTDIR)$(PREFIX)/bin - $(INSTALL) $(PROG) $(DESTDIR)$(PREFIX)/bin/$(PROG) - chmod 755 $(DESTDIR)$(PREFIX)/bin/$(PROG) +install: + $(INSTALL) nvmutil $(DESTDIR)$(PREFIX)/bin/nvmutil uninstall: - rm -f $(DESTDIR)$(PREFIX)/bin/$(PROG) - -clean: - rm -f $(PROG) $(OBJS) - -distclean: clean + rm -f $(DESTDIR)$(PREFIX)/bin/nvmutil -# mode targets (portable replacement for ifeq) +distclean: + rm -f nvmutil -warn: - $(MAKE) CFLAGS_MODE="$(WARN)" - -strict: - $(MAKE) CFLAGS_MODE="$(STRICT)" - -hell: - $(MAKE) CFLAGS_MODE="$(HELLFLAGS)" CC_MODE="$(HELLCC)" +clean: + rm -f nvmutil diff --git a/util/nvmutil/README.md b/util/nvmutil/README.md new file mode 100644 index 00000000..03a25bc4 --- /dev/null +++ b/util/nvmutil/README.md @@ -0,0 +1,4 @@ + +This documentation has become part of lbwww. See: + +<https://libreboot.org/docs/install/nvmutil.html> diff --git a/util/nvmutil/include/common.h b/util/nvmutil/include/common.h deleted file mode 100644 index 46fbcb38..00000000 --- a/util/nvmutil/include/common.h +++ /dev/null @@ -1,522 +0,0 @@ -/* SPDX-License-Identifier: MIT - * Copyright (c) 2022-2026 Leah Rowe <leah@libreboot.org> - */ - -#ifndef COMMON_H -#define COMMON_H - -#include <sys/types.h> -#include <sys/stat.h> -#include <limits.h> - -/* for linux getrandom - */ -#if defined(__linux__) -#include <errno.h> -#if defined(__has_include) -#if __has_include(<sys/random.h>) -#include <sys/random.h> -#define HAVE_GETRANDOM 1 -#endif -#endif -#if !defined(HAVE_GETRANDOM) -#include <sys/syscall.h> -#if defined(SYS_getrandom) -#define HAVE_GETRANDOM_SYSCALL 1 -#endif -#endif - -#endif - -#define items(x) (sizeof((x)) / sizeof((x)[0])) - -/* system prototypes - */ - -int fchmod(int fd, mode_t mode); - -/* analog of SSIZE_MAX - */ - -#ifndef X_LONG_MAX -#define X_LONG_MAX ((long)(~((long)1 << (sizeof(long)*CHAR_BIT-1)))) -#endif - - -/* build config - */ - -#ifndef NVMUTIL_H -#define NVMUTIL_H - -#define MAX_CMD_LEN 50 - -#ifndef PATH_LEN -#define PATH_LEN 4096 -#endif - -#define OFF_ERR 0 -#ifndef OFF_RESET -#define OFF_RESET 1 -#endif - -#ifndef S_ISVTX -#define S_ISVTX 01000 -#endif - -#if defined(S_IFMT) && ((S_ISVTX & S_IFMT) != 0) -#error "Unexpected bit layout" -#endif - -#ifndef MAX_ZERO_RW_RETRY -#define MAX_ZERO_RW_RETRY 5 -#endif - -#ifndef HAVE_REAL_PREAD_PWRITE -#define HAVE_REAL_PREAD_PWRITE 0 -#endif - -#ifndef LOOP_EAGAIN -#define LOOP_EAGAIN 1 -#endif -#ifndef LOOP_EINTR -#define LOOP_EINTR 1 -#endif - -#ifndef _FILE_OFFSET_BITS -#define _FILE_OFFSET_BITS 64 -#endif - -#ifndef EXIT_FAILURE -#define EXIT_FAILURE 1 -#endif - -#ifndef EXIT_SUCCESS -#define EXIT_SUCCESS 0 -#endif - -#ifndef O_ACCMODE -#define O_ACCMODE (O_RDONLY | O_WRONLY | O_RDWR) -#endif - -#ifndef O_BINARY -#define O_BINARY 0 -#endif - -#ifndef O_EXCL -#define O_EXCL 0 -#endif - -#ifndef O_CREAT -#define O_CREAT 0 -#endif - -#ifndef O_NONBLOCK -#define O_NONBLOCK 0 -#endif - -#ifndef O_CLOEXEC -#define O_CLOEXEC 0 -#endif - -#ifndef O_NOFOLLOW -#define O_NOFOLLOW 0 -#endif - -#ifndef FD_CLOEXEC -#define FD_CLOEXEC 0 -#endif - -/* Sizes in bytes: - */ - -#define SIZE_1KB 1024 -#define SIZE_4KB (4 * SIZE_1KB) -#define SIZE_8KB (8 * SIZE_1KB) -#define SIZE_16KB (16 * SIZE_1KB) -#define SIZE_128KB (128 * SIZE_1KB) - -#define GBE_BUF_SIZE (SIZE_128KB) - -/* First 128 bytes of gbe.bin is NVM. - * Then extended area. All of NVM must - * add up to BABA, truncated (LE) - * - * First 4KB of each half of the file - * contains NVM+extended. - */ - -#define GBE_WORK_SIZE (SIZE_8KB) -#define GBE_PART_SIZE (GBE_WORK_SIZE >> 1) -#define NVM_CHECKSUM 0xBABA -#define NVM_SIZE 128 -#define NVM_WORDS (NVM_SIZE >> 1) -#define NVM_CHECKSUM_WORD (NVM_WORDS - 1) - -/* argc minimum (dispatch) - */ - -#define ARGC_3 3 -#define ARGC_4 4 - -#define NO_LOOP_EAGAIN 0 -#define NO_LOOP_EINTR 0 - -/* For checking if an fd is a normal file. - * Portable for old Unix e.g. v7 (S_IFREG), - * 4.2BSD (S_IFMT), POSIX (S_ISREG). - * - * IFREG: assumed 0100000 (classic bitmask) - */ - -#ifndef S_ISREG -#if defined(S_IFMT) && defined(S_IFREG) -#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) -#elif defined(S_IFREG) -#define S_ISREG(m) (((m) & S_IFREG) != 0) -#else -#error "can't determine types with stat()" -#endif -#endif - -#define IO_READ 0 -#define IO_WRITE 1 -#define IO_PREAD 2 -#define IO_PWRITE 3 - -/* for nvmutil commands - */ - -#define CMD_DUMP 0 -#define CMD_SETMAC 1 -#define CMD_SWAP 2 -#define CMD_COPY 3 -#define CMD_CAT 4 -#define CMD_CAT16 5 -#define CMD_CAT128 6 - -#define ARG_NOPART 0 -#define ARG_PART 1 - -#define SKIP_CHECKSUM_READ 0 -#define CHECKSUM_READ 1 - -#define SKIP_CHECKSUM_WRITE 0 -#define CHECKSUM_WRITE 1 - -/* command table - */ - -struct commands { - unsigned long chk; - char *str; - void (*run)(void); - int argc; - unsigned char arg_part; - unsigned char chksum_read; - unsigned char chksum_write; - unsigned long rw_size; /* within the 4KB GbE part */ - int flags; /* e.g. O_RDWR or O_RDONLY */ -}; - -/* mac address - */ - -struct macaddr { - char *str; /* set to rmac, or argv string */ - char rmac[18]; /* xx:xx:xx:xx:xx:xx */ - unsigned short mac_buf[3]; -}; - -/* gbe.bin and tmpfile - */ - -struct xfile { - int gbe_fd; - struct stat gbe_st; - - int tmp_fd; - struct stat tmp_st; - - char *tname; /* path of tmp file */ - char *fname; /* path of gbe file */ - - unsigned char *buf; /* work memory for files */ - - int io_err_gbe; /* intermediary write (verification) */ - int io_err_gbe_bin; /* final write (real file) */ - int rw_check_err_read[2]; - int rw_check_partial_read[2]; - int rw_check_bad_part[2]; - - int post_rw_checksum[2]; - - off_t gbe_file_size; - off_t gbe_tmp_size; - - unsigned long part; - unsigned char part_modified[2]; - unsigned char part_valid[2]; - - unsigned char real_buf[GBE_BUF_SIZE]; - unsigned char bufcmp[GBE_BUF_SIZE]; /* compare gbe/tmp/reads */ - - unsigned char pad[GBE_WORK_SIZE]; /* the file that wouldn't die */ -}; - -/* Command table, MAC address, files - * - * BE CAREFUL when editing this - * to ensure that you also update - * the tables in xstatus() - */ - -struct xstate { - struct commands cmd[7]; - struct macaddr mac; - struct xfile f; - - char *argv0; - - unsigned long i; /* index to cmd[] for current command */ - int no_cmd; - - /* Cat commands set this. - the cat cmd helpers check it */ - int cat; -}; - - - -struct xstate *xstatus(int argc, char *argv[]); - -/* Sanitize command tables. - */ - -void sanitize_command_list(void); -void sanitize_command_index(unsigned long c); - -/* Argument handling (user input) - */ - -void set_cmd(int argc, char *argv[]); -void set_cmd_args(int argc, char *argv[]); -unsigned long conv_argv_part_num(const char *part_str); -int xstrxcmp(const char *a, const char *b, unsigned long maxlen); - -/* Prep files for reading - */ - -void open_gbe_file(void); -int lock_file(int fd, int flags); -int same_file(int fd, struct stat *st_old, int check_size); -void xopen(int *fd, const char *path, int flags, struct stat *st); - -/* Read GbE file and verify checksums - */ - -void copy_gbe(void); -void read_file(void); -void read_checksums(void); -int good_checksum(unsigned long partnum); - -/* validate commands - */ - -void check_command_num(unsigned long c); -unsigned char valid_command(unsigned long c); - -/* Helper functions for command: setmac - */ - -void cmd_helper_setmac(void); -void parse_mac_string(void); -unsigned long xstrxlen(const char *scmp, unsigned long maxlen); -void set_mac_byte(unsigned long mac_byte_pos); -void set_mac_nib(unsigned long mac_str_pos, - unsigned long mac_byte_pos, unsigned long mac_nib_pos); -unsigned short hextonum(char ch_s); -unsigned long rlong(void); -#if !(defined(FALLBACK_RAND_1989) && \ - ((FALLBACK_RAND_1989) > 0)) -#if defined(__linux__) -#if defined(HAVE_GETRANDOM) || \ - defined(HAVE_GETRANDOM_SYSCALL) -int fallback_rand_getrandom(void *buf, size_t len); -#endif -#endif -#else -unsigned long fallback_rand_1989(void); -unsigned long entropy_jitter(void); -#endif -void write_mac_part(unsigned long partnum); - -/* Helper functions for command: dump - */ - -void cmd_helper_dump(void); -void print_mac_from_nvm(unsigned long partnum); -void hexdump(unsigned long partnum); - -/* Helper functions for command: swap - */ - -void cmd_helper_swap(void); - -/* Helper functions for command: copy - */ - -void cmd_helper_copy(void); - -/* Helper functions for commands: - * cat, cat16 and cat128 - */ - -void cmd_helper_cat(void); -void cmd_helper_cat16(void); -void cmd_helper_cat128(void); -void cat(unsigned long nff); -void cat_buf(unsigned char *b); - -/* Command verification/control - */ - -void check_cmd(void (*fn)(void), const char *name); -void cmd_helper_err(void); - -/* Write GbE files to disk - */ - -void write_gbe_file(void); -void set_checksum(unsigned long part); -unsigned short calculated_checksum(unsigned long p); - -/* NVM read/write - */ - -unsigned short nvm_word(unsigned long pos16, unsigned long part); -void set_nvm_word(unsigned long pos16, - unsigned long part, unsigned short val16); -void set_part_modified(unsigned long p); -void check_nvm_bound(unsigned long pos16, unsigned long part); -void check_bin(unsigned long a, const char *a_name); - -/* GbE file read/write - */ - -void rw_gbe_file_part(unsigned long p, int rw_type, - const char *rw_type_str); -void write_to_gbe_bin(void); -int gbe_mv(void); -void check_written_part(unsigned long p); -void report_io_err_rw(void); -unsigned char *gbe_mem_offset(unsigned long part, const char *f_op); -off_t gbe_file_offset(unsigned long part, const char *f_op); -off_t gbe_x_offset(unsigned long part, const char *f_op, - const char *d_type, off_t nsize, off_t ncmp); -long rw_gbe_file_exact(int fd, unsigned char *mem, unsigned long nrw, - off_t off, int rw_type); - -/* Generic read/write - */ - -int fsync_dir(const char *path); -long rw_file_exact(int fd, unsigned char *mem, unsigned long len, - off_t off, int rw_type, int loop_eagain, int loop_eintr, - unsigned long max_retries, int off_reset); -long prw(int fd, void *mem, unsigned long nrw, - off_t off, int rw_type, int loop_eagain, int loop_eintr, - int off_reset); -int io_args(int fd, void *mem, unsigned long nrw, - off_t off, int rw_type); -int check_file(int fd, struct stat *st); -long rw_over_nrw(long r, unsigned long nrw); -#if !defined(HAVE_REAL_PREAD_PWRITE) || \ - HAVE_REAL_PREAD_PWRITE < 1 -off_t lseek_on_eintr(int fd, off_t off, - int whence, int loop_eagain, int loop_eintr); -#endif -int try_err(int loop_err, int errval); - -/* Error handling and cleanup - */ - -void usage(void); -void err(int nvm_errval, const char *msg, ...); -int exit_cleanup(void); -const char *getnvmprogname(void); - -/* Portable libc functions - */ - -char *new_tmpfile(int *fd, int local, const char *path); -int mkstemp_n(char *template); -char *get_tmpdir(void); -int close_on_eintr(int fd); -int fsync_on_eintr(int fd); - -/* asserts */ - -/* type asserts */ -typedef char static_assert_char_is_8_bits[(CHAR_BIT == 8) ? 1 : -1]; -typedef char static_assert_char_is_1[(sizeof(char) == 1) ? 1 : -1]; -typedef char static_assert_unsigned_char_is_1[ - (sizeof(unsigned char) == 1) ? 1 : -1]; -typedef char static_assert_unsigned_short_is_2[ - (sizeof(unsigned short) >= 2) ? 1 : -1]; -typedef char static_assert_short_is_2[(sizeof(short) >= 2) ? 1 : -1]; -typedef char static_assert_unsigned_int_is_4[ - (sizeof(unsigned int) >= 4) ? 1 : -1]; -typedef char static_assert_unsigned_long_is_4[ - (sizeof(unsigned long) >= 4) ? 1 : -1]; -typedef char static_assert_long_ulong[ - (sizeof(unsigned long) == sizeof(long)) ? 1 : -1]; -typedef char static_assert_int_ge_32[(sizeof(int) >= 4) ? 1 : -1]; -typedef char static_assert_twos_complement[ - ((-1 & 3) == 3) ? 1 : -1 -]; -typedef char assert_unsigned_long_ptr[ - (sizeof(unsigned long) >= sizeof(void *)) ? 1 : -1 -]; - -/* - * We set _FILE_OFFSET_BITS 64, but we only handle - * but we only need smaller files, so require 4-bytes. - * Some operating systems ignore the define, hence assert: - */ -typedef char static_assert_off_t_is_32[(sizeof(off_t) >= 4) ? 1 : -1]; - -/* - * asserts (variables/defines sanity check) - */ -typedef char assert_argc3[(ARGC_3==3)?1:-1]; -typedef char assert_argc4[(ARGC_4==4)?1:-1]; -typedef char assert_read[(IO_READ==0)?1:-1]; -typedef char assert_write[(IO_WRITE==1)?1:-1]; -typedef char assert_pread[(IO_PREAD==2)?1:-1]; -typedef char assert_pwrite[(IO_PWRITE==3)?1:-1]; -typedef char assert_pathlen[(PATH_LEN>=256)?1:-1]; -/* commands */ -typedef char assert_cmd_dump[(CMD_DUMP==0)?1:-1]; -typedef char assert_cmd_setmac[(CMD_SETMAC==1)?1:-1]; -typedef char assert_cmd_swap[(CMD_SWAP==2)?1:-1]; -typedef char assert_cmd_copy[(CMD_COPY==3)?1:-1]; -typedef char assert_cmd_cat[(CMD_CAT==4)?1:-1]; -typedef char assert_cmd_cat16[(CMD_CAT16==5)?1:-1]; -typedef char assert_cmd_cat128[(CMD_CAT128==6)?1:-1]; -/* bool */ -typedef char bool_arg_nopart[(ARG_NOPART==0)?1:-1]; -typedef char bool_arg_part[(ARG_PART==1)?1:-1]; -typedef char bool_skip_checksum_read[(SKIP_CHECKSUM_READ==0)?1:-1]; -typedef char bool_checksum_read[(CHECKSUM_READ==1)?1:-1]; -typedef char bool_skip_checksum_write[(SKIP_CHECKSUM_WRITE==0)?1:-1]; -typedef char bool_checksum_write[(CHECKSUM_WRITE==1)?1:-1]; -typedef char bool_loop_eintr[(LOOP_EINTR==1||LOOP_EINTR==0)?1:-1]; -typedef char bool_loop_eagain[(LOOP_EAGAIN==1||LOOP_EAGAIN==0)?1:-1]; -typedef char bool_no_loop_eintr[(NO_LOOP_EINTR==0)?1:-1]; -typedef char bool_no_loop_eagain[(NO_LOOP_EAGAIN==0)?1:-1]; -typedef char bool_off_err[(OFF_ERR==0)?1:-1]; -typedef char bool_off_reset[(OFF_RESET==0||OFF_RESET==1)?1:-1]; - -#endif -#endif diff --git a/util/nvmutil/lib/checksum.c b/util/nvmutil/lib/checksum.c deleted file mode 100644 index 8565361b..00000000 --- a/util/nvmutil/lib/checksum.c +++ /dev/null @@ -1,108 +0,0 @@ -/* SPDX-License-Identifier: MIT - * Copyright (c) 2022-2026 Leah Rowe <leah@libreboot.org> - * - * Functions related to GbE NVM checksums. - */ - -#include <sys/types.h> -#include <sys/stat.h> - -#include <errno.h> -#include <limits.h> -#include <stddef.h> -#include <stdlib.h> - -#include "../include/common.h" - -void -read_checksums(void) -{ - struct xstate *x = xstatus(0, NULL); - struct commands *cmd = &x->cmd[x->i]; - struct xfile *f = &x->f; - - unsigned long _p; - unsigned long _skip_part; - - unsigned char _num_invalid; - unsigned char _max_invalid; - - f->part_valid[0] = 0; - f->part_valid[1] = 0; - - if (!cmd->chksum_read) - return; - - _num_invalid = 0; - _max_invalid = 2; - - if (cmd->arg_part) - _max_invalid = 1; - - /* Skip verification on this part, - * but only when arg_part is set. - */ - _skip_part = f->part ^ 1; - - for (_p = 0; _p < 2; _p++) { - - /* Only verify a part if it was *read* - */ - if (cmd->arg_part && (_p == _skip_part)) - continue; - - f->part_valid[_p] = good_checksum(_p); - if (!f->part_valid[_p]) - ++_num_invalid; - } - - if (_num_invalid >= _max_invalid) { - - if (_max_invalid == 1) - err(ECANCELED, "%s: part %lu has a bad checksum", - f->fname, (unsigned long)f->part); - - err(ECANCELED, "%s: No valid checksum found in file", - f->fname); - } -} - -int -good_checksum(unsigned long partnum) -{ - unsigned short expected_checksum; - unsigned short actual_checksum; - - expected_checksum = - calculated_checksum(partnum); - - actual_checksum = - nvm_word(NVM_CHECKSUM_WORD, partnum); - - if (expected_checksum == actual_checksum) { - return 1; - } else { - return 0; - } -} - -void -set_checksum(unsigned long p) -{ - check_bin(p, "part number"); - set_nvm_word(NVM_CHECKSUM_WORD, p, calculated_checksum(p)); -} - -unsigned short -calculated_checksum(unsigned long p) -{ - unsigned long c; - unsigned int val16; - - val16 = 0; - - for (c = 0; c < NVM_CHECKSUM_WORD; c++) - val16 += (unsigned int)nvm_word(c, p); - - return (unsigned short)((NVM_CHECKSUM - val16) & 0xffff); -} diff --git a/util/nvmutil/lib/command.c b/util/nvmutil/lib/command.c deleted file mode 100644 index 95e1b4f7..00000000 --- a/util/nvmutil/lib/command.c +++ /dev/null @@ -1,546 +0,0 @@ -/* SPDX-License-Identifier: MIT - * Copyright (c) 2022-2026 Leah Rowe <leah@libreboot.org> - */ - -#include <sys/types.h> -#include <sys/stat.h> - -#include <errno.h> -#include <fcntl.h> -#include <limits.h> -#include <stdio.h> -#include <stddef.h> -#include <string.h> -#include <unistd.h> - -#include "../include/common.h" - -/* Guard against regressions by maintainers (command table) - */ - -void -sanitize_command_list(void) -{ - struct xstate *x = xstatus(0, NULL); - - unsigned long c; - unsigned long num_commands; - - num_commands = items(x->cmd); - - for (c = 0; c < num_commands; c++) - sanitize_command_index(c); -} - -void -sanitize_command_index(unsigned long c) -{ - struct xstate *x = xstatus(0, NULL); - struct commands *cmd = &x->cmd[c]; - - int _flag; - unsigned long gbe_rw_size; - - check_command_num(c); - - if (cmd->argc < 3) - err(EINVAL, "cmd index %lu: argc below 3, %d", - (unsigned long)c, cmd->argc); - - if (cmd->str == NULL) - err(EINVAL, "cmd index %lu: NULL str", - (unsigned long)c); - - if (*cmd->str == '\0') - err(EINVAL, "cmd index %lu: empty str", - (unsigned long)c); - - if (xstrxlen(cmd->str, MAX_CMD_LEN + 1) > - MAX_CMD_LEN) { - err(EINVAL, "cmd index %lu: str too long: %s", - (unsigned long)c, cmd->str); - } - - if (cmd->run == NULL) - err(EINVAL, "cmd index %lu: cmd ptr null", - (unsigned long)c); - - check_bin(cmd->arg_part, "cmd.arg_part"); - check_bin(cmd->chksum_read, "cmd.chksum_read"); - check_bin(cmd->chksum_write, "cmd.chksum_write"); - - gbe_rw_size = cmd->rw_size; - - switch (gbe_rw_size) { - case GBE_PART_SIZE: - case NVM_SIZE: - break; - default: - err(EINVAL, "Unsupported rw_size: %lu", - (unsigned long)gbe_rw_size); - } - - if (gbe_rw_size > GBE_PART_SIZE) - err(EINVAL, "rw_size larger than GbE part: %lu", - (unsigned long)gbe_rw_size); - - _flag = (cmd->flags & O_ACCMODE); - - if (_flag != O_RDONLY && - _flag != O_RDWR) - err(EINVAL, "invalid cmd.flags setting"); -} - -void -set_cmd(int argc, char *argv[]) -{ - struct xstate *x = xstatus(0, NULL); - const char *cmd; - - unsigned long c; - - for (c = 0; c < items(x->cmd); c++) { - - cmd = x->cmd[c].str; - - /* not the right command */ - if (xstrxcmp(argv[2], cmd, MAX_CMD_LEN) != 0) - continue; - - /* valid command found */ - if (argc >= x->cmd[c].argc) { - x->no_cmd = 0; - x->i = c; /* set command */ - - return; - } - - err(EINVAL, - "Too few args on command '%s'", cmd); - } - - x->no_cmd = 1; -} - -void -set_cmd_args(int argc, char *argv[]) -{ - struct xstate *x = xstatus(0, NULL); - unsigned long i = x->i; - struct commands *cmd = &x->cmd[i]; - struct xfile *f = &x->f; - - if (!valid_command(i) || argc < 3) - usage(); - - if (x->no_cmd) - usage(); - - /* Maintainer bug - */ - if (cmd->arg_part && argc < 4) - err(EINVAL, - "arg_part set for command that needs argc4"); - - if (cmd->arg_part && i == CMD_SETMAC) - err(EINVAL, - "arg_part set on CMD_SETMAC"); - - if (i == CMD_SETMAC) { - - if (argc >= 4) - x->mac.str = argv[3]; - else - x->mac.str = x->mac.rmac; - - } else if (cmd->arg_part) { - - f->part = conv_argv_part_num(argv[3]); - } -} - -unsigned long -conv_argv_part_num(const char *part_str) -{ - unsigned char ch; - - if (part_str[0] == '\0' || part_str[1] != '\0') - err(EINVAL, "Partnum string '%s' wrong length", part_str); - - /* char signedness is implementation-defined - */ - ch = (unsigned char)part_str[0]; - if (ch < '0' || ch > '1') - err(EINVAL, "Bad part number (%c)", ch); - - return (unsigned long)(ch - '0'); -} - -void -check_command_num(unsigned long c) -{ - if (!valid_command(c)) - err(EINVAL, "Invalid run_cmd arg: %lu", - (unsigned long)c); -} - -unsigned char -valid_command(unsigned long c) -{ - struct xstate *x = xstatus(0, NULL); - struct commands *cmd; - - if (c >= items(x->cmd)) - return 0; - - cmd = &x->cmd[c]; - - if (c != cmd->chk) - err(EINVAL, - "Invalid cmd chk value (%lu) vs arg: %lu", - cmd->chk, c); - - return 1; -} - -void -cmd_helper_setmac(void) -{ - struct xstate *x = xstatus(0, NULL); - struct macaddr *mac = &x->mac; - - unsigned long partnum; - - check_cmd(cmd_helper_setmac, "setmac"); - - printf("MAC address to be written: %s\n", mac->str); - parse_mac_string(); - - for (partnum = 0; partnum < 2; partnum++) - write_mac_part(partnum); -} - -void -parse_mac_string(void) -{ - struct xstate *x = xstatus(0, NULL); - struct macaddr *mac = &x->mac; - - unsigned long mac_byte; - - if (xstrxlen(x->mac.str, 18) != 17) - err(EINVAL, "MAC address is the wrong length"); - - memset(mac->mac_buf, 0, sizeof(mac->mac_buf)); - - for (mac_byte = 0; mac_byte < 6; mac_byte++) - set_mac_byte(mac_byte); - - if ((mac->mac_buf[0] | mac->mac_buf[1] | mac->mac_buf[2]) == 0) - err(EINVAL, "Must not specify all-zeroes MAC address"); - - if (mac->mac_buf[0] & 1) - err(EINVAL, "Must not specify multicast MAC address"); -} - -void -set_mac_byte(unsigned long mac_byte_pos) -{ - struct xstate *x = xstatus(0, NULL); - struct macaddr *mac = &x->mac; - - char separator; - - unsigned long mac_str_pos; - unsigned long mac_nib_pos; - - mac_str_pos = mac_byte_pos * 3; - - if (mac_str_pos < 15) { - if ((separator = mac->str[mac_str_pos + 2]) != ':') - err(EINVAL, "Invalid MAC address separator '%c'", - separator); - } - - for (mac_nib_pos = 0; mac_nib_pos < 2; mac_nib_pos++) - set_mac_nib(mac_str_pos, mac_byte_pos, mac_nib_pos); -} - -void -set_mac_nib(unsigned long mac_str_pos, - unsigned long mac_byte_pos, unsigned long mac_nib_pos) -{ - struct xstate *x = xstatus(0, NULL); - struct macaddr *mac = &x->mac; - - char mac_ch; - unsigned short hex_num; - - mac_ch = mac->str[mac_str_pos + mac_nib_pos]; - - if ((hex_num = hextonum(mac_ch)) > 15) - err(EINVAL, "Invalid character '%c'", - mac->str[mac_str_pos + mac_nib_pos]); - - /* If random, ensure that local/unicast bits are set. - */ - if ((mac_byte_pos == 0) && (mac_nib_pos == 1) && - ((mac_ch | 0x20) == 'x' || - (mac_ch == '?'))) - hex_num = (hex_num & 0xE) | 2; /* local, unicast */ - - /* MAC words stored big endian in-file, little-endian - * logically, so we reverse the order. - */ - mac->mac_buf[mac_byte_pos >> 1] |= hex_num << - (((mac_byte_pos & 1) << 3) /* left or right byte? */ - | ((mac_nib_pos ^ 1) << 2)); /* left or right nib? */ -} - -void -write_mac_part(unsigned long partnum) -{ - struct xstate *x = xstatus(0, NULL); - struct xfile *f = &x->f; - struct macaddr *mac = &x->mac; - - unsigned long w; - - check_bin(partnum, "part number"); - if (!f->part_valid[partnum]) - return; - - for (w = 0; w < 3; w++) - set_nvm_word(w, partnum, mac->mac_buf[w]); - - printf("Wrote MAC address to part %lu: ", - (unsigned long)partnum); - print_mac_from_nvm(partnum); -} - -void -cmd_helper_dump(void) -{ - struct xstate *x = xstatus(0, NULL); - struct xfile *f = &x->f; - - unsigned long p; - - check_cmd(cmd_helper_dump, "dump"); - - f->part_valid[0] = good_checksum(0); - f->part_valid[1] = good_checksum(1); - - for (p = 0; p < 2; p++) { - - if (!f->part_valid[p]) { - - fprintf(stderr, - "BAD checksum %04x in part %lu (expected %04x)\n", - nvm_word(NVM_CHECKSUM_WORD, p), - (unsigned long)p, - calculated_checksum(p)); - } - - printf("MAC (part %lu): ", - (unsigned long)p); - - print_mac_from_nvm(p); - - hexdump(p); - } -} - -void -print_mac_from_nvm(unsigned long partnum) -{ - unsigned long c; - unsigned short val16; - - for (c = 0; c < 3; c++) { - - val16 = nvm_word(c, partnum); - - printf("%02x:%02x", - (unsigned int)(val16 & 0xff), - (unsigned int)(val16 >> 8)); - - if (c == 2) - printf("\n"); - else - printf(":"); - } -} - -void -hexdump(unsigned long partnum) -{ - unsigned long c; - unsigned long row; - unsigned short val16; - - for (row = 0; row < 8; row++) { - - printf("%08lx ", - (unsigned long)((unsigned long)row << 4)); - - for (c = 0; c < 8; c++) { - - val16 = nvm_word((row << 3) + c, partnum); - - if (c == 4) - printf(" "); - - printf(" %02x %02x", - (unsigned int)(val16 & 0xff), - (unsigned int)(val16 >> 8)); - - } - - printf("\n"); - } -} - -void -cmd_helper_swap(void) -{ - struct xstate *x = xstatus(0, NULL); - struct xfile *f = &x->f; - - check_cmd(cmd_helper_swap, "swap"); - - memcpy( - f->buf + (unsigned long)GBE_WORK_SIZE, - f->buf, - GBE_PART_SIZE); - - memcpy( - f->buf, - f->buf + (unsigned long)GBE_PART_SIZE, - GBE_PART_SIZE); - - memcpy( - f->buf + (unsigned long)GBE_PART_SIZE, - f->buf + (unsigned long)GBE_WORK_SIZE, - GBE_PART_SIZE); - - set_part_modified(0); - set_part_modified(1); -} - -void -cmd_helper_copy(void) -{ - struct xstate *x = xstatus(0, NULL); - struct xfile *f = &x->f; - - check_cmd(cmd_helper_copy, "copy"); - - memcpy( - f->buf + (unsigned long)((f->part ^ 1) * GBE_PART_SIZE), - f->buf + (unsigned long)(f->part * GBE_PART_SIZE), - GBE_PART_SIZE); - - set_part_modified(f->part ^ 1); -} - -void -cmd_helper_cat(void) -{ - struct xstate *x = xstatus(0, NULL); - - check_cmd(cmd_helper_cat, "cat"); - - x->cat = 0; - cat(0); -} - -void -cmd_helper_cat16(void) -{ - struct xstate *x = xstatus(0, NULL); - - check_cmd(cmd_helper_cat16, "cat16"); - - x->cat = 1; - cat(1); -} - -void -cmd_helper_cat128(void) -{ - struct xstate *x = xstatus(0, NULL); - - check_cmd(cmd_helper_cat128, "cat128"); - - x->cat = 15; - cat(15); -} - -void -cat(unsigned long nff) -{ - struct xstate *x = xstatus(0, NULL); - struct xfile *f = &x->f; - - unsigned long p; - unsigned long ff; - - p = 0; - ff = 0; - - if ((unsigned long)x->cat != nff) { - - err(ECANCELED, "erroneous call to cat"); - } - - fflush(NULL); - - memset(f->pad, 0xff, GBE_PART_SIZE); - - for (p = 0; p < 2; p++) { - - cat_buf(f->bufcmp + - (unsigned long)(p * (f->gbe_file_size >> 1))); - - for (ff = 0; ff < nff; ff++) { - - cat_buf(f->pad); - } - } -} - -void -cat_buf(unsigned char *b) -{ - if (b == NULL) - err(errno, "null pointer in cat command"); - - if (rw_file_exact(STDOUT_FILENO, b, - GBE_PART_SIZE, 0, IO_WRITE, LOOP_EAGAIN, LOOP_EINTR, - MAX_ZERO_RW_RETRY, OFF_ERR) < 0) - err(errno, "stdout: cat"); -} -void -check_cmd(void (*fn)(void), - const char *name) -{ - struct xstate *x = xstatus(0, NULL); - unsigned long i = x->i; - - if (x->cmd[i].run != fn) - err(ECANCELED, "Running %s, but cmd %s is set", - name, x->cmd[i].str); - - /* prevent second command - */ - for (i = 0; i < items(x->cmd); i++) - x->cmd[i].run = cmd_helper_err; -} - -void -cmd_helper_err(void) -{ - err(ECANCELED, - "Erroneously running command twice"); -} diff --git a/util/nvmutil/lib/file.c b/util/nvmutil/lib/file.c deleted file mode 100644 index b4925ccd..00000000 --- a/util/nvmutil/lib/file.c +++ /dev/null @@ -1,890 +0,0 @@ -/* SPDX-License-Identifier: MIT - * Copyright (c) 2026 Leah Rowe <leah@libreboot.org> - */ - -#include <sys/types.h> -#include <sys/stat.h> - -#include <errno.h> -#include <fcntl.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#include "../include/common.h" - -/* check that a file changed - */ - -int -same_file(int fd, struct stat *st_old, - int check_size) -{ - struct stat st; - int saved_errno = errno; - - if (st_old == NULL || fd < 0) - goto err_same_file; - - if (fstat(fd, &st) == -1) - return -1; - - if (st.st_dev != st_old->st_dev || - st.st_ino != st_old->st_ino || - !S_ISREG(st.st_mode)) - goto err_same_file; - - if (check_size && - st.st_size != st_old->st_size) - goto err_same_file; - - errno = saved_errno; - return 0; - -err_same_file: - - errno = EIO; - return -1; -} - -/* open() but with abort traps - */ - -void -xopen(int *fd_ptr, const char *path, int flags, struct stat *st) -{ - if ((*fd_ptr = open(path, flags)) == -1) - err(errno, "%s", path); - - if (fstat(*fd_ptr, st) == -1) - err(errno, "%s: stat", path); - - if (!S_ISREG(st->st_mode)) - err(errno, "%s: not a regular file", path); - - if (lseek(*fd_ptr, 0, SEEK_CUR) == (off_t)-1) - err(errno, "%s: file not seekable", path); -} - -/* fsync() the directory of a file, - * useful for atomic writes - */ - -int -fsync_dir(const char *path) -{ - int saved_errno = errno; - - unsigned long pathlen; - unsigned long maxlen; - - char *dirbuf; - int dirfd; - - char *slash; - - struct stat st; - -#if defined(PATH_LEN) && \ - (PATH_LEN) >= 256 - maxlen = PATH_LEN; -#else - maxlen = 1024; -#endif - - dirbuf = NULL; - dirfd = -1; - - pathlen = xstrxlen(path, maxlen); - - if (pathlen >= maxlen) { - fprintf(stderr, "Path too long for fsync_parent_dir\n"); - goto err_fsync_dir; - } - - if (pathlen == 0) - { - errno = EINVAL; - goto err_fsync_dir; - } - - dirbuf = malloc(pathlen + 1); - if (dirbuf == NULL) - goto err_fsync_dir; - - memcpy(dirbuf, path, pathlen + 1); - slash = strrchr(dirbuf, '/'); - - if (slash != NULL) { - *slash = '\0'; - if (*dirbuf == '\0') { - dirbuf[0] = '/'; - dirbuf[1] = '\0'; - } - } else { - dirbuf[0] = '.'; - dirbuf[1] = '\0'; - } - - dirfd = open(dirbuf, O_RDONLY -#ifdef O_DIRECTORY - | O_DIRECTORY -#endif -#ifdef O_NOFOLLOW - | O_NOFOLLOW -#endif - ); - if (dirfd == -1) - goto err_fsync_dir; - - if (fstat(dirfd, &st) < 0) - goto err_fsync_dir; - - if (!S_ISDIR(st.st_mode)) { - fprintf(stderr, "%s: not a directory\n", dirbuf); - goto err_fsync_dir; - } - - /* sync file on disk */ - if (fsync_on_eintr(dirfd) == -1) - goto err_fsync_dir; - - if (close_on_eintr(dirfd) == -1) - goto err_fsync_dir; - - if (dirbuf != NULL) - free(dirbuf); - - errno = saved_errno; - return 0; - -err_fsync_dir: - if (!errno) - errno = EIO; - - if (errno != saved_errno) - fprintf(stderr, "%s: %s\n", path, strerror(errno)); - - if (dirbuf != NULL) - free(dirbuf); - - if (dirfd > -1) - close_on_eintr(dirfd); - - errno = saved_errno; - - return -1; -} - -/* returns ptr to path (string). if local>0: - * make tmpfile in the same directory as the - * file. if local==0, use TMPDIR - * - * if local==0, the 3rd argument is ignored - */ - -char * -new_tmpfile(int *fd, int local, const char *path) -{ - unsigned long maxlen; - struct stat st; - - /* please do not modify the - * strings or I will get mad - */ - char tmp_none[] = ""; - char tmp_default[] = "/tmp"; - char default_tmpname[] = "tmpXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; - char *tmpname; - - char *base = NULL; - char *dest = NULL; - - unsigned long tmpdir_len = 0; - unsigned long tmpname_len = 0; - unsigned long tmppath_len = 0; - - int fd_tmp = -1; - int flags; - -#if defined(PATH_LEN) && \ - (PATH_LEN) >= 256 - maxlen = PATH_LEN; -#else - maxlen = 1024; -#endif - - tmpname = default_tmpname; - if (local) { - if (path == NULL) - goto err_new_tmpfile; - if (*path == '\0') - goto err_new_tmpfile; - - if (stat(path, &st) == -1) - goto err_new_tmpfile; - - if (!S_ISREG(st.st_mode)) - goto err_new_tmpfile; - - tmpname = (char *)path; - } - - if (local) { - base = tmp_none; - - /* appended to filename for tmp: - */ - tmpdir_len = xstrxlen(default_tmpname, maxlen); - } else { - base = get_tmpdir(); - - if (base == NULL) - base = tmp_default; - if (*base == '\0') - base = tmp_default; - - tmpdir_len = xstrxlen(base, maxlen); - } - - tmpname_len = xstrxlen(tmpname, maxlen); - - tmppath_len = tmpdir_len + tmpname_len; - ++tmppath_len; /* for '/' or '.' */ - - /* max length -1 of maxlen - * for termination - */ - if (tmpdir_len > maxlen - tmpname_len - 1) - goto err_new_tmpfile; - - /* +1 for NULL */ - dest = malloc(tmppath_len + 1); - if (dest == NULL) - goto err_new_tmpfile; - - if (local) { - - *dest = '.'; /* hidden file */ - - memcpy(dest + (unsigned long)1, tmpname, tmpname_len); - - memcpy(dest + (unsigned long)1 + tmpname_len, - default_tmpname, tmpdir_len); - } else { - - memcpy(dest, base, tmpdir_len); - - dest[tmpdir_len] = '/'; - - memcpy(dest + tmpdir_len + 1, tmpname, tmpname_len); - } - - dest[tmppath_len] = '\0'; - - fd_tmp = mkstemp_n(dest); - if (fd_tmp == -1) - goto err_new_tmpfile; - - if (fchmod(fd_tmp, 0600) == -1) - goto err_new_tmpfile; - - flags = fcntl(fd_tmp, F_GETFL); - - if (flags == -1) - goto err_new_tmpfile; - - /* - * O_APPEND would permit offsets - * to be ignored, which breaks - * positional read/write - */ - if (flags & O_APPEND) - goto err_new_tmpfile; - - if (lock_file(fd_tmp, flags) == -1) - goto err_new_tmpfile; - - if (fstat(fd_tmp, &st) == -1) - goto err_new_tmpfile; - - /* - * Extremely defensive - * likely pointless checks - */ - - /* check if it's a file */ - if (!S_ISREG(st.st_mode)) - goto err_new_tmpfile; - - /* check if it's seekable */ - if (lseek(fd_tmp, 0, SEEK_CUR) == (off_t)-1) - goto err_new_tmpfile; - - /* tmpfile has >1 hardlinks */ - if (st.st_nlink > 1) - goto err_new_tmpfile; - - /* tmpfile unlinked while opened */ - if (st.st_nlink == 0) - goto err_new_tmpfile; - - *fd = fd_tmp; - - return dest; - -err_new_tmpfile: - - if (dest != NULL) - free(dest); - - if (fd_tmp > -1) - close_on_eintr(fd_tmp); - - return NULL; -} - -int -lock_file(int fd, int flags) -{ - struct flock fl; - - memset(&fl, 0, sizeof(fl)); - - if ((flags & O_ACCMODE) == O_RDONLY) - fl.l_type = F_RDLCK; - else - fl.l_type = F_WRLCK; - - fl.l_whence = SEEK_SET; - - if (fcntl(fd, F_SETLK, &fl) == -1) - return -1; - - return 0; -} - -/* return TMPDIR, or fall back - * to portable defaults - */ - -char * -get_tmpdir(void) -{ - char *t; - struct stat st; - - t = getenv("TMPDIR"); - - if (t && *t) { - - if (stat(t, &st) == 0 && S_ISDIR(st.st_mode)) { - - if ((st.st_mode & S_IWOTH) && !(st.st_mode & S_ISVTX)) - return NULL; - - return t; - } - } - - if (stat("/tmp", &st) == 0 && S_ISDIR(st.st_mode)) - return "/tmp"; - - if (stat("/var/tmp", &st) == 0 && S_ISDIR(st.st_mode)) - return "/var/tmp"; - - return "."; -} - -/* portable mkstemp - */ - -int -mkstemp_n(char *template) -{ - int fd; - unsigned long i, j; - unsigned long len; - char *p; - - unsigned long xc = 0; - - static char ch[] = - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - - unsigned long r; -#if defined(PATH_LEN) && \ - (PATH_LEN) >= 256 - unsigned long max_len = PATH_LEN; -#else - unsigned long max_len = 4096; -#endif - - len = xstrxlen(template, max_len); - - if (len < 6) { - errno = EINVAL; - return -1; - } - - p = template + len; - - while (p > template && p[-1] == 'X') { - --p; - ++xc; - } - - if (xc < 6) { - errno = EINVAL; - return -1; - } - - for (i = 0; i < 200; i++) { - - for (j = 0; j < xc; j++) { - - r = rlong(); - - p[j] = ch[(unsigned long)(r >> 1) % (sizeof(ch) - 1)]; - } - - fd = open(template, - O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC, 0600); - - if (fd >= 0) - return fd; - - if (errno != EEXIST) - return -1; - } - - errno = EEXIST; - return -1; -} - -/* - * Safe I/O functions wrapping around - * read(), write() and providing a portable - * analog of both pread() and pwrite(). - * These functions are designed for maximum - * robustness, checking NULL inputs, overflowed - * outputs, and all kinds of errors that the - * standard libc functions don't. - * - * Looping on EINTR and EAGAIN is supported. - * EINTR/EAGAIN looping is done indefinitely. - */ - -/* rw_file_exact() - Read perfectly or die - * - * Read/write, and absolutely insist on an - * absolute read; e.g. if 100 bytes are - * requested, this MUST return 100. - * - * This function will never return zero. - * It will only return below (error), - * or above (success). On error, -1 is - * returned and errno is set accordingly. - * - * Zero-byte returns are not allowed. - * It will re-spin a finite number of - * times upon zero-return, to recover, - * otherwise it will return an error. - */ - -long -rw_file_exact(int fd, unsigned char *mem, unsigned long nrw, - off_t off, int rw_type, int loop_eagain, - int loop_eintr, unsigned long max_retries, - int off_reset) -{ - long rval; - long rc; - - unsigned long nrw_cur; - - off_t off_cur; - void *mem_cur; - - unsigned long retries_on_zero; - - rval = 0; - - rc = 0; - retries_on_zero = 0; - - if (io_args(fd, mem, nrw, off, rw_type) == -1) - return -1; - - while (1) { - - /* Prevent theoretical overflow */ - if (rval >= 0 && (unsigned long)rval > (nrw - rc)) - goto err_rw_file_exact; - - rc += rval; - if ((unsigned long)rc >= nrw) - break; - - mem_cur = (void *)(mem + (unsigned long)rc); - nrw_cur = (unsigned long)(nrw - (unsigned long)rc); - if (off < 0) - goto err_rw_file_exact; - off_cur = off + (off_t)rc; - - rval = prw(fd, mem_cur, nrw_cur, off_cur, - rw_type, loop_eagain, loop_eintr, - off_reset); - - if (rval < 0) - return -1; - - if (rval == 0) { - if (retries_on_zero++ < max_retries) - continue; - goto err_rw_file_exact; - } - - retries_on_zero = 0; - } - - if ((unsigned long)rc != nrw) - goto err_rw_file_exact; - - return rw_over_nrw(rc, nrw); - -err_rw_file_exact: - errno = EIO; - return -1; -} - -/* prw() - portable read-write with more - * safety checks than barebones libc - * - * portable pwrite/pread on request, or real - * pwrite/pread libc functions can be used. - * the portable (non-libc) pread/pwrite is not - * thread-safe, because it does not prevent or - * mitigate race conditions on file descriptors - * - * If you need real pwrite/pread, just compile - * with flag: HAVE_REAL_PREAD_PWRITE=1 - * - * A fallback is provided for regular read/write. - * rw_type can be IO_READ (read), IO_WRITE (write), - * IO_PREAD (pread) or IO_PWRITE - * - * loop_eagain does a retry loop on EAGAIN if set - * loop_eintr does a retry loop on EINTR if set - * - * race conditions on non-libc pread/pwrite: - * if a file offset changes, abort, to mitage. - * - * off_reset 1: reset the file offset *once* if - * a change was detected, assuming - * nothing else is touching it now - * off_reset 0: never reset if changed - */ - -long -prw(int fd, void *mem, unsigned long nrw, - off_t off, int rw_type, - int loop_eagain, int loop_eintr, - int off_reset) -{ - long r; - int positional_rw; - struct stat st; -#if !defined(HAVE_REAL_PREAD_PWRITE) || \ - HAVE_REAL_PREAD_PWRITE < 1 - int saved_errno; - off_t verified; - off_t off_orig; - off_t off_last; -#endif - - if (io_args(fd, mem, nrw, off, rw_type) - == -1) { - return -1; - } - - r = -1; - - /* do not use loop_eagain on - * normal files - */ - - if (!loop_eagain) { - /* check whether the file - * changed - */ - - if (check_file(fd, &st) == -1) - return -1; - } - - if (rw_type >= IO_PREAD) - positional_rw = 1; /* pread/pwrite */ - else - positional_rw = 0; /* read/write */ - -try_rw_again: - - if (!positional_rw) { -#if defined(HAVE_REAL_PREAD_PWRITE) && \ - HAVE_REAL_PREAD_PWRITE > 0 -real_pread_pwrite: -#endif - if (rw_type == IO_WRITE) - r = write(fd, mem, nrw); - else if (rw_type == IO_READ) - r = read(fd, mem, nrw); -#if defined(HAVE_REAL_PREAD_PWRITE) && \ - HAVE_REAL_PREAD_PWRITE > 0 - else if (rw_type == IO_PWRITE) - r = pwrite(fd, mem, nrw, off); - else if (rw_type == IO_PREAD) - r = pread(fd, mem, nrw, off); -#endif - - if (r == -1 && (errno == try_err(loop_eintr, EINTR) - || errno == try_err(loop_eagain, EAGAIN))) - goto try_rw_again; - - return rw_over_nrw(r, nrw); - } - -#if defined(HAVE_REAL_PREAD_PWRITE) && \ - HAVE_REAL_PREAD_PWRITE > 0 - goto real_pread_pwrite; -#else - if ((off_orig = lseek_on_eintr(fd, (off_t)0, SEEK_CUR, - loop_eagain, loop_eintr)) == (off_t)-1) { - r = -1; - } else if (lseek_on_eintr(fd, off, SEEK_SET, - loop_eagain, loop_eintr) == (off_t)-1) { - r = -1; - } else { - verified = lseek_on_eintr(fd, (off_t)0, SEEK_CUR, - loop_eagain, loop_eintr); - - /* abort if the offset changed, - * indicating race condition. if - * off_reset enabled, reset *ONCE* - */ - - if (off_reset && off != verified) - lseek_on_eintr(fd, off, SEEK_SET, - loop_eagain, loop_eintr); - - do { - /* check offset again, repeatedly. - * even if off_reset is set, this - * aborts if offsets change again - */ - - verified = lseek_on_eintr(fd, (off_t)0, SEEK_CUR, - loop_eagain, loop_eintr); - - if (off != verified) - goto err_prw; - - if (rw_type == IO_PREAD) - r = read(fd, mem, nrw); - else if (rw_type == IO_PWRITE) - r = write(fd, mem, nrw); - - if (rw_over_nrw(r, nrw) == -1) { - errno = EIO; - break; - } - - } while (r == -1 && - (errno == try_err(loop_eintr, EINTR) || - errno == try_err(loop_eagain, EAGAIN))); - } - - saved_errno = errno; - - off_last = lseek_on_eintr(fd, off_orig, SEEK_SET, - loop_eagain, loop_eintr); - - if (off_last != off_orig) { - errno = saved_errno; - goto err_prw; - } - - errno = saved_errno; - - return rw_over_nrw(r, nrw); -#endif - -err_prw: - errno = EIO; - return -1; -} - -int -io_args(int fd, void *mem, unsigned long nrw, - off_t off, int rw_type) -{ - /* obviously */ - if (mem == NULL) - goto err_io_args; - - /* uninitialised fd */ - if (fd < 0) - goto err_io_args; - - /* negative offset */ - if (off < 0) - goto err_io_args; - - /* prevent zero-byte rw */ - if (!nrw) - goto err_io_args; - - /* prevent overflow */ - if (nrw > (unsigned long)X_LONG_MAX) - goto err_io_args; - - /* prevent overflow */ - if (((unsigned long)off + nrw) < (unsigned long)off) - goto err_io_args; - - if (rw_type > IO_PWRITE) - goto err_io_args; - - return 0; - -err_io_args: - errno = EIO; - return -1; -} - -int -check_file(int fd, struct stat *st) -{ - if (fstat(fd, st) == -1) - goto err_is_file; - - if (!S_ISREG(st->st_mode)) - goto err_is_file; - - return 0; - -err_is_file: - errno = EIO; - return -1; -} - -/* POSIX can say whatever it wants. - * specification != implementation - */ - -long -rw_over_nrw(long r, unsigned long nrw) -{ - /* not a libc bug, but we - * don't like the number zero - */ - if (!nrw) - goto err_rw_over_nrw; - - if (r == -1) - return r; - - if ((unsigned long) - r > X_LONG_MAX) { - - /* Theoretical buggy libc - * check. Extremely academic. - * - * Specifications never - * allow this return value - * to exceed SSIZE_T, but - * spec != implementation - * - * Check this after using - * [p]read() or [p]write() - * - * NOTE: here, we assume - * long integers are the - * same size as SSIZE_T - */ - - goto err_rw_over_nrw; - } - - /* Theoretical buggy libc: - * Should never return a number of - * bytes above the requested length. - */ - if ((unsigned long)r > nrw) - goto err_rw_over_nrw; - - return r; - -err_rw_over_nrw: - - errno = EIO; - return -1; -} - -#if !defined(HAVE_REAL_PREAD_PWRITE) || \ - HAVE_REAL_PREAD_PWRITE < 1 -off_t -lseek_on_eintr(int fd, off_t off, int whence, - int loop_eagain, int loop_eintr) -{ - off_t old; - - old = -1; - - do { - old = lseek(fd, off, whence); - } while (old == (off_t)-1 && ( - errno == try_err(loop_eintr, EINTR) || - errno == try_err(loop_eagain, EAGAIN))); - - return old; -} -#endif - -int -try_err(int loop_err, int errval) -{ - if (loop_err) - return errval; - - return -1; -} - -int -close_on_eintr(int fd) -{ - int r; - int saved_errno = errno; - - do { - r = close(fd); - } while (r == -1 && errno == EINTR); - - if (r > -1) - errno = saved_errno; - - return r; -} - -int -fsync_on_eintr(int fd) -{ - int r; - - do { - r = fsync(fd); - } while (r == -1 && errno == EINTR); - - return r; -} diff --git a/util/nvmutil/lib/io.c b/util/nvmutil/lib/io.c deleted file mode 100644 index 5769dd05..00000000 --- a/util/nvmutil/lib/io.c +++ /dev/null @@ -1,649 +0,0 @@ -/* SPDX-License-Identifier: MIT - * Copyright (c) 2026 Leah Rowe <leah@libreboot.org> - * - * I/O functions specific to nvmutil. - */ - -#include <sys/types.h> -#include <sys/stat.h> - -#include <errno.h> -#include <fcntl.h> -#include <limits.h> -#include <stddef.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#include "../include/common.h" - -void -open_gbe_file(void) -{ - struct xstate *x = xstatus(0, NULL); - struct commands *cmd = &x->cmd[x->i]; - struct xfile *f = &x->f; - - int _flags; - - xopen(&f->gbe_fd, f->fname, - cmd->flags | O_BINARY | - O_NOFOLLOW | O_CLOEXEC, &f->gbe_st); - - if (f->gbe_st.st_nlink > 1) - err(EINVAL, - "%s: warning: file has multiple (%lu) hard links\n", - f->fname, (unsigned long)f->gbe_st.st_nlink); - - if (f->gbe_st.st_nlink == 0) - err(EIO, "%s: file unlinked while open", f->fname); - - _flags = fcntl(f->gbe_fd, F_GETFL); - if (_flags == -1) - err(errno, "%s: fcntl(F_GETFL)", f->fname); - - /* O_APPEND allows POSIX write() to ignore - * the current write offset and write at EOF, - * which would break positional read/write - */ - - if (_flags & O_APPEND) - err(EIO, "%s: O_APPEND flag", f->fname); - - f->gbe_file_size = f->gbe_st.st_size; - - switch (f->gbe_file_size) { - case SIZE_8KB: - case SIZE_16KB: - case SIZE_128KB: - break; - default: - err(EINVAL, "File size must be 8KB, 16KB or 128KB"); - } - - if (lock_file(f->gbe_fd, cmd->flags) == -1) - err(errno, "%s: can't lock", f->fname); -} - -void -copy_gbe(void) -{ - struct xstate *x = xstatus(0, NULL); - struct xfile *f = &x->f; - - read_file(); - - if (f->gbe_file_size == SIZE_8KB) - return; - - memcpy(f->buf + (unsigned long)GBE_PART_SIZE, - f->buf + (unsigned long)(f->gbe_file_size >> 1), - (unsigned long)GBE_PART_SIZE); -} - -void -read_file(void) -{ - struct xstate *x = xstatus(0, NULL); - struct xfile *f = &x->f; - - struct stat _st; - long _r; - - /* read main file - */ - _r = rw_file_exact(f->gbe_fd, f->buf, f->gbe_file_size, - 0, IO_PREAD, NO_LOOP_EAGAIN, LOOP_EINTR, - MAX_ZERO_RW_RETRY, OFF_ERR); - - if (_r < 0) - err(errno, "%s: read failed", f->fname); - - /* copy to tmpfile - */ - _r = rw_file_exact(f->tmp_fd, f->buf, f->gbe_file_size, - 0, IO_PWRITE, NO_LOOP_EAGAIN, LOOP_EINTR, - MAX_ZERO_RW_RETRY, OFF_ERR); - - if (_r < 0) - err(errno, "%s: %s: copy failed", - f->fname, f->tname); - - /* file size comparison - */ - if (fstat(f->tmp_fd, &_st) == -1) - err(errno, "%s: stat", f->tname); - - f->gbe_tmp_size = _st.st_size; - - if (f->gbe_tmp_size != f->gbe_file_size) - err(EIO, "%s: %s: not the same size", - f->fname, f->tname); - - /* needs sync, for verification - */ - if (fsync_on_eintr(f->tmp_fd) == -1) - err(errno, "%s: fsync (tmpfile copy)", f->tname); - - _r = rw_file_exact(f->tmp_fd, f->bufcmp, f->gbe_file_size, - 0, IO_PREAD, NO_LOOP_EAGAIN, LOOP_EINTR, - MAX_ZERO_RW_RETRY, OFF_ERR); - - if (_r < 0) - err(errno, "%s: read failed (cmp)", f->tname); - - if (memcmp(f->buf, f->bufcmp, f->gbe_file_size) != 0) - err(errno, "%s: %s: read contents differ (pre-test)", - f->fname, f->tname); -} - -void -write_gbe_file(void) -{ - struct xstate *x = xstatus(0, NULL); - struct commands *cmd = &x->cmd[x->i]; - struct xfile *f = &x->f; - - unsigned long p; - unsigned char update_checksum; - - if ((cmd->flags & O_ACCMODE) == O_RDONLY) - return; - - if (same_file(f->tmp_fd, &f->tmp_st, 0) < 0) - err(errno, "%s: file inode/device changed", f->tname); - - if (same_file(f->gbe_fd, &f->gbe_st, 1) < 0) - err(errno, "%s: file has changed", f->fname); - - update_checksum = cmd->chksum_write; - - for (p = 0; p < 2; p++) { - if (!f->part_modified[p]) - continue; - - if (update_checksum) - set_checksum(p); - - rw_gbe_file_part(p, IO_PWRITE, "pwrite"); - } -} - -void -rw_gbe_file_part(unsigned long p, int rw_type, - const char *rw_type_str) -{ - struct xstate *x = xstatus(0, NULL); - struct commands *cmd = &x->cmd[x->i]; - struct xfile *f = &x->f; - - long rval; - - off_t file_offset; - - unsigned long gbe_rw_size; - unsigned char *mem_offset; - - gbe_rw_size = cmd->rw_size; - - if (rw_type < IO_PREAD || rw_type > IO_PWRITE) - err(errno, "%s: %s: part %lu: invalid rw_type, %d", - f->fname, rw_type_str, (unsigned long)p, rw_type); - - mem_offset = gbe_mem_offset(p, rw_type_str); - file_offset = (off_t)gbe_file_offset(p, rw_type_str); - - rval = rw_gbe_file_exact(f->tmp_fd, mem_offset, - gbe_rw_size, file_offset, rw_type); - - if (rval == -1) - err(errno, "%s: %s: part %lu", - f->fname, rw_type_str, (unsigned long)p); - - if ((unsigned long)rval != gbe_rw_size) - err(EIO, "%s: partial %s: part %lu", - f->fname, rw_type_str, (unsigned long)p); -} - -void -write_to_gbe_bin(void) -{ - struct xstate *x = xstatus(0, NULL); - struct commands *cmd = &x->cmd[x->i]; - struct xfile *f = &x->f; - - int saved_errno; - int mv; - - if ((cmd->flags & O_ACCMODE) != O_RDWR) - return; - - write_gbe_file(); - - /* We may otherwise read from - * cache, so we must sync. - */ - - if (fsync_on_eintr(f->tmp_fd) == -1) - err(errno, "%s: fsync (pre-verification)", - f->tname); - - check_written_part(0); - check_written_part(1); - - report_io_err_rw(); - - if (f->io_err_gbe) - err(EIO, "%s: bad write", f->fname); - - saved_errno = errno; - - if (close_on_eintr(f->tmp_fd) == -1) { - fprintf(stderr, "FAIL: %s: close\n", f->tname); - f->io_err_gbe_bin = 1; - } - - if (close_on_eintr(f->gbe_fd) == -1) { - fprintf(stderr, "FAIL: %s: close\n", f->fname); - f->io_err_gbe_bin = 1; - } - - errno = saved_errno; - - /* tmpfile written, now we - * rename it back to the main file - * (we do atomic writes) - */ - - f->tmp_fd = -1; - f->gbe_fd = -1; - - if (!f->io_err_gbe_bin) { - - mv = gbe_mv(); - - if (mv < 0) { - - f->io_err_gbe_bin = 1; - - fprintf(stderr, "%s: %s\n", - f->fname, strerror(errno)); - } else { - - /* removed by rename - */ - - if (f->tname != NULL) - free(f->tname); - - f->tname = NULL; - } - } - - if (!f->io_err_gbe_bin) - return; - - fprintf(stderr, "FAIL (rename): %s: skipping fsync\n", - f->fname); - if (errno) - fprintf(stderr, - "errno %d: %s\n", errno, strerror(errno)); -} - -void -check_written_part(unsigned long p) -{ - struct xstate *x = xstatus(0, NULL); - struct commands *cmd = &x->cmd[x->i]; - struct xfile *f = &x->f; - - long rval; - - unsigned long gbe_rw_size; - - off_t file_offset; - unsigned char *mem_offset; - - unsigned char *buf_restore; - - if (!f->part_modified[p]) - return; - - gbe_rw_size = cmd->rw_size; - - mem_offset = gbe_mem_offset(p, "pwrite"); - file_offset = (off_t)gbe_file_offset(p, "pwrite"); - - memset(f->pad, 0xff, sizeof(f->pad)); - - if (same_file(f->tmp_fd, &f->tmp_st, 0) < 0) - err(errno, "%s: file inode/device changed", f->tname); - - if (same_file(f->gbe_fd, &f->gbe_st, 1) < 0) - err(errno, "%s: file changed during write", f->fname); - - rval = rw_gbe_file_exact(f->tmp_fd, f->pad, - gbe_rw_size, file_offset, IO_PREAD); - - if (rval == -1) - f->rw_check_err_read[p] = f->io_err_gbe = 1; - else if ((unsigned long)rval != gbe_rw_size) - f->rw_check_partial_read[p] = f->io_err_gbe = 1; - else if (memcmp(mem_offset, f->pad, gbe_rw_size) != 0) - f->rw_check_bad_part[p] = f->io_err_gbe = 1; - - if (f->rw_check_err_read[p] || - f->rw_check_partial_read[p]) - return; - - /* We only load one part on-file, into memory but - * always at offset zero, for post-write checks. - * That's why we hardcode good_checksum(0) - */ - - buf_restore = f->buf; - - /* good_checksum works on f->buf - * so let's change f->buf for now - */ - - f->buf = f->pad; - - if (good_checksum(0)) - f->post_rw_checksum[p] = 1; - - f->buf = buf_restore; -} - -void -report_io_err_rw(void) -{ - struct xstate *x = xstatus(0, NULL); - struct xfile *f = &x->f; - - unsigned long p; - - if (!f->io_err_gbe) - return; - - for (p = 0; p < 2; p++) { - if (!f->part_modified[p]) - continue; - - if (f->rw_check_err_read[p]) - fprintf(stderr, - "%s: pread: p%lu (post-verification)\n", - f->fname, (unsigned long)p); - if (f->rw_check_partial_read[p]) - fprintf(stderr, - "%s: partial pread: p%lu (post-verification)\n", - f->fname, (unsigned long)p); - if (f->rw_check_bad_part[p]) - fprintf(stderr, - "%s: pwrite: corrupt write on p%lu\n", - f->fname, (unsigned long)p); - - if (f->rw_check_err_read[p] || - f->rw_check_partial_read[p]) { - fprintf(stderr, - "%s: p%lu: skipped checksum verification " - "(because read failed)\n", - f->fname, (unsigned long)p); - - continue; - } - - fprintf(stderr, "%s: ", f->fname); - - if (f->post_rw_checksum[p]) - fprintf(stderr, "GOOD"); - else - fprintf(stderr, "BAD"); - - fprintf(stderr, " checksum in p%lu on-disk.\n", - (unsigned long)p); - - if (f->post_rw_checksum[p]) { - fprintf(stderr, - " This does NOT mean it's safe. it may be\n" - " salvageable if you use the cat feature.\n"); - } - } -} - -int -gbe_mv(void) -{ - struct xstate *x = xstatus(0, NULL); - struct xfile *f = &x->f; - - int rval; - - int saved_errno; - int tmp_gbe_bin_exists; - - char *dest_tmp; - int dest_fd; - - /* will be set 0 if it doesn't - */ - tmp_gbe_bin_exists = 1; - - dest_tmp = NULL; - dest_fd = -1; - - saved_errno = errno; - - rval = rename(f->tname, f->fname); - - if (rval > -1) { - - /* rename on same filesystem - */ - - tmp_gbe_bin_exists = 0; - - if (fsync_dir(f->fname) < 0) { - f->io_err_gbe_bin = 1; - rval = -1; - } - - goto ret_gbe_mv; - } - - if (errno != EXDEV) - goto ret_gbe_mv; - - /* - * OR, cross-filesystem rename: - */ - - if ((rval = f->tmp_fd = open(f->tname, - O_RDONLY | O_BINARY)) == -1) - goto ret_gbe_mv; - - /* create replacement temp in target directory - */ - dest_tmp = new_tmpfile(&dest_fd, 1, f->fname); - if (dest_tmp == NULL) - goto ret_gbe_mv; - - /* copy data - */ - rval = rw_file_exact(f->tmp_fd, f->bufcmp, - f->gbe_file_size, 0, IO_PREAD, - NO_LOOP_EAGAIN, LOOP_EINTR, - MAX_ZERO_RW_RETRY, OFF_ERR); - - if (rval < 0) - goto ret_gbe_mv; - - rval = rw_file_exact(dest_fd, f->bufcmp, - f->gbe_file_size, 0, IO_PWRITE, - NO_LOOP_EAGAIN, LOOP_EINTR, - MAX_ZERO_RW_RETRY, OFF_ERR); - - if (rval < 0) - goto ret_gbe_mv; - - if (fsync_on_eintr(dest_fd) == -1) - goto ret_gbe_mv; - - if (close_on_eintr(dest_fd) == -1) - goto ret_gbe_mv; - - if (rename(dest_tmp, f->fname) == -1) - goto ret_gbe_mv; - - if (fsync_dir(f->fname) < 0) { - f->io_err_gbe_bin = 1; - goto ret_gbe_mv; - } - - free(dest_tmp); - dest_tmp = NULL; - -ret_gbe_mv: - - if (f->gbe_fd > -1) { - if (close_on_eintr(f->gbe_fd) < 0) - rval = -1; - if (fsync_dir(f->fname) < 0) { - f->io_err_gbe_bin = 1; - rval = -1; - } - f->gbe_fd = -1; - } - - if (f->tmp_fd > -1) { - if (close_on_eintr(f->tmp_fd) < 0) - rval = -1; - - f->tmp_fd = -1; - } - - /* before this function is called, - * tmp_fd may have been moved - */ - if (tmp_gbe_bin_exists) { - if (unlink(f->tname) < 0) - rval = -1; - else - tmp_gbe_bin_exists = 0; - } - - if (rval < 0) { - /* if nothing set errno, - * we assume EIO, or we - * use what was set - */ - if (errno == saved_errno) - errno = EIO; - } else { - errno = saved_errno; - } - - return rval; -} - -/* This one is similar to gbe_file_offset, - * but used to check Gbe bounds in memory, - * and it is *also* used during file I/O. - */ -unsigned char * -gbe_mem_offset(unsigned long p, const char *f_op) -{ - struct xstate *x = xstatus(0, NULL); - struct xfile *f = &x->f; - - off_t gbe_off; - - gbe_off = gbe_x_offset(p, f_op, "mem", - GBE_PART_SIZE, GBE_WORK_SIZE); - - return (unsigned char *) - (f->buf + (unsigned long)gbe_off); -} - -/* I/O operations filtered here. These operations must - * only write from the 0th position or the half position - * within the GbE file, and write 4KB of data. - */ -off_t -gbe_file_offset(unsigned long p, const char *f_op) -{ - struct xstate *x = xstatus(0, NULL); - struct xfile *f = &x->f; - - off_t gbe_file_half_size; - - gbe_file_half_size = f->gbe_file_size >> 1; - - return gbe_x_offset(p, f_op, "file", - gbe_file_half_size, f->gbe_file_size); -} - -off_t -gbe_x_offset(unsigned long p, const char *f_op, const char *d_type, - off_t nsize, off_t ncmp) -{ - struct xstate *x = xstatus(0, NULL); - struct xfile *f = &x->f; - - off_t off; - - check_bin(p, "part number"); - - off = ((off_t)p) * (off_t)nsize; - - if (off > ncmp - GBE_PART_SIZE) - err(ECANCELED, "%s: GbE %s %s out of bounds", - f->fname, d_type, f_op); - - if (off != 0 && off != ncmp >> 1) - err(ECANCELED, "%s: GbE %s %s at bad offset", - f->fname, d_type, f_op); - - return off; -} - -long -rw_gbe_file_exact(int fd, unsigned char *mem, unsigned long nrw, - off_t off, int rw_type) -{ - struct xstate *x = xstatus(0, NULL); - struct xfile *f = &x->f; - - long r; - - if (io_args(fd, mem, nrw, off, rw_type) == -1) - return -1; - - if (mem != (void *)f->pad) { - if (mem < f->buf) - goto err_rw_gbe_file_exact; - - if ((unsigned long)(mem - f->buf) >= GBE_WORK_SIZE) - goto err_rw_gbe_file_exact; - } - - if (off < 0 || off >= f->gbe_file_size) - goto err_rw_gbe_file_exact; - - if (nrw > (unsigned long)(f->gbe_file_size - off)) - goto err_rw_gbe_file_exact; - - if (nrw > (unsigned long)GBE_PART_SIZE) - goto err_rw_gbe_file_exact; - - r = rw_file_exact(fd, mem, nrw, off, rw_type, - NO_LOOP_EAGAIN, LOOP_EINTR, MAX_ZERO_RW_RETRY, - OFF_ERR); - - return rw_over_nrw(r, nrw); - -err_rw_gbe_file_exact: - errno = EIO; - return -1; -} diff --git a/util/nvmutil/lib/num.c b/util/nvmutil/lib/num.c deleted file mode 100644 index bbb5a83e..00000000 --- a/util/nvmutil/lib/num.c +++ /dev/null @@ -1,349 +0,0 @@ -/* SPDX-License-Identifier: MIT - * Copyright (c) 2026 Leah Rowe <leah@libreboot.org> - * - * Numerical functions. - */ - -#ifdef __OpenBSD__ -#include <sys/param.h> -#endif -#include <sys/types.h> -#if defined(FALLBACK_RAND_1989) && \ - (FALLBACK_RAND_1989) > 0 -#include <sys/time.h> -#endif - -#include <errno.h> -#if !((defined(__OpenBSD__) && (OpenBSD) >= 201) || \ - defined(__FreeBSD__) || \ - defined(__NetBSD__) || defined(__APPLE__)) -#include <fcntl.h> /* if not arc4random: /dev/urandom */ -#endif -#include <limits.h> -#include <stddef.h> -#include <string.h> -#if defined(FALLBACK_RAND_1989) && \ - (FALLBACK_RAND_1989) > 0 -#include <time.h> -#endif -#include <unistd.h> - -#include "../include/common.h" - -unsigned short -hextonum(char ch_s) -{ - unsigned char ch; - - ch = (unsigned char)ch_s; - - if ((unsigned int)(ch - '0') <= 9) - return ch - '0'; - - ch |= 0x20; - - if ((unsigned int)(ch - 'a') <= 5) - return ch - 'a' + 10; - - if (ch == '?' || ch == 'x') - return (unsigned short)rlong() & 0xf; - - return 16; /* invalid character */ -} - -/* Random numbers - */ - -unsigned long -rlong(void) -{ -#if !(defined(FALLBACK_RAND_1989) && \ - ((FALLBACK_RAND_1989) > 0)) -#if (defined(__OpenBSD__) && (OpenBSD) >= 201) || \ - defined(__FreeBSD__) || \ - defined(__NetBSD__) || defined(__APPLE__) - - unsigned long rval; - arc4random_buf(&rval, sizeof(unsigned long)); - - return rval; -#else - static int fd = -1; - static long nr = -1; - static unsigned long off = 0; -#if defined (BUFSIZ) - static char rbuf[BUFSIZ]; -#else -#ifndef PORTABLE - static char rbuf[4096]; -#elif ((PORTABLE) > 0) - static char rbuf[256]; /* scarce memory on old systems */ -#else - static char rbuf[4096]; /* typical 32-bit BUFSIZ */ -#endif -#endif - unsigned long rval; - long new_nr; - - int retries = 0; - int max_retries = 100; - -#if defined(__linux__) -#if defined(HAVE_GETRANDOM) || \ - defined(HAVE_GETRANDOM_SYSCALL) - - /* linux getrandom() - * - * we *can* use arc4random on - * modern linux, but not on - * every libc. better use the - * official linux function - * - * similar benefits to arc4random - * e.g. works in chroot, blocks - * until it has enough entropy, - * and works even when /dev/urandom - * is available (doesn't use it); - * it's generally more reliable - */ - - if (fallback_rand_getrandom(&rval, sizeof(rval)) == 0) - return rval; - - /* - * now fall back to urandom if getrandom failed: - */ -#endif -#endif - - /* reading from urandom is inherently - * unreliable on old systems, even if - * newer systems make it more reliable - * - * modern linux/bsd make it safe, but - * we have to assume that someone is - * compiling this on linux from 1999 - * - * this logic therefore applies various - * tricks to mitigate possible os bugs - */ - -retry_urandom_read: - - if (++retries > max_retries) - goto rlong_next; - - if (nr < 0 || nr < (long)sizeof(unsigned long)) { - - if (fd < 0) { - - fd = open("/dev/urandom", - O_RDONLY | O_BINARY | O_NOFOLLOW | - O_CLOEXEC); - -#ifdef USE_OLD_DEV_RANDOM -#if (USE_OLD_DEV_RANDOM) > 0 - /* WARNING: - * /dev/random may block - * forever and does **NOT** - * guarantee better entropy - * on old systems - * - * only use it if needed - */ - - if (fd < 0) - fd = open("/dev/random", - O_RDONLY | O_BINARY | O_NOFOLLOW | - O_CLOEXEC); -#endif -#endif - - if (fd < 0) - goto retry_urandom_read; - - retries = 0; - } - - new_nr = rw_file_exact(fd, (unsigned char *)rbuf, - sizeof(rbuf), 0, IO_READ, LOOP_EAGAIN, - LOOP_EINTR, MAX_ZERO_RW_RETRY, OFF_ERR); - - if (new_nr < 0 || new_nr < (long)sizeof(rbuf)) - goto retry_urandom_read; - - /* only reset buffer after successful refill */ - nr = new_nr; - off = 0; - - /* to mitigate file descriptor - * injection, we do not re-use - * the same descriptor each time - */ - (void) close_on_eintr(fd); - fd = -1; - } - - fd = -1; - retries = 0; - - memcpy(&rval, rbuf + off, sizeof(unsigned long)); - - nr -= (long)sizeof(unsigned long); - off += sizeof(unsigned long); - - return rval; - -rlong_next: - - fd = -1; - off = 0; - nr = -1; - - err(EIO, "Can't read from /dev/[ua]random"); - return 0; - -#endif -#else /* FALLBACK_RAND_1989 */ - /* your computer is from a museum - */ - unsigned long mix = 0; - int nr; - - /* 100 times, for entropy - */ - for (nr = 0; nr < 100; nr++) - mix ^= fallback_rand_1989(); - - /* 101 times ;) - */ - return fallback_rand_1989(); -#endif -} - -#if !(defined(FALLBACK_RAND_1989) && \ - ((FALLBACK_RAND_1989) > 0)) -#if defined(__linux__) -#if defined(HAVE_GETRANDOM) || \ - defined(HAVE_GETRANDOM_SYSCALL) -int -fallback_rand_getrandom(void *buf, unsigned long len) -{ - unsigned long off = 0; - long rval = -1; - - if (!len) - return -1; - - if (buf == NULL) - return -1; - -#if defined(HAVE_GETRANDOM) || \ - defined(HAVE_GETRANDOM_SYSCALL) - - while (off < len) { - -#if defined(HAVE_GETRANDOM) - rval = (long)getrandom((char *)buf + off, len - off, 0); -#elif defined(HAVE_GETRANDOM_SYSCALL) - rval = (long)syscall(SYS_getrandom, - (char *)buf + off, len - off, 0); -#endif - - if (rval < 0) { - if (errno == EINTR) - continue; - - return -1; /* unsupported by kernel */ - } - - off += (unsigned long)rval; - } - - return 0; - -#else - (void)buf; - (void)len; - - return -1; -#endif -} -#endif -#endif -#else -/* nobody should use this - * (not crypto-safe) - */ -unsigned long -fallback_rand_1989(void) -{ - static unsigned long mix = 0; - static unsigned long counter = 0; - - struct timeval tv; - - gettimeofday(&tv, NULL); - - mix ^= (unsigned long)tv.tv_sec - ^ (unsigned long)tv.tv_usec - ^ (unsigned long)getpid() - ^ (unsigned long)&mix - ^ counter++ - ^ entropy_jitter(); - - /* - * Stack addresses can vary between - * calls, thus increasing entropy. - */ - mix ^= (unsigned long)&mix; - mix ^= (unsigned long)&tv; - mix ^= (unsigned long)&counter; - - return mix; -} - -unsigned long -entropy_jitter(void) -{ - unsigned long mix; - - struct timeval a, b; - long mix_diff; - - int c; - - mix = 0; - - gettimeofday(&a, NULL); - - for (c = 0; c < 32; c++) { - - getpid(); - gettimeofday(&b, NULL); - - /* - * prevent negative numbers to prevent overflow, - * which would bias rand to large numbers - */ - mix_diff = (long)(b.tv_usec - a.tv_usec); - if (mix_diff < 0) - mix_diff = -mix_diff; - - mix ^= (unsigned long)(mix_diff); - - mix ^= (unsigned long)&mix; - - } - - return mix; -} -#endif - -void -check_bin(unsigned long a, const char *a_name) -{ - if (a > 1) - err(EINVAL, "%s must be 0 or 1, but is %lu", - a_name, (unsigned long)a); -} diff --git a/util/nvmutil/lib/state.c b/util/nvmutil/lib/state.c deleted file mode 100644 index 02a3e51c..00000000 --- a/util/nvmutil/lib/state.c +++ /dev/null @@ -1,280 +0,0 @@ -/* SPDX-License-Identifier: MIT - * Copyright (c) 2022-2026 Leah Rowe <leah@libreboot.org> - * - * State machine (singleton) for nvmutil data. - */ - -#ifdef __OpenBSD__ -#include <sys/param.h> -#endif -#include <sys/types.h> -#include <sys/stat.h> - -#include <errno.h> -#include <fcntl.h> -#include <limits.h> -#include <stdarg.h> -#include <stddef.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#include "../include/common.h" - -struct xstate * -xstatus(int argc, char *argv[]) -{ - static struct xstate us = { - /* DO NOT MESS THIS UP, OR THERE WILL BE DEMONS */ - { - { - CMD_DUMP, "dump", cmd_helper_dump, ARGC_3, - ARG_NOPART, - SKIP_CHECKSUM_READ, SKIP_CHECKSUM_WRITE, - NVM_SIZE, O_RDONLY - }, { - CMD_SETMAC, "setmac", cmd_helper_setmac, ARGC_3, - ARG_NOPART, - CHECKSUM_READ, CHECKSUM_WRITE, - NVM_SIZE, O_RDWR - }, { - CMD_SWAP, "swap", cmd_helper_swap, ARGC_3, - ARG_NOPART, - CHECKSUM_READ, SKIP_CHECKSUM_WRITE, - GBE_PART_SIZE, O_RDWR - }, { - CMD_COPY, "copy", cmd_helper_copy, ARGC_4, - ARG_PART, - CHECKSUM_READ, SKIP_CHECKSUM_WRITE, - GBE_PART_SIZE, O_RDWR - }, { - CMD_CAT, "cat", cmd_helper_cat, ARGC_3, - ARG_NOPART, - CHECKSUM_READ, SKIP_CHECKSUM_WRITE, - GBE_PART_SIZE, O_RDONLY - }, { - CMD_CAT16, "cat16", cmd_helper_cat16, ARGC_3, - ARG_NOPART, - CHECKSUM_READ, SKIP_CHECKSUM_WRITE, - GBE_PART_SIZE, O_RDONLY - }, { - CMD_CAT128, "cat128", cmd_helper_cat128, ARGC_3, - ARG_NOPART, - CHECKSUM_READ, SKIP_CHECKSUM_WRITE, - GBE_PART_SIZE, O_RDONLY - } - }, - - /* ->mac */ - {NULL, "xx:xx:xx:xx:xx:xx", {0, 0, 0}}, /* .str, .rmac, .mac_buf */ - - /* .f */ - {0}, - - /* .argv0 (for our getprogname implementation) */ - NULL, - - /* ->i (index to cmd[]) */ - 0, - - /* .no_cmd (set 0 when a command is found) */ - 1, - - /* .cat (cat helpers set this) */ - -1 - - }; - - static int first_run = 1; - - if (!first_run) - return &us; - - us.f.buf = us.f.real_buf; - - first_run = 0; - us.argv0 = argv[0]; - - if (argc > 1) - us.f.fname = argv[1]; - - if (argc < 3) - usage(); - -/* https://man.openbsd.org/pledge.2 - https://man.openbsd.org/unveil.2 */ -#if defined(__OpenBSD__) && defined(OpenBSD) -#if (OpenBSD) >= 604 - if (pledge("stdio flock rpath wpath cpath unveil", NULL) == -1) - err(errno, "pledge plus unveil"); -#elif (OpenBSD) >= 509 - if (pledge("stdio flock rpath wpath cpath", NULL) == -1) - err(errno, "pledge"); -#endif -#endif - -#ifndef S_ISREG - err(ECANCELED, "Can't determine file types (S_ISREG undefined)"); -#endif - -#ifndef CHAR_BIT - err(ECANCELED, "Unknown char size"); -#else - if (CHAR_BIT != 8) - err(EINVAL, "Unsupported char size"); -#endif - -#if defined(__OpenBSD__) && defined(OpenBSD) && \ - (OpenBSD) >= 604 - /* can only use local tmp on openbsd, due to unveil */ - us.f.tname = new_tmpfile(&us.f.tmp_fd, 1, NULL); -#else - us.f.tname = new_tmpfile(&us.f.tmp_fd, 0, NULL); -#endif - if (us.f.tname == NULL) - err(errno, "Can't create tmpfile"); - if (*us.f.tname == '\0') - err(errno, "tmp dir is an empty string"); - -#if defined(__OpenBSD__) && defined(OpenBSD) && \ - OpenBSD >= 604 - if (unveil(us.f.tname, "rwc") == -1) - err(errno, "unveil rwc: %s", us.f.tname); -#endif - if (fstat(us.f.tmp_fd, &us.f.tmp_st) < 0) - err(errno, "%s: stat", us.f.tname); - - sanitize_command_list(); - - /* parse user command */ - set_cmd(argc, argv); - set_cmd_args(argc, argv); - -#if defined(__OpenBSD__) && defined(OpenBSD) && \ - (OpenBSD) >= 604 - if ((us.cmd[i].flags & O_ACCMODE) == O_RDONLY) { - if (unveil(us.f.fname, "r") == -1) - err(errno, "%s: unveil r", us.f.fname); - } else { - if (unveil(us.f.fname, "rwc") == -1) - err(errno, "%s: unveil rw", us.f.fname); - } - - if (unveil(us.f.tname, "rwc") == -1) - err(errno, "%s: unveil rwc", us.f.tname); - - if (unveil(NULL, NULL) == -1) - err(errno, "unveil block (rw)"); - - if (pledge("stdio flock rpath wpath cpath", NULL) == -1) - err(errno, "pledge (kill unveil)"); -#endif - - open_gbe_file(); - - memset(us.f.real_buf, 0, sizeof(us.f.real_buf)); - memset(us.f.bufcmp, 0, sizeof(us.f.bufcmp)); - - /* for good measure */ - memset(us.f.pad, 0, sizeof(us.f.pad)); - - copy_gbe(); - read_checksums(); - - return &us; -} - -void -err(int nvm_errval, const char *msg, ...) -{ - struct xstate *x = xstatus(0, NULL); - - va_list args; - - if (errno == 0) - errno = nvm_errval; - if (!errno) - errno = ECANCELED; - - (void)exit_cleanup(); - - if (x != NULL) - fprintf(stderr, "%s: ", getnvmprogname()); - - va_start(args, msg); - vfprintf(stderr, msg, args); - va_end(args); - - fprintf(stderr, ": %s\n", strerror(errno)); - - exit(EXIT_FAILURE); -} - -const char * -getnvmprogname(void) -{ - struct xstate *x = xstatus(0, NULL); - - const char *p; - static char fallback[] = "nvmutil"; - - char *rval = fallback; - - if (x != NULL) { - if (x->argv0 == NULL || *x->argv0 == '\0') - return ""; - - rval = x->argv0; - } - - p = strrchr(rval, '/'); - - if (p) - return p + 1; - else - return rval; -} - -int -exit_cleanup(void) -{ - struct xstate *x = xstatus(0, NULL); - struct xfile *f; - - int close_err; - int saved_errno; - - close_err = 0; - saved_errno = errno; - - if (x != NULL) { - f = &x->f; - - if (f->gbe_fd > -1) { - if (close_on_eintr(f->gbe_fd) == -1) - close_err = 1; - f->gbe_fd = -1; - } - - if (f->tmp_fd > -1) { - if (close_on_eintr(f->tmp_fd) == -1) - close_err = 1; - } - - if (f->tname != NULL) { - if (unlink(f->tname) == -1) - close_err = 1; - } - - f->tmp_fd = -1; - } - - if (saved_errno) - errno = saved_errno; - - if (close_err) - return -1; - - return 0; -} diff --git a/util/nvmutil/lib/string.c b/util/nvmutil/lib/string.c deleted file mode 100644 index b1a5c3e2..00000000 --- a/util/nvmutil/lib/string.c +++ /dev/null @@ -1,75 +0,0 @@ -/* SPDX-License-Identifier: MIT - * Copyright (c) 2026 Leah Rowe <leah@libreboot.org> - * - * String functions - */ - -#include <sys/types.h> -#include <sys/stat.h> - -#include <errno.h> -#include <stddef.h> -#include <unistd.h> - -#include "../include/common.h" - -/* Portable strncmp() that blocks - * NULL/empty/unterminated strings - */ - -int -xstrxcmp(const char *a, const char *b, unsigned long maxlen) -{ - unsigned long i; - - if (a == NULL || b == NULL) - err(EINVAL, "NULL input to xstrxcmp"); - - if (*a == '\0' || *b == '\0') - err(EINVAL, "Empty string in xstrxcmp"); - - for (i = 0; i < maxlen; i++) { - - unsigned char ac = (unsigned char)a[i]; - unsigned char bc = (unsigned char)b[i]; - - if (ac == '\0' || bc == '\0') { - if (ac == bc) - return 0; - return ac - bc; - } - - if (ac != bc) - return ac - bc; - } - - err(EINVAL, "Unterminated string in xstrxcmp"); - - errno = EINVAL; - return -1; -} - -/* Portable strncmp() that blocks - * NULL/empty/unterminated strings - */ - -unsigned long -xstrxlen(const char *scmp, unsigned long maxlen) -{ - unsigned long xstr_index; - - if (scmp == NULL) - err(EINVAL, "NULL input to xstrxlen"); - - if (*scmp == '\0') - err(EINVAL, "Empty string in xstrxlen"); - - for (xstr_index = 0; - xstr_index < maxlen && scmp[xstr_index] != '\0'; - xstr_index++); - - if (xstr_index == maxlen) - err(EINVAL, "Unterminated string in xstrxlen"); - - return xstr_index; -} diff --git a/util/nvmutil/lib/usage.c b/util/nvmutil/lib/usage.c deleted file mode 100644 index 3b0614e8..00000000 --- a/util/nvmutil/lib/usage.c +++ /dev/null @@ -1,30 +0,0 @@ -/* SPDX-License-Identifier: MIT - * Copyright (c) 2023 Riku Viitanen <riku.viitanen@protonmail.com> - * Copyright (c) 2026 Leah Rowe <leah@libreboot.org> - */ - -#include <errno.h> -#include <stdio.h> - -#include "../include/common.h" - -void -usage(void) -{ - const char *util = getnvmprogname(); - - fprintf(stderr, - "Modify Intel GbE NVM images e.g. set MAC\n" - "USAGE:\n" - "\t%s FILE dump\n" - "\t%s FILE setmac [MAC]\n" - "\t%s FILE swap\n" - "\t%s FILE copy 0|1\n" - "\t%s FILE cat\n" - "\t%s FILE cat16\n" - "\t%s FILE cat128\n", - util, util, util, util, - util, util, util); - - err(EINVAL, "Too few arguments"); -} diff --git a/util/nvmutil/lib/word.c b/util/nvmutil/lib/word.c deleted file mode 100644 index 5d9220c7..00000000 --- a/util/nvmutil/lib/word.c +++ /dev/null @@ -1,68 +0,0 @@ -/* SPDX-License-Identifier: MIT - * Copyright (c) 2022-2026 Leah Rowe <leah@libreboot.org> - * - * Manipulate Intel GbE NVM words, which are 16-bit little - * endian in the files (MAC address words are big endian). - */ - -#include <sys/types.h> - -#include <errno.h> -#include <stddef.h> - -#include "../include/common.h" - -unsigned short -nvm_word(unsigned long pos16, unsigned long p) -{ - struct xstate *x = xstatus(0, NULL); - struct xfile *f = &x->f; - - unsigned long pos; - - check_nvm_bound(pos16, p); - pos = (pos16 << 1) + (p * GBE_PART_SIZE); - - return (unsigned short)f->buf[pos] | - ((unsigned short)f->buf[pos + 1] << 8); -} - -void -set_nvm_word(unsigned long pos16, unsigned long p, unsigned short val16) -{ - struct xstate *x = xstatus(0, NULL); - struct xfile *f = &x->f; - - unsigned long pos; - - check_nvm_bound(pos16, p); - pos = (pos16 << 1) + (p * GBE_PART_SIZE); - - f->buf[pos] = (unsigned char)(val16 & 0xff); - f->buf[pos + 1] = (unsigned char)(val16 >> 8); - - set_part_modified(p); -} - -void -set_part_modified(unsigned long p) -{ - struct xstate *x = xstatus(0, NULL); - struct xfile *f = &x->f; - - check_bin(p, "part number"); - f->part_modified[p] = 1; -} - -void -check_nvm_bound(unsigned long c, unsigned long p) -{ - /* Block out of bound NVM access - */ - - check_bin(p, "part number"); - - if (c >= NVM_WORDS) - err(ECANCELED, "check_nvm_bound: out of bounds %lu", - (unsigned long)c); -} diff --git a/util/nvmutil/nvmutil.c b/util/nvmutil/nvmutil.c index 670b7110..1f211d6c 100644 --- a/util/nvmutil/nvmutil.c +++ b/util/nvmutil/nvmutil.c @@ -1,50 +1,725 @@ -/* SPDX-License-Identifier: MIT - * Copyright (c) 2022-2026 Leah Rowe <leah@libreboot.org> - * - * This tool lets you modify Intel GbE NVM (Gigabit Ethernet - * Non-Volatile Memory) images, e.g. change the MAC address. - * These images configure your Intel Gigabit Ethernet adapter. - */ +/* SPDX-License-Identifier: MIT */ +/* Copyright (c) 2022-2025 Leah Rowe <leah@libreboot.org> */ +/* Copyright (c) 2023 Riku Viitanen <riku.viitanen@protonmail.com> */ #include <sys/types.h> #include <sys/stat.h> +#include <err.h> #include <errno.h> #include <fcntl.h> -#include <limits.h> +#include <stdarg.h> #include <stddef.h> +#include <stdio.h> +#include <string.h> #include <stdlib.h> +#include <unistd.h> +#include <limits.h> +#include <stdint.h> + +void cmd_setchecksum(void), cmd_brick(void), swap(int partnum), writeGbe(void), + cmd_dump(void), cmd_setmac(void), readGbe(void), + macf(int partnum), hexdump(int partnum), openFiles(const char *path), + cmd_copy(void), parseMacString(const char *strMac, uint16_t *mac), + cmd_swap(void), xclose(int *fd); +int goodChecksum(int partnum), open_on_eintr(const char *pathname, int flags), + fs_retry(int saved_errno, int rval), + rw_retry(int saved_errno, ssize_t rval), if_err(int condition, int errval), + if_err_sys(int condition); +ssize_t rw_exact(int fd, unsigned char *mem, size_t nrw, + off_t off, int rw_type); +ssize_t rw(int fd, void *mem, size_t nrw, + off_t off, int rw_type); +int io_args(int fd, void *mem, size_t nrw, + off_t off, int rw_type); +int with_fallback_errno(int fallback); +ssize_t rw_over_nrw(ssize_t r, size_t nrw); +uint8_t hextonum(char chs), rhex(void); + +#define COMMAND argv[2] +#define MAC_ADDRESS argv[3] +#define PARTN argv[3] +#define NVM_CHECKSUM 0xBABA +#define NVM_CHECKSUM_WORD 0x3F +#define NVM_SIZE 128 + +#define SIZE_4KB 0x1000 +#define SIZE_8KB 0x2000 +#define SIZE_16KB 0x4000 +#define SIZE_128KB 0x20000 + +#define IO_READ 0 +#define IO_WRITE 1 +#define IO_PREAD 2 +#define IO_PWRITE 3 + +uint16_t mac[3] = {0, 0, 0}; +ssize_t nf; +size_t partsize, gbe[2]; +uint8_t nvmPartChanged[2] = {0, 0}, do_read[2] = {1, 1}; +int flags, rfd, fd, part; + +const char *strMac = NULL, *strRMac = "xx:xx:xx:xx:xx:xx", *filename = NULL; + +typedef struct op { + char *str; + void (*cmd)(void); + int args; +} op_t; +op_t op[] = { +{ .str = "dump", .cmd = cmd_dump, .args = 3}, +{ .str = "setmac", .cmd = cmd_setmac, .args = 3}, +{ .str = "swap", .cmd = cmd_swap, .args = 3}, +{ .str = "copy", .cmd = cmd_copy, .args = 4}, +{ .str = "brick", .cmd = cmd_brick, .args = 4}, +{ .str = "setchecksum", .cmd = cmd_setchecksum, .args = 4}, +}; +void (*cmd)(void) = NULL; + +#define err_if(x) if (x) err(EXIT_FAILURE, "%s", filename) + +#define xopen(f,l,p) \ + do { \ + if ((f = open_on_eintr(l, p)) == -1) \ + err(EXIT_FAILURE, "%s", l); \ + if (fstat(f, &st) == -1) \ + err(EXIT_FAILURE, "%s", l); \ + } while(0) -#include "include/common.h" +#define word(pos16, partnum) ((uint16_t *) gbe[partnum])[pos16] +#define setWord(pos16, p, val16) if (word(pos16, p) != val16) \ + nvmPartChanged[p] = 1 | (word(pos16, p) = val16) + +#define SUCCESS(x) ((x) >= 0) + +#define reset_caller_errno(return_value) \ + do { \ + if (SUCCESS(return_value) && (!errno)) \ + errno = saved_errno; \ + } while (0) int main(int argc, char *argv[]) { - struct xstate *x = xstatus(argc, argv); - struct commands *cmd = &x->cmd[x->i]; - struct xfile *f = &x->f; +#ifdef __OpenBSD__ + err_if(pledge("stdio rpath wpath unveil", NULL) == -1); +#endif + + if (argc < 2) { +#ifdef __OpenBSD__ + err_if(pledge("stdio", NULL) == -1); +#endif + fprintf(stderr, "Modify Intel GbE NVM images e.g. set MAC\n"); + fprintf(stderr, "USAGE:\n"); + fprintf(stderr, " %s FILE dump\n", argv[0]); + fprintf(stderr, " %s FILE\n # same as setmac without arg\n", + argv[0]); + fprintf(stderr, " %s FILE setmac [MAC]\n", argv[0]); + fprintf(stderr, " %s FILE swap\n", argv[0]); + fprintf(stderr, " %s FILE copy 0|1\n", argv[0]); + fprintf(stderr, " %s FILE brick 0|1\n", argv[0]); + fprintf(stderr, " %s FILE setchecksum 0|1\n", argv[0]); + errno = EINVAL; + err(EXIT_FAILURE, "Too few arguments"); + } - unsigned long c; + filename = argv[1]; - if (cmd->run == NULL) - err(errno, "Command not set"); + flags = O_RDWR; - cmd->run(); + if (argc > 2) { + if (strcmp(COMMAND, "dump") == 0) { + flags = O_RDONLY; +#ifdef __OpenBSD__ + err_if(pledge("stdio rpath unveil", NULL) == -1); +#endif + } + } - for (c = 0; c < items(x->cmd); c++) - x->cmd[c].run = cmd_helper_err; +#ifdef __OpenBSD__ + err_if(unveil("/dev/urandom", "r") == -1); - if ((cmd->flags & O_ACCMODE) == O_RDWR) - write_to_gbe_bin(); + if (flags == O_RDONLY) { + err_if(unveil(filename, "r") == -1); + err_if(unveil(NULL, NULL) == -1); + err_if(pledge("stdio rpath", NULL) == -1); + } else { + err_if(unveil(filename, "rw") == -1); + err_if(unveil(NULL, NULL) == -1); + err_if(pledge("stdio rpath wpath", NULL) == -1); + } +#endif - if (exit_cleanup() == -1) - err(EIO, "%s: close", f->fname); + openFiles(filename); +#ifdef __OpenBSD__ + err_if(pledge("stdio", NULL) == -1); +#endif - if (f->io_err_gbe_bin) - err(EIO, "%s: error writing final file"); + if (argc > 2) { + for (int i = 0; (i < 6) && (cmd == NULL); i++) { + if (strcmp(COMMAND, op[i].str) != 0) + continue; + if (argc >= op[i].args) { + cmd = op[i].cmd; + break; + } + errno = EINVAL; + err(EXIT_FAILURE, "Too few args on command '%s'", + op[i].str); + } + } else { + cmd = cmd_setmac; + } - if (f->tname != NULL) - free(f->tname); + if ((cmd == NULL) && (argc > 2)) { /* nvm gbe [MAC] */ + strMac = COMMAND; + cmd = cmd_setmac; + } else if (cmd == cmd_setmac) { /* nvm gbe setmac [MAC] */ + strMac = strRMac; /* random MAC */ + if (argc > 3) + strMac = MAC_ADDRESS; + } else if ((cmd != NULL) && (argc > 3)) { /* user-supplied partnum */ + err_if((errno = (!((part = PARTN[0] - '0') == 0 || part == 1)) + || PARTN[1] ? EINVAL : 0)); /* only allow '0' or '1' */ + } + if (cmd == NULL) { + errno = EINVAL; + err(EXIT_FAILURE, "Bad command"); + } + + readGbe(); + (*cmd)(); + writeGbe(); return EXIT_SUCCESS; } + +void +openFiles(const char *path) +{ + struct stat st; + + xopen(fd, path, flags); + + switch(st.st_size) { + case SIZE_8KB: + case SIZE_16KB: + case SIZE_128KB: + partsize = st.st_size >> 1; + break; + default: + errno = ECANCELED; + err(EXIT_FAILURE, "Invalid file size (not 8/16/128KiB)"); + break; + } + + xopen(rfd, "/dev/urandom", O_RDONLY); +} + +void +readGbe(void) +{ + if ((cmd == cmd_swap) || (cmd == cmd_copy)) + nf = SIZE_4KB; + else + nf = NVM_SIZE; + + if ((cmd == cmd_copy) || (cmd == cmd_setchecksum) || (cmd == cmd_brick)) + do_read[part ^ 1] = 0; + + char *buf = malloc(nf << (do_read[0] & do_read[1])); + if (buf == NULL) + err(EXIT_FAILURE, "malloc"); + + gbe[0] = (size_t) buf; + gbe[1] = gbe[0] + (nf * (do_read[0] & do_read[1])); + + ssize_t tnr = 0; + + for (int p = 0; p < 2; p++) { + if (!do_read[p]) + continue; + + ssize_t nr = rw_exact(fd, (uint8_t *) gbe[p], + nf, p * partsize, IO_PREAD); + err_if(nr == -1); + if (nr != nf) + err(EXIT_FAILURE, + "%ld bytes read from '%s', expected %ld bytes\n", + nr, filename, nf); + + tnr += nr; + swap(p); /* handle big-endian host CPU */ + } + + printf("%ld bytes read from file '%s'\n", tnr, filename); +} + +void +cmd_setmac(void) +{ + int mac_updated = 0; + parseMacString(strMac, mac); + + printf("MAC address to be written: %s\n", strMac); + + for (int partnum = 0; partnum < 2; partnum++) { + if (!goodChecksum(part = partnum)) + continue; + + for (int w = 0; w < 3; w++) + setWord(w, partnum, mac[w]); + + printf("Wrote MAC address to part %d: ", partnum); + macf(partnum); + + cmd_setchecksum(); + mac_updated = 1; + } + + if (mac_updated) + return; + + errno = EINVAL; + err(EXIT_FAILURE, "Error updating MAC address"); +} + +void +parseMacString(const char *strMac, uint16_t *mac) +{ + uint64_t total = 0; + if (strnlen(strMac, 20) != 17) { + errno = EINVAL; + err(EXIT_FAILURE, "Invalid MAC address string length"); + } + + for (uint8_t h, i = 0; i < 16; i += 3) { + if (i != 15) + if (strMac[i + 2] != ':') { + errno = EINVAL; + err(EXIT_FAILURE, + "Invalid MAC address separator '%c'", + strMac[i + 2]); + } + + int byte = i / 3; + + for (int nib = 0; nib < 2; nib++, total += h) { + if ((h = hextonum(strMac[i + nib])) > 15) { + errno = EINVAL; + err(EXIT_FAILURE, "Invalid character '%c'", + strMac[i + nib]); + } + + /* If random, ensure that local/unicast bits are set */ + if ((byte == 0) && (nib == 1)) + if ((strMac[i + nib] == '?') || + (strMac[i + nib] == 'x') || + (strMac[i + nib] == 'X')) /* random */ + h = (h & 0xE) | 2; /* local, unicast */ + + mac[byte >> 1] |= ((uint16_t ) h) + << ((8 * (byte % 2)) + (4 * (nib ^ 1))); + } + } + + if (!((total == 0) || (mac[0] & 1))) + return; + + errno = EINVAL; + + if (total == 0) + err(EXIT_FAILURE, "Invalid MAC (all-zero MAC address)"); + if (mac[0] & 1) + err(EXIT_FAILURE, "Invalid MAC (multicast bit set)"); +} + +uint8_t +hextonum(char ch) +{ + if ((ch >= '0') && (ch <= '9')) + return ch - '0'; + else if ((ch >= 'A') && (ch <= 'F')) + return ch - 'A' + 10; + else if ((ch >= 'a') && (ch <= 'f')) + return ch - 'a' + 10; + else if ((ch == '?') || (ch == 'x') || (ch == 'X')) + return rhex(); /* random hex value */ + else + return 16; /* error: invalid character */ +} + +uint8_t +rhex(void) +{ + static uint8_t n = 0, rnum[16]; + if (!n) + err_if(rw_exact(rfd, (uint8_t *) &rnum, + (n = 15) + 1, 0, IO_READ) == -1); + return rnum[n--] & 0xf; +} + +void +cmd_dump(void) +{ + for (int partnum = 0, numInvalid = 0; partnum < 2; partnum++) { + if ((cmd != cmd_dump) && (flags != O_RDONLY) && + (!nvmPartChanged[partnum])) + continue; + + if (!goodChecksum(partnum)) + ++numInvalid; + + printf("MAC (part %d): ", partnum); + macf(partnum); + hexdump(partnum); + } +} + +void +macf(int partnum) +{ + for (int c = 0; c < 3; c++) { + uint16_t val16 = word(c, partnum); + printf("%02x:%02x", val16 & 0xff, val16 >> 8); + if (c == 2) + printf("\n"); + else + printf(":"); + } +} + +void +hexdump(int partnum) +{ + for (int row = 0; row < 8; row++) { + printf("%08x ", row << 4); + for (int c = 0; c < 8; c++) { + uint16_t val16 = word((row << 3) + c, partnum); + if (c == 4) + printf(" "); + printf(" %02x %02x", val16 & 0xff, val16 >> 8); + } + printf("\n"); + } +} + +void +cmd_setchecksum(void) +{ + uint16_t val16 = 0; + for (int c = 0; c < NVM_CHECKSUM_WORD; c++) + val16 += word(c, part); + + setWord(NVM_CHECKSUM_WORD, part, NVM_CHECKSUM - val16); +} + +void +cmd_brick(void) +{ + if (goodChecksum(part)) + setWord(NVM_CHECKSUM_WORD, part, + ((word(NVM_CHECKSUM_WORD, part)) ^ 0xFF)); +} + +void +cmd_copy(void) +{ + nvmPartChanged[part ^ 1] = goodChecksum(part); +} + +void +cmd_swap(void) { + err_if(!(goodChecksum(0) || goodChecksum(1))); + errno = 0; + + gbe[0] ^= gbe[1]; + gbe[1] ^= gbe[0]; + gbe[0] ^= gbe[1]; + + nvmPartChanged[0] = nvmPartChanged[1] = 1; +} + +int +goodChecksum(int partnum) +{ + uint16_t total = 0; + for(int w = 0; w <= NVM_CHECKSUM_WORD; w++) + total += word(w, partnum); + + if (total == NVM_CHECKSUM) + return 1; + + fprintf(stderr, "WARNING: BAD checksum in part %d\n", partnum); + return 0; +} + +void +writeGbe(void) +{ + ssize_t tnw = 0; + + for (int p = 0; p < 2; p++) { + if ((!nvmPartChanged[p]) || (flags == O_RDONLY)) + continue; + + swap(p); /* swap bytes on big-endian host CPUs */ + ssize_t nw = rw_exact(fd, (uint8_t *) gbe[p], nf, + p * partsize, IO_PWRITE); + err_if(nw == -1); + if (nw != nf) { + errno = ECANCELED; + err(EXIT_SUCCESS, + "%ld bytes written to '%s', expected %ld bytes\n", + nw, filename, nf); + } + + tnw += nf; + } + + if ((flags != O_RDONLY) && (cmd != cmd_dump)) { + if (nvmPartChanged[0] || nvmPartChanged[1]) + printf("The following nvm words were written:\n"); + cmd_dump(); + } + + if ((!tnw) && (flags != O_RDONLY)) + fprintf(stderr, "No changes needed on file '%s'\n", filename); + else if (tnw) + printf("%ld bytes written to file '%s'\n", tnw, filename); + + xclose(&fd); +} + +void +xclose(int *fd) +{ + int saved_errno = errno; + int rval = 0; + + if (fd == NULL) + err(EXIT_FAILURE, "xclose: null pointer"); + if (*fd < 0) + return; + + errno = 0; + if ((rval = close(*fd)) < 0) { + if (errno != EINTR) + err(EXIT_FAILURE, "xclose: could not close"); + + /* regard EINTR as a successful close */ + rval = 0; + } + + *fd = -1; + + reset_caller_errno(rval); +} + +void +swap(int partnum) +{ + size_t w, x; + uint8_t *n = (uint8_t *) gbe[partnum]; + int e = 1; + + for (w = NVM_SIZE * ((uint8_t *) &e)[0], x = 1; w < NVM_SIZE; + w += 2, x += 2) { + n[w] ^= n[x]; + n[x] ^= n[w]; + n[w] ^= n[x]; + } +} + +int +open_on_eintr(const char *pathname, + int flags) +{ + int saved_errno = errno; + int rval = 0; + errno = 0; + + while (fs_retry(saved_errno, + rval = open(pathname, flags))); + + reset_caller_errno(rval); + return rval; +} + + +ssize_t +rw_exact(int fd, unsigned char *mem, size_t nrw, + off_t off, int rw_type) +{ + int saved_errno = errno; + ssize_t rval = 0; + ssize_t rc = 0; + size_t nrw_cur; + off_t off_cur; + void *mem_cur; + errno = 0; + + if (io_args(fd, mem, nrw, off, rw_type) == -1) + goto err_rw_exact; + + while (1) { + + /* Prevent theoretical overflow */ + if (if_err(rval >= 0 && (size_t)rval > (nrw - (size_t)rc), + EOVERFLOW)) + goto err_rw_exact; + + rc += rval; + if ((size_t)rc >= nrw) + break; + + mem_cur = (void *)(mem + (size_t)rc); + nrw_cur = (size_t)(nrw - (size_t)rc); + + if (if_err(off < 0, EOVERFLOW)) + goto err_rw_exact; + + off_cur = off + (off_t)rc; + + if ((rval = rw(fd, mem_cur, nrw_cur, off_cur, rw_type)) <= 0) + goto err_rw_exact; + } + + if (if_err((size_t)rc != nrw, EIO) || + (rval = rw_over_nrw(rc, nrw)) < 0) + goto err_rw_exact; + + reset_caller_errno(rval); + return rval; + +err_rw_exact: + return with_fallback_errno(EIO); +} + +ssize_t +rw(int fd, void *mem, size_t nrw, + off_t off, int rw_type) +{ + ssize_t rval = 0; + ssize_t r = -1; + int saved_errno = errno; + errno = 0; + + if (io_args(fd, mem, nrw, off, rw_type) == -1 || + if_err(mem == NULL, EFAULT) || + if_err(fd < 0, EBADF) || + if_err(off < 0, EFAULT) || + if_err(nrw == 0, EINVAL)) + return with_fallback_errno(EIO); + + do { + switch (rw_type) { + case IO_READ: + r = read(fd, mem, nrw); + break; + case IO_WRITE: + r = write(fd, mem, nrw); + break; + case IO_PREAD: + r = pread(fd, mem, nrw, off); + break; + case IO_PWRITE: + r = pwrite(fd, mem, nrw, off); + break; + default: + errno = EINVAL; + break; + } + + } while (rw_retry(saved_errno, r)); + + if ((rval = rw_over_nrw(r, nrw)) < 0) + return with_fallback_errno(EIO); + + reset_caller_errno(rval); + return rval; +} + +int +io_args(int fd, void *mem, size_t nrw, + off_t off, int rw_type) +{ + int saved_errno = errno; + errno = 0; + + if (if_err(mem == NULL, EFAULT) || + if_err(fd < 0, EBADF) || + if_err(off < 0, ERANGE) || + if_err(!nrw, EPERM) || /* TODO: toggle zero-byte check */ + if_err(nrw > (size_t)SSIZE_MAX, ERANGE) || + if_err(((size_t)off + nrw) < (size_t)off, ERANGE) || + if_err(rw_type > IO_PWRITE, EINVAL)) + goto err_io_args; + + reset_caller_errno(0); + return 0; + +err_io_args: + return with_fallback_errno(EINVAL); +} + +ssize_t +rw_over_nrw(ssize_t r, size_t nrw) +{ + if (if_err(!nrw, EIO) || + (r == -1) || + if_err((size_t)r > SSIZE_MAX, ERANGE) || + if_err((size_t)r > nrw, ERANGE)) + return with_fallback_errno(EIO); + + return r; +} + +int +with_fallback_errno(int fallback) +{ + if (!errno) + errno = fallback; + return -1; +} + +/* two functions that reduce sloccount by + * two hundred lines */ +int +if_err(int condition, int errval) +{ + if (!condition) + return 0; + if (errval) + errno = errval; + return 1; +} +int +if_err_sys(int condition) +{ + if (!condition) + return 0; + return 1; +} + +#define fs_err_retry() \ + do { \ + if ((rval == -1) && \ + (errno == EINTR)) \ + return 1; \ + if (rval >= 0 && !errno) \ + errno = saved_errno; \ + return 0; \ + } while(0) + +int +fs_retry(int saved_errno, int rval) +{ + fs_err_retry(); +} + +int +rw_retry(int saved_errno, ssize_t rval) +{ + fs_err_retry(); +} diff --git a/util/spkmodem_decode/.gitignore b/util/spkmodem_decode/.gitignore deleted file mode 100644 index 42814fe4..00000000 --- a/util/spkmodem_decode/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -spkmodem-recv -spkmodem-decode diff --git a/util/spkmodem_decode/Makefile b/util/spkmodem_decode/Makefile deleted file mode 100644 index b00c4f43..00000000 --- a/util/spkmodem_decode/Makefile +++ /dev/null @@ -1,30 +0,0 @@ -# SPDX-License-Identifier: MIT -# SPDX-FileCopyrightText: 2022,2026 Leah Rowe <leah@libreboot.org> -# SPDX-FileCopyrightText: 2023 Riku Viitanen <riku.viitanen@protonmail.com> - -CC?=cc -CFLAGS?=-Os -Wall -Wextra -Werror -pedantic -std=c90 -DESTDIR?= -PREFIX?=/usr/local -INSTALL?=install - -PROG=spkmodem-decode - -all: $(PROG) - -$(PROG): spkmodem-decode.c - $(CC) $(CFLAGS) spkmodem-decode.c -o $(PROG) - -install: $(PROG) - mkdir -p $(DESTDIR)$(PREFIX)/bin/ - install -c $(PROG) $(DESTDIR)$(PREFIX)/bin/ - -uninstall: - rm -f $(DESTDIR)$(PREFIX)/bin/$(PROG) - -clean: - rm -f $(PROG) - -distclean: clean - -.PHONY: all install uninstall clean distclean diff --git a/util/spkmodem_decode/spkmodem-decode.c b/util/spkmodem_decode/spkmodem-decode.c deleted file mode 100644 index 3b3b33f8..00000000 --- a/util/spkmodem_decode/spkmodem-decode.c +++ /dev/null @@ -1,725 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-2.0-or-later - * Copyright (c) 2013 Free Software Foundation, Inc. - * Copyright (c) 2023, 2026 Leah Rowe <leah@libreboot.org> - * - * This program receives text encoded as pulses on the PC speaker, - * and decodes them via simple FSK (Frequency Shift Keying) - * demodulation and FIR (Finite Impulse Response) frequency - * discriminator. - * - * It waits for specific tones at specific intervals. - * It detects tones within the audio stream and reconstructs - * characters bit-by-bit as the encoded modem signal is received. - * This is performance-efficient on most CPUs, and has relatively - * high tolerance for noisy signals (similar to techniques used - * for data stored on audio cassette tapes). - * - * This is a special interface provided by coreboot and GNU GRUB, - * for computers that lack serial ports (useful for debugging). - * Note that GRUB and coreboot can both send these signals; this - * tool merely decodes them. This tool does not *encode*, only - * decode. - * - * Usage example (NOTE: little endian!): - * parec --channels=1 --rate=48000 --format=s16le | ./spkmodem-decode - * - * Originally provided by GNU GRUB, this version is a heavily - * modified fork that complies with the OpenBSD Kernel Source - * File Style Guide (KNF) instead of GNU coding standards; it - * emphasises strict error handling, portability and code - * quality, as characterised by OpenBSD projects. Several magic - * numbers have been tidied up, calculated (not hardcoded) and - * thoroughly explained, unlike in the original version. - * - * The original version was essentially a blob, masquerading as - * source code. This forked source code is therefore the result - * of extensive reverse engineering (of the GNU source code)! - * This cleaned up code and extensive commenting will thoroughly - * explain how the decoding works. This was done as an academic - * exercise in 2023, continuing in 2026. - * - * This fork of spkmodem-recv, called spkmodem-decode, is provided - * with Libreboot releases: - * https://libreboot.org/ - * - * The original GNU version is here, if you're morbidly curious: - * https://cgit.git.savannah.gnu.org/cgit/grub.git/plain/util/spkmodem-recv.c?id=3dce38eb196f47bdf86ab028de74be40e13f19fd - * - * Libreboot's version was renamed to spkmodem-decode on 12 March 2026, - * since Libreboot's version no longer closely resembles the GNU - * version at all; ergo, a full rename was in order. GNU's version - * was called spkmodem-recv. - */ - -#define _POSIX_SOURCE - -/* - * For OpenBSD define, to detect version - * for deciding whether to use pledge(2) - */ -#ifdef __OpenBSD__ -#include <sys/param.h> -#endif - -#include <errno.h> -#include <limits.h> -#include <stdio.h> -#include <stdarg.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -/* - * spkmodem is essentially using FSK (Frequency Shift Keying) - * with two primary tones representing encoded bits, - * separated by a framing tone. - * Very cheap on CPU cycles and avoids needing something more - * expensive like FFT or Goertzel filters, and tolerates - * weak/noisy signals. - */ - -/* - * Frequency of audio in Hz - * WARNING: if changing, make sure to adjust - * SAMPLES_PER_FRAME accordingly (see maths below) - */ -#define SAMPLE_RATE 48000 - -/* - * One analysis frame spans 5 ms. - * - * frame_time = SAMPLES_PER_FRAME / SAMPLE_RATE - * - * With the default sample rate (48 kHz): - * - * frame_time = N / 48000 - * 0.005 s = N / 48000 - * N = 0.005 × 48000 = 240 samples - */ -#define SAMPLES_PER_FRAME 240 - -/* - * Number of analysis frames per second. - * - * Each increment in the frequency counters corresponds - * roughly to this many Hertz of tone frequency. - * - * With the default values: - * FRAME_RATE = 48000 / 240 = 200 Hz - */ -#define FRAME_RATE ((SAMPLE_RATE) / (SAMPLES_PER_FRAME)) - -/* - * Two FIR windows are maintained; one for data tone, - * and one for the separator tone. They are positioned - * one frame apart in the ring buffer. - */ -#define MAX_SAMPLES (2 * (SAMPLES_PER_FRAME)) - -/* - * Approx byte offset for ring buffer span, just for - * easier debug output correlating to the audio stream. - */ -#define SAMPLE_OFFSET ((MAX_SAMPLES) * (sizeof(short))) - -/* - * Expected tone ranges (approximate, derived from spkmodem). - * These values are intentionally wide because real-world setups - * often involve microphones, room acoustics, and cheap ADCs. - */ -#define SEP_TONE_MIN_HZ 1000 -#define SEP_TONE_MAX_HZ 3000 - -#define SEP_TOLERANCE_PULSES \ - (((SEP_TONE_MAX_HZ) - (SEP_TONE_MIN_HZ)) / (2 * (FRAME_RATE))) - -#define DATA_TONE_MIN_HZ 3000 -#define DATA_TONE_MAX_HZ 12000 - -/* Mid point used to distinguish the two data tones. */ -#define DATA_TONE_THRESHOLD_HZ 5000 - -/* - * Convert tone frequency ranges into pulse counts within the - * sliding analysis window. - * - * pulse_count = tone_frequency / FRAME_RATE - * where FRAME_RATE = SAMPLE_RATE / SAMPLES_PER_FRAME. - */ -#define FREQ_SEP_MIN ((SEP_TONE_MIN_HZ) / (FRAME_RATE)) -#define FREQ_SEP_MAX ((SEP_TONE_MAX_HZ) / (FRAME_RATE)) - -#define FREQ_DATA_MIN ((DATA_TONE_MIN_HZ) / (FRAME_RATE)) -#define FREQ_DATA_MAX ((DATA_TONE_MAX_HZ) / (FRAME_RATE)) - -#define FREQ_DATA_THRESHOLD ((DATA_TONE_THRESHOLD_HZ) / (FRAME_RATE)) - -/* - * These determine how long the program will wait during - * tone auto-detection, before shifting to defaults. - * It is done every LEARN_FRAMES number of frames. - */ -#define LEARN_SECONDS 1 -#define LEARN_FRAMES ((LEARN_SECONDS) * (FRAME_RATE)) - -/* - * Sample amplitude threshold used to convert the waveform - * into a pulse stream. Values near zero are regarded as noise. - */ -#define THRESHOLD 500 - -#define READ_BUF 4096 - -struct decoder_state { - unsigned char pulse[MAX_SAMPLES]; - - signed short inbuf[READ_BUF]; - size_t inpos; - size_t inlen; - - int ringpos; - int sep_pos; - - /* - * Sliding window pulse counters - * used to detect modem tones - */ - int freq_data; - int freq_separator; - int sample_count; - - int ascii_bit; - unsigned char ascii; - - int debug; - int swap_bytes; - - /* dynamic separator calibration */ - int sep_sum; - int sep_samples; - int sep_min; - int sep_max; - - /* for automatic tone detection */ - int freq_min; - int freq_max; - int freq_threshold; - int learn_frames; - - /* previous sample used for edge detection */ - signed short prev_sample; -}; - -static const char *argv0; - -/* - * 16-bit little endian words are read - * continuously. we will swap them, if - * the host cpu is big endian. - */ -static int host_is_big_endian(void); - -/* main loop */ -static void handle_audio(struct decoder_state *st); - -/* separate tone tolerances */ -static void select_separator_tone(struct decoder_state *st); -static int is_valid_signal(struct decoder_state *st); - -/* output to terminal */ -static int set_ascii_bit(struct decoder_state *st); -static void print_char(struct decoder_state *st); -static void reset_char(struct decoder_state *st); - -/* process samples/frames */ -static void decode_pulse(struct decoder_state *st); -static signed short read_sample(struct decoder_state *st); -static void read_words(struct decoder_state *st); - -/* continually adjust tone */ -static void detect_tone(struct decoder_state *st); -static int silent_signal(struct decoder_state *st); -static void select_low_tone(struct decoder_state *st); - -/* debug */ -static void print_stats(struct decoder_state *st); - -/* error handling / usage */ -static void err(int errval, const char *msg, ...); -static void usage(void); -static const char *progname(void); - -/* portability (old systems) */ -int getopt(int, char * const *, const char *); -extern char *optarg; -extern int optind; -extern int opterr; -extern int optopt; - -#ifndef CHAR_BIT -#define CHAR_BIT 8 -#endif - -typedef char static_assert_char_is_8_bits[(CHAR_BIT == 8) ? 1 : -1]; -typedef char static_assert_char_is_1[(sizeof(char) == 1) ? 1 : -1]; -typedef char static_assert_short[(sizeof(short) == 2) ? 1 : -1]; -typedef char static_assert_int_is_4[(sizeof(int) >= 4) ? 1 : -1]; -typedef char static_assert_twos_complement[ - ((-1 & 3) == 3) ? 1 : -1 -]; - -int -main(int argc, char **argv) -{ - struct decoder_state st; - int c; - - argv0 = argv[0]; - -#if defined (__OpenBSD__) && defined(OpenBSD) -#if OpenBSD >= 509 - if (pledge("stdio", NULL) == -1) - err(errno, "pledge"); -#endif -#endif - - memset(&st, 0, sizeof(st)); - - while ((c = getopt(argc, argv, "d")) != -1) { - if (c != 'd') - usage(); - st.debug = 1; - break; - } - - /* fallback in case tone detection fails */ - st.freq_min = 100000; - st.freq_max = 0; - st.freq_threshold = FREQ_DATA_THRESHOLD; - - /* - * Used for separator calibration - */ - st.sep_min = FREQ_SEP_MIN; - st.sep_max = FREQ_SEP_MAX; - - st.ascii_bit = 7; - - st.ringpos = 0; - st.sep_pos = SAMPLES_PER_FRAME; - - if (host_is_big_endian()) - st.swap_bytes = 1; - - setvbuf(stdout, NULL, _IONBF, 0); - - for (;;) - handle_audio(&st); - - return EXIT_SUCCESS; -} - -static int -host_is_big_endian(void) -{ - unsigned int x = 1; - return (*(unsigned char *)&x == 0); -} - -static void -handle_audio(struct decoder_state *st) -{ - int sample; - - /* - * If the modem signal disappears for several (read: 3) - * frames, discard the partially assembled character. - */ - if (st->sample_count >= (3 * SAMPLES_PER_FRAME) || - st->freq_separator <= 0) - reset_char(st); - - st->sample_count = 0; - - /* process exactly one frame */ - for (sample = 0; sample < SAMPLES_PER_FRAME; sample++) - decode_pulse(st); - - select_separator_tone(st); - - if (set_ascii_bit(st) < 0) - print_char(st); - - /* Detect tone per each frame */ - detect_tone(st); -} - -/* - * collect separator tone statistics - * (and auto-adjust tolerances) - */ -static void -select_separator_tone(struct decoder_state *st) -{ - int avg; - - if (!is_valid_signal(st)) - return; - - st->sep_sum += st->freq_separator; - st->sep_samples++; - - if (st->sep_samples != 50) - return; - - avg = st->sep_sum / st->sep_samples; - - st->sep_min = avg - SEP_TOLERANCE_PULSES; - st->sep_max = avg + SEP_TOLERANCE_PULSES; - - /* reset calibration accumulators */ - st->sep_sum = 0; - st->sep_samples = 0; - - if (st->debug) - printf("separator calibrated: %dHz\n", - avg * FRAME_RATE); -} - -/* - * Verify that the observed pulse densities fall within the - * expected ranges for spkmodem tones. This prevents random noise - * from being misinterpreted as data. - */ -static int -is_valid_signal(struct decoder_state *st) -{ - if (st->freq_data <= 0) - return 0; - - if (st->freq_separator < st->sep_min || - st->freq_separator > st->sep_max) - return 0; - - return 1; -} - -/* - * Each validated frame contributes one bit of modem data. - * Bits are accumulated MSB-first into the ASCII byte. - */ -static int -set_ascii_bit(struct decoder_state *st) -{ - if (st->debug) - print_stats(st); - - if (!is_valid_signal(st)) - return st->ascii_bit; - - if (st->freq_data < st->freq_threshold) - st->ascii |= (1 << st->ascii_bit); - - st->ascii_bit--; - - return st->ascii_bit; -} - -static void -print_char(struct decoder_state *st) -{ - if (st->debug) - printf("<%c,%x>", st->ascii, st->ascii); - else - putchar(st->ascii); - - reset_char(st); -} - -static void -reset_char(struct decoder_state *st) -{ - st->ascii = 0; - st->ascii_bit = 7; -} - -/* - * Main demodulation step (moving-sum FIR filter). - */ -static void -decode_pulse(struct decoder_state *st) -{ - unsigned char old_ring, old_sep; - unsigned char new_pulse; - signed short sample; - int ringpos; - int sep_pos; - int diff_edge; - int diff_amp; - - ringpos = st->ringpos; - sep_pos = st->sep_pos; - - /* - * Sliding rectangular FIR (Finite Impulse Response) filter. - * - * After thresholding, the signal becomes a stream of 0/1 pulses. - * The decoder measures pulse density over two windows: - * - * freq_data: pulses in the "data" window - * freq_separator: pulses in the "separator" window - * - * Instead of calculating each window every time (O(N) per frame), we - * update the window sums incrementally: - * - * sum_new = sum_old - pulse_leaving + pulse_entering - * - * This keeps the filter O(1) per sample instead of O(N). - * The technique is equivalent to a rectangular FIR filter - * implemented as a sliding moving sum. - * - * The two windows are exactly SAMPLES_PER_FRAME apart in the ring - * buffer, so the pulse leaving the data window is simultaneously - * entering the separator window. - */ - old_ring = st->pulse[ringpos]; - old_sep = st->pulse[sep_pos]; - st->freq_data -= old_ring; - st->freq_data += old_sep; - st->freq_separator -= old_sep; - - sample = read_sample(st); - - /* - * Avoid startup edge. Since - * it's zero at startup, this - * may wrongly produce a pulse - */ - if (st->sample_count == 0) - st->prev_sample = sample; - - /* - * Detect edges instead of amplitude. - * This is more tolerant of weak microphones - * and speaker distortion.. - * - * However, we check both slope edges and - * amplitude, to mitagate noise. - */ - diff_amp = sample; - diff_edge = sample - st->prev_sample; - if (diff_edge < 0) - diff_edge = -diff_edge; - if (diff_amp < 0) - diff_amp = -diff_amp; - if (diff_edge > THRESHOLD && - diff_amp > THRESHOLD) - new_pulse = 1; - else - new_pulse = 0; - st->prev_sample = sample; - - st->pulse[ringpos] = new_pulse; - st->freq_separator += new_pulse; - - /* - * Advance both FIR windows through the ring buffer. - * The separator window always stays one frame ahead - * of the data window. - */ - if (++ringpos >= MAX_SAMPLES) - ringpos = 0; - if (++sep_pos >= MAX_SAMPLES) - sep_pos = 0; - - st->ringpos = ringpos; - st->sep_pos = sep_pos; - - st->sample_count++; -} - -static signed short -read_sample(struct decoder_state *st) -{ - signed short sample; - unsigned short u; - - while (st->inpos >= st->inlen) - read_words(st); - - sample = st->inbuf[st->inpos++]; - - if (st->swap_bytes) { - u = (unsigned short)sample; - u = (u >> 8) | (u << 8); - - sample = (signed short)u; - } - - return sample; -} - -static void -read_words(struct decoder_state *st) -{ - size_t n; - - n = fread(st->inbuf, sizeof(st->inbuf[0]), - READ_BUF, stdin); - - if (n != 0) { - st->inpos = 0; - st->inlen = n; - - return; - } - - if (ferror(stdin)) - err(errno, "stdin read"); - if (feof(stdin)) - exit(EXIT_SUCCESS); -} - -/* - * Automatically detect spkmodem tone - */ -static void -detect_tone(struct decoder_state *st) -{ - if (st->learn_frames >= LEARN_FRAMES) - return; - - st->learn_frames++; - - if (silent_signal(st)) - return; - - select_low_tone(st); - - if (st->learn_frames != LEARN_FRAMES) - return; - - /* - * If the observed frequencies are too close, - * learning likely failed (only one tone seen). - * Keep the default threshold. - */ - if (st->freq_max - st->freq_min < 2) - return; - - st->freq_threshold = - (st->freq_min + st->freq_max) / 2; - - if (st->debug) - printf("auto threshold: %dHz\n", - st->freq_threshold * FRAME_RATE); -} - -/* - * Ignore silence / near silence. - * Both FIR windows will be near zero when no signal exists. - */ -static int -silent_signal(struct decoder_state *st) -{ - return (st->freq_data <= 2 && - st->freq_separator <= 2); -} - -/* - * Choose the lowest active tone. - * Separator frames carry tone in the separator window, - * data frames carry tone in the data window. - */ -static void -select_low_tone(struct decoder_state *st) -{ - int f; - - f = st->freq_data; - - if (f <= 0 || (st->freq_separator > 0 && - st->freq_separator < f)) - f = st->freq_separator; - - if (f <= 0) - return; - - if (f < st->freq_min) - st->freq_min = f; - - if (f > st->freq_max) - st->freq_max = f; -} - -static void -print_stats(struct decoder_state *st) -{ - long pos; - - int data_hz = st->freq_data * FRAME_RATE; - int sep_hz = st->freq_separator * FRAME_RATE; - int sep_hz_min = st->sep_min * FRAME_RATE; - int sep_hz_max = st->sep_max * FRAME_RATE; - - if ((pos = ftell(stdin)) == -1) { - printf("%d %d %d data=%dHz sep=%dHz(min %dHz %dHz)\n", - st->freq_data, - st->freq_separator, - st->freq_threshold, - data_hz, - sep_hz, - sep_hz_min, - sep_hz_max); - return; - } - - printf("%d %d %d @%ld data=%dHz sep=%dHz(min %dHz %dHz)\n", - st->freq_data, - st->freq_separator, - st->freq_threshold, - pos - SAMPLE_OFFSET, - data_hz, - sep_hz, - sep_hz_min, - sep_hz_max); -} - -static void -err(int errval, const char *msg, ...) -{ - va_list ap; - - fprintf(stderr, "%s: ", progname()); - - va_start(ap, msg); - vfprintf(stderr, msg, ap); - va_end(ap); - - fprintf(stderr, ": %s\n", strerror(errval)); - exit(EXIT_FAILURE); -} - -static void -usage(void) -{ - fprintf(stderr, "usage: %s [-d]\n", progname()); - exit(EXIT_FAILURE); -} - -static const char * -progname(void) -{ - const char *p; - - if (argv0 == NULL || *argv0 == '\0') - return ""; - - p = strrchr(argv0, '/'); - - if (p) - return p + 1; - else - return argv0; -} diff --git a/util/spkmodem_recv/.gitignore b/util/spkmodem_recv/.gitignore new file mode 100644 index 00000000..2f5c946c --- /dev/null +++ b/util/spkmodem_recv/.gitignore @@ -0,0 +1 @@ +spkmodem-recv diff --git a/util/spkmodem_recv/Makefile b/util/spkmodem_recv/Makefile new file mode 100644 index 00000000..3c5dc51f --- /dev/null +++ b/util/spkmodem_recv/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +CC?=cc +CFLAGS?=-Os -Wall -Wextra -Werror -pedantic +DESTDIR?= +PREFIX?=/usr/local +INSTALL?=install + +spkmodem-recv: + $(CC) $(CFLAGS) -o $@ $@.c +install: spkmodem-recv + $(INSTALL) -d $(DESTDIR)$(PREFIX)/bin/ + $(INSTALL) $< -t $(DESTDIR)$(PREFIX)/bin/ +clean: + rm -f spkmodem-recv diff --git a/util/spkmodem_recv/spkmodem-recv.c b/util/spkmodem_recv/spkmodem-recv.c new file mode 100644 index 00000000..4467282d --- /dev/null +++ b/util/spkmodem_recv/spkmodem-recv.c @@ -0,0 +1,124 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* SPDX-FileCopyrightText: 2013 Free Software Foundation, Inc. */ +/* Usage: parec --channels=1 --rate=48000 --format=s16le | ./spkmodem-recv */ + +/* Forked from coreboot's version, at util/spkmodem_recv/ in coreboot.git, + * revision 5c2b5fcf2f9c9259938fd03cfa3ea06b36a007f0 as of 3 January 2022. + * This version is heavily modified, re-written based on OpenBSD Kernel Source + * File Style Guide (KNF); this change is Copyright 2023 Leah Rowe. */ + +#include <err.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define SAMPLES_PER_FRAME 240 +#define MAX_SAMPLES (2 * SAMPLES_PER_FRAME) +#define FREQ_SEP_MIN 5 +#define FREQ_SEP_MAX 15 +#define FREQ_DATA_MIN 15 +#define FREQ_DATA_THRESHOLD 25 +#define FREQ_DATA_MAX 60 +#define THRESHOLD 500 + +#define ERR() (errno = errno ? errno : ECANCELED) +#define reset_char() ascii = 0, ascii_bit = 7 + +signed short frame[MAX_SAMPLES], pulse[MAX_SAMPLES]; +int ringpos, debug, freq_data, freq_separator, sample_count, ascii_bit = 7; +char ascii = 0; + +void handle_audio(void); +void decode_pulse(void); +int set_ascii_bit(void); +void print_char(void); +void print_stats(void); + +int +main(int argc, char *argv[]) +{ + int c; +#ifdef __OpenBSD__ + if (pledge("stdio", NULL) == -1) + err(ERR(), "pledge"); +#endif + while ((c = getopt(argc, argv, "d")) != -1) + if (!(debug = (c == 'd'))) + err(errno = EINVAL, NULL); + setvbuf(stdout, NULL, _IONBF, 0); + while (!feof(stdin)) + handle_audio(); + if (errno && debug) + err(errno, "Unhandled error, errno %d", errno); + return errno; +} + +void +handle_audio(void) +{ + if (sample_count > (3 * SAMPLES_PER_FRAME)) + sample_count = reset_char(); + if ((freq_separator <= FREQ_SEP_MIN) || (freq_separator >= FREQ_SEP_MAX) + || (freq_data <= FREQ_DATA_MIN) || (freq_data >= FREQ_DATA_MAX)) { + decode_pulse(); + return; + } + + if (set_ascii_bit() < 0) + print_char(); + sample_count = 0; + for (int sample = 0; sample < SAMPLES_PER_FRAME; sample++) + decode_pulse(); +} + +void +decode_pulse(void) +{ + int next_ringpos = (ringpos + SAMPLES_PER_FRAME) % MAX_SAMPLES; + freq_data -= pulse[ringpos]; + freq_data += pulse[next_ringpos]; + freq_separator -= pulse[next_ringpos]; + + fread(frame + ringpos, 1, sizeof(frame[0]), stdin); + if (ferror(stdin) != 0) + err(ERR(), "Could not read from frame."); + + if ((pulse[ringpos] = (abs(frame[ringpos]) > THRESHOLD) ? 1 : 0)) + ++freq_separator; + ++ringpos; + ringpos %= MAX_SAMPLES; + ++sample_count; +} + +int +set_ascii_bit(void) +{ + if (debug) + print_stats(); + if (freq_data < FREQ_DATA_THRESHOLD) + ascii |= (1 << ascii_bit); + --ascii_bit; + return ascii_bit; +} + +void +print_char(void) +{ + if (debug) + printf("<%c, %x>", ascii, ascii); + else + printf("%c", ascii); + reset_char(); +} + +void +print_stats(void) +{ + long stdin_pos; + if ((stdin_pos = ftell(stdin)) == -1) + err(ERR(), NULL); + printf ("%d %d %d @%ld\n", freq_data, freq_separator, + FREQ_DATA_THRESHOLD, stdin_pos - sizeof(frame)); +} |
