/**
 * Copyright © 2019, Oracle and/or its affiliates. All rights reserved.
 *
 * Licensed under the Universal Permissive License v 1.0 as shown at
 * http://oss.oracle.com/licenses/upl
 *
 */
#define _GNU_SOURCE
#include "putils.h"
#include "malloc.h"
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <ftw.h>
#include "common/spew.h"

FILE* do_creat(const char *fullname) {
    int fd = open(fullname, O_CREAT | O_WRONLY | O_TRUNC, S_IWUSR);
    if (fd == -1) {
        if (errno == EEXIST || errno == EISDIR) {
            return NULL;
        }
        fprintf(stderr, "Trying to open %s:\n", fullname);
        perror("open");
        spew(SPEW_ERROR, "Create of %s failed errno=%d (%s)",
             fullname, errno, strerror(errno));
        abort();
    }
    assert(fd >= 0);
    FILE *f = fdopen(fd, "w");
    if (!f) {
        perror("fdopen");
        spew(SPEW_ERROR, "fdopen of %s for write failed errno=%d (%s)",
             fullname, errno, strerror(errno));
    }
    assert(f);
    return f;
}

FILE* do_open_for_read(const char *fullname) {
    int fd = open(fullname, O_RDONLY);
    if (fd == -1) {
        fprintf(stderr, "Trying to open %s:\n", fullname);
        perror("open");
        spew(SPEW_ERROR, "Open of %s failed errno=%d (%s)",
             fullname, errno, strerror(errno));
        abort();
    }
    assert(fd >= 0);
    FILE *f = fdopen(fd, "r");
    if (!f) {
        perror("fdopen");
        spew(SPEW_ERROR, "fdopen of %s for read failed errno=%d (%s)",
             fullname, errno, strerror(errno));
    }
    assert(f);
    return f;
}


void do_fclose(FILE *f) {
    int r = fclose(f);
    if (r != 0) {
        perror("fclose");
        spew(SPEW_ERROR, "fclose %p failed errno=%d (%s)",
             f, errno, strerror(errno));
    }
    assert(r == 0);
}

void do_write(FILE *f, const char *bytes, size_t size) {
    size_t r = fwrite(bytes, 1, size, f);
    if (r != size) {
        perror("fwrite");
        spew(SPEW_ERROR, "fwrite failed errno=%d (%s)",
             errno, strerror(errno));
    }
    assert(r == size);
}

size_t do_read(FILE *f, char *bytes, size_t size) {
    // We might be reading from a socket or a pipe, so keep reading till we get all the bytes.
    size_t r = fread(bytes, 1, size, f);
    if (r == 0) {
        if (feof(f)) {
            return 0;
        }
        assert(ferror(f));
        fprintf(stderr, "do_read got error %d\n", ferror(f));
        spew(SPEW_ERROR, "fread failed errno=%d (%s)",
             errno, strerror(errno));
        assert(0);
    }
    if (r < size) {
        return r + do_read(f, bytes+r, size-r);
    } else {
        return r;
    }
}

int do_mkdir(const char *fullname) {
    if (0) fprintf(stderr, "mkdir(\"%s\");\n", fullname);
    int r = mkdir(fullname, S_IRUSR|S_IWUSR|S_IXUSR);
    if (r != 0) {
        if (errno == EEXIST) {
            return r;
        }
        // We ensure that the parent exists before we call do_mkdir,
        // so ENOENT is a real error.
        fprintf(stderr, "Trying to mkdir %s\n", fullname);
        perror("mkdir");
        spew(SPEW_ERROR, "mkdir %s failed errno=%d (%s)",
             fullname, errno, strerror(errno));
        abort();
    }
    assert(r == 0);
    return 0; //returns Success
}

int do_mkfifo(const char *fullname, mode_t mode) {
    int r = mkfifo(fullname, mode);
    if (r) {
        if (errno == EEXIST) {
            return r;
        }
        fprintf(stderr, "Trying to mkfifo(%s, %d)\n", fullname, mode);
        perror("mkfifo");
        spew(SPEW_ERROR, "mkfifo %s failed errno=%d (%s)",
             fullname, errno, strerror(errno));
        abort();
    }
    return 0; //returns Success
}

int do_link(const char *linkto, const char *fullname)
// Effect create a hardlink.  If the file already exists delete it and then
// link.
{
    int r = link(linkto, fullname);
    if (r && errno == EEXIST) {
        return r;
    }
    if (r) {
        fprintf(stderr, "Trying to link \"%s\" --> \"%s\"\nerrno=%d\n", fullname, linkto, errno);
        perror("link");
        spew(SPEW_ERROR, "linking \"%s\" --> \"%s\" failed errno=%d (%s)",
             fullname, linkto, errno, strerror(errno));
        abort();
    }
    return 0; //returns Success
}


int do_symlink(const char *contents, const char *fullname) {
    int r = symlink(contents, fullname);
    return r;
}

void do_unlink(const char *fullname) {
    int r = unlink(fullname);
    assert(r == 0);
}
void do_rmdir(const char *fullname) {
    int r = rmdir(fullname);
    assert(r == 0);
}

/*
 * This function is callback for nftw call that is made
 * with the intention of deleting conflicts. The function
 * deletes file or directory provided in the argument 'path'
 */
int delete_subtree(const char *path, const struct stat *st, int typeflag, struct FTW *d_ftw) {
    int ret = 1;
    if (typeflag == 4 || d_ftw == NULL) {
        return ret;
    }
    if (S_ISDIR(st->st_mode)) {
        ret = rmdir(path);
    } else {
        ret = unlink(path);
    }
    return ret;
}

/* Recursively deletes files/directories in directory path passed */
void delete_dir_recursively(const char *path) {
    int ret = nftw(path, delete_subtree, 10, FTW_DEPTH);
    if (ret != 0) {
        spew(SPEW_ERROR, "Unable to delete subtree for %s. Aborting\n", path);
        abort();
    }
} 


char *pathcat(const char *a, const char *b) {
    char *result = NULL;
    int r = asprintf(&result, "%s/%s", a, b);
    assert(r > 0);
    return result;
}

size_t parse_number_or_help(const char *numstring) {
    errno = 0;
    char *end;
    size_t result = strtoul(numstring, &end, 0);
    if (0
        || errno != 0
        || *end != 0)
    {
        fprintf(stderr, "Trying to parse %s as a number\n", numstring);
        spew(SPEW_DEBUG, "Trying to parse %s as a number", numstring);
        help(1);
        return 0;
    }
    return result;
}

void
maybe_error(int r, const char *fun, const char *fname, const char *srcfile, int line) {
    if (r != 0) {
        fprintf(stderr, "%s:%d Could not %s(\"%s\").  errno=%d (%s)\n",
                srcfile, line, fun, fname, errno, strerror(errno));
        spew(SPEW_ERROR, "Could not %s(\"%s\").  errno=%d (%s)",
             fun, fname, errno, strerror(errno));
        exit(1);
    }
}

void
maybe_error2(int r, const char *fun, const char *fname, const char *fname2, const char *srcfile, int line) {
    if (r != 0) {
        fprintf(stderr, "%s:%d Could not %s(\"%s\", \"%s\").  errno=%d (%s)\n",
                srcfile, line,
                fun, fname, fname2, errno, strerror(errno));
        spew(SPEW_ERROR, "Could not %s(\"%s\", \"%s\").  errno=%d (%s)",
             fun, fname, fname2, errno, strerror(errno));
        exit(1);
    }
}
