/*
 *  zzuf - general purpose fuzzer
 *  Copyright (c) 2006 Sam Hocevar <sam@zoy.org>
 *                All Rights Reserved
 *
 *  $Id$
 *
 *  This program is free software. It comes without any warranty, to
 *  the extent permitted by applicable law. You can redistribute it
 *  and/or modify it under the terms of the Do What The Fuck You Want
 *  To Public License, Version 2, as published by Sam Hocevar. See
 *  http://sam.zoy.org/wtfpl/COPYING for more details.
 */

/*
 *  load-stream.c: loaded stream functions
 */

#include "config.h"

#define _GNU_SOURCE /* for getline() and getdelim() */

#if defined HAVE_STDINT_H
#   include <stdint.h>
#elif defined HAVE_INTTYPES_H
#   include <inttypes.h>
#endif
#include <stdlib.h>

#include <stdio.h>
#include <sys/types.h>
#if defined HAVE___SREFILL && defined HAVE_UNISTD_H
#   include <unistd.h> /* Needed for __srefill’s lseek() call */
#endif

#include "libzzuf.h"
#include "lib-load.h"
#include "debug.h"
#include "fuzz.h"
#include "fd.h"

#if defined HAVE___SREFILL
int NEW(__srefill)(FILE *fp);
#endif

/* Library functions that we divert */
static FILE *  (*ORIG(fopen))    (const char *path, const char *mode);
#if defined HAVE_FOPEN64
static FILE *  (*ORIG(fopen64))  (const char *path, const char *mode);
#endif
static FILE *  (*ORIG(freopen))  (const char *path, const char *mode,
                                  FILE *stream);
static int     (*ORIG(fseek))    (FILE *stream, long offset, int whence);
#if defined HAVE_FSEEKO
static int     (*ORIG(fseeko))   (FILE *stream, off_t offset, int whence);
#endif
static void    (*ORIG(rewind))   (FILE *stream);
static size_t  (*ORIG(fread))    (void *ptr, size_t size, size_t nmemb,
                                  FILE *stream);
#if defined HAVE_FREAD_UNLOCKED
static size_t  (*ORIG(fread_unlocked))  (void *ptr, size_t size, size_t nmemb,
                                         FILE *stream);
#endif
static int     (*ORIG(getc))     (FILE *stream);
static int     (*ORIG(getchar))  (void);
static int     (*ORIG(fgetc))    (FILE *stream);
#if defined HAVE__IO_GETC
static int     (*ORIG(_IO_getc)) (FILE *stream);
#endif
#if defined HAVE_GETC_UNLOCKED
static int     (*ORIG(getc_unlocked))    (FILE *stream);
#endif
#if defined HAVE_GETCHAR_UNLOCKED
static int     (*ORIG(getchar_unlocked)) (void);
#endif
#if defined HAVE_FGETC_UNLOCKED
static int     (*ORIG(fgetc_unlocked))   (FILE *stream);
#endif
static char *  (*ORIG(fgets))    (char *s, int size, FILE *stream);
#if defined HAVE_FGETS_UNLOCKED
static char *  (*ORIG(fgets_unlocked))   (char *s, int size, FILE *stream);
#endif
static int     (*ORIG(ungetc))   (int c, FILE *stream);
static int     (*ORIG(fclose))   (FILE *fp);

/* Additional GNUisms */
#if defined HAVE_GETLINE
static ssize_t (*ORIG(getline))    (char **lineptr, size_t *n, FILE *stream);
#endif
#if defined HAVE_GETDELIM
static ssize_t (*ORIG(getdelim))   (char **lineptr, size_t *n, int delim,
                                    FILE *stream);
#endif
#if defined HAVE___GETDELIM
static ssize_t (*ORIG(__getdelim)) (char **lineptr, size_t *n, int delim,
                                    FILE *stream);
#endif

/* Additional BSDisms */
#if defined HAVE_FGETLN
static char *  (*ORIG(fgetln))    (FILE *stream, size_t *len);
#endif
#if defined HAVE___SREFILL
int            (*ORIG(__srefill)) (FILE *fp);
#endif

