/* Copyright (C) 2001-2019 Artifex Software, Inc.
   All Rights Reserved.

   This software is provided AS-IS with no warranty, either express or
   implied.

   This software is distributed under license and may not be copied,
   modified or distributed except as expressly authorized under the terms
   of the license contained in the file LICENSE in this distribution.

   Refer to licensing information at http://www.artifex.com or contact
   Artifex Software, Inc.,  1305 Grant Avenue - Suite 200, Novato,
   CA 94945, U.S.A., +1(415)492-9861, for further information.
*/

/* %ram% file device implementation */

/*
 *  This file implements a simple ram-based file system.
 *
 * The fs has no subdirs, only a root directory. Files are stored as a
 * resizable array of pointers to blocks.  Timestamps are not implemented
 * for performance reasons.
 *
 * The implementation is in two parts - a ramfs interface that works
 * mostly like the unix file system calls, and a layer to hook this up
 * to ghostscript.
 *
 * Macros define an upper limit on the number of blocks a ram device
 * can take up, and (in ramfs.c) the size of each block.
 *
 * Routines for the gs stream interface were graciously stolen from
 * sfxstdio.c et al.
 */

#include "string_.h"
#include "unistd_.h"
#include "gx.h"
#include "gserrors.h"
#include "gp.h"
#include "gscdefs.h"
#include "gsparam.h"
#include "gsstruct.h"
#include "gxiodev.h"
#include "gsutil.h"
#include "stream.h"
#include "ramfs.h"

/* Function prototypes */
static iodev_proc_init(iodev_ram_init);
static iodev_proc_finit(iodev_ram_finit);
static iodev_proc_open_file(ram_open_file);
static iodev_proc_delete_file(ram_delete);
static iodev_proc_rename_file(ram_rename);
static iodev_proc_file_status(ram_status);
static iodev_proc_enumerate_files(ram_enumerate_init);
static iodev_proc_enumerate_next(ram_enumerate_next);
static iodev_proc_enumerate_close(ram_enumerate_close);
static iodev_proc_get_params(ram_get_params);
static void ram_finalize(const gs_memory_t *memory, void * vptr);

const gx_io_device gs_iodev_ram = {
    "%ram%", "FileSystem", {
        iodev_ram_init, iodev_ram_finit, iodev_no_open_device,
        ram_open_file, iodev_no_fopen, iodev_no_fclose,
        ram_delete, ram_rename, ram_status,
        ram_enumerate_init, ram_enumerate_next, ram_enumerate_close,
        ram_get_params, iodev_no_put_params
    }
};

typedef struct ramfs_state_s {
    gs_memory_t *memory;
    ramfs* fs;
} ramfs_state;

#define GETRAMFS(state) (((ramfs_state*)(state))->fs)

gs_private_st_simple_final(st_ramfs_state, struct ramfs_state_s, "ramfs_state", ram_finalize);

typedef struct gsram_enum_s {
    char *pattern;
    ramfs_enum* e;
    gs_memory_t *memory;
} gsram_enum;

gs_private_st_ptrs3(st_gsram_enum, struct gsram_enum_s, "gsram_enum",
    gsram_enum_enum_ptrs, gsram_enum_reloc_ptrs, pattern, e, memory);

/* could make this runtime configurable later.  It doesn't allocate
   all the blocks in one go so it's not critical */
#define MAXBLOCKS 2000000

#define DEFAULT_BUFFER_SIZE 2048

/* stream stuff */

static int
s_ram_available(stream *, gs_offset_t *),
 s_ram_read_seek(stream *, gs_offset_t),
 s_ram_read_close(stream *),
 s_ram_read_process(stream_state *, stream_cursor_read *,
     stream_cursor_write *, bool);
static int
s_ram_write_seek(stream *, gs_offset_t),
 s_ram_write_flush(stream *),
 s_ram_write_close(stream *),
 s_ram_write_process(stream_state *, stream_cursor_read *,
     stream_cursor_write *, bool);
