mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2024-11-25 13:51:36 +00:00
12c84046f3
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.
403 lines
7.8 KiB
C
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;
|
|
}
|