If you have comments or questions concerning this source file, discuss them in the forum.
/*
Copyright (c) 2002 Nicolai Haehnle

See the license.txt for details. If that file was not included in the
source distributions, please email <prefect@rtts.org>
*/
// lib_files.cpp -- uniform file access

#include "library.h"

#include <sys/types.h>
#include <sys/stat.h>

#ifdef _WIN32
#include <io.h>

#define stat _stat
#else
#include <glob.h>
#endif

/*
==============
L_AutoExtension

Append extension (".foo") to the filename if it doesn't already have an extension
==============
*/
char *L_AutoExtension(char *buf, int bufsize, const char *ext)
{
    char *dot;
    char *p;
    int extlen;

    dot = 0;

    for(p = buf; *p; p++) {
        if (*p == '/' || *p == '\\')
            dot = 0;
        else if (*p == '.')
            dot = p;
    }

    if (!dot) {
        extlen = strlen(ext);

        if (p - buf + extlen < bufsize)
            memcpy(p, ext, extlen + 1);
    }

    return buf;
}

/*
==============
L_StripExtension

Strip the extension (if any) from the filename
==============
*/
char *L_StripExtension(char *fname)
{
    char *p;
    char *dot = 0;

    for(p = fname; *p; p++) {
        if (*p == '/' || *p == '\\')
            dot = 0;
        else if (*p == '.')
            dot = p;
    }

    if (dot)
        *dot = 0;

    return fname;
}

/*
==============
L_RelativePath

Translate filename so that it is relative to basefile.
Basically concatenates the two strings, but removes the filename part
of basefile (if any)
==============
*/
char *L_RelativePath(char *buf, int buflen, const char *basefile, const char *filename)
{
    const char *p;
    int endbase;

    if (*filename == '/' || *filename == '\\') { // it's an absolute filename
        xstrcpy(buf, buflen, filename);
        return buf;
    }

    // find the end of the basefile name
    endbase = 0;
    for(p = basefile; *p; p++) {
        if (*p == '/' || *p == '\\')
            endbase = p-basefile+1;
    }

    if (!endbase) { // no path in basefile
        xstrcpy(buf, buflen, filename);
        return buf;
    }

    // copy the base path
    xstrcpy(buf, buflen, basefile);
    if (endbase < buflen)
        buf[endbase] = 0;
    xstrcat(buf, buflen, filename);

    return buf;
}

/*
==============
L_FindFiles

Returns the number of files found, and stores a pointer to an
array of strings in results (the *** is way too cool, so I
didn't make the function return that array :D).
There doesn't seem to be an even remotely cross-platform way of
doing this
==============
*/
#ifdef _WIN32
int L_FindFiles(const char *path, const char *pattern, char ***results)
{
    char buf[MAX_OSPATH];
    struct _finddata_t c_file;
    long hFile;
    int count;

    if (path)
        snprintf(buf, sizeof(buf), "%s\\%s", path, pattern);
    else
        xstrcpy(buf, sizeof(buf), pattern);

    *results = 0;
    count = 0;

    hFile = _findfirst(buf, &c_file);
    if (hFile == -1)
        return 0;

    do {
        *results = (char **)L_Realloc(*results, sizeof(char *)*(count+1), TAG_FILES);
        (*results)[count++] = L_Strdup(c_file.name, TAG_FILES);
    } while(_findnext(hFile, &c_file) == 0);

    _findclose(hFile);

    *results = (char **)L_Realloc(*results, sizeof(char *)*(count+1), TAG_FILES);
    (*results)[count] = 0;

    return count;
}
#else
int L_FindFiles(const char *path, const char *pattern, char ***results)
{
    char buf[MAX_OSPATH];
    glob_t gl;
    int i, count;
    int ofs;

    if (path) {
        snprintf(buf, sizeof(buf), "%s/%s", path, pattern);
        ofs = strlen(path)+1;
    } else {
        xstrcpy(buf, sizeof(buf), pattern);
        ofs = 0;
    }

    *results = 0;

    if (glob(buf, 0, NULL, &gl))
        return 0;

    count = gl.gl_pathc;

    *results = (char **)L_Malloc(sizeof(char *)*(count+1), TAG_FILES);
    for(i = 0; i < count; i++)
        (*results)[i] = L_Strdup(gl.gl_pathv[i] + ofs, TAG_FILES);
    (*results)[i] = 0;

    globfree(&gl);

    return count;
}
#endif

