// SPDX-License-Identifier: Apache-2.0

#define _LARGEFILE64_SOURCE

#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <unistd.h>
#include <iostream>
#include <string>

#define SECTOR_SIZE  ((__u64)512)
#define BUFFER_BYTES 4096

#define MAX(a, b)    ((a) > (b) ? (a) : (b))

/* This should be replaced with linux/dm-user.h. */
#ifndef _LINUX_DM_USER_H
#define _LINUX_DM_USER_H

#include <linux/types.h>

#define DM_USER_REQ_MAP_READ                    0
#define DM_USER_REQ_MAP_WRITE                   1
#define DM_USER_REQ_MAP_FLUSH                   2
#define DM_USER_REQ_MAP_DISCARD                 3
#define DM_USER_REQ_MAP_SECURE_ERASE            4
#define DM_USER_REQ_MAP_WRITE_SAME              5
#define DM_USER_REQ_MAP_WRITE_ZEROES            6
#define DM_USER_REQ_MAP_ZONE_OPEN               7
#define DM_USER_REQ_MAP_ZONE_CLOSE              8
#define DM_USER_REQ_MAP_ZONE_FINISH             9
#define DM_USER_REQ_MAP_ZONE_APPEND             10
#define DM_USER_REQ_MAP_ZONE_RESET              11
#define DM_USER_REQ_MAP_ZONE_RESET_ALL          12

#define DM_USER_REQ_MAP_FLAG_FAILFAST_DEV       0x00001
#define DM_USER_REQ_MAP_FLAG_FAILFAST_TRANSPORT 0x00002
#define DM_USER_REQ_MAP_FLAG_FAILFAST_DRIVER    0x00004
#define DM_USER_REQ_MAP_FLAG_SYNC               0x00008
#define DM_USER_REQ_MAP_FLAG_META               0x00010
#define DM_USER_REQ_MAP_FLAG_PRIO               0x00020
#define DM_USER_REQ_MAP_FLAG_NOMERGE            0x00040
#define DM_USER_REQ_MAP_FLAG_IDLE               0x00080
#define DM_USER_REQ_MAP_FLAG_INTEGRITY          0x00100
#define DM_USER_REQ_MAP_FLAG_FUA                0x00200
#define DM_USER_REQ_MAP_FLAG_PREFLUSH           0x00400
#define DM_USER_REQ_MAP_FLAG_RAHEAD             0x00800
#define DM_USER_REQ_MAP_FLAG_BACKGROUND         0x01000
#define DM_USER_REQ_MAP_FLAG_NOWAIT             0x02000
#define DM_USER_REQ_MAP_FLAG_CGROUP_PUNT        0x04000
#define DM_USER_REQ_MAP_FLAG_NOUNMAP            0x08000
#define DM_USER_REQ_MAP_FLAG_HIPRI              0x10000
#define DM_USER_REQ_MAP_FLAG_DRV                0x20000
#define DM_USER_REQ_MAP_FLAG_SWAP               0x40000

#define DM_USER_RESP_SUCCESS                    0
#define DM_USER_RESP_ERROR                      1
#define DM_USER_RESP_UNSUPPORTED                2

struct dm_user_message {
    __u64 seq;
    __u64 type;
    __u64 flags;
    __u64 sector;
    __u64 len;
    __u8 buf[];
};

#endif

static bool verbose = false;

ssize_t write_all(int fd, void* buf, size_t len) {
    char* buf_c = (char*)buf;
    ssize_t total = 0;
    ssize_t once;

    while (total < static_cast<ssize_t>(len)) {
        once = write(fd, buf_c + total, len - total);
        if (once < 0) return once;
        if (once == 0) {
            errno = ENOSPC;
            return 0;
        }
        total += once;
    }

    return total;
}

ssize_t read_all(int fd, void* buf, size_t len) {
    char* buf_c = (char*)buf;
    ssize_t total = 0;
    ssize_t once;

    while (total < static_cast<ssize_t>(len)) {
        once = read(fd, buf_c + total, len - total);
        if (once < 0) return once;
        if (once == 0) {
            errno = ENOSPC;
            return 0;
        }
        total += once;
    }

    return total;
}

int not_splice(int from, int to, __u64 count) {
    while (count > 0) {
        char buf[BUFFER_BYTES];
        __u64 max = count > BUFFER_BYTES ? BUFFER_BYTES : count;

        if (read_all(from, buf, max) <= 0) {
            perror("Unable to read");
            return -EIO;
        }

        if (write_all(to, buf, max) <= 0) {
            perror("Unable to write");
            return -EIO;
        }

        count -= max;
    }

    return 0;
}

