mirror of
https://codeberg.org/libreboot/lbmk.git
synced 2026-07-11 05:52:36 +02:00
util/nvmutil: split up nvmutil.c
i still use a global variable, but now only one, which is a structure containing the state of the entire program now i can easily start modifying it to make functions generic, and then i can start making parts of it into easy libraries Signed-off-by: Leah Rowe <leah@libreboot.org>
This commit is contained in:
+418
-918
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,463 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (c) 2022-2026 Leah Rowe <leah@libreboot.org>
|
||||
*/
|
||||
|
||||
#ifndef NVMUTIL_H
|
||||
#define NVMUTIL_H
|
||||
|
||||
#define MAX_CMD_LEN 50
|
||||
|
||||
#ifndef PATH_LEN
|
||||
#define PATH_LEN 1024
|
||||
#endif
|
||||
|
||||
#define OFF_ERR 0
|
||||
#ifndef OFF_RESET
|
||||
#define OFF_RESET 1
|
||||
#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
|
||||
|
||||
/*
|
||||
* Older versions of BSD to the early 2000s
|
||||
* could compile nvmutil, but pledge was
|
||||
* added in the 2010s. Therefore, for extra
|
||||
* portability, we will only pledge/unveil
|
||||
* on OpenBSD versions that have it.
|
||||
*/
|
||||
|
||||
#if defined(__OpenBSD__) && defined(OpenBSD)
|
||||
#if OpenBSD >= 604
|
||||
#ifndef NVMUTIL_UNVEIL
|
||||
#define NVMUTIL_UNVEIL 1
|
||||
#endif
|
||||
#endif
|
||||
#if OpenBSD >= 509
|
||||
#ifndef NVMUTIL_PLEDGE
|
||||
#define NVMUTIL_PLEDGE 1
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef EXIT_FAILURE
|
||||
#define EXIT_FAILURE 1
|
||||
#endif
|
||||
|
||||
#ifndef EXIT_SUCCESS
|
||||
#define EXIT_SUCCESS 0
|
||||
#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 a GbE part contains
|
||||
* the regular NVM (Non-Volatile-Memory)
|
||||
* area. All of these bytes must add up,
|
||||
* truncated to 0xBABA.
|
||||
*
|
||||
* The full GbE region is 4KB, but only
|
||||
* the first 128 bytes are used here.
|
||||
*
|
||||
* There is a second 4KB part with the same
|
||||
* rules, and it *should* be identical.
|
||||
*/
|
||||
|
||||
#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)
|
||||
|
||||
/*
|
||||
* Portable macro based on BSD nitems.
|
||||
* Used to count the number of commands (see below).
|
||||
*/
|
||||
|
||||
#define items(x) (sizeof((x)) / sizeof((x)[0]))
|
||||
|
||||
/*
|
||||
* GbE files can be 8KB, 16KB or 128KB,
|
||||
* but we only need the two 4KB parts
|
||||
* from offset zero and offset 64KB in
|
||||
* a 128KB file, or zero and 8KB in a 16KB
|
||||
* file, or zero and 4KB in an 8KB file.
|
||||
*
|
||||
* The code will handle this properly.
|
||||
*/
|
||||
|
||||
#ifndef X_LONG_MAX
|
||||
#define X_LONG_MAX ((long)(~((long)1 << (sizeof(long)*CHAR_BIT-1))))
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Use these for .argc in command[]:
|
||||
*/
|
||||
|
||||
#define ARGC_3 3
|
||||
#define ARGC_4 4
|
||||
|
||||
#define NO_LOOP_EAGAIN 0
|
||||
#define NO_LOOP_EINTR 0
|
||||
|
||||
/*
|
||||
* Used for checking whether.
|
||||
* a file is a file via stat().
|
||||
*
|
||||
* Portable macro for compatibility
|
||||
* with older unix e.g. v7 unix (has S_IFREG),
|
||||
* 4.2bsd (has S_IFMT) or POSIX (has S_ISREG)
|
||||
*
|
||||
* Fallback works where S_IFREG == 0100000
|
||||
* (classic unix 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
|
||||
|
||||
/*
|
||||
* Used as indices for command[]
|
||||
* MUST be in the same order as entries in command[]
|
||||
*/
|
||||
|
||||
#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
|
||||
|
||||
/*
|
||||
* portable timeval
|
||||
*/
|
||||
struct x_st_timeval {
|
||||
long tv_sec;
|
||||
long tv_usec;
|
||||
};
|
||||
|
||||
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 */
|
||||
};
|
||||
|
||||
struct macaddr {
|
||||
char *str; /* set to rmac, or argv string */
|
||||
char rmac[18]; /* xx:xx:xx:xx:xx:xx */
|
||||
unsigned short mac_buf[3];
|
||||
};
|
||||
|
||||
struct xfile {
|
||||
int gbe_fd;
|
||||
int tmp_fd;
|
||||
|
||||
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];
|
||||
|
||||
dev_t gbe_dev;
|
||||
ino_t gbe_ino;
|
||||
|
||||
dev_t tmp_dev;
|
||||
ino_t tmp_ino;
|
||||
|
||||
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 */
|
||||
};
|
||||
|
||||
/*
|
||||
* BE CAREFUL when editing this
|
||||
* to ensure that you also update
|
||||
* the tables in new_xstate()
|
||||
*/
|
||||
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;
|
||||
|
||||
/* store size of a struct here.
|
||||
(can be used to copy old state) */
|
||||
unsigned long xsize;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* 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);
|
||||
void xopen(int *fd, const char *path, int flags, struct stat *st);
|
||||
|
||||
/*
|
||||
* Read GbE file and verify
|
||||
* checksums.
|
||||
*
|
||||
* After this, we can run commands.
|
||||
*/
|
||||
void copy_gbe(void);
|
||||
void read_checksums(void);
|
||||
int good_checksum(unsigned long partnum);
|
||||
|
||||
/*
|
||||
* Execute user command on GbE data.
|
||||
* These are stubs that call helpers.
|
||||
*/
|
||||
void run_cmd(void);
|
||||
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);
|
||||
unsigned long entropy_jitter(void);
|
||||
int x_i_gettimeofday(struct x_st_timeval *tv, void *tz);
|
||||
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 cat_buf(unsigned char *b);
|
||||
|
||||
/*
|
||||
* After command processing, write
|
||||
* the modified GbE file back.
|
||||
*
|
||||
* These are stub functions: check
|
||||
* below for the actual functions.
|
||||
*/
|
||||
void write_gbe_file(void);
|
||||
void set_checksum(unsigned long part);
|
||||
unsigned short calculated_checksum(unsigned long p);
|
||||
|
||||
/*
|
||||
* Helper functions for accessing
|
||||
* the NVM area during operation.
|
||||
*/
|
||||
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);
|
||||
|
||||
/*
|
||||
* Helper functions for stub functions
|
||||
* that handle GbE file reads/writes.
|
||||
*/
|
||||
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);
|
||||
int fsync_dir(const char *path);
|
||||
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);
|
||||
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_loop(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);
|
||||
|
||||
/*
|
||||
* a special kind of hell
|
||||
*/
|
||||
char *new_tmpfile(int *fd, int local, const char *path);
|
||||
int x_i_mkstemp(char *template);
|
||||
char *x_c_strrchr(const char *s, int c);
|
||||
/* x_i_rename not suitable
|
||||
* for atomic writes. kept
|
||||
* commentted for use in a
|
||||
* library in the future */
|
||||
/*
|
||||
int x_i_rename(const char *src, const char *dst);
|
||||
*/
|
||||
char *x_c_tmpdir(void);
|
||||
int x_i_close(int fd);
|
||||
void *x_v_memcpy(void *dst,
|
||||
const void *src, unsigned long n);
|
||||
int x_i_memcmp(const void *a,
|
||||
const void *b, unsigned long n);
|
||||
int x_i_fchmod(int fd, mode_t mode);
|
||||
int x_try_fdpath(const char *prefix,
|
||||
int fd, mode_t mode);
|
||||
unsigned long x_conv_fd(char *buf,
|
||||
unsigned long n);
|
||||
int x_i_fsync(int fd);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,134 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (c) 2026 Leah Rowe <leah@libreboot.org>
|
||||
*
|
||||
* Five Year Plan
|
||||
*/
|
||||
|
||||
/*
|
||||
* Major TODO: split this into multiple files.
|
||||
* This program has become quite large now, mostly
|
||||
* due to all the extra sanity checks / portability.
|
||||
* Make most of nvmutil a *library* for re-use
|
||||
*
|
||||
* TODO: gettimeofday not posible - use portable functions.
|
||||
* TODO: ux fallback: modify the program instead
|
||||
* to run on 16-bit systems: smaller buffers, and do
|
||||
* operations byte-based instead of word-based.
|
||||
*
|
||||
* TODO: _XOPEN_SOURCE 500 probably not needed anymore.
|
||||
* the portable fallbacks alone are likely enough.
|
||||
* e.g. i don't need stdint, and i don't use pwrite/pread
|
||||
* anymore.
|
||||
*
|
||||
* TODO: version detection of various BSDs to detect
|
||||
* arc4random, use that if available. but also work on
|
||||
* older versions of those BSDs (also MacOS) that lack it.
|
||||
*
|
||||
* TODO: portability/testing on non-Unix systems:
|
||||
* old DOS. all windows versions (probably irrelevant
|
||||
* because you can use cygwin/wsl, whatever), classic MacOS,
|
||||
* also test really old unix e.g. sunos and irix. Be/Haiku too!
|
||||
*
|
||||
* TODO: reliance on global variables for status. make
|
||||
* functions use structs passed as args instead, make
|
||||
* functions re-useable (including libraries), etc.
|
||||
*
|
||||
* TODO: bound checks for files per-command, e.g. only
|
||||
* first 6 bytes for CMD_SETMAC
|
||||
*
|
||||
* TODO: in command sanitizer: verify that each given
|
||||
* entry corresponds to the correct function, in the
|
||||
* pointer (this check is currently missing)
|
||||
*
|
||||
* TODO: general modularisierung of the entire codebase.
|
||||
* TODO: better explain copy/swap read inversion trick
|
||||
* by improving existing comments
|
||||
* TODO: lots of overwritten comments in code. tidy it up.
|
||||
*
|
||||
* TODO: use getopt for nvmutil args, so that multiple
|
||||
* operations can be performed, and also on many
|
||||
* files at once (noting limitations with cat)
|
||||
* BONUS: implement own getopt(), for portability
|
||||
*
|
||||
* TODO: document fuzzing / analysis methods
|
||||
* for the code, and:
|
||||
* TODO: implement rigorous unit tests (separate util)
|
||||
* NOTE: this would *include* known good test files
|
||||
* in various configurations, also invalid files.
|
||||
* the tests would likely be portable posix shell
|
||||
* scripts rather than a new C program, but a modularisiert
|
||||
* codebase would allow me to write a separate C
|
||||
* program to test some finer intricacies
|
||||
* TODO: the unit tests would basically test regressions
|
||||
* TODO: after writing back a gbe to file, x_i_close() and
|
||||
* open() it again, read it again, and check that
|
||||
* the contents were written correctly, providing
|
||||
* a warning if they were. do this in the main
|
||||
* program.
|
||||
* TODO: the unit tests would include an aggressive set
|
||||
* of fuzz tests, under controlled conditions
|
||||
*
|
||||
* TODO: also document the layout of Intel GbE files, so
|
||||
* that wily individuals can easily expand the
|
||||
* featureset of nvmutil.
|
||||
* TODO: write a manpage
|
||||
* TODO: simplify the command sanitization, implement more
|
||||
* of it as build time checks, e.g. asserts.
|
||||
* generally remove cleverness from the code, instead
|
||||
* prefyerring readibility
|
||||
* TODO: also document nvmutil's coding style, which is
|
||||
* its own style at this point!
|
||||
* TODO: when all the above (and possibly more) is done,
|
||||
* submit this tool to coreboot with a further change
|
||||
* to their build system that lets users modify
|
||||
* GbE images, especially set MAC addresses, when
|
||||
* including GbE files in coreboot configs.
|
||||
*/
|
||||
/*
|
||||
BONUS TODO:
|
||||
CI/CD. woodpecker is good enough, sourcehut also has one.
|
||||
tie this in with other things mentioned here,
|
||||
e.g. fuzzer / unit tests
|
||||
*/
|
||||
|
||||
/* Major TODO: reproducible builds
|
||||
Test with and without these:
|
||||
|
||||
CFLAGS += -fno-record-gcc-switches
|
||||
CFLAGS += -ffile-prefix-map=$(PWD)=.
|
||||
CFLAGS += -fdebug-prefix-map=$(PWD)=.
|
||||
|
||||
I already avoid unique timestamps per-build,
|
||||
by not using them, e.g. not reporting build
|
||||
time in the program.
|
||||
|
||||
When splitting the nvmutil.c file later, do e.g.:
|
||||
|
||||
SRC = main.c io.c nvm.c cmd.c
|
||||
OBJ = $(SRC:.c=.o)
|
||||
|
||||
^ explicitly declare the order in which to build
|
||||
*/
|
||||
|
||||
/*
|
||||
TODO:
|
||||
further note when fuzzing is implemented:
|
||||
use deterministic randomisation, with a
|
||||
guaranteed seed - so e.g. don't use /dev/urandom
|
||||
in test builds. e.g. just use normal rand()
|
||||
but with a seed e.g. 1234
|
||||
*/
|
||||
/*
|
||||
TODO: stricter build flags, e.g.
|
||||
CFLAGS += -fstack-protector-strong
|
||||
CFLAGS += -fno-common
|
||||
CFLAGS += -D_FORTIFY_SOURCE=2
|
||||
CFLAGS += -fPIE
|
||||
|
||||
also consider:
|
||||
-fstack-clash-protection
|
||||
-Wl,-z,relro
|
||||
-Wl,-z,now
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user