quakeforge/qw/source/crudefile.c
Bill Currie 5f93c115ff [util] Make developer flag names easier to manage
They're now an enum, and the flag part of the name is all lowercase, but
now the flag definitions and names list will never get out of sync.
2021-03-29 22:38:47 +09:00

396 lines
7.6 KiB
C

/*
sv_crudefile.c
(description)
Copyright (C) 2001 Adam Olsen
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#ifdef HAVE_CTYPE_H
# include <ctype.h>
#endif
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "QF/cvar.h"
#include "QF/dstring.h"
#include "QF/quakefs.h"
#include "QF/sys.h"
#include "QF/va.h"
#include "QF/zone.h"
#include "compat.h"
#include "qw/include/crudefile.h"
int cf_maxsize; // max combined file size (eg quota)
int cf_cursize; // current combined file size
typedef struct cf_file_s {
QFile *file;
char *path;
char *buf;
int size;
int writtento;
char mode; // 'r' for read, 'w' for write, 'a' for append
} cf_file_t;
cf_file_t *cf_filep;
cvar_t *crudefile_quota;
int cf_filepcount; // elements in array
int cf_openfiles; // used elements
#define CF_DIR "cf/"
#define CF_MAXFILES 100
#define CF_BUFFERSIZE 256
/*
CF_ValidDesc
Returns 1 if the file descriptor is valid.
*/
static int
CF_ValidDesc (int desc)
{
if (desc >= 0 && desc < cf_filepcount && cf_filep[desc].file)
return 1;
return 0;
}
/*
CF_AlreadyOpen
Returns 1 if mode == 'r' and the file is already open for
writing, or if if mode == 'w' or 'a' and the file's already open at all.
*/
static int
CF_AlreadyOpen (const char * path, char mode)
{
int i;
for (i = 0; i < cf_filepcount; i++) {
if (!cf_filep[i].file)
continue;
if (mode == 'r' && (cf_filep[i].mode == 'w' || cf_filep[i].mode == 'a') &&
strequal (path, cf_filep[i].path))
return 1;
if ((mode == 'w' || mode == 'a')&& strequal (path, cf_filep[i].path))
return 1;
}
return 0;
}
/*
CF_GetFileSize
Returns the size of the specified file
*/
static int
CF_GetFileSize (const char *path)
{
struct stat buf;
if (!stat (path, &buf))
return 0;
return buf.st_size;
}
/*
CF_BuildQuota
Calculates the currently used space
*/
static void
CF_BuildQuota (void)
{
static dstring_t *path;
struct dirent *i;
DIR *dir;
if (!path)
path = dstring_new ();
dsprintf (path, "%s/%s/%s", qfs_userpath, qfs_gamedir->dir.def, CF_DIR);
dir = opendir (path->str);
if (!dir)
return;
cf_cursize = 0;
while ((i = readdir (dir))) {
cf_cursize += CF_GetFileSize (va (0, "%s/%s", path->str, i->d_name));
}
closedir (dir);
}
/*
CF_Init
Ye ol' Init function :)
*/
void
CF_Init (void)
{
CF_BuildQuota ();
crudefile_quota = Cvar_Get ("crudefile_quota", "-1", CVAR_ROM, NULL,
"Maximum space available to the Crude File "
"system, -1 to totally disable file writing");
cf_maxsize = crudefile_quota->int_val;
}
/*
CF_CloseAllFiles
Closes all open files, printing warnings if developer is on
*/
void
CF_CloseAllFiles ()
{
int i;
for (i = 0; i < cf_filepcount; i++)
if (cf_filep[i].file) {
Sys_MaskPrintf (SYS_dev, "Warning: closing Crude File %d left "
"over from last map\n", i);
CF_Close (i);
}
}
/*
CF_Open
cfopen opens a file, either for reading or writing (not both).
returns a file descriptor >= 0 on success, < 0 on failure.
mode is either r or w.
*/
int
CF_Open (const char *path, const char *mode)
{
char *j;
dstring_t *fullpath = dstring_new ();
int desc, oldsize, i;
QFile *file;
if (cf_openfiles >= CF_MAXFILES) {
return -1;
}
// check for paths with ..
if (strequal (path, "..")
|| !strncmp (path, "../", 3)
|| strstr (path, "/../")
|| (strlen (path) >= 3
&& strequal (path + strlen (path) - 3, "/.."))) {
return -1;
}
if (!(strequal(mode, "w") || strequal(mode, "r") || strequal(mode, "a"))) {
return -1;
}
if (mode[0] == 'w' && cf_maxsize < 0) { // can't even delete if quota < 0
return -1;
}
dsprintf (fullpath, "%s/%s/%s", qfs_gamedir->dir.def, CF_DIR, path);
j = fullpath->str + strlen (fullpath->str) - strlen (path);
for (i = 0; path[i]; i++, j++) // strcpy, but force lowercase
*j = tolower ((byte) path[i]);
*j = '\0';
if (CF_AlreadyOpen (fullpath->str, mode[0])) {
dstring_delete (fullpath);
return -1;
}
if (mode[0] == 'w')
oldsize = CF_GetFileSize (fullpath->str);
else
oldsize = 0;
file = QFS_Open (fullpath->str, mode);
if (file) {
if (cf_openfiles >= cf_filepcount) {
cf_filepcount++;
cf_filep = realloc (cf_filep, sizeof (cf_file_t) * cf_filepcount);
if (!cf_filep) {
Sys_Error ("CF_Open: memory allocation error!");
}
cf_filep[cf_filepcount - 1].file = 0;
}
for (desc = 0; cf_filep[desc].file; desc++)
;
cf_filep[desc].path = fullpath->str;
cf_filep[desc].file = file;
cf_filep[desc].buf = 0;
cf_filep[desc].size = 0;
cf_filep[desc].writtento = 0;
cf_filep[desc].mode = mode[0];
cf_cursize -= oldsize;
cf_openfiles++;
return desc;
}
return -1;
}
/*
CF_Close
cfclose closes a file descriptor. returns nothing. to prevent
leakage, all open files are closed on map load, and warnings are
printed if developer is set to 1.
*/
void
CF_Close (int desc)
{
char *path;
if (!CF_ValidDesc (desc))
return;
if (cf_filep[desc].mode == 'w' && !cf_filep[desc].writtento)
unlink (cf_filep[desc].path);
path = cf_filep[desc].path;
Qclose (cf_filep[desc].file);
cf_filep[desc].file = 0;
free (cf_filep[desc].buf);
cf_openfiles--;
cf_cursize -= CF_GetFileSize (path);
free (path);
}
/*
CF_Read
cfread returns a single string read in from the file. an empty
string either means an empty string or eof, use cfeof to check.
*/
const char *
CF_Read (int desc)
{
int len = 0;
if (!CF_ValidDesc (desc) || cf_filep[desc].mode != 'r') {
return "";
}
do {
int foo;
if (cf_filep[desc].size <= len) {
char *t = realloc (cf_filep[desc].buf, cf_filep[desc].size +
CF_BUFFERSIZE);
if (!t) {
Sys_Error ("CF_Read: memory allocation error!");
}
cf_filep[desc].buf = t;
cf_filep[desc].size += CF_BUFFERSIZE;
}
foo = Qgetc (cf_filep[desc].file);
if (foo == EOF)
foo = 0;
cf_filep[desc].buf[len] = (char) foo;
len++;
} while (cf_filep[desc].buf[len - 1]);
return cf_filep[desc].buf;
}
/*
CF_Write
cfwrite writes a string to the file, including a trailing nul,
returning the number of characters written. It returns 0 if
there was an error in writing, such as if the quota would have
been exceeded.
*/
int
CF_Write (int desc, const char *buf) // should be const char *, but Qwrite isn't...
{
int len;
if (!CF_ValidDesc (desc) || !(cf_filep[desc].mode == 'w' || cf_filep[desc].mode == 'a') || cf_cursize >=
cf_maxsize) {
return 0;
}
len = strlen (buf) + 1;
if (len > cf_maxsize - cf_cursize) {
return 0;
}
len = Qwrite (cf_filep[desc].file, buf, len);
if (len < 0)
len = 0;
cf_cursize += len;
cf_filep[desc].writtento = 1;
return len;
}
/*
CF_EOF
cfeof returns 1 if you're at the end of the file, 0 if not, and
-1 on a bad descriptor.
*/
int
CF_EOF (int desc)
{
if (!CF_ValidDesc (desc)) {
return -1;
}
return Qeof (cf_filep[desc].file);
}
/*
CF_Quota
returns the number of characters left in the quota, or <= 0 if it's met or
(somehow) exceeded.
*/
int
CF_Quota ()
{
return cf_maxsize - cf_cursize;
}