* /\ _ \ /\_ \ /\_ \
* \ \ \L\ \\//\ \ \//\ \ __ __ _ __ ___
* \ \ __ \ \ \ \ \ \ \ /'__`\ /'_ `\/\`'__\/ __`\
* \ \ \/\ \ \_\ \_ \_\ \_/\ __//\ \L\ \ \ \//\ \L\ \
* \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
* \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
* /\____/
* \_/__/
*
* File I/O.
*
* See LICENSE.txt for copyright information.
*/
#include "allegro5/allegro.h"
#if defined ALLEGRO_HAVE_FTELLO && defined ALLEGRO_HAVE_FSEEKO
#ifndef _LARGEFILE_SOURCE
#define _LARGEFILE_SOURCE
#endif
#ifndef _FILE_OFFSET_BITS
#define _FILE_OFFSET_BITS 64
#endif
#endif
#include <stdio.h>
#include "allegro5/internal/aintern.h"
#include "allegro5/internal/aintern_file.h"
#include "allegro5/internal/aintern_wunicode.h"
#include ALLEGRO_INTERNAL_HEADER
#ifdef ALLEGRO_HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
ALLEGRO_DEBUG_CHANNEL("stdio")
const struct ALLEGRO_FILE_INTERFACE _al_file_interface_stdio;
#ifndef PATH_MAX
#define PATH_MAX 4096
#endif
typedef struct
{
FILE *fp;
int errnum;
char errmsg[80];
} USERDATA;
static USERDATA *get_userdata(ALLEGRO_FILE *f)
{
if (f)
return al_get_file_userdata(f);
else
return NULL;
}
*/
ALLEGRO_FILE *al_fopen_fd(int fd, const char *mode)
{
ALLEGRO_FILE *f;
USERDATA *userdata;
FILE *fp;
userdata = al_malloc(sizeof(USERDATA));
if (!userdata)
return NULL;
* so delay the fdopen() call to last.
*/
userdata->fp = NULL;
userdata->errnum = 0;
f = al_create_file_handle(&_al_file_interface_stdio, userdata);
if (!f) {
al_free(userdata);
return NULL;
}
fp = fdopen(fd, mode);
if (!fp) {
al_set_errno(errno);
al_fclose(f);
return NULL;
}
userdata->fp = fp;
return f;
}
static void *file_stdio_fopen(const char *path, const char *mode)
{
FILE *fp;
USERDATA *userdata;
ALLEGRO_DEBUG("opening %s %s\n", path, mode);
#ifdef ALLEGRO_WINDOWS
{
wchar_t *wpath = _al_win_utf8_to_utf16(path);
wchar_t *wmode = _al_win_utf8_to_utf16(mode);
fp = _wfopen(wpath, wmode);
al_free(wpath);
al_free(wmode);
}
#else
fp = fopen(path, mode);
#endif
if (!fp) {
al_set_errno(errno);
return NULL;
}
userdata = al_malloc(sizeof(USERDATA));
if (!userdata) {
fclose(fp);
return NULL;
}
userdata->fp = fp;
userdata->errnum = 0;
return userdata;
}
static bool file_stdio_fclose(ALLEGRO_FILE *f)
{
USERDATA *userdata = get_userdata(f);
bool ret;
if (userdata->fp == NULL) {
ret = true;
}
else if (fclose(userdata->fp) == 0) {
ret = true;
}
else {
al_set_errno(errno);
ret = false;
}
al_free(userdata);
return ret;
}
static size_t file_stdio_fread(ALLEGRO_FILE *f, void *ptr, size_t size)
{
USERDATA *userdata = get_userdata(f);
if (size == 1) {
int c = fgetc(userdata->fp);
if (c == EOF) {
userdata->errnum = errno;
al_set_errno(errno);
return 0;
}
*((char *)ptr) = (char)c;
return 1;
}
else {
size_t ret = fread(ptr, 1, size, userdata->fp);
if (ret < size) {
userdata->errnum = errno;
al_set_errno(errno);
}
return ret;
}
}
static size_t file_stdio_fwrite(ALLEGRO_FILE *f, const void *ptr, size_t size)
{
USERDATA *userdata = get_userdata(f);
size_t ret;
ret = fwrite(ptr, 1, size, userdata->fp);
if (ret < size) {
userdata->errnum = errno;
al_set_errno(errno);
}
return ret;
}
static bool file_stdio_fflush(ALLEGRO_FILE *f)
{
USERDATA *userdata = get_userdata(f);
if (fflush(userdata->fp) == EOF) {
userdata->errnum = errno;
al_set_errno(errno);
return false;
}
return true;
}
static int64_t file_stdio_ftell(ALLEGRO_FILE *f)
{
USERDATA *userdata = get_userdata(f);
int64_t ret;
#if defined(ALLEGRO_HAVE_FTELLO)
ret = ftello(userdata->fp);
#elif defined(ALLEGRO_HAVE_FTELLI64)
ret = _ftelli64(userdata->fp);
#else
ret = ftell(userdata->fp);
#endif
if (ret == -1) {
userdata->errnum = errno;
al_set_errno(errno);
}
return ret;
}
static bool file_stdio_fseek(ALLEGRO_FILE *f, int64_t offset,
int whence)
{
USERDATA *userdata = get_userdata(f);
int rc;
switch (whence) {
case ALLEGRO_SEEK_SET: whence = SEEK_SET; break;
case ALLEGRO_SEEK_CUR: whence = SEEK_CUR; break;
case ALLEGRO_SEEK_END: whence = SEEK_END; break;
}
#if defined(ALLEGRO_HAVE_FSEEKO)
rc = fseeko(userdata->fp, offset, whence);
#elif defined(ALLEGRO_HAVE_FSEEKI64)
rc = _fseeki64(userdata->fp, offset, whence);
#else
rc = fseek(userdata->fp, offset, whence);
#endif
if (rc == -1) {
userdata->errnum = errno;
al_set_errno(errno);
return false;
}
return true;
}
static bool file_stdio_feof(ALLEGRO_FILE *f)
{
USERDATA *userdata = get_userdata(f);
return feof(userdata->fp);
}
static int file_stdio_ferror(ALLEGRO_FILE *f)
{
USERDATA *userdata = get_userdata(f);
return ferror(userdata->fp);
}
static const char *file_stdio_ferrmsg(ALLEGRO_FILE *f)
{
USERDATA *userdata = get_userdata(f);
if (userdata->errnum == 0)
return "";
#if defined(ALLEGRO_HAVE_STRERROR_R)
{
int rc = strerror_r(userdata->errnum, userdata->errmsg,
sizeof(userdata->errmsg));
if (rc == 0) {
return userdata->errmsg;
}
}
#endif
#if defined(ALLEGRO_HAVE_STRERROR_S)
{
errno_t rc = strerror_s(userdata->errmsg, sizeof(userdata->errmsg),
userdata->errnum);
if (rc == 0) {
return userdata->errmsg;
}
}
#endif
return "";
}
static void file_stdio_fclearerr(ALLEGRO_FILE *f)
{
USERDATA *userdata = get_userdata(f);
clearerr(userdata->fp);
}
static int file_stdio_fungetc(ALLEGRO_FILE *f, int c)
{
USERDATA *userdata = get_userdata(f);
int rc;
rc = ungetc(c, userdata->fp);
if (rc == EOF) {
userdata->errnum = errno;
al_set_errno(errno);
}
return rc;
}
static off_t file_stdio_fsize(ALLEGRO_FILE *f)
{
int64_t old_pos;
int64_t new_pos;
old_pos = file_stdio_ftell(f);
if (old_pos == -1)
return -1;
if (!file_stdio_fseek(f, 0, ALLEGRO_SEEK_END))
return -1;
new_pos = file_stdio_ftell(f);
if (new_pos == -1)
return -1;
if (!file_stdio_fseek(f, old_pos, ALLEGRO_SEEK_SET))
return -1;
return new_pos;
}
const struct ALLEGRO_FILE_INTERFACE _al_file_interface_stdio =
{
file_stdio_fopen,
file_stdio_fclose,
file_stdio_fread,
file_stdio_fwrite,
file_stdio_fflush,
file_stdio_ftell,
file_stdio_fseek,
file_stdio_feof,
file_stdio_ferror,
file_stdio_ferrmsg,
file_stdio_fclearerr,
file_stdio_fungetc,
file_stdio_fsize
};
*/
void al_set_standard_file_interface(void)
{
al_set_new_file_interface(&_al_file_interface_stdio);
}
#define MAX_MKTEMP_TRIES 1000
static void mktemp_replace_XX(const char *template, char *dst)
{
static const char chars[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
size_t len = strlen(template);
unsigned i;
for (i=0; i<len; i++) {
if (template[i] != 'X') {
dst[i] = template[i];
}
else {
dst[i] = chars[_al_rand() % (sizeof(chars) - 1)];
}
}
dst[i] = '\0';
}
static ALLEGRO_FILE *make_temp_file(const char *template, char *temp_filename,
ALLEGRO_PATH *path)
{
ALLEGRO_FILE *f;
int fd;
int i;
* the file later. If we return a relative path, the user might change the
* working directory in the mean time and then try to remove the wrong file.
* Mostly likely there won't be any such file, but the temporary file
* wouldn't be removed.
*/
for (i=0; i<MAX_MKTEMP_TRIES; ++i) {
mktemp_replace_XX(template, temp_filename);
al_set_path_filename(path, temp_filename);
#ifndef ALLEGRO_MSVC
fd = open(al_path_cstr(path, ALLEGRO_NATIVE_PATH_SEP),
O_EXCL | O_CREAT | O_RDWR, S_IRWXU);
#else
fd = open(al_path_cstr(path, ALLEGRO_NATIVE_PATH_SEP),
O_EXCL | O_CREAT | O_RDWR, _S_IWRITE | _S_IREAD);
#endif
if (fd != -1)
break;
}
if (fd == -1) {
al_set_errno(errno);
return NULL;
}
f = al_fopen_fd(fd, "rb+");
if (!f) {
al_set_errno(errno);
close(fd);
unlink(al_path_cstr(path, ALLEGRO_NATIVE_PATH_SEP));
return NULL;
}
return f;
}
*/
ALLEGRO_FILE *al_make_temp_file(const char *template, ALLEGRO_PATH **ret_path)
{
char *temp_filename;
ALLEGRO_PATH *path;
ALLEGRO_FILE *f;
temp_filename = al_malloc(strlen(template) + 1);
path = al_get_standard_path(ALLEGRO_TEMP_PATH);
if (temp_filename && path)
f = make_temp_file(template, temp_filename, path);
else
f = NULL;
al_free(temp_filename);
if (f && ret_path)
*ret_path = path;
else
al_destroy_path(path);
return f;
}