static int
s_ram_switch(stream *, bool);

static int
ramfs_errno_to_code(int errno) {
    switch (errno) {
    case RAMFS_NOTFOUND:
        return_error(gs_error_undefinedfilename);
    case RAMFS_NOACCESS:
        return_error(gs_error_invalidfileaccess);
    case RAMFS_NOMEM:
        return_error(gs_error_VMerror);
    /* just in case */
    default:
        return_error(gs_error_ioerror);
    }
}

static void
sread_ram(register stream * s, ramhandle * file, byte * buf, uint len),
 swrite_ram(register stream * s, ramhandle * file, byte * buf, uint len),
 sappend_ram(register stream * s, ramhandle * file, byte * buf, uint len);

static int
ram_open_file(gx_io_device * iodev, const char *fname, uint len,
    const char *file_access, stream ** ps, gs_memory_t * mem)
{
    int code = 0;
    ramhandle * file;
    char fmode[4];  /* r/w/a, [+], [b], null */
    int openmode=RAMFS_READ;
    ramfs * fs;
    char * namestr = NULL;

    /* Is there a more efficient way to do this? */
    namestr = (char *)gs_alloc_bytes(mem, len + 1, "temporary filename string");
    if(!namestr)
        return_error(gs_error_VMerror);
    strncpy(namestr,fname,len);
    namestr[len] = 0;

    if (!iodev) {/*iodev = iodev_default;*/
        gs_free_object(mem, namestr, "free temporary filename string");
        return gs_note_error(gs_error_invalidaccess);
    }
    fs = GETRAMFS(iodev->state);
    code = file_prepare_stream(fname, len, file_access, DEFAULT_BUFFER_SIZE,
    ps, fmode, mem
    );
    if (code < 0) goto error;
    if (fname == 0) {
        gs_free_object(mem, namestr, "free temporary filename string");
        return 0;
    }

    switch (fmode[0]) {
        case 'a':
          openmode = RAMFS_WRITE | RAMFS_APPEND;
          break;
        case 'r':
          openmode = RAMFS_READ;
          if (fmode[1] == '+')
            openmode |= RAMFS_WRITE;
          break;
        case 'w':
          openmode |= RAMFS_WRITE | RAMFS_TRUNC | RAMFS_CREATE;
          if (fmode[1] == '+')
             openmode |= RAMFS_READ;
    }

    /* For now, we cheat here in the same way that sfxstdio.c et al cheat -
       append mode is faked by opening in write mode and seeking to EOF just
       once. This is different from unix semantics, which seeks atomically
       before each write, and is actually useful as a distinct mode. */
    /* if (fmode[0] == 'a') openmode |= RAMFS_APPEND; */

    file = ramfs_open(mem, fs,namestr,openmode);
    if(!file) { code = ramfs_errno_to_code(ramfs_error(fs)); goto error; }

    switch (fmode[0]) {
    case 'a':
    sappend_ram(*ps, file, (*ps)->cbuf, (*ps)->bsize);
    break;
    case 'r':
    sread_ram(*ps, file, (*ps)->cbuf, (*ps)->bsize);
    break;
    case 'w':
    swrite_ram(*ps, file, (*ps)->cbuf, (*ps)->bsize);
    }
    if (fmode[1] == '+') {
      (*ps)->modes = (*ps)->file_modes |= s_mode_read | s_mode_write;
    }
    (*ps)->save_close = (*ps)->procs.close;
    (*ps)->procs.close = file_close_file;
 error:
    gs_free_object(mem, namestr, "free temporary filename string");
    /* XXX free stream stuff? */
    return code;
}