/* Our function wrappers */
#define FOPEN(fn) \
    do \
    { \
        LOADSYM(fn); \
        if(!_zz_ready) \
            return ORIG(fn)(path, mode); \
        _zz_lock(-1); \
        ret = ORIG(fn)(path, mode); \
        _zz_unlock(-1); \
        if(ret && _zz_mustwatch(path)) \
        { \
            int fd = fileno(ret); \
            _zz_register(fd); \
            debug("%s(\"%s\", \"%s\") = [%i]", __func__, path, mode, fd); \
        } \
    } while(0)

FILE *NEW(fopen)(const char *path, const char *mode)
{
    FILE *ret; FOPEN(fopen); return ret;
}

#if defined HAVE_FOPEN64
FILE *NEW(fopen64)(const char *path, const char *mode)
{
    FILE *ret; FOPEN(fopen64); return ret;
}
#endif

FILE *NEW(freopen)(const char *path, const char *mode, FILE *stream)
{
    FILE *ret;
    int fd0 = -1, fd1 = -1, disp = 0;

    LOADSYM(freopen);
    if(_zz_ready && (fd0 = fileno(stream)) >= 0 && _zz_iswatched(fd0))
    {
        _zz_unregister(fd0);
        disp = 1;
    }

    _zz_lock(-1);
    ret = ORIG(freopen)(path, mode, stream);
    _zz_unlock(-1);

    if(ret && _zz_mustwatch(path))
    {
        fd1 = fileno(ret);
        _zz_register(fd1);
        disp = 1;
    }

    if(disp)
        debug("%s(\"%s\", \"%s\", [%i]) = [%i]", __func__,
              path, mode, fd0, fd1);

    return ret;
}

#if defined HAVE___SREFILL /* Don't fuzz or seek if we have __srefill() */
#   define FSEEK_FUZZ(fn2)
#else
#   define FSEEK_FUZZ(fn2) \
        if(ret == 0) \
        { \
            /* FIXME: check what happens when fseek()ing a pipe */ \
            switch(whence) \
            { \
                case SEEK_END: \
                    offset = fn2(stream); \
                    /* fall through */ \
                case SEEK_SET: \
                    _zz_setpos(fd, offset); \
                    break; \
                case SEEK_CUR: \
                    _zz_addpos(fd, offset); \
                    break; \
            } \
        }
#endif

#define FSEEK(fn, fn2) \
    do \
    { \
        int fd; \
        LOADSYM(fn); \
        fd = fileno(stream); \
        if(!_zz_ready || !_zz_iswatched(fd) || !_zz_isactive(fd)) \
            return ORIG(fn)(stream, offset, whence); \
        _zz_lock(fd); \
        ret = ORIG(fn)(stream, offset, whence); \
        _zz_unlock(fd); \
        debug("%s([%i], %lli, %i) = %i", __func__, \
              fd, (long long int)offset, whence, ret); \
        FSEEK_FUZZ(fn2) \
    } while(0)

int NEW(fseek)(FILE *stream, long offset, int whence)
{
    int ret; FSEEK(fseek, ftell); return ret;
}

#if defined HAVE_FSEEKO
int NEW(fseeko)(FILE *stream, off_t offset, int whence)
{
    int ret; FSEEK(fseeko, ftello); return ret;
}
#endif

void NEW(rewind)(FILE *stream)
{
    int fd;

    LOADSYM(rewind);
    fd = fileno(stream);
    if(!_zz_ready || !_zz_iswatched(fd) || !_zz_isactive(fd))
    {
        ORIG(rewind)(stream);
        return;
    }

    _zz_lock(fd);
    ORIG(rewind)(stream);
    _zz_unlock(fd);
    debug("%s([%i])", __func__, fd);

#if defined HAVE___SREFILL /* Don't fuzz or seek if we have __srefill() */
#else
    /* FIXME: check what happens when rewind()ing a pipe */
    _zz_setpos(fd, 0);
#endif
}

#if defined HAVE___SREFILL /* Don't fuzz or seek if we have __srefill() */
#   define FREAD_FUZZ() \
    do \
    { \
        debug("%s(%p, %li, %li, [%i]) = %li", __func__, ptr, \
              (long int)size, (long int)nmemb, fd, (long int)ret); \
    } while(0)
