#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/xattr.h>
#include <errno.h>

#include "bit-rot-object-version.h"

/* NOTE: no size discovery */
int
brstub_validate_version(char *bpath, unsigned long version)
{
    int ret = 0;
    int match = 0;
    size_t xsize = 0;
    br_version_t *xv = NULL;

    xsize = sizeof(br_version_t);

    xv = calloc(1, xsize);
    if (!xv) {
        match = -1;
        goto err;
    }

    ret = getxattr(bpath, "trusted.bit-rot.version", xv, xsize);
    if (ret < 0) {
        if (errno == ENODATA)
            match = -2;
        goto err;
    }

    if (xv->ongoingversion != version) {
        match = -3;
        fprintf(stderr, "ongoingversion: %lu\n", xv->ongoingversion);
    }
    free(xv);

err:
    return match;
}

int
brstub_write_validation(char *filp, char *bpath, unsigned long startversion)
{
    int fd1 = 0;
    int fd2 = 0;
    int ret = 0;
    char *string = "string\n";

    /* read only check */
    fd1 = open(filp, O_RDONLY);
    if (fd1 < 0)
        goto err;
    close(fd1);

    ret = brstub_validate_version(bpath, startversion);
    if (ret != -2)
        goto err;

    /* single open (write/) check */
    fd1 = open(filp, O_RDWR);
    if (fd1 < 0)
        goto err;

    ret = write(fd1, string, strlen(string));
    if (ret <= 0)
        goto err;
    /**
     * Fsync is done so that the write call has properly reached the
     * disk. For fuse mounts write-behind xlator would have held the
     * writes with itself and for nfs, client would have held the
     * write in its cache. So write fop would not have triggered the
     * versioning as it would have not reached the bit-rot-stub.
     */
    fsync(fd1);
    ret = brstub_validate_version(bpath, startversion);
    if (ret != 0)
        goto err;
    ret = write(fd1, string, strlen(string));
    if (ret <= 0)
        goto err;
    fsync(fd1); /* let it reach the disk */

    ret = brstub_validate_version(bpath, startversion);
    if (ret != 0)
        goto err;

    close(fd1);

    /**
     * Well, this is not a _real_ test per se . For this test to pass
     * the inode should not get a forget() in the interim. Therefore,
     * perform this test asap.
     */

    /* multi open (write/) check */
    fd1 = open(filp, O_RDWR);
    if (fd1 < 0)
        goto err;
    fd2 = open(filp, O_WRONLY);
    if (fd1 < 0)
        goto err;

    ret = write(fd1, string, strlen(string));
    if (ret <= 0)
        goto err;

    ret = write(fd2, string, strlen(string));
    if (ret <= 0)
        goto err;

    /* probably do a syncfs() */
    fsync(fd1);
    fsync(fd2);

    close(fd1);
    close(fd2);

    /**
     * incremented once per write()/write().../close()/close() sequence
     */
    ret = brstub_validate_version(bpath, startversion);
    if (ret != 0)
        goto err;

    return 0;

err:
    return -1;
}

int
brstub_new_object_validate(char *filp, char *brick)
{
    int ret = 0;
    char *fname = NULL;
    char bpath[PATH_MAX] = {
        0,
    };

    fname = basename(filp);
    if (!fname)
        goto err;

    (void)snprintf(bpath, PATH_MAX, "%s/%s", brick, fname);

    printf("Validating initial version..\n");
    ret = brstub_validate_version(bpath, 2);
    if (ret != -2) /* version _should_ be missing */
        goto err;

    printf("Validating version on modifications..\n");
    ret = brstub_write_validation(filp, bpath, 2);
    if (ret < 0)
        goto err;

    return 0;

err:
    return -1;
}

int
main(int argc, char **argv)
{
    int ret = 0;
    char *filp = NULL;
    char *brick = NULL;

    if (argc != 3) {
        printf("Usage: %s <path> <brick>\n", argv[0]);
        goto err;
    }

    filp = argv[1];
    brick = argv[2];

    printf("Validating object version [%s]\n", filp);
    ret = brstub_new_object_validate(filp, brick);
    if (ret < 0)
        goto err;

    return 0;

err:
    return -1;
}