/* Initialize a stream for reading an OS file. */
static void
sread_ram(register stream * s, ramhandle * file, byte * buf, uint len)
{
    static const stream_procs p = {
    s_ram_available, s_ram_read_seek, s_std_read_reset,
    s_std_read_flush, s_ram_read_close, s_ram_read_process,
    s_ram_switch
    };

    s_std_init(s, buf, len, &p,s_mode_read + s_mode_seek);
    s->file = (FILE*)file;
    s->file_modes = s->modes;
    s->file_offset = 0;
    ramfile_seek(file, 0, RAMFS_SEEK_END);
    s->file_limit = ramfile_tell(file);
    ramfile_seek(file, 0, RAMFS_SEEK_SET);
}

/* Procedures for reading from a file */
static int
s_ram_available(register stream * s, gs_offset_t *pl)
{
    long max_avail = s->file_limit - stell(s);

    *pl = max_avail;
    if(*pl == 0 && ramfile_eof((ramhandle*)s->file))
    *pl = -1;        /* EOF */
    return 0;
}

static int
s_ram_read_seek(register stream * s, gs_offset_t pos)
{
    uint end = s->cursor.r.limit - s->cbuf + 1;
    long offset = pos - s->position;

    if (offset >= 0 && offset <= end) {  /* Staying within the same buffer */
        s->cursor.r.ptr = s->cbuf + offset - 1;
        return 0;
    }
    if (pos < 0 || pos > s->file_limit ||
        ramfile_seek((ramhandle*)s->file, s->file_offset + pos, RAMFS_SEEK_SET) != 0
    )
    return ERRC;
    s->cursor.r.ptr = s->cursor.r.limit = s->cbuf - 1;
    s->end_status = 0;
    s->position = pos;
    return 0;
}
static int
s_ram_read_close(stream * s)
{
    ramhandle *file = (ramhandle*)s->file;

    if (file != 0) {
    s->file = 0;
    ramfile_close(file);
    }
    return 0;
}

/*
 * Process a buffer for a file reading stream.
 * This is the first stream in the pipeline, so pr is irrelevant.
 */
static int
s_ram_read_process(stream_state * st, stream_cursor_read * ignore_pr,
    stream_cursor_write * pw, bool last)
{
    stream *s = (stream *)st;    /* no separate state */
    ramhandle *file = (ramhandle*)s->file;
    uint max_count = pw->limit - pw->ptr;
    int status = 1;
    int count;

    if (s->file_limit < S_FILE_LIMIT_MAX) {
    long limit_count = s->file_offset + s->file_limit -
    ramfile_tell(file);

    if (max_count > limit_count)
        max_count = limit_count, status = EOFC;
    }
    count = ramfile_read(file,pw->ptr + 1, max_count);
    if (count < 0) return ERRC;
    pw->ptr += count;
    /*    process_interrupts(s->memory); */
    return ramfile_eof(file) ? EOFC : status;
}

/* ------ File writing ------ */

/* Initialize a stream for writing a file. */
static void
swrite_ram(register stream * s, ramhandle * file, byte * buf, uint len)
{
    static const stream_procs p = {
    s_std_noavailable, s_ram_write_seek, s_std_write_reset,
    s_ram_write_flush, s_ram_write_close, s_ram_write_process,
    s_ram_switch
    };

    s_std_init(s, buf, len, &p, s_mode_write + s_mode_seek);
    s->file = (FILE*)file;
    s->file_modes = s->modes;
    s->file_offset = 0;        /* in case we switch to reading later */
    s->file_limit = S_FILE_LIMIT_MAX;
}

/* Initialize for appending to a file. */
static void
sappend_ram(register stream * s, ramhandle * file, byte * buf, uint len)
{
    swrite_ram(s, file, buf, len);
    s->modes = s_mode_write + s_mode_append;    /* no seek */
    s->file_modes = s->modes;
    ramfile_seek(file,0,RAMFS_SEEK_END);
    s->position = ramfile_tell(file);
}

/* Procedures for writing on a file */
static int
s_ram_write_seek(stream * s, gs_offset_t pos)
{
    /* We must flush the buffer to reposition. */
    int code = sflush(s);

    if (code < 0) return code;
    if (ramfile_seek((ramhandle*)s->file, pos, RAMFS_SEEK_SET) != 0)
    return ERRC;
    s->position = pos;
    return 0;
}