#else
#   define FREAD_FUZZ() \
    do \
    { \
        int64_t newpos = ftell(stream); \
        /* XXX: the number of bytes read is not ret * size, because \
         * a partial read may have advanced the stream pointer. However, \
         * when reading from a pipe ftell() will return 0, and ret * size \
         * is then better than nothing. */ \
        if(newpos <= 0) \
        { \
            pos = _zz_getpos(fd); \
            newpos = pos + ret * size; \
        } \
        if(newpos != pos) \
        { \
            char *b = ptr; \
            _zz_fuzz(fd, ptr, newpos - pos); \
            _zz_setpos(fd, newpos); \
            if(newpos >= pos + 4) \
                debug("%s(%p, %li, %li, [%i]) = %li \"%c%c%c%c...", __func__, \
                      ptr, (long int)size, (long int)nmemb, fd, \
                      (long int)ret, b[0], b[1], b[2], b[3]); \
            else \
                debug("%s(%p, %li, %li, [%i]) = %li \"%c...", __func__, ptr, \
                      (long int)size, (long int)nmemb, fd, \
                      (long int)ret, b[0]); \
        } \
        else \
            debug("%s(%p, %li, %li, [%i]) = %li", __func__, ptr, \
                  (long int)size, (long int)nmemb, fd, (long int)ret); \
    } while(0)
#endif

#define FREAD(fn) \
    do \
    { \
        int64_t pos; \
        int fd; \
        LOADSYM(fn); \
        fd = fileno(stream); \
        if(!_zz_ready || !_zz_iswatched(fd) || !_zz_isactive(fd)) \
            return ORIG(fn)(ptr, size, nmemb, stream); \
        pos = ftell(stream); \
        _zz_lock(fd); \
        ret = ORIG(fn)(ptr, size, nmemb, stream); \
        _zz_unlock(fd); \
        FREAD_FUZZ(); \
    } while(0)

size_t NEW(fread)(void *ptr, size_t size, size_t nmemb, FILE *stream)
{
    size_t ret; FREAD(fread); return ret;
}

#if defined HAVE_FREAD_UNLOCKED
#undef fread_unlocked /* can be a macro; we don’t want that */
size_t NEW(fread_unlocked)(void *ptr, size_t size, size_t nmemb, FILE *stream)
{
    size_t ret; FREAD(fread_unlocked); return ret;
}
#endif

#if defined HAVE___SREFILL /* Don't fuzz or seek if we have __srefill() */
#   define FGETC_FUZZ
#else
#   define FGETC_FUZZ \
        if(ret != EOF) \
        { \
            uint8_t ch = ret; \
            _zz_fuzz(fd, &ch, 1); \
            _zz_addpos(fd, 1); \
            ret = ch; \
        }
#endif

#define FGETC(fn, s, arg) \
    do { \
        int fd; \
        LOADSYM(fn); \
        fd = fileno(s); \
        if(!_zz_ready || !_zz_iswatched(fd) || !_zz_isactive(fd)) \
            return ORIG(fn)(arg); \
        _zz_lock(fd); \
        ret = ORIG(fn)(arg); \
        _zz_unlock(fd); \
        FGETC_FUZZ \
        if(ret == EOF) \
            debug("%s([%i]) = EOF", __func__, fd); \
        else \
            debug("%s([%i]) = '%c'", __func__, fd, ret); \
    } while(0)

#undef getc /* can be a macro; we don’t want that */
int NEW(getc)(FILE *stream)
{
    int ret; FGETC(getc, stream, stream); return ret;
}

#undef getchar /* can be a macro; we don’t want that */
int NEW(getchar)(void)
{
    int ret; FGETC(getchar, stdin, /* empty */); return ret;
}

int NEW(fgetc)(FILE *stream)
{
    int ret; FGETC(fgetc, stream, stream); return ret;
}

#if defined HAVE__IO_GETC
int NEW(_IO_getc)(FILE *stream)
{
    int ret; FGETC(_IO_getc, stream, stream); return ret;
}
#endif

#if defined HAVE_GETC_UNLOCKED
#undef getc_unlocked /* can be a macro; we don’t want that */
int NEW(getc_unlocked)(FILE *stream)
{
    int ret; FGETC(getc_unlocked, stream, stream); return ret;
}
#endif

#if defined HAVE_GETCHAR_UNLOCKED
#undef getchar_unlocked /* can be a macro; we don’t want that */
int NEW(getchar_unlocked)(void)
{
    int ret; FGETC(getchar_unlocked, stdin, /* empty */); return ret;
}
#endif