static int simple_daemon(const std::string& control_path, const std::string& backing_path) {
    int control_fd = open(control_path.c_str(), O_RDWR);
    if (control_fd < 0) {
        fprintf(stderr, "Unable to open control device %s\n", control_path.c_str());
        return -1;
    }

    int backing_fd = open(backing_path.c_str(), O_RDWR);
    if (backing_fd < 0) {
        fprintf(stderr, "Unable to open backing device %s\n", backing_path.c_str());
        return -1;
    }

    while (1) {
        struct dm_user_message msg;
        char* base;
        __u64 type;

        if (verbose) std::cerr << "dmuserd: Waiting for message...\n";

        if (read_all(control_fd, &msg, sizeof(msg)) < 0) {
            if (errno == ENOTBLK) return 0;

            perror("unable to read msg");
            return -1;
        }

        if (verbose) {
            std::string type;
            switch (msg.type) {
                case DM_USER_REQ_MAP_WRITE:
                    type = "write";
                    break;
                case DM_USER_REQ_MAP_READ:
                    type = "read";
                    break;
                case DM_USER_REQ_MAP_FLUSH:
                    type = "flush";
                    break;
                default:
                    /*
                     * FIXME: Can't I do "whatever"s here rather that
                     * std::string("whatever")?
                     */
                    type = std::string("(unknown, id=") + std::to_string(msg.type) + ")";
                    break;
            }

            std::string flags;
            if (msg.flags & DM_USER_REQ_MAP_FLAG_SYNC) {
                if (!flags.empty()) flags += "|";
                flags += "S";
            }
            if (msg.flags & DM_USER_REQ_MAP_FLAG_META) {
                if (!flags.empty()) flags += "|";
                flags += "M";
            }
            if (msg.flags & DM_USER_REQ_MAP_FLAG_FUA) {
                if (!flags.empty()) flags += "|";
                flags += "FUA";
            }
            if (msg.flags & DM_USER_REQ_MAP_FLAG_PREFLUSH) {
                if (!flags.empty()) flags += "|";
                flags += "F";
            }

            std::cerr << "dmuserd: Got " << type << " request " << flags << " for sector "
                      << std::to_string(msg.sector) << " with length " << std::to_string(msg.len)
                      << "\n";
        }

        type = msg.type;
        switch (type) {
            case DM_USER_REQ_MAP_READ:
                msg.type = DM_USER_RESP_SUCCESS;
                break;
            case DM_USER_REQ_MAP_WRITE:
                if (msg.flags & DM_USER_REQ_MAP_FLAG_PREFLUSH ||
                    msg.flags & DM_USER_REQ_MAP_FLAG_FUA) {
                    if (fsync(backing_fd) < 0) {
                        perror("Unable to fsync(), just sync()ing instead");
                        sync();
                    }
                }
                msg.type = DM_USER_RESP_SUCCESS;
                if (lseek64(backing_fd, msg.sector * SECTOR_SIZE, SEEK_SET) < 0) {
                    perror("Unable to seek");
                    return -1;
                }
                if (not_splice(control_fd, backing_fd, msg.len) < 0) {
                    if (errno == ENOTBLK) return 0;
                    std::cerr << "unable to handle write data\n";
                    return -1;
                }
                if (msg.flags & DM_USER_REQ_MAP_FLAG_FUA) {
                    if (fsync(backing_fd) < 0) {
                        perror("Unable to fsync(), just sync()ing instead");
                        sync();
                    }
                }
                break;
            case DM_USER_REQ_MAP_FLUSH:
                msg.type = DM_USER_RESP_SUCCESS;
                if (fsync(backing_fd) < 0) {
                    perror("Unable to fsync(), just sync()ing instead");
                    sync();
                }
                break;
            default:
                std::cerr << "dmuserd: unsupported op " << std::to_string(msg.type) << "\n";
                msg.type = DM_USER_RESP_UNSUPPORTED;
                break;
        }

        if (verbose) std::cerr << "dmuserd: Responding to message\n";

        if (write_all(control_fd, &msg, sizeof(msg)) < 0) {
            if (errno == ENOTBLK) return 0;
            perror("unable to write msg");
            return -1;
        }

        switch (type) {
            case DM_USER_REQ_MAP_READ:
                if (verbose) std::cerr << "dmuserd: Sending read data\n";
                if (lseek64(backing_fd, msg.sector * SECTOR_SIZE, SEEK_SET) < 0) {
                    perror("Unable to seek");
                    return -1;
                }
                if (not_splice(backing_fd, control_fd, msg.len) < 0) {
                    if (errno == ENOTBLK) return 0;
                    std::cerr << "unable to handle read data\n";
                    return -1;
                }
                break;
        }
    }

    /* The daemon doesn't actully terminate for this test. */
    perror("Unable to read from control device");
    return -1;
}

void usage(char* prog) {
    printf("Usage: %s\n", prog);
    printf("	Handles block requests in userspace, backed by memory\n");
    printf("  -h			Display this help message\n");
    printf("  -c <control dev>		Control device to use for the test\n");
    printf("  -b <store path>		The file to use as a backing store, otherwise memory\n");
    printf("  -v                        Enable verbose mode\n");
}

int main(int argc, char* argv[]) {
    std::string control_path;
    std::string backing_path;
    char* store;
    int c;

    prctl(PR_SET_IO_FLUSHER, 0, 0, 0, 0);

    while ((c = getopt(argc, argv, "h:c:s:b:v")) != -1) {
        switch (c) {
            case 'h':
                usage(basename(argv[0]));
                exit(0);
            case 'c':
                control_path = optarg;
                break;
            case 'b':
                backing_path = optarg;
                break;
            case 'v':
                verbose = true;
                break;
            default:
                usage(basename(argv[0]));
                exit(1);
        }
    }

    int r = simple_daemon(control_path, backing_path);
    if (r) fprintf(stderr, "simple_daemon() errored out\n");
    return r;
}
