summaryrefslogtreecommitdiffstats
path: root/arch/um/drivers/cow_user.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 01:02:30 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 01:02:30 +0000
commit76cb841cb886eef6b3bee341a2266c76578724ad (patch)
treef5892e5ba6cc11949952a6ce4ecbe6d516d6ce58 /arch/um/drivers/cow_user.c
parentInitial commit. (diff)
downloadlinux-upstream/4.19.249.tar.xz
linux-upstream/4.19.249.zip
Adding upstream version 4.19.249.upstream/4.19.249upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'arch/um/drivers/cow_user.c')
-rw-r--r--arch/um/drivers/cow_user.c443
1 files changed, 443 insertions, 0 deletions
diff --git a/arch/um/drivers/cow_user.c b/arch/um/drivers/cow_user.c
new file mode 100644
index 000000000..0ee9cc6cc
--- /dev/null
+++ b/arch/um/drivers/cow_user.c
@@ -0,0 +1,443 @@
+/*
+ * Copyright (C) 2007 Jeff Dike (jdike@{linux.intel,addtoit}.com)
+ * Licensed under the GPL
+ */
+
+/*
+ * _XOPEN_SOURCE is needed for pread, but we define _GNU_SOURCE, which defines
+ * that.
+ */
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <arpa/inet.h>
+#include <endian.h>
+#include "cow.h"
+#include "cow_sys.h"
+
+#define PATH_LEN_V1 256
+
+typedef __u32 time32_t;
+
+struct cow_header_v1 {
+ __s32 magic;
+ __s32 version;
+ char backing_file[PATH_LEN_V1];
+ time32_t mtime;
+ __u64 size;
+ __s32 sectorsize;
+} __attribute__((packed));
+
+/*
+ * Define PATH_LEN_V3 as the usual value of MAXPATHLEN, just hard-code it in
+ * case other systems have different values for MAXPATHLEN.
+ *
+ * The same must hold for V2 - we want file format compatibility, not anything
+ * else.
+ */
+#define PATH_LEN_V3 4096
+#define PATH_LEN_V2 PATH_LEN_V3
+
+struct cow_header_v2 {
+ __u32 magic;
+ __u32 version;
+ char backing_file[PATH_LEN_V2];
+ time32_t mtime;
+ __u64 size;
+ __s32 sectorsize;
+} __attribute__((packed));
+
+/*
+ * Changes from V2 -
+ * PATH_LEN_V3 as described above
+ * Explicitly specify field bit lengths for systems with different
+ * lengths for the usual C types. Not sure whether char or
+ * time_t should be changed, this can be changed later without
+ * breaking compatibility
+ * Add alignment field so that different alignments can be used for the
+ * bitmap and data
+ * Add cow_format field to allow for the possibility of different ways
+ * of specifying the COW blocks. For now, the only value is 0,
+ * for the traditional COW bitmap.
+ * Move the backing_file field to the end of the header. This allows
+ * for the possibility of expanding it into the padding required
+ * by the bitmap alignment.
+ * The bitmap and data portions of the file will be aligned as specified
+ * by the alignment field. This is to allow COW files to be
+ * put on devices with restrictions on access alignments, such as
+ * /dev/raw, with a 512 byte alignment restriction. This also
+ * allows the data to be more aligned more strictly than on
+ * sector boundaries. This is needed for ubd-mmap, which needs
+ * the data to be page aligned.
+ * Fixed (finally!) the rounding bug
+ */
+
+/*
+ * Until Dec2005, __attribute__((packed)) was left out from the below
+ * definition, leading on 64-bit systems to 4 bytes of padding after mtime, to
+ * align size to 8-byte alignment. This shifted all fields above (no padding
+ * was present on 32-bit, no other padding was added).
+ *
+ * However, this _can be detected_: it means that cow_format (always 0 until
+ * now) is shifted onto the first 4 bytes of backing_file, where it is otherwise
+ * impossible to find 4 zeros. -bb */
+
+struct cow_header_v3 {
+ __u32 magic;
+ __u32 version;
+ __u32 mtime;
+ __u64 size;
+ __u32 sectorsize;
+ __u32 alignment;
+ __u32 cow_format;
+ char backing_file[PATH_LEN_V3];
+} __attribute__((packed));
+
+/* This is the broken layout used by some 64-bit binaries. */
+struct cow_header_v3_broken {
+ __u32 magic;
+ __u32 version;
+ __s64 mtime;
+ __u64 size;
+ __u32 sectorsize;
+ __u32 alignment;
+ __u32 cow_format;
+ char backing_file[PATH_LEN_V3];
+};
+
+/* COW format definitions - for now, we have only the usual COW bitmap */
+#define COW_BITMAP 0
+
+union cow_header {
+ struct cow_header_v1 v1;
+ struct cow_header_v2 v2;
+ struct cow_header_v3 v3;
+ struct cow_header_v3_broken v3_b;
+};
+
+#define COW_MAGIC 0x4f4f4f4d /* MOOO */
+#define COW_VERSION 3
+
+#define DIV_ROUND(x, len) (((x) + (len) - 1) / (len))
+#define ROUND_UP(x, align) DIV_ROUND(x, align) * (align)
+
+void cow_sizes(int version, __u64 size, int sectorsize, int align,
+ int bitmap_offset, unsigned long *bitmap_len_out,
+ int *data_offset_out)
+{
+ if (version < 3) {
+ *bitmap_len_out = (size + sectorsize - 1) / (8 * sectorsize);
+
+ *data_offset_out = bitmap_offset + *bitmap_len_out;
+ *data_offset_out = (*data_offset_out + sectorsize - 1) /
+ sectorsize;
+ *data_offset_out *= sectorsize;
+ }
+ else {
+ *bitmap_len_out = DIV_ROUND(size, sectorsize);
+ *bitmap_len_out = DIV_ROUND(*bitmap_len_out, 8);
+
+ *data_offset_out = bitmap_offset + *bitmap_len_out;
+ *data_offset_out = ROUND_UP(*data_offset_out, align);
+ }
+}
+
+static int absolutize(char *to, int size, char *from)
+{
+ char save_cwd[256], *slash;
+ int remaining;
+
+ if (getcwd(save_cwd, sizeof(save_cwd)) == NULL) {
+ cow_printf("absolutize : unable to get cwd - errno = %d\n",
+ errno);
+ return -1;
+ }
+ slash = strrchr(from, '/');
+ if (slash != NULL) {
+ *slash = '\0';
+ if (chdir(from)) {
+ *slash = '/';
+ cow_printf("absolutize : Can't cd to '%s' - "
+ "errno = %d\n", from, errno);
+ return -1;
+ }
+ *slash = '/';
+ if (getcwd(to, size) == NULL) {
+ cow_printf("absolutize : unable to get cwd of '%s' - "
+ "errno = %d\n", from, errno);
+ return -1;
+ }
+ remaining = size - strlen(to);
+ if (strlen(slash) + 1 > remaining) {
+ cow_printf("absolutize : unable to fit '%s' into %d "
+ "chars\n", from, size);
+ return -1;
+ }
+ strcat(to, slash);
+ }
+ else {
+ if (strlen(save_cwd) + 1 + strlen(from) + 1 > size) {
+ cow_printf("absolutize : unable to fit '%s' into %d "
+ "chars\n", from, size);
+ return -1;
+ }
+ strcpy(to, save_cwd);
+ strcat(to, "/");
+ strcat(to, from);
+ }
+ if (chdir(save_cwd)) {
+ cow_printf("absolutize : Can't cd to '%s' - "
+ "errno = %d\n", save_cwd, errno);
+ return -1;
+ }
+ return 0;
+}
+
+int write_cow_header(char *cow_file, int fd, char *backing_file,
+ int sectorsize, int alignment, unsigned long long *size)
+{
+ struct cow_header_v3 *header;
+ unsigned long modtime;
+ int err;
+
+ err = cow_seek_file(fd, 0);
+ if (err < 0) {
+ cow_printf("write_cow_header - lseek failed, err = %d\n", -err);
+ goto out;
+ }
+
+ err = -ENOMEM;
+ header = cow_malloc(sizeof(*header));
+ if (header == NULL) {
+ cow_printf("write_cow_header - failed to allocate COW V3 "
+ "header\n");
+ goto out;
+ }
+ header->magic = htobe32(COW_MAGIC);
+ header->version = htobe32(COW_VERSION);
+
+ err = -EINVAL;
+ if (strlen(backing_file) > sizeof(header->backing_file) - 1) {
+ /* Below, %zd is for a size_t value */
+ cow_printf("Backing file name \"%s\" is too long - names are "
+ "limited to %zd characters\n", backing_file,
+ sizeof(header->backing_file) - 1);
+ goto out_free;
+ }
+
+ if (absolutize(header->backing_file, sizeof(header->backing_file),
+ backing_file))
+ goto out_free;
+
+ err = os_file_modtime(header->backing_file, &modtime);
+ if (err < 0) {
+ cow_printf("write_cow_header - backing file '%s' mtime "
+ "request failed, err = %d\n", header->backing_file,
+ -err);
+ goto out_free;
+ }
+
+ err = cow_file_size(header->backing_file, size);
+ if (err < 0) {
+ cow_printf("write_cow_header - couldn't get size of "
+ "backing file '%s', err = %d\n",
+ header->backing_file, -err);
+ goto out_free;
+ }
+
+ header->mtime = htobe32(modtime);
+ header->size = htobe64(*size);
+ header->sectorsize = htobe32(sectorsize);
+ header->alignment = htobe32(alignment);
+ header->cow_format = COW_BITMAP;
+
+ err = cow_write_file(fd, header, sizeof(*header));
+ if (err != sizeof(*header)) {
+ cow_printf("write_cow_header - write of header to "
+ "new COW file '%s' failed, err = %d\n", cow_file,
+ -err);
+ goto out_free;
+ }
+ err = 0;
+ out_free:
+ cow_free(header);
+ out:
+ return err;
+}
+
+int file_reader(__u64 offset, char *buf, int len, void *arg)
+{
+ int fd = *((int *) arg);
+
+ return pread(fd, buf, len, offset);
+}
+
+/* XXX Need to sanity-check the values read from the header */
+
+int read_cow_header(int (*reader)(__u64, char *, int, void *), void *arg,
+ __u32 *version_out, char **backing_file_out,
+ time_t *mtime_out, unsigned long long *size_out,
+ int *sectorsize_out, __u32 *align_out,
+ int *bitmap_offset_out)
+{
+ union cow_header *header;
+ char *file;
+ int err, n;
+ unsigned long version, magic;
+
+ header = cow_malloc(sizeof(*header));
+ if (header == NULL) {
+ cow_printf("read_cow_header - Failed to allocate header\n");
+ return -ENOMEM;
+ }
+ err = -EINVAL;
+ n = (*reader)(0, (char *) header, sizeof(*header), arg);
+ if (n < offsetof(typeof(header->v1), backing_file)) {
+ cow_printf("read_cow_header - short header\n");
+ goto out;
+ }
+
+ magic = header->v1.magic;
+ if (magic == COW_MAGIC)
+ version = header->v1.version;
+ else if (magic == be32toh(COW_MAGIC))
+ version = be32toh(header->v1.version);
+ /* No error printed because the non-COW case comes through here */
+ else goto out;
+
+ *version_out = version;
+
+ if (version == 1) {
+ if (n < sizeof(header->v1)) {
+ cow_printf("read_cow_header - failed to read V1 "
+ "header\n");
+ goto out;
+ }
+ *mtime_out = header->v1.mtime;
+ *size_out = header->v1.size;
+ *sectorsize_out = header->v1.sectorsize;
+ *bitmap_offset_out = sizeof(header->v1);
+ *align_out = *sectorsize_out;
+ file = header->v1.backing_file;
+ }
+ else if (version == 2) {
+ if (n < sizeof(header->v2)) {
+ cow_printf("read_cow_header - failed to read V2 "
+ "header\n");
+ goto out;
+ }
+ *mtime_out = be32toh(header->v2.mtime);
+ *size_out = be64toh(header->v2.size);
+ *sectorsize_out = be32toh(header->v2.sectorsize);
+ *bitmap_offset_out = sizeof(header->v2);
+ *align_out = *sectorsize_out;
+ file = header->v2.backing_file;
+ }
+ /* This is very subtle - see above at union cow_header definition */
+ else if (version == 3 && (*((int*)header->v3.backing_file) != 0)) {
+ if (n < sizeof(header->v3)) {
+ cow_printf("read_cow_header - failed to read V3 "
+ "header\n");
+ goto out;
+ }
+ *mtime_out = be32toh(header->v3.mtime);
+ *size_out = be64toh(header->v3.size);
+ *sectorsize_out = be32toh(header->v3.sectorsize);
+ *align_out = be32toh(header->v3.alignment);
+ if (*align_out == 0) {
+ cow_printf("read_cow_header - invalid COW header, "
+ "align == 0\n");
+ }
+ *bitmap_offset_out = ROUND_UP(sizeof(header->v3), *align_out);
+ file = header->v3.backing_file;
+ }
+ else if (version == 3) {
+ cow_printf("read_cow_header - broken V3 file with"
+ " 64-bit layout - recovering content.\n");
+
+ if (n < sizeof(header->v3_b)) {
+ cow_printf("read_cow_header - failed to read V3 "
+ "header\n");
+ goto out;
+ }
+
+ /*
+ * this was used until Dec2005 - 64bits are needed to represent
+ * 2038+. I.e. we can safely do this truncating cast.
+ *
+ * Additionally, we must use be32toh() instead of be64toh(), since
+ * the program used to use the former (tested - I got mtime
+ * mismatch "0 vs whatever").
+ *
+ * Ever heard about bug-to-bug-compatibility ? ;-) */
+ *mtime_out = (time32_t) be32toh(header->v3_b.mtime);
+
+ *size_out = be64toh(header->v3_b.size);
+ *sectorsize_out = be32toh(header->v3_b.sectorsize);
+ *align_out = be32toh(header->v3_b.alignment);
+ if (*align_out == 0) {
+ cow_printf("read_cow_header - invalid COW header, "
+ "align == 0\n");
+ }
+ *bitmap_offset_out = ROUND_UP(sizeof(header->v3_b), *align_out);
+ file = header->v3_b.backing_file;
+ }
+ else {
+ cow_printf("read_cow_header - invalid COW version\n");
+ goto out;
+ }
+ err = -ENOMEM;
+ *backing_file_out = cow_strdup(file);
+ if (*backing_file_out == NULL) {
+ cow_printf("read_cow_header - failed to allocate backing "
+ "file\n");
+ goto out;
+ }
+ err = 0;
+ out:
+ cow_free(header);
+ return err;
+}
+
+int init_cow_file(int fd, char *cow_file, char *backing_file, int sectorsize,
+ int alignment, int *bitmap_offset_out,
+ unsigned long *bitmap_len_out, int *data_offset_out)
+{
+ unsigned long long size, offset;
+ char zero = 0;
+ int err;
+
+ err = write_cow_header(cow_file, fd, backing_file, sectorsize,
+ alignment, &size);
+ if (err)
+ goto out;
+
+ *bitmap_offset_out = ROUND_UP(sizeof(struct cow_header_v3), alignment);
+ cow_sizes(COW_VERSION, size, sectorsize, alignment, *bitmap_offset_out,
+ bitmap_len_out, data_offset_out);
+
+ offset = *data_offset_out + size - sizeof(zero);
+ err = cow_seek_file(fd, offset);
+ if (err < 0) {
+ cow_printf("cow bitmap lseek failed : err = %d\n", -err);
+ goto out;
+ }
+
+ /*
+ * does not really matter how much we write it is just to set EOF
+ * this also sets the entire COW bitmap
+ * to zero without having to allocate it
+ */
+ err = cow_write_file(fd, &zero, sizeof(zero));
+ if (err != sizeof(zero)) {
+ cow_printf("Write of bitmap to new COW file '%s' failed, "
+ "err = %d\n", cow_file, -err);
+ if (err >= 0)
+ err = -EINVAL;
+ goto out;
+ }
+
+ return 0;
+ out:
+ return err;
+}