/*
==============
L_FreeFindFiles

Frees the array allocated by L_FindFiles
==============
*/
void L_FreeFindFiles(char **results)
{
    char **p;

    if (!results)
        return; // this is valid

    for(p = results; *p; p++)
        L_Free(*p);
    L_Free(results);
}

/*
==============
CanonicalizeName

Turn the given path into a simpler one. Returns false for illegal paths.
==============
*/
static bool CanonicalizeName(char *buf, int bufsize, const char *path)
{
    const char *tok;
    int toklen;
    char *p;

    p = buf;
    while(*path) {
        path += strspn(path, "/\\");
        if (!*path)
            return false; // can't open directories

        // scan for the slash
        tok = path;
        toklen = strcspn(path, "/\\");
        path += toklen;

        // check for '.', '..', ...
        if (toklen == 1 && strncmp(tok, ".", 1))
            continue;
        if (toklen == 2 && strncmp(tok, "..", 2)) {
            p--;
            while(p > buf) {
                if (*(p-1) == '/')
                    break;
                p--;
            }
            if (p < buf)
                return false; // too many '..'s
        }

        // use the token
        if (p + toklen >= buf + bufsize)
            return false; // path is too long

        memcpy(p, tok, toklen);
        p += toklen;
        if (*path)
            *p++ = '/';
    }
    *p = 0;

    return true;
}


/*
==============
L_FileExists

Returns true if the given file exists, and false if it doesn't.
Also returns false if the pathname is invalid
==============
*/
bool L_FileExists(const char *path)
{
    char canonical[MAX_OSPATH];
    struct stat st;

    if (!CanonicalizeName(canonical, sizeof(canonical), path))
        return false;

    if (stat(canonical, &st) == -1)
        return false;

    return true;
}


/*
==============================================================================

LFileRead IMPLEMENTATION

==============================================================================
*/

/*
==============
LFileRead::LFileRead

Create the object with nothing to read
==============
*/
LFileRead::LFileRead()
{
    data = 0;
}

/*
==============
LFileRead::~LFileRead

Close the file if open
==============
*/
LFileRead::~LFileRead()
{
    if (data)
        Close();
}

/*
==============
LFileRead::Open

Loads a file into memory.
Reserves one additional byte which is zeroed, so that text files can
be handled like a normal C string.
Throws an exception if the file couldn't be loaded for whatever reason.
==============
*/
void LFileRead::Open(const char *fname)
{
    char canonical[MAX_OSPATH];
    FILE *file;

    lassert(!data);

    if (!CanonicalizeName(canonical, sizeof(canonical), fname))
        throw LError("Bad filename: %s", fname);

    file = 0;

    try
    {
        file = fopen(canonical, "rb");
        if (!file)
            throw LError("Couldn't open %s", fname);

        // determine the size of the file (rather quirky, but it doesn't require
        // potentially unportable functions)
        fseek(file, 0, SEEK_END);
        length = ftell(file);
        fseek(file, 0, SEEK_SET);

        // allocate a buffer and read the entire file into it
        data = L_Malloc(length + 1, TAG_FILES);
        if (fread(data, length, 1, file) != 1)
            throw LError("Read failed for %s", canonical);
        ((byte *)data)[length] = 0;

        fclose(file);
        file = 0;
    }
    catch(...)
    {
        if (file)
            fclose(file);
        if (data) {
            L_Free(data);
            data = 0;
        }
        throw;
    }

    filepos = 0;
}

/*
==============
LFileRead::TryOpen

Works just like Open, but returns false when the load fails.
==============
*/
bool LFileRead::TryOpen(const char *fname)
{
    try {
        Open(fname);
    } catch(LError &err) {
        L_Printf("%s\n", err.Get());
        return false;
    }

    return true;
}