#if defined HAVE_FGETC_UNLOCKED
#undef fgetc_unlocked /* can be a macro; we don’t want that */
int NEW(fgetc_unlocked)(FILE *stream)
{
    int ret; FGETC(fgetc_unlocked, stream, stream); return ret;
}
#endif

#if defined HAVE___SREFILL /* Don't fuzz or seek if we have __srefill() */
#   define FGETS_FUZZ(fn, fn2) \
        _zz_lock(fd); \
        ret = ORIG(fn)(s, size, stream); \
        _zz_unlock(fd);
#else
#   define FGETS_FUZZ(fn, fn2) \
        if(size <= 0) \
            ret = NULL; \
        else if(size == 1) \
            s[0] = '\0'; \
        else \
        { \
            int i; \
            for(i = 0; i < size - 1; i++) \
            { \
                int ch; \
                _zz_lock(fd); \
                ch = ORIG(fn2)(stream); \
                _zz_unlock(fd); \
                if(ch == EOF) \
                { \
                    s[i] = '\0'; \
                    if(!i) \
                        ret = NULL; \
                    break; \
                } \
                s[i] = (char)(unsigned char)ch; \
                _zz_fuzz(fd, (uint8_t *)s + i, 1); /* rather inefficient */ \
                _zz_addpos(fd, 1); \
                if(s[i] == '\n') \
                { \
                    s[i + 1] = '\0'; \
                    break; \
                } \
            } \
        }
#endif

#define FGETS(fn, fn2) \
    do \
    { \
        int fd; \
        ret = s; \
        LOADSYM(fn); \
        LOADSYM(fn2); \
        fd = fileno(stream); \
        if(!_zz_ready || !_zz_iswatched(fd) || !_zz_isactive(fd)) \
            return ORIG(fn)(s, size, stream); \
        FGETS_FUZZ(fn, fn2) \
        debug("%s(%p, %i, [%i]) = %p", __func__, s, size, fd, ret); \
    } while(0)

char *NEW(fgets)(char *s, int size, FILE *stream)
{
    char *ret; FGETS(fgets, fgetc); return ret;
}

#if defined HAVE_FGETS_UNLOCKED
char *NEW(fgets_unlocked)(char *s, int size, FILE *stream)
{
    char *ret; FGETS(fgets_unlocked, fgetc_unlocked); return ret;
}
#endif

int NEW(ungetc)(int c, FILE *stream)
{
    int ret, fd;

    LOADSYM(ungetc);
    fd = fileno(stream);
    if(!_zz_ready || !_zz_iswatched(fd) || !_zz_isactive(fd))
        return ORIG(ungetc)(c, stream);

    _zz_lock(fd);
    ret = ORIG(ungetc)(c, stream);
    _zz_unlock(fd);

    if(ret != EOF)
    {
        struct fuzz *fuzz = _zz_getfuzz(fd);
        fuzz->uflag = 1;
        fuzz->upos = _zz_getpos(fd) - 1;
        fuzz->uchar = c;
#if defined HAVE___SREFILL /* Don't fuzz or seek if we have __srefill() */
#else
        _zz_addpos(fd, -1);
#endif
    }

    if(ret == EOF)
        debug("%s(0x%02x, [%i]) = EOF", __func__, c, fd);
    else
        debug("%s(0x%02x, [%i]) = '%c'", __func__, c, fd, ret);

    return ret;
}

int NEW(fclose)(FILE *fp)
{
    int ret, fd;

    LOADSYM(fclose);
    fd = fileno(fp);
    if(!_zz_ready || !_zz_iswatched(fd))
        return ORIG(fclose)(fp);

    _zz_lock(fd);
    ret = ORIG(fclose)(fp);
    _zz_unlock(fd);
    debug("%s([%i]) = %i", __func__, fd, ret);
    _zz_unregister(fd);

    return ret;
}

