ocarina/core/file.c
Anna Schumaker 1633946981 core/file: Check if a file is too new to be opened
I'm hitting this problem while developing 6.5, since file formats are
going to change.  Let's handle this situation gracefully rather than
segfaulting.

This patch changes versioning problems into fatal errors to prevent us
from overwriting data already on disk.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
2016-08-28 10:07:27 -04:00

304 lines
6.1 KiB
C

/*
* Copyright 2013 (c) Anna Schumaker.
*/
#include <core/file.h>
#include <core/string.h>
#include <core/version.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#define REPORT_ERROR(fname, error) \
g_printerr("%s (%s:%d): %s: %s\n", __func__, __FILE__, __LINE__, fname, error)
#define REPORT_ERRNO(fname) REPORT_ERROR(fname, strerror(errno))
static gchar *__file_path(const gchar *base, const gchar *dir,
const gchar *name)
{
return g_build_filename(base, OCARINA_NAME, dir ? dir : "", name, NULL);
}
static gchar *__file_build_path(const gchar *base, const gchar *dir,
const gchar *name)
{
if (string_length(name) == 0)
return g_strdup("");
return __file_path(base, dir, name);
}
static gchar *__file_build_tmp(const gchar *base, const gchar *dir,
const gchar *name)
{
gchar *tmp, *res;
if (string_length(name) == 0)
return g_strdup("");
tmp = g_strdup_printf(".%s.tmp", name);
res = __file_path(base, dir, tmp);
g_free(tmp);
return res;
}
static bool __file_exists(gchar *path)
{
bool ret = g_file_test(path, G_FILE_TEST_EXISTS);
g_free(path);
return ret;
}
static FILE *__file_open(gchar *path, const gchar *mode)
{
FILE *ret = g_fopen(path, mode);
if (!ret)
REPORT_ERRNO(path);
g_free(path);
return ret;
}
static void __file_close(FILE *file, gchar *path, gchar *tmp)
{
if (file) {
fclose(file);
if (path && tmp)
g_rename(tmp, path);
}
g_free(path);
g_free(tmp);
}
static bool __file_mkdir(const gchar *basedir, const gchar *subdir)
{
gchar *dir = __file_path(basedir, subdir, NULL);
int ret = g_mkdir_with_parents(dir, 0755);
if (ret != 0)
REPORT_ERRNO(dir);
g_free(dir);
return ret == 0;
}
static bool __file_can_write(struct file *file)
{
gchar *path = file_path(file);
bool ret = true;
if (g_access(path, F_OK) == 0 && g_access(path, W_OK) < 0)
ret = false;
g_free(path);
return ret;
}
void file_init(struct file *file, const gchar *name,
unsigned int version, unsigned int min)
{
file->f_mode = OPEN_READ;
file->f_version = version;
file->f_prev = 0;
file->f_min = 0;
file->f_file = NULL;
file->f_name = name;
}
void cache_file_init(struct cache_file *file, gchar *subdir, gchar *name)
{
file->cf_file = NULL;
file->cf_name = name;
file->cf_subdir = subdir;
}
gchar *file_path(struct file *file)
{
return __file_build_path(g_get_user_data_dir(), NULL, file->f_name);
}
gchar *cache_file_path(struct cache_file *file)
{
return __file_build_path(g_get_user_cache_dir(), file->cf_subdir,
file->cf_name);
}
gchar *file_write_path(struct file *file)
{
return __file_build_tmp(g_get_user_data_dir(), NULL, file->f_name);
}
gchar *cache_file_write_path(struct cache_file *file)
{
return __file_build_tmp(g_get_user_cache_dir(), file->cf_subdir,
file->cf_name);
}
const unsigned int file_version(struct file *file)
{
if (file->f_file && (file->f_mode == OPEN_READ))
return file->f_prev;
return file->f_version;
}
bool file_exists(struct file *file)
{
return __file_exists(file_path(file));
}
bool cache_file_exists(struct cache_file *file)
{
return __file_exists(cache_file_path(file));
}
static bool __file_open_read(struct file *file)
{
if (!file_exists(file))
return false;
file->f_file = __file_open(file_path(file), "r");
if (!file->f_file)
return false;
file->f_mode = OPEN_READ;
if (file_readf(file, "%u\n", &file->f_prev) != 1)
return false;
if (file->f_prev < file->f_min) {
REPORT_ERROR(file->f_name, "File too old to be upgraded.");
file_close(file);
exit(1);
}
if (file->f_prev > file->f_version) {
REPORT_ERROR(file->f_name, "File too new to be opened.");
file_close(file);
exit(1);
}
return true;
}
static bool __file_open_write(struct file *file)
{
if (!__file_mkdir(g_get_user_data_dir(), NULL))
return false;
if (!__file_can_write(file))
return false;
file->f_file = __file_open(file_write_path(file), "w");
if (!file->f_file)
return false;
file->f_mode = OPEN_WRITE;
return file_writef(file, "%d\n", file->f_version) > 0;
}
bool file_open(struct file *file, enum open_mode mode)
{
if ((string_length(file->f_name) == 0) || (file->f_file != NULL))
return false;
if (mode == OPEN_READ)
return __file_open_read(file);
return __file_open_write(file);
}
bool cache_file_open(struct cache_file *file, enum open_mode mode)
{
if (mode == OPEN_READ)
return false;
if ((string_length(file->cf_name) == 0) || (file->cf_file != NULL))
return false;
if (!__file_mkdir(g_get_user_cache_dir(), file->cf_subdir))
return false;
file->cf_file = __file_open(cache_file_write_path(file), "wb");
return file->cf_file != NULL;
}
void file_close(struct file *file)
{
__file_close(file->f_file,
file->f_mode == OPEN_WRITE ? file_path(file) : NULL,
file->f_mode == OPEN_WRITE ? file_write_path(file) : NULL);
file->f_file = NULL;
}
void cache_file_close(struct cache_file *file)
{
__file_close(file->cf_file,
cache_file_path(file),
cache_file_write_path(file));
file->cf_file = NULL;
}
int file_readf(struct file *file, const char *fmt, ...)
{
va_list argp;
int ret;
va_start(argp, fmt);
ret = vfscanf(file->f_file, fmt, argp);
va_end(argp);
return ret;
}
gchar *file_readl(struct file *file)
{
gchar *res;
if (file_readf(file, "%m[^\n]\n", &res) == 0)
return g_strdup("");
g_strstrip(res);
return res;
}
int file_writef(struct file *file, const char *fmt, ...)
{
va_list argp;
int ret;
va_start(argp, fmt);
ret = g_vfprintf(file->f_file, fmt, argp);
va_end(argp);
if (ret < 0)
REPORT_ERRNO(file->f_name);
return ret;
}
int cache_file_write(struct cache_file *file, const void *data, size_t len)
{
if (fwrite(data, len, 1, file->cf_file) == 1)
return len;
return -1;
}
bool cache_file_import(struct cache_file *file, const gchar *srcpath)
{
gchar *contents = NULL;
gsize length = 0;
if (!file->cf_file || !srcpath)
return false;
if (!g_file_get_contents(srcpath, &contents, &length, NULL))
return false;
cache_file_write(file, contents, length);
return true;
}
bool file_remove(struct file *file)
{
int ret = -1;
gchar *path;
if (!file->f_file) {
path = file_path(file);
ret = g_unlink(path);
g_free(path);
}
return ret == 0;
}