quakeforge/qw/source/crudefile.c
Bill Currie 12c84046f3 [cvar] Make cvars properly typed
This is an extremely extensive patch as it hits every cvar, and every
usage of the cvars. Cvars no longer store the value they control,
instead, they use a cexpr value object to reference the value and
specify the value's type (currently, a null type is used for strings).
Non-string cvars are passed through cexpr, allowing expressions in the
cvars' settings. Also, cvars have returned to an enhanced version of the
original (id quake) registration scheme.

As a minor benefit, relevant code having direct access to the
cvar-controlled variables is probably a slight optimization as it
removed a pointer dereference, and the variables can be located for data
locality.

The static cvar descriptors are made private as an additional safety
layer, though there's nothing stopping external modification via
Cvar_FindVar (which is needed for adding listeners).

While not used yet (partly due to working out the design), cvars can
have a validation function.

Registering a cvar allows a primary listener (and its data) to be
specified: it will always be called first when the cvar is modified. The
combination of proper listeners and direct access to the controlled
variable greatly simplifies the more complex cvar interactions as much
less null checking is required, and there's no need for one cvar's
callback to call another's.

nq-x11 is known to work at least well enough for the demos. More testing
will come.
2022-04-24 19:15:22 +09:00

403 lines
7.8 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;
int crudefile_quota;
static cvar_t crudefile_quota_cvar = {
.name = "crudefile_quota",
.description =
"Maximum space available to the Crude File system, -1 to totally "
"disable file writing",
.default_value = "-1",
.flags = CVAR_ROM,
.value = { .type = &cexpr_int, .value = &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 ();
Cvar_Register (&crudefile_quota_cvar, 0, 0);
cf_maxsize = crudefile_quota;
}
/*
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;
}