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);
}