load_ramdisk_fd: improved IO error handling

- while loop terminates on read error **without** resetting
  `seems_ok` to 0, thus read errors won't be noticed
- no check if bytes_read matches the expected image size
- ram_fd is leaked on write error
- short writes are treated as errors (when in fact they are
  perfectly fine)

Therefore:

- Handle *all* read errors, not just the very first one
- If the image size is known in advance verify if we actually
  read the whole image
- close `ram_fd` in error code paths
- Handle short writes properly

Closes: #40803
This commit is contained in:
Alexey Sheplyakov 2021-08-20 20:18:14 +04:00
parent dc87263d47
commit 63008e11b2

75
tools.c
View File

@ -294,15 +294,63 @@ static void save_stuff_for_rescue(void)
}
/**
* @brief copy exactly len bytes from buffer to file descriptor
*/
static int write_exactly(int fd, const char *buf, size_t len)
{
ssize_t dl;
while (len > 0) {
dl = write(fd, buf, len);
if (dl < 0) {
log_message("%s: write: %s", __func__, strerror(errno));
return -1;
}
buf += (size_t)dl;
len -= (size_t)dl;
}
return 0;
}
/*
* @brief Copy size bytes from src_fd to dst_fd
* @param exact if non-zero check if exactly size bytes have been copied
* @return 0 on success, -1 on error
* @note if size == 0 keep copying until EOF on src_fd
*/
static int copy_loop(int dst_fd, int src_fd, unsigned long size)
{
char buf[32768];
unsigned long bytes_written = 0;
ssize_t dl;
for (;;) {
dl = read(src_fd, buf, sizeof(buf));
if (dl < 0) {
log_message("%s: read: %s", __func__, strerror(errno));
return -1;
} else if (dl == 0) {
break;
}
if (write_exactly(dst_fd, buf, (size_t)dl) < 0) {
return -1;
}
bytes_written += (size_t)dl;
update_progression((int)BYTES2MB(bytes_written));
}
if (size > 0 && bytes_written != size) {
log_message("%s: expected %lu bytes, copied %lu instead\n",
__func__, size, bytes_written);
errno = EIO;
return -1;
}
return 0;
}
enum return_type load_ramdisk_fd(int source_fd, unsigned long size)
{
char * ramdisk = "/dev/ram3"; /* warning, verify that this file exists in the initrd, and that root=/dev/ram3 is actually passed to the kernel at boot time */
int ram_fd;
char buffer[32768];
char * wait_msg = "Loading program into memory...";
unsigned long bytes_read = 0;
ssize_t actually;
int seems_ok = 0;
ram_fd = open(ramdisk, O_WRONLY);
if (ram_fd == -1) {
@ -312,22 +360,8 @@ enum return_type load_ramdisk_fd(int source_fd, unsigned long size)
}
init_progression(wait_msg, (int)BYTES2MB(size));
while ((actually = read(source_fd, buffer, sizeof(buffer))) > 0) {
seems_ok = 1;
if (write(ram_fd, buffer, actually) != actually) {
log_perror("writing ramdisk");
remove_wait_message();
return RETURN_ERROR;
}
bytes_read += actually;
update_progression((int)BYTES2MB(bytes_read));
if (bytes_read == size)
break;
}
if (!seems_ok) {
log_message("reading compressed ramdisk: %s", strerror(errno));
if (copy_loop(ram_fd, source_fd, size) < 0) {
log_message("%s: failed to load image to RAM", __func__);
close(ram_fd);
remove_wait_message();
stg1_error_message("Could not load second stage ramdisk. "
@ -335,7 +369,6 @@ enum return_type load_ramdisk_fd(int source_fd, unsigned long size)
"(this may be caused by a hardware failure or a Linux kernel bug)");
return RETURN_ERROR;
}
end_progression();
close(ram_fd);