/*
 * Created by jiaqiang.ye@lnxall.com
 *
 * MHMP protocol implmentation
 *
 * 2022/03/29
 */

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <unistd.h>

#include <time.h>
#include <poll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/uio.h>
#include <sys/stat.h>
#include <signal.h>

#include "mhmp.h"

/* dump MHMP messages */
int mhmp_verbose;

const char * const MHMP_GETREALTIMEDATA      = "getRealTimeData";
const char * const MHMP_GETHISTORYDATA       = "getHistoryData";
const char * const MHMP_GETWARNINGINFO       = "getWarningInfo";
const char * const MHMP_GETHISTORYWARNDATA   = "getHistoryWarnData";
const char * const MHMP_KEEPALIVE            = "keepAlive";
const char * const MHMP_PUSHWARNINGINFO      = "pushWarnInfo";
const char * const MHMP_ALL                  = "all";

static int mhmp_create_socket(struct mhmp_var * mvar)
{
    int sockfd;

    if (mvar->sockfd != -1) {
        close(mvar->sockfd);
        mvar->sockfd = -1;
    }

    sockfd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
    if (sockfd == -1) {
        fprintf(stderr, "Error, failed to create TCP socket: %s\n",
            strerror(errno));
        fflush(stderr);
        return -1;
    }
    mvar->sockfd = sockfd;
    return 0;
}

static unsigned int mhmp_msgid(struct mhmp_var * mvar)
{
    unsigned int rval;
    rval = __atomic_add_fetch(&mvar->msgid, 0x1u, __ATOMIC_SEQ_CST);
    if (__builtin_expect(rval >= 0x7FFFFFFF, 0)) {
        rval = 1;
        __atomic_store_n(&mvar->msgid, 0x1u, __ATOMIC_SEQ_CST);
    }
    return rval;
}