/*
==============
LFileRead::Close

Frees allocated memory
==============
*/
void LFileRead::Close()
{
    lassert(data);

    L_Free(data);
}

/*
==============
LFileRead::SetFilePos

Set the file pointer to the given location.
Raises an exception when the pointer is out of bound
==============
*/
void LFileRead::SetFilePos(int pos)
{
    lassert(data);

    if (pos < 0 || pos >= length)
        throw LError("SetFilePos: %i out of bound", pos);

    filepos = pos;
}

/*
==============
LFileRead::CString

Read a zero-terminated string from the file
==============
*/
char *LFileRead::CString(int pos)
{
    char *string, *p;
    int i;

    lassert(data);

    i = pos;
    if (pos < 0)
        i = filepos;
    if (i >= length)
        throw LError("File boundary exceeded");

    string = (char *)data + i;
    for(p = string; *p; p++, i++) ;
    i++; // beyond the NUL

    if (i > length)
        throw LError("File boundary exceeded");

    if (pos < 0)
        filepos = i;

    return string;
}

/*
==============================================================================

LFileWrite IMPLEMENTATION

==============================================================================
*/

/*
==============
LFileWrite::LFileWrite

Set the buffer to empty
==============
*/
LFileWrite::LFileWrite()
{
    data = 0;
    length = 0;
    maxsize = 0;
    filepos = 0;
}

/*
==============
LFileWrite::~LFileWrite

Clear any remaining allocated data
==============
*/
LFileWrite::~LFileWrite()
{
    if (data)
        Clear();
}

/*
==============
LFileWrite::Clear

Clears the object's buffer
==============
*/
void LFileWrite::Clear()
{
    if (data)
        L_Free(data);

    data = 0;
    length = 0;
    maxsize = 0;
    filepos = 0;
}

/*
==============
LFileWrite::Write

Actually write the file out to disk.
If successful, this clears the buffers. Otherwise, an exception
is raised but the buffer remains intact (don't worry, it will be
cleared by the destructor).
==============
*/
void LFileWrite::Write(const char *filename)
{
    char canonical[MAX_OSPATH];
    FILE *f;
    int c;

    if (!CanonicalizeName(canonical, sizeof(canonical), filename))
        throw LError("Bad filename: %s", filename);

    f = fopen(canonical, "wb");
    if (!f)
        throw LError("Couldn't open %s for writing", canonical);

    c = fwrite(data, length, 1, f);
    fclose(f);

    if (!f)
        throw LError("Write to %s failed", canonical);

    Clear();
}

/*
==============
LFileWrite::TryWrite

Same as Write, but returns falls if the write fails
==============
*/
bool LFileWrite::TryWrite(const char *filename)
{
    try {
        Write(filename);
        return true;
    } catch(LError &err) {
        L_Printf("%s\n", err.Get());
        return false;
    }
}

/*
==============
LFileWrite::SetFilePos

Set the file pointer to a new location. The position can be beyond
the current end of file.
==============
*/
void LFileWrite::SetFilePos(int pos)
{
    lassert(pos >= 0);
    filepos = pos;
}

/*
==============
LFileWrite::Data

Write data at the given location. If pos is -1, write at the
file pointer and advance the file pointer.
==============
*/
void LFileWrite::Data(const void *buf, int size, int pos)
{
    int i;

    lassert(data || !length);

    i = pos;
    if (pos < 0) {
        i = filepos;
        filepos += size;
    }

    if (i+size > length) {
        if (i+size > maxsize) {
            maxsize += 4096;
            if (i+size > maxsize)
                maxsize = i+size;

            data = L_Realloc(data, maxsize, TAG_FILES);
        }

        length = i+size;
    }

    memcpy((byte *)data + i, buf, size);
}

/*
==============
LFileWrite::Printf

This is a perfectly normal printf
==============
*/
void LFileWrite::Printf(const char *fmt, ...)
{
    char buf[2048];
    va_list va;
    int i;

    va_start(va, fmt);
    i = vsnprintf(buf, sizeof(buf), fmt, va);
    va_end(va);

    if (i < 0)
        throw LError("LFileWrite::Printf: buffer exceeded");

    Data(buf, i, -1);
}