avp/src/files.c
2019-08-20 02:22:37 +02:00

575 lines
11 KiB
C

#define _BSD_SOURCE
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <fnmatch.h>
#include "files.h"
static char *local_dir;
static char *global_dir;
/*
Sets the local and global directories used by the other functions.
Local = ~/.dir, where config and user-installed files are kept.
Global = installdir, where installed data is stored.
*/
int SetGameDirectories(const char *local, const char *global)
{
struct stat buf;
local_dir = strdup(local);
global_dir = strdup(global);
if (stat(local_dir, &buf) == -1) {
printf("Creating local directory %s...\n", local_dir);
mkdir(local_dir, S_IRWXU);
}
return 0;
}
#define DIR_SEPARATOR "/"
static char *FixFilename(const char *filename, const char *prefix, int force)
{
char *f, *ptr;
int flen;
flen = strlen(filename) + strlen(prefix) + 2;
f = (char *)malloc(flen);
strcpy(f, prefix);
strcat(f, DIR_SEPARATOR);
strcat(f, filename);
ptr = f;
while (*ptr) {
if ((*ptr == '/') || (*ptr == '\\') || (*ptr == ':')) {
*ptr = DIR_SEPARATOR[0];
} else if (*ptr == '\r' || *ptr == '\n') {
*ptr = 0;
break;
} else {
if (force)
*ptr = tolower(*ptr);
}
ptr++;
}
return f;
}
/*
Open a file of type type, with mode mode.
Mode can be:
#define FILEMODE_READONLY 0x01
#define FILEMODE_WRITEONLY 0x02
#define FILEMODE_READWRITE 0x04
#define FILEMODE_APPEND 0x08
Type is (mode = ReadOnly):
#define FILETYPE_PERM 0x08 // try the global dir only
#define FILETYPE_OPTIONAL 0x10 // try the global dir first, then try the local dir
#define FILETYPE_CONFIG 0x20 // try the local dir only
Type is (mode = WriteOnly or ReadWrite):
FILETYPE_PERM: error
FILETYPE_OPTIONAL: error
FILETYPE_CONFIG: try the local dir only
*/
FILE *OpenGameFile(const char *filename, int mode, int type)
{
char *rfilename;
char *openmode;
FILE *fp;
if ((type != FILETYPE_CONFIG) && (mode != FILEMODE_READONLY))
return NULL;
switch(mode) {
case FILEMODE_READONLY:
openmode = "rb";
break;
case FILEMODE_WRITEONLY:
openmode = "wb";
break;
case FILEMODE_READWRITE:
openmode = "w+";
break;
case FILEMODE_APPEND:
openmode = "ab";
break;
default:
return NULL;
}
if (type != FILETYPE_CONFIG) {
rfilename = FixFilename(filename, global_dir, 0);
fp = fopen(rfilename, openmode);
free(rfilename);
if (fp != NULL) {
return fp;
}
rfilename = FixFilename(filename, global_dir, 1);
fp = fopen(rfilename, openmode);
free(rfilename);
if (fp != NULL) {
return fp;
}
}
if (type != FILETYPE_PERM) {
rfilename = FixFilename(filename, local_dir, 0);
fp = fopen(rfilename, openmode);
free(rfilename);
if (fp != NULL) {
return fp;
}
rfilename = FixFilename(filename, local_dir, 1);
fp = fopen(rfilename, openmode);
free(rfilename);
return fp;
}
return NULL;
}
/*
Close a fd returned from OpenGameFile
Currently this just uses stdio, so CloseGameFile is redundant.
*/
int CloseGameFile(FILE *pfd)
{
return fclose(pfd);
}
/*
Get the filesystem attributes of a file
#define FILEATTR_DIRECTORY 0x0100
#define FILEATTR_READABLE 0x0200
#define FILEATTR_WRITABLE 0x0400
Error or can't access it: return value of 0 (What is the game going to do about it anyway?)
*/
static int GetFA(const char *filename)
{
struct stat buf;
int attr;
attr = 0;
if (stat(filename, &buf) == 0) {
if (S_ISDIR(buf.st_mode)) {
attr |= FILEATTR_DIRECTORY;
}
if (access(filename, R_OK) == 0) {
attr |= FILEATTR_READABLE;
}
if (access(filename, W_OK) == 0) {
attr |= FILEATTR_WRITABLE;
}
}
return attr;
}
static time_t GetTS(const char *filename)
{
struct stat buf;
if (stat(filename, &buf) == 0) {
return buf.st_mtime;
}
return 0;
}
int GetGameFileAttributes(const char *filename, int type)
{
struct stat buf;
char *rfilename;
int attr;
attr = 0;
if (type != FILETYPE_CONFIG) {
rfilename = FixFilename(filename, global_dir, 0);
if (stat(rfilename, &buf) == 0) {
if (S_ISDIR(buf.st_mode)) {
attr |= FILEATTR_DIRECTORY;
}
if (access(rfilename, R_OK) == 0) {
attr |= FILEATTR_READABLE;
}
if (access(rfilename, W_OK) == 0) {
attr |= FILEATTR_WRITABLE;
}
free(rfilename);
return attr;
}
free(rfilename);
rfilename = FixFilename(filename, global_dir, 1);
if (stat(rfilename, &buf) == 0) {
if (S_ISDIR(buf.st_mode)) {
attr |= FILEATTR_DIRECTORY;
}
if (access(rfilename, R_OK) == 0) {
attr |= FILEATTR_READABLE;
}
if (access(rfilename, W_OK) == 0) {
attr |= FILEATTR_WRITABLE;
}
free(rfilename);
return attr;
}
free(rfilename);
}
if (type != FILETYPE_PERM) {
rfilename = FixFilename(filename, local_dir, 0);
if (stat(rfilename, &buf) == 0) {
if (S_ISDIR(buf.st_mode)) {
attr |= FILEATTR_DIRECTORY;
}
if (access(rfilename, R_OK) == 0) {
attr |= FILEATTR_READABLE;
}
if (access(rfilename, W_OK) == 0) {
attr |= FILEATTR_WRITABLE;
}
free(rfilename);
return attr;
}
free(rfilename);
rfilename = FixFilename(filename, local_dir, 1);
if (stat(rfilename, &buf) == 0) {
if (S_ISDIR(buf.st_mode)) {
attr |= FILEATTR_DIRECTORY;
}
if (access(rfilename, R_OK) == 0) {
attr |= FILEATTR_READABLE;
}
if (access(rfilename, W_OK) == 0) {
attr |= FILEATTR_WRITABLE;
}
free(rfilename);
return attr;
}
free(rfilename);
}
return 0;
}
/*
Delete a file: local dir only
*/
int DeleteGameFile(const char *filename)
{
char *rfilename;
int ret;
rfilename = FixFilename(filename, local_dir, 0);
ret = unlink(rfilename);
free(rfilename);
if (ret == -1) {
rfilename = FixFilename(filename, local_dir, 1);
ret = unlink(rfilename);
free(rfilename);
}
return ret;
}
/*
Create a directory: local dir only
TODO: maybe also mkdir parent directories, if they do not exist?
*/
int CreateGameDirectory(const char *dirname)
{
char *rfilename;
int ret;
rfilename = FixFilename(dirname, local_dir, 0);
ret = mkdir(rfilename, S_IRWXU);
free(rfilename);
if (ret == -1) {
rfilename = FixFilename(dirname, local_dir, 1);
ret = mkdir(rfilename, S_IRWXU);
free(rfilename);
}
return ret;
}
/* This struct is private. */
typedef struct GameDirectory
{
DIR *localdir; /* directory opened with opendir */
DIR *globaldir;
char *localdirname;
char *globaldirname;
char *pat; /* pattern to match */
GameDirectoryFile tmp; /* Temp space */
} GameDirectory;
/*
"Open" a directory dirname, with type type
Returns a pointer to a directory datatype
Pattern is the pattern to match
*/
void *OpenGameDirectory(const char *dirname, const char *pattern, int type)
{
char *localdirname, *globaldirname;
DIR *localdir, *globaldir;
GameDirectory *gd;
globaldir = NULL;
globaldirname = NULL;
if (type != FILETYPE_CONFIG) {
globaldirname = FixFilename(dirname, global_dir, 0);
globaldir = opendir(globaldirname);
if (globaldir == NULL) {
free(globaldirname);
globaldirname = FixFilename(dirname, global_dir, 1);
globaldir = opendir(globaldirname);
if (globaldir == NULL)
free(globaldirname);
}
}
localdir = NULL;
localdirname = NULL;
if (type != FILETYPE_PERM) {
localdirname = FixFilename(dirname, local_dir, 0);
localdir = opendir(localdirname);
if (localdir == NULL) {
free(localdirname);
localdirname = FixFilename(dirname, local_dir, 1);
localdir = opendir(localdirname);
if (localdir == NULL)
free(localdirname);
}
}
if (localdir == NULL && globaldir == NULL)
return NULL;
gd = (GameDirectory *)malloc(sizeof(GameDirectory));
gd->localdir = localdir;
gd->globaldir = globaldir;
gd->localdirname = localdirname;
gd->globaldirname = globaldirname;
gd->pat = strdup(pattern);
return gd;
}
/*
This struct is public.
typedef struct GameDirectoryFile
{
char *filename;
int attr;
} GameDirectoryFile;
*/
/*
Returns the next match of pattern with the contents of dir
f is the current file
*/
GameDirectoryFile *ScanGameDirectory(void *dir)
{
char *ptr;
struct dirent *file;
GameDirectory *directory;
directory = (GameDirectory *)dir;
if (directory->globaldir) {
while ((file = readdir(directory->globaldir)) != NULL) {
if (fnmatch(directory->pat, file->d_name, FNM_PATHNAME) == 0) {
ptr = FixFilename(file->d_name, directory->globaldirname, 0);
directory->tmp.attr = GetFA(ptr);
free(ptr);
directory->tmp.filename = file->d_name;
return &directory->tmp;
}
}
closedir(directory->globaldir);
free(directory->globaldirname);
directory->globaldir = NULL;
directory->globaldirname = NULL;
}
if (directory->localdir) {
while ((file = readdir(directory->localdir)) != NULL) {
if (fnmatch(directory->pat, file->d_name, FNM_PATHNAME) == 0) {
ptr = FixFilename(file->d_name, directory->localdirname, 0);
directory->tmp.attr = GetFA(ptr);
directory->tmp.timestamp = GetTS(ptr);
free(ptr);
directory->tmp.filename = file->d_name;
return &directory->tmp;
}
}
closedir(directory->localdir);
free(directory->localdirname);
directory->localdir = NULL;
directory->localdirname = NULL;
}
return NULL;
}
/*
Close directory
*/
int CloseGameDirectory(void *dir)
{
GameDirectory *directory = (GameDirectory *)dir;
if (directory) {
free(directory->pat);
if (directory->localdirname)
free(directory->localdirname);
if (directory->globaldirname)
free(directory->globaldirname);
if (directory->localdir)
closedir(directory->localdir);
if (directory->globaldir)
closedir(directory->globaldir);
return 0;
}
return -1;
}
#ifdef FILES_DRIVER
int main(int argc, char *argv[])
{
FILE *fp;
char buf[64];
void *dir;
SetGameDirectories("tmp1", "tmp2");
fp = OpenGameFile("tester", FILEMODE_WRITEONLY, FILETYPE_CONFIG);
fputs("test\n", fp);
CloseGameFile(fp);
CreateGameDirectory("yaya");
CreateGameDirectory("tester2");
CreateGameDirectory("tester2/blah");
fp = OpenGameFile("tester", FILEMODE_READONLY, FILETYPE_OPTIONAL);
printf("Read: %s", fgets(buf, 60, fp));
CloseGameFile(fp);
fp = OpenGameFile("tester", FILEMODE_READONLY, FILETYPE_CONFIG);
printf("Read: %s", fgets(buf, 60, fp));
CloseGameFile(fp);
dir = OpenGameDirectory(".", "*", FILETYPE_OPTIONAL);
if (dir != NULL) {
GameDirectoryFile *gd;
while ((gd = ScanGameDirectory(dir)) != NULL) {
printf("Name: %s, Attr: %08X\n", gd->filename, gd->attr);
}
CloseGameDirectory(dir);
} else {
printf("Could not open the directory...\n");
}
DeleteGameFile("tester");
return 0;
}
#endif