writeimg/writeimg.c
2026-02-07 13:51:44 +01:00

222 lines
5.3 KiB
C

#include "crc32.h"
#include <assert.h>
#include <fcntl.h>
#include <getopt.h>
#include <libgen.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#ifndef GITREV
#define GITREV "unknown"
#endif
#ifndef VERSION
#define VERSION "unknown"
#endif
#ifndef CR_YEAR
#define CR_YEAR "2026"
#endif
#ifndef BLOCKSIZE
#define BLOCKSIZE (4 * 1024 * 1024)
#endif
// clang-format off
const char help[] =
"Usage:\n"
" writeimg [-v] -d <device> <file.img>\n"
"\n"
"Args:\n"
" <file.img> Binary image file\n"
" -v Verify only\n"
" -d device Target block device\n"
" -h, --help Print this help message\n"
" -V, --version Print version\n"
"\0";
// clang-format on
const char copyright[] = "Copyright (C) %s Imbus, BSD-2-Clause\n";
struct write_job {
char *filename;
char *dev_name;
char *buffer;
char *buffer2; /* For memcmp integrity checks */
size_t bufsize;
size_t block_size;
char verify_only;
} wjob = {0};
typedef struct write_job write_job_t;
void int_handler(int signum) {
fprintf(stderr, "\nInterrupted: %s\n", strsignal(signum));
if (wjob.buffer)
free(wjob.buffer);
if (wjob.buffer2)
free(wjob.buffer2);
exit(EXIT_FAILURE);
}
int perform_write(write_job_t *job) {
int block_fd = open(job->dev_name, O_RDWR);
int file_fd = open(job->filename, O_RDONLY);
/* TODO: Checks */
assert(job->bufsize >= job->block_size);
int crc = crc32_init();
while (!job->verify_only) {
ssize_t read_bytes = read(file_fd, job->buffer, job->block_size);
if (read_bytes == 0) {
fsync(block_fd);
crc = crc32_finalize(crc);
break; /* Finished */
}
if (read_bytes < 0) {
fprintf(stderr, "%s: Read error\n", job->filename);
perror("Read");
exit(EXIT_FAILURE);
}
crc = crc32_update(crc, job->buffer, read_bytes);
ssize_t written_bytes = write(block_fd, job->buffer, read_bytes);
if (written_bytes < 0) {
fprintf(stderr, "%s: Write error\n", job->dev_name);
perror("Write");
exit(EXIT_FAILURE);
}
}
lseek(block_fd, 0, SEEK_SET);
lseek(file_fd, 0, SEEK_SET);
memset(job->buffer, 0, BLOCKSIZE);
memset(job->buffer2, 0, BLOCKSIZE);
int crc_back = crc32_init();
while (1) {
ssize_t read_file = read(file_fd, job->buffer2, job->block_size);
ssize_t read_block = read(block_fd, job->buffer, read_file);
assert(read_block == read_file);
assert(read_block >= 0);
assert(read_file >= 0);
if (read_file == 0) {
crc_back = crc32_finalize(crc_back);
assert(crc_back == crc);
break;
}
crc_back = crc32_update(crc_back, job->buffer, read_file);
int cmp = memcmp((const void *)job->buffer2, (const void *)job->buffer, (size_t)read_file);
if (0 != cmp) {
fprintf(stderr, "WARNING: File did not verify correctly!\n");
exit(EXIT_FAILURE);
}
}
close(block_fd);
close(file_fd);
return 1;
}
static const struct option longopts[] = {
{"help", 0, 0, 'h'},
{"version", 0, 0, 'V'},
{"device", 1, 0, 'd'},
{NULL, 0, 0, 0},
};
int main(int argc, char *argv[]) {
printf("%s %s, Rev. %s\n", basename(argv[0]), VERSION, GITREV);
fprintf(stdout, copyright, CR_YEAR);
#ifdef BLDDATE
printf("Build date: %s\n", BLDDATE);
#endif
printf("\n");
/* Line buffering, system allocated */
setvbuf(stdout, NULL, _IOLBF, 0);
setvbuf(stderr, NULL, _IOLBF, 0);
/* Hang these up to alert the user that we were interrupted */
signal(SIGINT, int_handler);
signal(SIGHUP, int_handler);
signal(SIGTERM, int_handler);
int c = {0};
while ((c = getopt_long(argc, argv, "vd:hV", longopts, 0)) != -1) {
switch (c) {
case 'v': ++wjob.verify_only; continue;
case 'd': wjob.dev_name = optarg; continue;
case 'h': break;
case 'V': exit(EXIT_SUCCESS);
}
printf("%s\n", help);
exit(EXIT_SUCCESS);
}
argc -= optind;
argv += optind;
/* argv[0] should now be the image file*/
if (argc != 1) {
printf("You need to specify an image file.\n");
exit(EXIT_FAILURE);
}
wjob.filename = argv[0];
struct stat file_stat = {0};
if (0 != stat(wjob.filename, &file_stat)) {
printf("File does not exist...\n");
exit(EXIT_FAILURE);
}
if (NULL == wjob.dev_name) {
printf("Device required...\n");
exit(EXIT_FAILURE);
}
if (0 != strncmp(wjob.dev_name, "/dev/", 4)) {
printf("\"%s\" does not appear to be a block device...\n", wjob.dev_name);
exit(EXIT_FAILURE);
}
printf("Writing %s to %s\n", wjob.filename, wjob.dev_name);
wjob.buffer = malloc(BLOCKSIZE);
wjob.buffer2 = malloc(BLOCKSIZE);
wjob.bufsize = BLOCKSIZE;
wjob.block_size = BLOCKSIZE;
assert(file_stat.st_size >= 0);
perform_write(&wjob);
if (wjob.buffer)
free(wjob.buffer);
if (wjob.buffer2)
free(wjob.buffer2);
printf("\nOkay!\n");
exit(0);
return 0;
}