#define GETDELIM(fn, delim, need_delim) \
    do { \
        char *line; \
        ssize_t done, size; \
        int fd, finished = 0; \
        LOADSYM(fn); \
        LOADSYM(getdelim); \
        LOADSYM(fgetc); \
        fd = fileno(stream); \
        if(!_zz_ready || !_zz_iswatched(fd) || !_zz_isactive(fd)) \
            return ORIG(getdelim)(lineptr, n, delim, stream); \
        line = *lineptr; \
        size = line ? *n : 0; \
        ret = done = finished = 0; \
        for(;;) \
        { \
            int ch; \
            if(done >= size) /* highly inefficient but I don't care */ \
                line = realloc(line, size = done + 1); \
            if(finished) \
            { \
                line[done] = '\0'; \
                *n = size; \
                *lineptr = line; \
                break; \
            } \
            _zz_lock(fd); \
            ch = ORIG(fgetc)(stream); \
            _zz_unlock(fd); \
            if(ch == EOF) \
            { \
                finished = 1; \
                ret = done; \
            } \
            else \
            { \
                unsigned char c = ch; \
                _zz_fuzz(fd, &c, 1); /* even more inefficient */ \
                line[done++] = c; \
                _zz_addpos(fd, 1); \
                if(c == delim) \
                { \
                    finished = 1; \
                    ret = done; \
                } \
            } \
        } \
        if(need_delim) \
            debug("%s(%p, %p, '%c', [%i]) = %li", __func__, \
                  lineptr, n, delim, fd, (long int)ret); \
        else \
            debug("%s(%p, %p, [%i]) = %li", __func__, \
                  lineptr, n, fd, (long int)ret); \
        return ret; \
    } while(0)

#if defined HAVE_GETLINE
ssize_t NEW(getline)(char **lineptr, size_t *n, FILE *stream)
{
    ssize_t ret; GETDELIM(getline, '\n', 0); return ret;
}
#endif

#if defined HAVE_GETDELIM
ssize_t NEW(getdelim)(char **lineptr, size_t *n, int delim, FILE *stream)
{
    ssize_t ret; GETDELIM(getdelim, delim, 1); return ret;
}
#endif

#if defined HAVE___GETDELIM
ssize_t NEW(__getdelim)(char **lineptr, size_t *n, int delim, FILE *stream)
{
    ssize_t ret; GETDELIM(__getdelim, delim, 1); return ret;
}
#endif

#if defined HAVE_FGETLN
char *NEW(fgetln)(FILE *stream, size_t *len)
{
    char *ret;
#if defined HAVE___SREFILL /* Don't fuzz or seek if we have __srefill() */
#else
    struct fuzz *fuzz;
    size_t i, size;
#endif
    int fd;

    LOADSYM(fgetln);
    LOADSYM(fgetc);
    fd = fileno(stream);
    if(!_zz_ready || !_zz_iswatched(fd) || !_zz_isactive(fd))
        return ORIG(fgetln)(stream, len);

#if defined HAVE___SREFILL /* Don't fuzz or seek if we have __srefill() */
    _zz_lock(fd);
    ret = ORIG(fgetln)(stream, len);
    _zz_unlock(fd);
#else
    fuzz = _zz_getfuzz(fd);

    for(i = size = 0; ; /* i is incremented below */)
    {
        int ch;

        _zz_lock(fd);
        ch = ORIG(fgetc)(stream);
        _zz_unlock(fd);

        if(ch == EOF)
            break;

        if(i >= size)
            fuzz->tmp = realloc(fuzz->tmp, (size += 80));

        fuzz->tmp[i] = (char)(unsigned char)ch;
        _zz_fuzz(fd, (uint8_t *)fuzz->tmp + i, 1); /* rather inefficient */
        _zz_addpos(fd, 1);

        if(fuzz->tmp[i++] == '\n')
            break;
    }

    *len = i;
    ret = fuzz->tmp;
#endif

    debug("%s([%i], &%li) = %p", __func__, fd, (long int)*len, ret);
    return ret;
}
#endif

#if defined HAVE___SREFILL
int NEW(__srefill)(FILE *fp)
{
    off_t newpos;
    int ret, fd, tmp;

    LOADSYM(__srefill);
    fd = fileno(fp);
    if(!_zz_ready || !_zz_iswatched(fd) || !_zz_isactive(fd))
        return ORIG(__srefill)(fp);

    _zz_lock(fd);
    ret = ORIG(__srefill)(fp);
    newpos = lseek(fd, 0, SEEK_CUR);
    _zz_unlock(fd);
    if(ret != EOF)
    {
        if(newpos != -1)
            _zz_setpos(fd, newpos - fp->_r);
        _zz_fuzz(fd, fp->_p, fp->_r);
        _zz_addpos(fd, fp->_r);
    }

    if(!_zz_islocked(fd))
        debug("%s([%i]) = %i", __func__, fd, ret);

    return ret;
}
#endif

