/* Copyright (C) 1995 Bjoern Beutel. */
/* Description. =============================================================*/
/* Operations for files and file names. */
/* Includes. ================================================================*/
#define _POSIX_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <setjmp.h>
#include <glib.h>
#include "basic.h"
#include "files.h"
#ifdef POSIX
#include <unistd.h>
#include <pwd.h>
#include <sys/mman.h>
#include <fcntl.h>
#endif
#ifdef WIN32
#include <windows.h>
#endif
/* Constants. ===============================================================*/
enum {MAX_PATH_SIZE = 200}; /* Maximum path size in characters. */
/* Macros. ==================================================================*/
#define IS_LETTER(c) (((c) >= 'A' && (c) <= 'Z') || ((c) >= 'a' && (c) <= 'z'))
/* File operations. =========================================================*/
bool_t
file_exists( string_t file_name )
/* Return TRUE iff file FILE_NAME exists and can be read. */
{
FILE *stream;
stream = fopen( file_name, "r" );
if (stream == NULL)
return FALSE;
fclose( stream );
return TRUE;
}
/*---------------------------------------------------------------------------*/
FILE *
open_stream( string_t file_name, string_t stream_mode )
/* Open file FILE_NAME and create a stream from/to it in mode STREAM_MODE.
* Works like "fopen", but calls "error" if it doesn't work. */
{
FILE *stream;
stream = fopen( file_name, stream_mode );
if (stream == NULL)
complain( "Can't open \"%s\": %s.", file_name, strerror( errno ) );
return stream;
}
/*---------------------------------------------------------------------------*/
void
close_stream( FILE **stream_p, string_t file_name )
/* Close the stream *STREAM_P which is connected to the file FILE_NAME
* and set *STREAM_P to NULL. Don't do anything if *STREAM_P == NULL.
* Works like "fclose", but calls "error" if FILE_NAME != NULL and an error
* occurs during closing. */
{
FILE *stream = *stream_p;
*stream_p = NULL;
if (stream != NULL && fclose( stream ) != 0 && file_name != NULL)
complain( "Can't close \"%s\": %s.", file_name, strerror( errno ) );
}
/*---------------------------------------------------------------------------*/
void
write_vector( const void *address, int_t item_size, int_t item_count,
FILE *stream, string_t file_name )
/* Write ITEM_COUNT items, of size ITEM_SIZE each, stored at *ADDRESS,
* to STREAM, which is connected to file FILE_NAME.
* Works like "fwrite", but calls "error" if it doesn't work. */
{
if (fwrite( address, (size_t) item_size, (size_t) item_count, stream )
< (size_t) item_count)
{
complain( "Can't write to \"%s\": %s.", file_name, strerror( errno ) );
}
}
/*---------------------------------------------------------------------------*/
void
read_vector( void *address, int_t item_size, int_t item_count,
FILE *stream, string_t file_name )
/* Read ITEM_COUNT items, of size ITEM_SIZE each, from STREAM,
* which is connected to file FILE_NAME, and store them at *ADDRESS.
* Works like "fread", but calls "error" if it doesn't work. */
{
if (fread( address, (size_t) item_size, (size_t) item_count, stream )
< (size_t) item_count)
{
complain( "Can't read from \"%s\": %s.", file_name, strerror( errno ) );
}
}
/*---------------------------------------------------------------------------*/
void *
read_new_vector( int_t item_size, int_t item_count,
FILE *stream, string_t file_name )
/* Read ITEM_COUNT items, of size ITEM_SIZE each, from STREAM,
* which is connected to file FILE_NAME, into allocated memory block,
* and return a pointer to that block.
* The block must be freed after use. */
{
void *block;
block = new_vector( item_size, item_count );
read_vector( block, item_size, item_count, stream, file_name );
return block;
}
/*---------------------------------------------------------------------------*/
void
map_file( string_t file_name, void **address, int_t *length )
/* Map file "file_name" into the memory. It will be available in the
* memory region starting at *ADDRESS and will occupy LENGTH bytes.
* After usage, return the memory region via "unmap_file". */
{
#ifdef POSIX
int file_descriptor;
/* Get a file descriptor. */
file_descriptor = open( file_name, O_RDONLY );
if (file_descriptor == -1)
complain( "Can't open \"%s\": %s.", file_name, strerror( errno ) );
/* Get file length. */
*length = lseek( file_descriptor, 0, SEEK_END );
if (*length == -1)
{
complain( "Can't get length of \"%s\": %s.",
file_name, strerror( errno ) );
}
*address = mmap( NULL, *length, PROT_READ, MAP_SHARED, file_descriptor, 0 );
if (*address == (void *) -1)
complain( "Can't read \"%s\": %s.", file_name, strerror( errno ) );
/* The file descriptor is no longer needed. */
close( file_descriptor );
#endif
#ifdef WIN32
HANDLE file_handle, map_handle;
file_handle = CreateFile( file_name, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
if (file_handle == INVALID_HANDLE_VALUE)
complain( "Can't open \"%s\".", file_name );
map_handle = CreateFileMapping( file_handle, NULL, PAGE_READONLY,
0, 0, NULL );
if (map_handle == NULL)
complain( "Can't map \"%s\".", file_name );
*address = MapViewOfFile( map_handle, FILE_MAP_READ, 0, 0, 0 );
if (*address == NULL)
complain( "Can't map \"%s\".", file_name );
*length = GetFileSize( file_handle, NULL );
CloseHandle( map_handle );
CloseHandle( file_handle );
#endif
}
/*---------------------------------------------------------------------------*/
void
unmap_file( void **address, int_t length )
/* Return the memory region that has been allocated by "map_file".
* The region starts at *ADDRESS and occupies LENGTH bytes. */
{
#ifdef POSIX
munmap( *address, length );
#endif
#ifdef WIN32
UnmapViewOfFile( *address );
#endif
*address = NULL;
}
/* File name operations. ====================================================*/
string_t
name_in_path( string_t path_name )
/* Return the file name in PATH_NAME,
* i.e. the name after the last separator. */
{
string_t name;
for (name = path_name + strlen( path_name ); name > path_name; name--)
{
if (name[-1] == '/')
break;
#ifdef WIN32
if (name[-1] == '\\'
|| (name == path_name + 2 && IS_LETTER( name[-2] ) && name[-1] == ':'))
{
break;
}
#endif
}
return name;
}
/*---------------------------------------------------------------------------*/
static string_t
get_env( string_t name )
/* Get the content of the environment variable NAME.
* Emit an error if it is not defined. */
{
string_t content;
content = getenv( name );
if (content == NULL)
complain( "Can't read environment variable \"%s\".", name );
return content;
}
/*---------------------------------------------------------------------------*/
static void
tidy_path( char_t *path )
/* Remove all superfluous "..", "." and "/" in PATH.
* PATH must be absolute. */
{
char_t *src_p;
char_t *dest_p;
#ifdef POSIX
dest_p = src_p = path;
while (*src_p != EOS)
{
while (*src_p == '/')
src_p++;
*dest_p++ = '/';
if (src_p[0] == '.' && src_p[1] == '.'
&& (src_p[2] == '/' || src_p[2] == EOS))
{
/* Walk up only if we are not on root level. */
src_p += 2;
if (dest_p > path + 1)
dest_p -= 2;
while (*dest_p != '/')
dest_p--;
}
else if (src_p[0] == '.' && (src_p[1] == '/' || src_p[1] == EOS))
{
src_p++;
dest_p--;
}
else
{
while (*src_p != '/' && *src_p != EOS)
*dest_p++ = *src_p++;
}
}
if (dest_p > path + 1 && dest_p[-1] == '/')
dest_p--;
*dest_p = EOS;
#endif
#ifdef WIN32
/* The first two chars is the drive specification. */
if (! IS_LETTER( path[0] ) || path[1] != ':')
complain( "Missing drive name." );
dest_p = src_p = path + 2;
while (*src_p != EOS)
{
while (*src_p == '\\' || *src_p == '/')
src_p++;
*dest_p++ = '\\';
if (src_p[0] == '.' && src_p[1] == '.'
&& (src_p[2] == '\\' || src_p[2] == '/' || src_p[2] == EOS))
{
/* Walk up only if we are not on root level. */
src_p += 2;
if (dest_p > path + 3)
dest_p -= 2;
while (*dest_p != '\\')
dest_p--;
}
else if (src_p[0] == '.'
&& (src_p[1] == '\\' || src_p[1] == '/' || src_p[1] == EOS))
{
src_p++;
dest_p--;
}
else
{
while (*src_p != '\\' && *src_p != '/' && *src_p != EOS)
*dest_p++ = *src_p++;
}
}
if (dest_p > path + 3 && dest_p[-1] == '\\')
dest_p--;
*dest_p = EOS;
#endif
}
/*---------------------------------------------------------------------------*/
char_t *
absolute_path( string_t src_path, string_t relative_to )
/* Return the absolute path name which is equivalent to SRC_PATH.
* If SRC_PATH starts with "~", it's replaced by the home directory of the
* user whose login name is following (current user if no login name).
* If RELATIVE_TO is not NULL, SRC_NAME is relative to that path name.
* RELATIVE_TO must be an absolute path name (a directory or a file).
* The returned path must be freed after use. */
{
#ifdef POSIX
text_t *path;
string_t src_path_p, login, login_p;
char_t *dest_path;
struct passwd *password;
string_t relative_end, relative_dir;
char_t current_dir[ MAX_PATH_SIZE ];
path = new_text();
/* Put a home directory in front. */
src_path_p = src_path;
if (*src_path_p == '~')
{
/* Put a users home directory in front. */
src_path_p++;
login_p = src_path_p;
while (*src_path_p != '/' && *src_path_p != EOS)
src_path_p++;
if (src_path_p == login_p)
add_to_text( path, get_env( "HOME" ) );
else
{
/* Put home directory of user LOGIN in front. */
login = new_string( login_p, src_path_p );
password = getpwnam( login );
if (password == NULL)
complain( "Can't find user \"%s\".", login );
add_to_text( path, password->pw_dir );
free_mem( &login );
}
}
else if (*src_path_p != '/')
{
if (relative_to != NULL)
{
/* Put RELATIVE_TO ahead (strip last name). */
relative_end = relative_to + strlen( relative_to );
while (relative_end[-1] != '/')
relative_end--;
relative_dir = new_string( relative_to, relative_end );
add_to_text( path, relative_dir );
free_mem( &relative_dir );
}
else
{
/* Put current directory in front. */
getcwd( current_dir, MAX_PATH_SIZE );
add_to_text( path, current_dir );
}
}
/* Copy rest of DEST_PATH, clean it up and return it. */
add_char_to_text( path, '/' );
add_to_text( path, src_path_p );
dest_path = text_to_string( &path );
if (*dest_path != '/')
complain( "Path \"%s\" must be absolute.", src_path );
tidy_path( dest_path );
return dest_path;
#endif
#ifdef WIN32
text_t *path;
string_t src_path_p, dest_path;
string_t relative_end, relative_dir;
char_t current_dir[ MAX_PATH_SIZE ];
path = new_text();
src_path_p = src_path;
if (src_path_p[0] == '~' && (src_path_p[1] == '\\' || src_path_p[1] == '/'))
{
/* Put the users home directory in front. */
src_path_p += 2;
relative_to = getenv( "USERPROFILE" );
if (relative_to == NULL)
relative_to = get_env( "SYSTEMDRIVE" );
add_to_text( path, relative_to );
add_char_to_text( path, '\\' );
}
else if (IS_LETTER( src_path_p[0] ) && src_path_p[1] == ':')
{
/* The path is already complete. */
if (src_path_p[2] != '\\' && src_path_p[2] != '/')
complain( "A drive name needs an absolute path." );
}
else if (src_path_p[0] == '\\' || src_path_p[0] == '/')
{
/* Put the current drive in front. */
src_path_p++;
if (relative_to != NULL)
{
add_char_to_text( path, relative_to[0] );
add_char_to_text( path, relative_to[1] );
}
else
{
GetCurrentDirectory( MAX_PATH_SIZE, current_dir );
add_char_to_text( path, current_dir[0] );
add_char_to_text( path, current_dir[1] );
}
add_char_to_text( path, '\\' );
}
else if (relative_to != NULL)
{
/* Put RELATIVE_TO ahead (strip last name). */
relative_end = relative_to + strlen( relative_to );
while (relative_end[-1] != '\\')
relative_end--;
relative_dir = new_string( relative_to, relative_end );
add_to_text( path, relative_dir );
add_char_to_text( path, '\\' );
free_mem( &relative_dir );
}
else
{
/* Put current directory in front. */
GetCurrentDirectory( MAX_PATH_SIZE, current_dir );
add_to_text( path, current_dir );
add_char_to_text( path, '\\' );
}
add_to_text( path, src_path_p );
dest_path = text_to_string( &path );
tidy_path( dest_path );
return dest_path;
#endif
}
/*---------------------------------------------------------------------------*/
char_t *
replace_vars_in_string( string_t string )
/* Replace environment variables of form "${X}" in STRING.
* Return the resulting string. It must be freed after use. */
{
text_t *text;
string_t string_p, variable_p, variable;
text = new_text();
for (string_p = string; *string_p != EOS; string_p++)
{
if (string_p[0] == '$' && string_p[1] == '{')
{
string_p += 2;
variable_p = string_p;
while (*string_p != '}')
{
if (*string_p == EOS)
complain( "Missing \"}\" in environment variable name." );
string_p++;
}
variable = new_string( variable_p, string_p );
add_to_text( text, get_env( variable ) );
free_mem( &variable );
}
else
add_char_to_text( text, *string_p );
}
return text_to_string( &text );
}
/*---------------------------------------------------------------------------*/
static string_t extension_start( string_t name )
/* Return a pointer to the start (the dot) of the extension in NAME,
* or to the end of the string if there is no extension. */
{
string_t s, t;
s = NULL;
for (t = name; *t != EOS; t++)
{
if (*t == '/')
s = NULL;
#ifdef WIN32
else if (*t == '\\')
s = NULL;
#endif
else if (*t == '.')
s = t;
}
return (s != NULL ? s : t);
}
/*---------------------------------------------------------------------------*/
bool_t
has_extension( string_t file_name, string_t extension )
/* Test if FILE_NAME has extension EXTENSION. */
{
string_t ext; /* The real extension of FILE_NAME (including "."). */
ext = extension_start( file_name );
return (*ext != EOS && strcmp( ext + 1, extension ) == 0);
}
/*---------------------------------------------------------------------------*/
char_t *
replace_extension( string_t file_name, string_t extension )
/* Return a new string that contains FILE_NAME with new EXTENSION.
* The string must be freed after use. */
{
string_t base;
char_t *s;
base = new_string( file_name, extension_start( file_name ) );
s = concat_strings( base, ".", extension, NULL );
free_mem( &base );
return s;
}
/*---------------------------------------------------------------------------*/
void
set_file_name( string_t *file_name_p, string_t file_name )
/* Set *FILE_NAME_P to absolute path FILE_NAME, relative to current dir.
* Print an error if *FILE_NAME_P is already set.
* The created file name must be freed after use. */
{
if (*file_name_p != NULL)
complain( "File \"%s\" is redundant.", file_name );
*file_name_p = absolute_path( file_name, NULL );
}
/*---------------------------------------------------------------------------*/
void
set_binary_file_name( string_t *file_name_p, string_t file_name )
/* Set *FILE_NAME_P to
* FILE_NAME plus "_l" for little endian, "_b" for big endian, "_c" else,
* converted to absolute path.
* Print an error if *FILE_NAME_P is already set.
* The created file name must be freed after use. */
{
union { char_t chars[4]; int_t integer; } format;
string_t suffix, binary_file_name;
if (*file_name_p != NULL)
complain( "File \"%s\" is redundant.", file_name );
format.integer = 0x12345678;
if (sizeof( int_t ) != 4)
suffix = "_c";
else if (format.chars[0] == 0x12 && format.chars[1] == 0x34
&& format.chars[2] == 0x56 && format.chars[3] == 0x78)
{
suffix = "_b";
}
else if (format.chars[0] == 0x78 && format.chars[1] == 0x56
&& format.chars[2] == 0x34 && format.chars[3] == 0x12)
{
suffix = "_l";
}
else
suffix = "_c";
binary_file_name = concat_strings( file_name, suffix, NULL );
*file_name_p = absolute_path( binary_file_name, NULL );
free_mem( &binary_file_name );
}
/* End of file. =============================================================*/