static int
s_ram_write_flush(register stream * s)
{
    int result = s_process_write_buf(s, false);
    return result;
}

static int
s_ram_write_close(register stream * s)
{
    s_process_write_buf(s, true);
    return s_ram_read_close(s);
}

/*
 * Process a buffer for a file writing stream.
 * This is the last stream in the pipeline, so pw is irrelevant.
 */
static int
s_ram_write_process(stream_state * st, stream_cursor_read * pr,
    stream_cursor_write * ignore_pw, bool last)
{
    uint count = pr->limit - pr->ptr;

    ramhandle *file = (ramhandle*)((stream *) st)->file;
    int written = ramfile_write(file,pr->ptr + 1, count);

    if (written < 0) return ERRC;
    pr->ptr += written;
    return 0;
}

static int
s_ram_switch(stream * s, bool writing)
{
    uint modes = s->file_modes;
    ramhandle *file = (ramhandle*)s->file;
    long pos;

    if (writing) {
    if (!(s->file_modes & s_mode_write)) return ERRC;
    pos = stell(s);
    ramfile_seek(file, pos, RAMFS_SEEK_SET);
    if (modes & s_mode_append) {
        sappend_ram(s, file, s->cbuf, s->cbsize);    /* sets position */
    } else {
        swrite_ram(s, file, s->cbuf, s->cbsize);
        s->position = pos;
    }
    s->modes = modes;
    } else {
    if (!(s->file_modes & s_mode_read)) return ERRC;
    pos = stell(s);
    if (sflush(s) < 0) return ERRC;
    sread_ram(s, file, s->cbuf, s->cbsize);
    s->modes |= modes & s_mode_append;    /* don't lose append info */
    s->position = pos;
    }
    s->file_modes = modes;
    return 0;
}


/* gx_io_device stuff */

static int
iodev_ram_init(gx_io_device * iodev, gs_memory_t * mem)
{
    ramfs* fs = ramfs_new(mem, MAXBLOCKS);
    ramfs_state* state = gs_alloc_struct(mem, ramfs_state, &st_ramfs_state,
    "ramfs_init(state)"
    );
    if (fs && state) {
    state->fs = fs;
    state->memory = mem;
    iodev->state = state;
    return 0;
    }
    if(fs) ramfs_destroy(mem, fs);
    if(state) gs_free_object(mem,state,"iodev_ram_init(state)");
    return_error(gs_error_VMerror);
}

static void
iodev_ram_finit(gx_io_device * iodev, gs_memory_t * mem)
{
    ramfs_state *state = (ramfs_state *)iodev->state;
    if (state != NULL)
    {
        iodev->state = NULL;
        gs_free_object(state->memory, state, "iodev_ram_finit");
    }
    return;
}

static void
ram_finalize(const gs_memory_t *memory, void * vptr)
{
    ramfs* fs = GETRAMFS((ramfs_state*)vptr);
    ramfs_destroy((gs_memory_t *)memory, fs);
    GETRAMFS((ramfs_state*)vptr) = NULL;
}

static int
ram_delete(gx_io_device * iodev, const char *fname)
{
    ramfs* fs = GETRAMFS(iodev->state);

    if(ramfs_unlink(fs,fname)!=0) {
    return_error(ramfs_errno_to_code(ramfs_error(fs)));
    }
    return 0;
}

static int
ram_rename(gx_io_device * iodev, const char *from, const char *to)
{
    ramfs* fs = GETRAMFS(iodev->state);

    if(ramfs_rename(fs,from,to)!=0) {
    return_error(ramfs_errno_to_code(ramfs_error(fs)));
    }
    return 0;
}