int mhmp_get_time(char * tstr, size_t tlen, time_t * pnowt, int hasslh)
{
    int idx;
    time_t nowt;
    struct tm tim, * ptm;

    nowt = *pnowt;
    if (nowt == 0)
        time(&nowt);
    
    memset(&tim, 0, sizeof(tim));
    ptm = localtime_r(&nowt, &tim);
    if (ptm == NULL)
        ptm = &tim;

    if (hasslh) {
        idx = snprintf(tstr, tlen, "%ld/%d-%02d-%02d %02d:%02d:%02d",
            (long) nowt, ptm->tm_year + 1900, ptm->tm_mon + 1,
            ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
    } else {
        idx = snprintf(tstr, tlen, "%d-%02d-%02d %02d:%02d:%02d",
            ptm->tm_year + 1900, ptm->tm_mon + 1,
            ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
    }
    if (idx >= (int) tlen) {
        idx = (int) (tlen - 1);
    } else if (idx < 0)
        idx = 0;

    tstr[idx] = '\0';
    *pnowt = nowt;
    return idx;
}

int mhmp_write_last_timetag(const char * tagfile, int overwrite)
{
    time_t nowt;
    int pfd, len;
    char timestr[64];
    if (tagfile == NULL)
        tagfile = TCP_MHMP_TIME_TAGFILE;

    if (!overwrite && access(tagfile, R_OK) == 0)
        return 0;

    pfd = open(tagfile, O_TRUNC | O_CREAT | O_WRONLY | O_CLOEXEC, 0644);
    if (pfd == -1) {
        fprintf(stderr, "Error, failed to open(%s): %s\n",
            tagfile, strerror(errno));
        fflush(stderr);
        return -1;
    }

    nowt = 0;
    len = mhmp_get_time(timestr, sizeof(timestr), &nowt, 1);
    if (len > 0 && len != (int) write(pfd, timestr, (size_t) len)) {
        fprintf(stderr, "Error, failed to write last timetag: %s\n",
            strerror(errno));
        fflush(stderr);
        close(pfd);
        return -1;
    }
    close(pfd);
    return 1;
}

#define MHMP_REQUST_BUFSIZ  4096
static int mhmp_history_request(struct mhmp_var * mvar,
    const char * cmdptr, const char * egwIP, const char * deviceID,
    const char * start_time, const char * end_time)
{
    char * reqbuf;
    int reqlen, ret;
    unsigned int msgid;

    ret = -1;
    reqbuf = (char *) calloc(0x1, MHMP_REQUST_BUFSIZ);
    if (reqbuf == NULL) {
        fputs("Error, system out of memory!\n", stderr);
        fflush(stderr);
        return -1;
    }

    msgid = mhmp_msgid(mvar);
    reqlen = snprintf(reqbuf, MHMP_REQUST_BUFSIZ - 1,
        "{\n\t\"cmd\":\"%s\",\n"
        "\t\"egwIP\":\"%s\",\n"
        "\t\"deviceID\":\"%s\",\n"
        "\t\"startTime\":\"%s\",\n"
        "\t\"endTime\":\"%s\",\n"
        "\t\"messageID\":\"%u\"\n}",
        cmdptr, egwIP, deviceID, start_time, end_time, msgid);
    if (reqlen > 0) {
        ret = mhmp_writemsg(mvar, reqbuf, reqlen);
        if (mhmp_verbose) {
            fprintf(stdout, "message sent: %d =>\n%s\n",
                ret, reqbuf);
            fflush(stdout);
        }
    } else
        ret = -1;
    free(reqbuf);
    return ret;
}

int mhmp_request_history(struct mhmp_var * mvar)
{
    int pfd, ret;
    ssize_t rl1;
    char timestr[64];
    time_t nowt, then, maxhist;
    char lasttime[64], * plsh;
    const char * max_history;
    const char * tagfile = TCP_MHMP_TIME_TAGFILE;

    pfd = open(tagfile, O_RDONLY | O_CLOEXEC);
    if (pfd == -1)
        return 0;

    maxhist = (time_t) TCP_MHMP_MAX_HIST;
    max_history = getenv("MHMP_MAX_HISTORY");
    if (max_history != NULL) {
        errno = 0;
        maxhist = (time_t) strtoul(max_history, NULL, 0);
        if (errno != 0 || maxhist == (time_t) ~0ul)
            maxhist = TCP_MHMP_MAX_HIST;
        fprintf(stdout, "MHMP maximum history in seconds: %ld\n",
            (long) maxhist);
        fflush(stdout);
    }

    rl1 = read(pfd, lasttime, sizeof(lasttime));
    if (rl1 <= 0 || rl1 >= sizeof(lasttime)) {
        close(pfd);
        unlink(tagfile);
        fprintf(stderr, "Error, read %s: %ld\n", tagfile, (long) rl1);
        fflush(stderr);
        return 0;
    }

    close(pfd); pfd = -1;

    lasttime[rl1] = '\0';
    plsh = strchr(lasttime, '/');
    if (plsh == NULL || plsh == lasttime) {
        fprintf(stderr, "Error, corrupted data: %s\n", lasttime);
        fflush(stderr);
        unlink(tagfile); /* remove the file */
        return 0;
    }

    *plsh++ = '\0';
    errno = 0;
    then = (time_t) strtoul(lasttime, NULL, 0);
    if (errno != 0 || then == (time_t) ~0ul) {
        fprintf(stderr, "Error, invalid MHMP last-time: %s\n", lasttime);
        fflush(stderr);
        unlink(tagfile); /* remove the file */
        return 0;
    }

    nowt = 0;
    mhmp_get_time(timestr, sizeof(timestr), &nowt, 0);
    if (nowt < then) {
        /* DO NOT remove: unlink(tagfile); */
        return 0;
    }

    if (nowt < (then + TCP_MHMP_REQUEST_INTERVAL)) {
        /* no need to request history data */
        unlink(tagfile);
        return 0;
    }

    /* OK, it is time to remove the tagfile */
    unlink(tagfile);
    if ((nowt - then) > maxhist) {
        then = nowt - maxhist;
        mhmp_get_time(lasttime, sizeof(lasttime), &then, 0);
        plsh = lasttime;
    }

    ret = mhmp_history_request(mvar, MHMP_GETHISTORYDATA,
        MHMP_ALL, MHMP_ALL, plsh, timestr);
    if (ret >= 0)
        ret = mhmp_history_request(mvar, MHMP_GETHISTORYWARNDATA,
            MHMP_ALL, MHMP_ALL, plsh, timestr);
    return ret;
}

static int mhmp_heartbeat(struct mhmp_var * mvar, unsigned int now)
{
    int dlen, ret;
    char hbdat[256];
    unsigned int then;
    unsigned int msgid;

    then = mvar->last_hb;
    if (now < (then + TCP_MHMP_HBINTERVAL))
        return 0;

    msgid = mhmp_msgid(mvar);
    dlen = snprintf(hbdat, sizeof(hbdat),
        "{\n\t\"cmd\":\"%s\",\n"
        "\t\"messageID\":\"%u\"\n}", MHMP_KEEPALIVE, msgid);
    if (dlen > 0) {
        ret = mhmp_writemsg(mvar, hbdat, dlen);
        if (mhmp_verbose) {
            fprintf(stdout, "message sent: %d =>\n%s\n", ret, hbdat);
            fflush(stdout);
        }
        if ((now - then) >= (TCP_MHMP_HBINTERVAL * 2)) {
            mvar->last_hb = now;
        } else {
            mvar->last_hb = then + TCP_MHMP_HBINTERVAL;
        }
        return (ret < 0) ? -1: 1;
    }
    return -1;
}

static int mhmp_send_request(struct mhmp_var * mvar,
    const char * cmdptr, const char * egwIP, const char * deviceID)
{
    char * reqbuf;
    int reqlen, ret;
    unsigned int msgid;

    ret = -1;
    reqbuf = (char *) calloc(0x1, MHMP_REQUST_BUFSIZ);
    if (reqbuf == NULL) {
        fputs("Error, system out of memory!\n", stderr);
        fflush(stderr);
        return -1;
    }

    msgid = mhmp_msgid(mvar);
    reqlen = snprintf(reqbuf, MHMP_REQUST_BUFSIZ - 1,
        "{\n\t\"cmd\":\"%s\",\n"
        "\t\"egwIP\":\"%s\",\n"
        "\t\"deviceID\":\"%s\",\n"
        "\t\"messageID\":\"%u\"\n}",
        cmdptr, egwIP, deviceID, msgid);
    if (reqlen > 0) {
        ret = mhmp_writemsg(mvar, reqbuf, reqlen);
        if (mhmp_verbose) {
            fprintf(stdout, "message sent: %d =>\n%s\n",
                ret, reqbuf);
            fflush(stdout);
        }
    } else
        ret = -1;
    free(reqbuf);
    return ret;
}

int mhmp_periodic(struct mhmp_var * mvar)
{
    int ret;
    unsigned int nowt;
    unsigned int then;

    nowt = mhmp_uptime(NULL, NULL);
    ret = mhmp_heartbeat(mvar, nowt);
    if (ret < 0)
        return -1;

    if (nowt >= (mvar->last_hb_recv + 4 * TCP_MHMP_HBINTERVAL)) {
        fprintf(stderr, "Error, heartbeat reply not received: %u <=> %u\n",
            nowt, mvar->last_hb_recv);
        fflush(stderr);
        return -1;
    }

    ret = 0;
    then = mvar->last_req;
    if (nowt >= (then + mvar->req_intval)) {
        ret = mhmp_send_request(mvar,
            MHMP_GETREALTIMEDATA, MHMP_ALL, MHMP_ALL);
        if (ret >= 0)
            ret = mhmp_send_request(mvar,
                MHMP_GETWARNINGINFO, MHMP_ALL, MHMP_ALL);

        if ((nowt - then) >= mvar->req_intval * 2) {
            mvar->last_req = nowt;
        } else {
            mvar->last_req = then + mvar->req_intval;
        }
    }

    return ret;
}

unsigned int mhmp_uptime(unsigned int * secp,
    unsigned int * nsecp)
{
    struct timespec spec;

    spec.tv_sec = 0;
    spec.tv_nsec = 0;
    clock_gettime(CLOCK_BOOTTIME, &spec);
    if (secp != NULL)
        *secp = (unsigned int) spec.tv_sec;
    if (nsecp != NULL)
        *nsecp = (unsigned int) spec.tv_nsec;
    return (unsigned int) spec.tv_sec;
}

int mhmp_writemsg(struct mhmp_var * mvar,
    const char * mptr, unsigned int mlen)
{
    ssize_t rl1;
    struct iovec vecs[2];
    struct mhmp_head mhead;

    mhead.tecommhm[0] = 'T';
    mhead.tecommhm[1] = 'E';
    mhead.tecommhm[2] = 'C';
    mhead.tecommhm[3] = 'O';
    mhead.tecommhm[4] = 'M';
    mhead.tecommhm[5] = 'M';
    mhead.tecommhm[6] = 'H';
    mhead.tecommhm[7] = 'M';
    mhead.reserved = 0;
    mhead.length = htonl(mlen);

    vecs[0].iov_base   = (void *) &mhead;
    vecs[0].iov_len    = (size_t) sizeof(mhead);
    vecs[1].iov_base   = (void *) mptr;
    vecs[1].iov_len    = (size_t) mlen;

    rl1 = writev(mvar->sockfd, vecs, 0x2);
    if (rl1 == -1) {
        int errn;
        errn = errno;
        fprintf(stderr, "Error, failed to write socket %d: %s\n",
            mvar->sockfd, strerror(errn));
        fflush(stderr);
        return -1;
    }

    return 0;
}

int mhmp_recvmsg(struct mhmp_var * mvar,
    int timeout, int * pkterr, unsigned char * * ppkt)
{
    ssize_t rl1;
    int ret, errn;
    struct pollfd pfd;
    unsigned int curlen;
    struct mhmp_head mhead;
    char tmpbuf[512], * retpkt = NULL;

    *pkterr = 0; /* no packet error */

    /* poll the socket before reading */
    pfd.fd = mvar->sockfd;
    pfd.events = POLLIN | POLLPRI | POLLERR;
    pfd.revents = 0;
    ret = poll(&pfd, 0x1, timeout);
    if (ret == 0)
        return 0;
    if (ret < 0) {
        errn = errno;
        if (errn == EINTR)
            return 0;
        fprintf(stderr, "Error, failed to poll(%d): %s\n", mvar->sockfd, strerror(errn));
        fflush(stderr);
        return -1;
    }

    if (pfd.revents & (POLLPRI | POLLERR)) {
        fprintf(stderr, "socket revents: %#x\n", (unsigned int) pfd.revents);
        fflush(stderr);
        return -1;
    }

    memset(&mhead, 0, sizeof(mhead));
    rl1 = recv(mvar->sockfd, &mhead, sizeof(mhead), MSG_DONTWAIT);
    if (rl1 == -1) {
        errn = errno;
        if (errn == EAGAIN || errn == EWOULDBLOCK)
            return 0; /* no data */
        fprintf(stderr, "Error, failed to read socket %d: %s\n", mvar->sockfd, strerror(errn));
        fflush(stderr);
        return -1;
    }

    if (rl1 != sizeof(mhead)) {
        *pkterr = 1; /* unable to handle packet */
        fprintf(stderr, "Error, socket read has returned: %d, %s\n", (int) rl1, mhead.tecommhm);
        fflush(stderr);
        return -1;
    }

    if (strncmp(mhead.tecommhm, "TECOMMHM", 0x8) != 0) {
        const unsigned char * tecom = (const unsigned char *) mhead.tecommhm;
        fprintf(stderr, "Error, invalid message header: %02x%02x%02x%02x%02x%02x%02x%02x\n",
            (unsigned int) tecom[0], (unsigned int) tecom[1], (unsigned int) tecom[2], (unsigned int) tecom[3],
            (unsigned int) tecom[4], (unsigned int) tecom[5], (unsigned int) tecom[6], (unsigned int) tecom[7]);
        fflush(stderr);
        *pkterr = 1; /* unable to handle packet */
        /* read until the socket buffer is empty */
        while (recv(mvar->sockfd, tmpbuf, sizeof(tmpbuf), MSG_DONTWAIT) > 0);
        return 0;
    }

    if (mhead.reserved != 0) {
        *pkterr = 1; /* unable to handle packet */
        fprintf(stderr, "Error, invalid reserved data: %#x\n", mhead.reserved);
        fflush(stderr);
        return 0;
    }

    mhead.length = htonl(mhead.length);
    if (mhead.length == 0 || mhead.length >= TCP_MHMP_MAX_BUFSIZ) {
        fprintf(stderr, "Error, MHMP payload length zero or too large: %#x\n",
            mhead.length);
        fflush(stderr);
        *pkterr = 1; /* unable to handle packet */
        /* read until the socket buffer is empty */
        while (recv(mvar->sockfd, tmpbuf, sizeof(tmpbuf), MSG_DONTWAIT) > 0);
        return 0;
    }

    retpkt = (char *) malloc((size_t) (mhead.length + 1));
    if (retpkt == NULL) {
        *pkterr = 1;
        fprintf(stderr, "Error, system out of memory: %u\n", mhead.length);
        fflush(stderr);
        return 0;
    }

    curlen = 0;
    while (curlen < mhead.length) {
        rl1 = recv(mvar->sockfd, retpkt + curlen, (size_t) (mhead.length - curlen), 0);
        if (rl1 == -1) {
            errn = errno;
            if (errn == EINTR) {
                /* interrupted */
                fprintf(stderr, "Warning, dropping message: %u, %u!\n",
                    curlen, mhead.length);
                fflush(stderr);
                free(retpkt); retpkt = NULL;
                return 0;
            }

            fprintf(stderr, "Error, failed to read payload data: %s\n", strerror(errn));
            fflush(stderr);
            break;
        }
        if (rl1 == 0)
            break;
        curlen += (unsigned int) rl1;
    }

    if (curlen != mhead.length) {
        free(retpkt); retpkt = NULL;
        *pkterr = 1; /* unable to handle packet */
        fprintf(stderr, "Incorrect MHMP payload length: %u, expected: %u\n",
            curlen, mhead.length);
        fflush(stderr);
        return 0;
    }

    retpkt[curlen] = '\0';
    *ppkt = (unsigned char *) retpkt;
    return (int) curlen;
}

/**
 * Poll an in-progress TCP connection.
 */
static int ipv4_ipv6_poll(int sockfd, int timeout)
{
	int ret, error;
	socklen_t slt;
	struct pollfd pfd;

again:
	pfd.fd = sockfd;
	pfd.events = POLLOUT | POLLPRI | POLLERR;
	pfd.revents = 0;
	ret = poll(&pfd, 0x1, timeout);
	if (ret < 0) {
		error = errno;
		if (error == EINTR)
			goto again;
		fprintf(stderr, "Error, failed to poll(%d): %s\n", sockfd, strerror(error));
		return -1;
	}

	if (ret == 0)
		return -1;

	error = 0;
	slt = sizeof(error);
	ret = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &slt);
	if (ret == -1) {
		error = errno;
		fprintf(stderr, "Error, failed to get error flag for socket %d: %s\n",
			sockfd, strerror(error));
		fflush(stderr);
		return -1;
	}
	return error ? -1 : 0;
}

int mhmp_connect(struct mhmp_var * mvar,
    const char * svraddr, int portno, int timeo)
{
    int ret;
    unsigned int nowt;
    const char * addr;
    unsigned short port;
    struct sockaddr_in saddr;

    addr = (svraddr != NULL) ? svraddr : mvar->svraddr;
    if (addr == NULL || addr[0] == '\0') {
        fputs("Error, no invalid serverip specified\n", stderr);
        fflush(stderr);
        return -1;
    }

    memset(&saddr, 0, sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = inet_addr(addr);
    port = (unsigned short) ((portno > 0) ? portno : mvar->svrport);
    saddr.sin_port = htons(port);

    ret = connect(mvar->sockfd, (struct sockaddr *) &saddr, sizeof(saddr));
    if (ret == -1) {
        int errn;
        errn = errno;
        if (errn == EINPROGRESS)
            ret = ipv4_ipv6_poll(mvar->sockfd, timeo);
        if (ret < 0) {
            fprintf(stderr, "Failed to connect to [%s:%d]: %s\n",
                addr, portno, strerror(errn));
            fflush(stderr);
            return ret;
        }
    }

    if (1) {
        int rc, lowrecv;
        struct timeval tval;
        /* blocked IO: */
        rc = fcntl(mvar->sockfd, F_SETFL, fcntl(mvar->sockfd, F_GETFL, 0) & ~O_NONBLOCK);
        if (rc == -1) {
            fputs("Error, cannot set socket to blocked mode!\n", stderr);
            fflush(stderr);
        }

        tval.tv_sec = 5;
        tval.tv_usec = 0;
        /* socket read timeout in 5 seconds */
        rc = setsockopt(mvar->sockfd, SOL_SOCKET, SO_RCVTIMEO, &tval, sizeof(tval));
        if (rc == -1) {
            fprintf(stderr, "Failed to set socket read time: %s\n", strerror(errno));
            fflush(stderr);
        }

        lowrecv = sizeof(struct mhmp_head); /* read full MHMP header */
        rc = setsockopt(mvar->sockfd, SOL_SOCKET, SO_RCVLOWAT, &lowrecv, sizeof(tval));
        if (rc == -1) {
            fprintf(stderr, "Failed to set socket read lowat: %s\n", strerror(errno));
            fflush(stderr);
        }
    }

    /* newly connected TCP, update timestamps */
    nowt = mhmp_uptime(NULL, NULL);
    mvar->last_hb = nowt;
    mvar->last_hb_recv = nowt;
    mvar->last_req = nowt;
    if (mhmp_verbose) {
        fprintf(stdout, "MHMP TCP connection established: %s:%u\n",
            addr, (unsigned int) port);
        fflush(stdout);
    }
    return 0;
}

struct mhmp_var * mhmp_create(const char * svraddr, int portno)
{
    int ret;
    unsigned int nowt;
    struct mhmp_var * mvar = NULL;

    mhmp_verbose = 0;
    if (1) {
        /* request from environment variable */
        const char * mvstr = getenv("MHMP_VERBOSE");
        if (mvstr && mvstr[0] == '1')
            mhmp_verbose = 1;
    }

    mvar = (struct mhmp_var *) calloc(0x1, sizeof(*mvar));
    if (mvar == NULL) {
        fputs("Error, system out of memory!\n", stderr);
        fflush(stderr);
        return NULL;
    }

    mvar->sockfd = -1;
    ret = mhmp_create_socket(mvar);
    if (ret < 0) {
        mhmp_destory(mvar);
        return NULL;
    }

    if (svraddr != NULL && svraddr[0] != '\0') {
        mvar->svraddr = strdup(svraddr);
        if (mvar->svraddr == NULL) {
            mhmp_destory(mvar);
            return NULL;
        }
    }

    mvar->svrport = portno;
    nowt = mhmp_uptime(NULL, NULL);
    mvar->last_hb = nowt;
    mvar->last_hb_recv = nowt;

    /* default request interval: 60 seconds */
    mvar->req_intval = TCP_MHMP_REQUEST_INTERVAL;
    mvar->last_req = nowt;
    return mvar;
}

void mhmp_destory(struct mhmp_var * mvar)
{
    if (mvar == NULL)
        return;
    if (mvar->sockfd != -1) {
        close(mvar->sockfd);
        mvar->sockfd = -1;
    }

    if (mvar->svraddr) {
        free(mvar->svraddr);
        mvar->svraddr = NULL;
    }
    mvar->svrport = 0;
    free(mvar);
}

unsigned char * mhmp_loadfile(
    const char * filp, size_t maxsize, size_t * fsize)
{
    int fd, ret;
    size_t rsize;
    ssize_t rval;
    struct stat sta;
    unsigned char * filbuf;

    fd = open(filp, O_RDONLY);
    if (fd == -1) {
        fprintf(stderr, "Error, failed to open(%s): %s\n",
            filp, strerror(errno));
        fflush(stderr);
        return NULL;
    }

    ret = fstat(fd, &sta);
    if (ret == -1) {
        fprintf(stderr, "Error, failed to stat(%s): %s\n",
            filp, strerror(errno));
        fflush(stderr);
        close(fd);
        return NULL;
    }

    if (!S_ISREG(sta.st_mode)) {
        fprintf(stderr, "Error, invalid file, empty? => %s\n", filp);
        fflush(stderr);
        close(fd);
        return NULL;
    }

    rsize = (size_t) sta.st_size;
    if (rsize > maxsize)
        rsize = maxsize;

    filbuf = malloc(rsize + 0x8);
    if (filbuf == NULL) {
        close(fd);
        fputs("Error, system out of memory!\n", stderr);
        fflush(stderr);
        return NULL;
    }

    rval = (rsize > 0) ? read(fd, filbuf, rsize) : 0;
    if (rsize != (size_t) rval) {
        int errn = errno;
        close(fd);
        free(filbuf);
        fprintf(stderr, "Error, failed to read %s: %s\n", filp, strerror(errn));
        fflush(stderr);
        return NULL;
    }

	close(fd);
    *fsize = rsize;
    return filbuf;
}
