1
0
mirror of https://git.code.sf.net/p/fuse-emulator/fuse synced 2026-01-28 14:20:54 +03:00
Files
fuse/utils.c
Stuart Brady 9f721ceb2a Merge 2007-06-09-plusd to r2992.
Legacy-ID: 2996
2007-06-17 15:45:38 +00:00

588 lines
14 KiB
C

/* utils.c: some useful helper functions
Copyright (c) 1999-2005 Philip Kendall
$Id$
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 the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Author contact information:
E-mail: philip-fuse@shadowmagic.org.uk
*/
#include <config.h>
#include <errno.h>
#include <fcntl.h>
#ifdef HAVE_LIBGEN_H
#include <libgen.h>
#endif /* #ifdef HAVE_LIBGEN_H */
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#ifdef HAVE_MMAP
#include <sys/mman.h>
#endif
#include <sys/stat.h>
#include <sys/types.h>
#include <ui/ui.h>
#include <unistd.h>
#include <libspectrum.h>
#include "dck.h"
#include "divide.h"
#include "fuse.h"
#include "if1.h"
#include "if2.h"
#include "machines/specplus3.h"
#include "memory.h"
#include "rzx.h"
#include "settings.h"
#include "simpleide.h"
#include "snapshot.h"
#include "tape.h"
#include "trdos.h"
#include "utils.h"
#include "zxatasp.h"
#include "zxcf.h"
typedef struct path_context {
int state;
utils_aux_type type;
char path[ PATH_MAX ];
} path_context;
static void init_path_context( path_context *ctx, utils_aux_type type );
static int get_next_path( path_context *ctx );
int
utils_is_absolute_path( const char *filename )
{
if( filename[0] == FUSE_DIR_SEP_CHR )
return 1;
#ifdef WIN32
if( filename[0] && filename[1] == ':' )
return 1;
#endif
return 0;
}
/* Open `filename' and do something sensible with it; autoload tapes
if `autoload' is true and return the type of file found in `type' */
int
utils_open_file( const char *filename, int autoload,
libspectrum_id_t *type_ptr)
{
utils_file file;
libspectrum_id_t type;
libspectrum_class_t class;
int error;
/* Read the file into a buffer */
if( utils_read_file( filename, &file ) ) return 1;
/* See if we can work out what it is */
if( libspectrum_identify_file_with_class( &type, &class, filename,
file.buffer, file.length ) ) {
utils_close_file( &file );
return 1;
}
error = 0;
switch( class ) {
case LIBSPECTRUM_CLASS_UNKNOWN:
ui_error( UI_ERROR_ERROR, "utils_open_file: couldn't identify `%s'",
filename );
utils_close_file( &file );
return 1;
case LIBSPECTRUM_CLASS_RECORDING:
error = rzx_start_playback_from_buffer( file.buffer, file.length );
break;
case LIBSPECTRUM_CLASS_SNAPSHOT:
error = snapshot_read_buffer( file.buffer, file.length, type );
break;
case LIBSPECTRUM_CLASS_TAPE:
error = tape_read_buffer( file.buffer, file.length, type, filename,
autoload );
break;
case LIBSPECTRUM_CLASS_DISK_PLUS3:
#ifdef HAVE_765_H
if( !( machine_current->capabilities &
LIBSPECTRUM_MACHINE_CAPABILITY_PLUS3_DISK ) ) {
error = machine_select( LIBSPECTRUM_MACHINE_PLUS3 ); if( error ) break;
}
error = specplus3_disk_insert( SPECPLUS3_DRIVE_A, filename, autoload );
break;
#else /* #ifdef HAVE_765_H */
ui_error( UI_ERROR_WARNING,
"lib765 not present so can't handle .dsk files" );
#endif /* #ifdef HAVE_765_H */
break;
case LIBSPECTRUM_CLASS_DISK_PLUSD:
#ifdef HAVE_LIBDSK_H
error = plusd_disk_insert( PLUSD_DRIVE_1, filename, autoload );
break;
#else /* #ifdef HAVE_LIBDSK_H */
ui_error( UI_ERROR_WARNING,
"libdsk not present so can't handle .mgt or .img files" );
#endif /* #ifdef HAVE_LIBDSK_H */
break;
case LIBSPECTRUM_CLASS_DISK_TRDOS:
if( !( machine_current->capabilities &
LIBSPECTRUM_MACHINE_CAPABILITY_TRDOS_DISK ) ) {
error = machine_select( LIBSPECTRUM_MACHINE_PENT ); if( error ) break;
}
error = trdos_disk_insert( TRDOS_DRIVE_A, filename, autoload );
break;
case LIBSPECTRUM_CLASS_CARTRIDGE_IF2:
error = if2_insert( filename );
break;
case LIBSPECTRUM_CLASS_MICRODRIVE:
if1_mdr_insert( filename, -1 );
break;
case LIBSPECTRUM_CLASS_CARTRIDGE_TIMEX:
error = machine_select( LIBSPECTRUM_MACHINE_TC2068 ); if( error ) break;
error = dck_insert( filename );
break;
case LIBSPECTRUM_CLASS_HARDDISK:
if( !settings_current.simpleide_active &&
!settings_current.zxatasp_active &&
!settings_current.divide_enabled &&
!settings_current.zxcf_active ) {
settings_current.zxcf_active = 1;
periph_update();
}
if( settings_current.zxcf_active ) {
error = zxcf_insert( filename );
} else if( settings_current.zxatasp_active ) {
error = zxatasp_insert( filename, LIBSPECTRUM_IDE_MASTER );
} else if( settings_current.simpleide_active ) {
error = simpleide_insert( filename, LIBSPECTRUM_IDE_MASTER );
} else {
error = divide_insert( filename, LIBSPECTRUM_IDE_MASTER );
}
if( error ) return error;
break;
default:
ui_error( UI_ERROR_ERROR, "utils_open_file: unknown class %d", type );
error = 1;
break;
}
if( error ) { utils_close_file( &file ); return error; }
if( utils_close_file( &file ) ) return 1;
if( type_ptr ) *type_ptr = type;
return 0;
}
/* Find the auxillary file called `filename'; returns a fd for the
file on success, -1 if it couldn't find the file */
int
utils_find_auxiliary_file( const char *filename, utils_aux_type type )
{
int fd;
char path[ PATH_MAX ];
path_context ctx;
/* If given an absolute path, just look there */
if( utils_is_absolute_path( filename ) )
return open( filename, O_RDONLY | O_BINARY );
/* Otherwise look in some likely locations */
init_path_context( &ctx, type );
while( get_next_path( &ctx ) ) {
snprintf( path, PATH_MAX, "%s" FUSE_DIR_SEP_STR "%s", ctx.path, filename );
fd = open( path, O_RDONLY | O_BINARY );
if( fd != -1 ) return fd;
}
/* Give up. Couldn't find this file */
return -1;
}
int
utils_find_file_path( const char *filename, char *ret_path,
utils_aux_type type )
{
path_context ctx;
struct stat stat_info;
/* If given an absolute path, just look there */
if( utils_is_absolute_path( filename ) ) {
strncpy( ret_path, filename, PATH_MAX );
return 0;
}
/* Otherwise look in some likely locations */
init_path_context( &ctx, type );
while( get_next_path( &ctx ) ) {
snprintf( ret_path, PATH_MAX, "%s" FUSE_DIR_SEP_STR "%s", ctx.path,
filename );
if( !stat( ret_path, &stat_info ) ) return 0;
}
return 1;
}
/* Something similar to that described at
http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html was
_very_ tempting here...
*/
static void
init_path_context( path_context *ctx, utils_aux_type type )
{
ctx->state = 0;
ctx->type = type;
}
static int
get_next_path( path_context *ctx )
{
char buffer[ PATH_MAX ], *path_segment, *path2;
switch( (ctx->state)++ ) {
/* First look relative to the current directory */
case 0: strncpy( ctx->path, ".", PATH_MAX ); return 1;
/* Then relative to the Fuse executable */
case 1:
switch( ctx->type ) {
case UTILS_AUXILIARY_LIB: path_segment = "lib"; break;
case UTILS_AUXILIARY_ROM: path_segment = "roms"; break;
case UTILS_AUXILIARY_WIDGET: path_segment = "widget"; break;
default:
ui_error( UI_ERROR_ERROR, "unknown auxiliary file type %d", ctx->type );
return 0;
}
if( utils_is_absolute_path( fuse_progname ) ) {
strncpy( buffer, fuse_progname, PATH_MAX );
buffer[ PATH_MAX - 1 ] = '\0';
} else {
snprintf( buffer, PATH_MAX, "%s%s", fuse_directory, fuse_progname );
}
path2 = dirname( buffer );
snprintf( ctx->path, PATH_MAX, "%s" FUSE_DIR_SEP_STR "%s", path2,
path_segment );
return 1;
/* Then where we may have installed the data files */
case 2:
#ifndef ROMSDIR
path2 = FUSEDATADIR;
#else /* #ifndef ROMSDIR */
path2 = ctx->type == UTILS_AUXILIARY_ROM ? ROMSDIR : FUSEDATADIR;
#endif /* #ifndef ROMSDIR */
strncpy( ctx->path, path2, PATH_MAX ); buffer[ PATH_MAX - 1 ] = '\0';
return 1;
case 3: return 0;
}
ui_error( UI_ERROR_ERROR, "unknown path_context state %d", ctx->state );
fuse_abort();
}
int
utils_read_file( const char *filename, utils_file *file )
{
int fd;
int error;
fd = open( filename, O_RDONLY | O_BINARY );
if( fd == -1 ) {
ui_error( UI_ERROR_ERROR, "couldn't open '%s': %s", filename,
strerror( errno ) );
return 1;
}
error = utils_read_fd( fd, filename, file );
if( error ) return error;
return 0;
}
int
utils_read_fd( int fd, const char *filename, utils_file *file )
{
struct stat file_info;
if( fstat( fd, &file_info) ) {
ui_error( UI_ERROR_ERROR, "Couldn't stat '%s': %s", filename,
strerror( errno ) );
close(fd);
return 1;
}
file->length = file_info.st_size;
#ifdef HAVE_MMAP
file->buffer = mmap( 0, file->length, PROT_READ, MAP_SHARED, fd, 0 );
if( file->buffer != (void*)-1 ) {
file->mode = UTILS_FILE_OPEN_MMAP;
if( close(fd) ) {
ui_error( UI_ERROR_ERROR, "Couldn't close '%s': %s", filename,
strerror( errno ) );
munmap( file->buffer, file->length );
return 1;
}
return 0;
}
#endif /* #ifdef HAVE_MMAP */
/* Either mmap() isn't available, or it failed for some reason so try
using normal IO */
file->mode = UTILS_FILE_OPEN_MALLOC;
file->buffer = malloc( file->length );
if( !file->buffer ) {
ui_error( UI_ERROR_ERROR, "Out of memory at %s:%d\n", __FILE__, __LINE__ );
return 1;
}
if( read( fd, file->buffer, file->length ) != file->length ) {
ui_error( UI_ERROR_ERROR, "Error reading from '%s': %s", filename,
strerror( errno ) );
free( file->buffer );
close( fd );
return 1;
}
if( close(fd) ) {
ui_error( UI_ERROR_ERROR, "Couldn't close '%s': %s", filename,
strerror( errno ) );
free( file->buffer );
return 1;
}
return 0;
}
int
utils_close_file( utils_file *file )
{
switch( file->mode ) {
case UTILS_FILE_OPEN_MMAP:
#ifdef HAVE_MMAP
if( file->length ) {
if( munmap( file->buffer, file->length ) ) {
ui_error( UI_ERROR_ERROR, "Couldn't munmap: %s\n", strerror( errno ) );
return 1;
}
}
break;
#else /* #ifdef HAVE_MMAP */
ui_error( UI_ERROR_ERROR, "utils_close_file: file->mode == UTILS_FILE_OPEN_MMAP, but mmap not available?!" );
fuse_abort();
#endif
case UTILS_FILE_OPEN_MALLOC:
free( file->buffer );
break;
default:
ui_error( UI_ERROR_ERROR, "Unknown file open mode %d\n", file->mode );
fuse_abort();
}
return 0;
}
int utils_write_file( const char *filename, const unsigned char *buffer,
size_t length )
{
FILE *f;
f=fopen( filename, "wb" );
if(!f) {
ui_error( UI_ERROR_ERROR, "error opening '%s': %s", filename,
strerror( errno ) );
return 1;
}
if( fwrite( buffer, 1, length, f ) != length ) {
ui_error( UI_ERROR_ERROR, "error writing to '%s': %s", filename,
strerror( errno ) );
fclose(f);
return 1;
}
if( fclose( f ) ) {
ui_error( UI_ERROR_ERROR, "error closing '%s': %s", filename,
strerror( errno ) );
return 1;
}
return 0;
}
/* Make a copy of a file in a temporary file */
int
utils_make_temp_file( int *fd, char *tempfilename, const char *filename,
const char *template )
{
int error;
utils_file file;
ssize_t bytes_written;
#ifdef AMIGA
snprintf( tempfilename, PATH_MAX, "%s%s", utils_get_temp_path(), template );
#else
snprintf( tempfilename, PATH_MAX, "%s" FUSE_DIR_SEP_STR "%s",
utils_get_temp_path(), template );
#endif
*fd = mkstemp( tempfilename );
if( *fd == -1 ) {
ui_error( UI_ERROR_ERROR, "couldn't create temporary file: %s",
strerror( errno ) );
}
error = utils_read_file( filename, &file );
if( error ) { close( *fd ); unlink( tempfilename ); return error; }
bytes_written = write( *fd, file.buffer, file.length );
if( bytes_written != file.length ) {
if( bytes_written == -1 ) {
ui_error( UI_ERROR_ERROR, "error writing to temporary file '%s': %s",
tempfilename, strerror( errno ) );
} else {
ui_error( UI_ERROR_ERROR,
"could write only %lu of %lu bytes to temporary file '%s'",
(unsigned long)bytes_written, (unsigned long)file.length,
tempfilename );
}
utils_close_file( &file ); close( *fd ); unlink( tempfilename );
return 1;
}
error = utils_close_file( &file );
if( error ) { close( *fd ); unlink( tempfilename ); return error; }
return 0;
}
/* Get a path where we can reasonably create temporary files */
const char*
utils_get_temp_path( void )
{
const char *dir;
#ifdef AMIGA
/* Amiga, just use T: */
return "T:";
#elif defined WIN32
/* Something close to this algorithm specified at
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/base/gettemppath.asp
*/
dir = getenv( "TMP" ); if( dir ) return dir;
dir = getenv( "TEMP" ); if( dir ) return dir;
return ".";
#else /* #ifdef WIN32 */
/* Unix-ish. Use TMPDIR if specified, if not /tmp */
dir = getenv( "TMPDIR" ); if( dir ) return dir;
return "/tmp";
#endif /* #ifdef WIN32 */
}
/* Return the path where we will store our configuration information etc */
const char*
utils_get_home_path( void )
{
const char *dir;
#ifdef AMIGA
dir = strdup("PROGDIR:settings");
if( dir ) return dir;
#elif defined WIN32
dir = getenv( "USERPROFILE" ); if( dir ) return dir;
dir = getenv( "WINDIR" ); if( dir ) return dir;
#else /* #ifdef WIN32 */
dir = getenv( "HOME" ); if( dir ) return dir;
#endif /* #ifdef WIN32 */
ui_error( UI_ERROR_ERROR, "couldn't find a plausible home directory" );
return NULL;
}