static int
ram_status(gx_io_device * iodev, const char *fname, struct stat *pstat)
{
    ramhandle * f;
    ramfs* fs = GETRAMFS(iodev->state);

    f = ramfs_open(((ramfs_state *)iodev->state)->memory, fs,fname,RAMFS_READ);
    if(!f) return_error(ramfs_errno_to_code(ramfs_error(fs)));

    memset(pstat, 0, sizeof(*pstat));
    pstat->st_size = ramfile_size(f);
    /* The Windows definition of struct stat doesn't include a st_blocks member
    pstat->st_blocks = (pstat->st_size+blocksize-1)/blocksize;*/
    /* XXX set mtime & ctime */
    ramfile_close(f);
    return 0;
}

static file_enum *
ram_enumerate_init(gx_io_device *iodev, const char *pat, uint patlen,
    gs_memory_t *mem)
{
    gsram_enum * penum = gs_alloc_struct(
    mem, gsram_enum, &st_gsram_enum,
    "ram_enumerate_files_init(file_enum)"
    );
    char *pattern = (char *)gs_alloc_bytes(
    mem, patlen+1, "ram_enumerate_file_init(pattern)"
    );
    ramfs_enum * e = ramfs_enum_new(GETRAMFS(iodev->state));
    if(penum && pattern && e) {
    memcpy(pattern, pat, patlen);
    pattern[patlen]=0;

    penum->memory = mem;
    penum->pattern = pattern;
    penum->e = e;
    return (file_enum *)penum;
    }
    if (penum) gs_free_object(mem,penum,"ramfs_enum_init(ramfs_enum)");
    if (pattern)
    gs_free_object(mem, pattern, "ramfs_enum_init(pattern)");
    if(e) ramfs_enum_end(e);
    return NULL;
}

static void
ram_enumerate_close(file_enum *pfen)
{
    gsram_enum *penum = (gsram_enum *)pfen;
    gs_memory_t *mem = penum->memory;

    ramfs_enum_end(penum->e);
    gs_free_object(mem, penum->pattern, "ramfs_enum_init(pattern)");
    gs_free_object(mem, penum, "ramfs_enum_init(ramfs_enum)");
}

static uint
ram_enumerate_next(file_enum *pfen, char *ptr, uint maxlen)
{
    gsram_enum *penum = (gsram_enum *)pfen;

    char * filename;
    while ((filename = ramfs_enum_next(penum->e))) {
    if (string_match((byte *)filename, strlen(filename),
        (byte *)penum->pattern,
        strlen(penum->pattern), 0)) {
        if (strlen(filename) < maxlen)
        memcpy(ptr, filename, strlen(filename));
        return strlen(filename);    /* if > maxlen, caller will detect rangecheck */
    }
    }
    /* ran off end of list, close the enum */
    ram_enumerate_close(pfen);
    return ~(uint)0;
}

static int
ram_get_params(gx_io_device * iodev, gs_param_list * plist)
{
    int code;
    int i0 = 0, so = 1;
    bool btrue = true, bfalse = false;
    ramfs* fs = GETRAMFS(iodev->state);
    int BlockSize;
    long Free, LogicalSize;

    BlockSize = ramfs_blocksize(fs);
    LogicalSize = MAXBLOCKS;
    Free = ramfs_blocksfree(fs);

    if (
    (code = param_write_bool(plist, "HasNames",        &btrue)) < 0 ||
    (code = param_write_int (plist, "BlockSize",       &BlockSize)) < 0 ||
    (code = param_write_long(plist, "Free",            &Free)) < 0 ||
    (code = param_write_int (plist, "InitializeAction",&i0)) < 0 ||
    (code = param_write_bool(plist, "Mounted",         &btrue)) < 0 ||
    (code = param_write_bool(plist, "Removable",       &bfalse)) < 0 ||
    (code = param_write_bool(plist, "Searchable",      &btrue)) < 0 ||
    (code = param_write_int (plist, "SearchOrder",     &so)) < 0 ||
    (code = param_write_bool(plist, "Writeable",       &btrue)) < 0 ||
    (code = param_write_long(plist, "LogicalSize",     &LogicalSize)) < 0
    )
    return code;
    return 0;
}
