blob: b995504582571af3cd988f6f9fad24f71aae68f9 [file] [log] [blame]
/* Copyright (C) 2007-2008 The Android Open Source Project
**
** This software is licensed under the terms of the GNU General Public
** License version 2, as published by the Free Software Foundation, and
** may be copied, distributed, and modified under those terms.
**
** 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.
*/
#include "android_utils.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#ifdef _WIN32
#include <process.h>
#include <shlobj.h>
#include <tlhelp32.h>
#include <io.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdint.h>
#include <limits.h>
#include <winbase.h>
#else
#include <unistd.h>
#include <sys/stat.h>
#include <time.h>
#include <signal.h>
#endif
#include "android.h"
#include "android_debug.h"
#define D(...) VERBOSE_PRINT(init,__VA_ARGS__)
#ifdef _WIN32
char*
win32_strsep(char** pline, const char* delim)
{
char* line = *pline;
char* p = line;
if (p == NULL)
return NULL;
for (;;) {
int c = *p++;
const char* q = delim;
if (c == 0) {
p = NULL;
break;
}
while (*q) {
if (*q == c) {
p[-1] = 0;
goto Exit;
}
q++;
}
}
Exit:
*pline = p;
return line;
}
#endif
/** PATH HANDLING ROUTINES
**
** path_parent() can be used to return the n-level parent of a given directory
** this understands . and .. when encountered in the input path
**/
static __inline__ int
ispathsep(int c)
{
#ifdef _WIN32
return (c == '/' || c == '\\');
#else
return (c == '/');
#endif
}
char* path_parent( const char* path, int levels )
{
const char* end = path + strlen(path);
char* result;
while (levels > 0) {
const char* base;
/* trim any trailing path separator */
while (end > path && ispathsep(end[-1]))
end--;
base = end;
while (base > path && !ispathsep(base[-1]))
base--;
if (base <= path) /* we can't go that far */
return NULL;
if (end == base+1 && base[0] == '.')
goto Next;
if (end == base+2 && base[0] == '.' && base[1] == '.') {
levels += 1;
goto Next;
}
levels -= 1;
Next:
end = base - 1;
}
result = malloc( end-path+1 );
if (result != NULL) {
memcpy( result, path, end-path );
result[end-path] = 0;
}
return result;
}
/** MISC FILE AND DIRECTORY HANDLING
**/
int
path_exists( const char* path )
{
int ret;
CHECKED(ret, access(path, F_OK));
return (ret == 0) || (errno != ENOENT);
}
/* checks that a path points to a regular file */
int
path_is_regular( const char* path )
{
int ret;
struct stat st;
CHECKED(ret, stat(path, &st));
if (ret < 0)
return 0;
return S_ISREG(st.st_mode);
}
/* checks that a path points to a directory */
int
path_is_dir( const char* path )
{
int ret;
struct stat st;
CHECKED(ret, stat(path, &st));
if (ret < 0)
return 0;
return S_ISDIR(st.st_mode);
}
/* checks that one can read/write a given (regular) file */
int
path_can_read( const char* path )
{
int ret;
CHECKED(ret, access(path, R_OK));
return (ret == 0);
}
int
path_can_write( const char* path )
{
int ret;
CHECKED(ret, access(path, R_OK));
return (ret == 0);
}
/* try to make a directory. returns 0 on success, -1 on failure
* (error code in errno) */
int
path_mkdir( const char* path, int mode )
{
#ifdef _WIN32
(void)mode;
return _mkdir(path);
#else
int ret;
CHECKED(ret, mkdir(path, mode));
return ret;
#endif
}
static int
path_mkdir_recursive( char* path, unsigned len, int mode )
{
char old_c;
int ret;
unsigned len2;
/* get rid of trailing separators */
while (len > 0 && ispathsep(path[len-1]))
len -= 1;
if (len == 0) {
errno = ENOENT;
return -1;
}
/* check that the parent exists, 'len2' is the length of
* the parent part of the path */
len2 = len-1;
while (len2 > 0 && !ispathsep(path[len2-1]))
len2 -= 1;
if (len2 > 0) {
old_c = path[len2];
path[len2] = 0;
ret = 0;
if ( !path_exists(path) ) {
/* the parent doesn't exist, so try to create it */
ret = path_mkdir_recursive( path, len2, mode );
}
path[len2] = old_c;
if (ret < 0)
return ret;
}
/* at this point, we now the parent exists */
old_c = path[len];
path[len] = 0;
ret = path_mkdir( path, mode );
path[len] = old_c;
return ret;
}
/* ensure that a given directory exists, create it if not,
0 on success, -1 on failure (error code in errno) */
int
path_mkdir_if_needed( const char* path, int mode )
{
int ret = 0;
if (!path_exists(path)) {
ret = path_mkdir(path, mode);
if (ret < 0 && errno == ENOENT) {
char temp[MAX_PATH];
unsigned len = (unsigned)strlen(path);
if (len > sizeof(temp)-1) {
errno = EINVAL;
return -1;
}
memcpy( temp, path, len );
temp[len] = 0;
return path_mkdir_recursive(temp, len, mode);
}
}
return ret;
}
/* return the size of a given file in '*psize'. returns 0 on
* success, -1 on failure (error code in errno) */
int
path_get_size( const char* path, uint64_t *psize )
{
#ifdef _WIN32
/* avoid _stat64 which is only defined in MSVCRT.DLL, not CRTDLL.DLL */
/* do not use OpenFile() because it has strange search behaviour that could */
/* result in getting the size of a different file */
LARGE_INTEGER size;
HANDLE file = CreateFile( /* lpFilename */ path,
/* dwDesiredAccess */ GENERIC_READ,
/* dwSharedMode */ FILE_SHARE_READ|FILE_SHARE_WRITE,
/* lpSecurityAttributes */ NULL,
/* dwCreationDisposition */ OPEN_EXISTING,
/* dwFlagsAndAttributes */ 0,
/* hTemplateFile */ NULL );
if (file == INVALID_HANDLE_VALUE) {
/* ok, just to play fair */
errno = ENOENT;
return -1;
}
if (!GetFileSizeEx(file, &size)) {
/* maybe we tried to get the size of a pipe or something like that ? */
*psize = 0;
}
else {
*psize = (uint64_t) size.QuadPart;
}
CloseHandle(file);
return 0;
#else
int ret;
struct stat st;
CHECKED(ret, stat(path, &st));
if (ret == 0) {
*psize = (uint64_t) st.st_size;
}
return ret;
#endif
}
/** USEFUL STRING BUFFER FUNCTIONS
**/
char*
vbufprint( char* buffer,
char* buffer_end,
const char* fmt,
va_list args )
{
int len = vsnprintf( buffer, buffer_end - buffer, fmt, args );
if (len < 0 || buffer+len >= buffer_end) {
if (buffer < buffer_end)
buffer_end[-1] = 0;
return buffer_end;
}
return buffer + len;
}
char*
bufprint(char* buffer, char* end, const char* fmt, ... )
{
va_list args;
char* result;
va_start(args, fmt);
result = vbufprint(buffer, end, fmt, args);
va_end(args);
return result;
}
/** USEFUL DIRECTORY SUPPORT
**
** bufprint_app_dir() returns the directory where the emulator binary is located
**
** get_android_home() returns a user-specific directory where the emulator will
** store its writable data (e.g. config files, profiles, etc...).
** on Unix, this is $HOME/.android, on Windows, this is something like
** "%USERPROFILE%/Local Settings/AppData/Android" on XP, and something different
** on Vista.
**
** both functions return a string that must be freed by the caller
**/
#ifdef __linux__
char*
bufprint_app_dir(char* buff, char* end)
{
char path[1024];
int len;
char* x;
len = readlink("/proc/self/exe", path, sizeof(path));
if (len <= 0 || len >= (int)sizeof(path)) goto Fail;
path[len] = 0;
x = strrchr(path, '/');
if (x == 0) goto Fail;
*x = 0;
return bufprint(buff, end, "%s", path);
Fail:
fprintf(stderr,"cannot locate application directory\n");
exit(1);
return end;
}
#elif defined(__APPLE__)
/* the following hack is needed in order to build with XCode 3.1
* don't ask me why, but it seems that there were changes in the
* GCC compiler that we don't have in our pre-compiled version
*/
#ifndef __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__
#define __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ MAC_OS_X_VERSION_10_4
#endif
#import <Carbon/Carbon.h>
#include <unistd.h>
char*
bufprint_app_dir(char* buff, char* end)
{
ProcessSerialNumber psn;
CFDictionaryRef dict;
CFStringRef value;
char s[PATH_MAX];
char* x;
GetCurrentProcess(&psn);
dict = ProcessInformationCopyDictionary(&psn, 0xffffffff);
value = (CFStringRef)CFDictionaryGetValue(dict,
CFSTR("CFBundleExecutable"));
CFStringGetCString(value, s, PATH_MAX - 1, kCFStringEncodingUTF8);
x = strrchr(s, '/');
if (x == 0) goto fail;
*x = 0;
return bufprint(buff, end, "%s", s);
fail:
fprintf(stderr,"cannot locate application directory\n");
exit(1);
return end;
}
#elif defined _WIN32
char*
bufprint_app_dir(char* buff, char* end)
{
char appDir[MAX_PATH];
char* sep;
GetModuleFileName( 0, appDir, sizeof(appDir)-1 );
sep = strrchr( appDir, '\\' );
if (sep)
*sep = 0;
return bufprint(buff, end, "%s", appDir);
}
#else
char*
bufprint_app_dir(char* buff, char* end)
{
return bufprint(buff, end, ".");
}
#endif
#define _ANDROID_PATH ".android"
char*
bufprint_config_path(char* buff, char* end)
{
#ifdef _WIN32
char path[MAX_PATH];
SHGetFolderPath( NULL, CSIDL_PROFILE,
NULL, 0, path);
return bufprint(buff, end, "%s\\%s", path, _ANDROID_PATH );
#else
const char* home = getenv("HOME");
if (home == NULL)
home = "/tmp";
return bufprint(buff, end, "%s/%s", home, _ANDROID_PATH );
#endif
}
char*
bufprint_config_file(char* buff, char* end, const char* suffix)
{
char* p;
p = bufprint_config_path(buff, end);
p = bufprint(p, end, PATH_SEP "%s", suffix);
return p;
}
char*
bufprint_temp_dir(char* buff, char* end)
{
#ifdef _WIN32
char path[MAX_PATH];
DWORD retval;
retval = GetTempPath( sizeof(path), path );
if (retval > sizeof(path) || retval == 0) {
D( "can't locate TEMP directory" );
pstrcpy(path, sizeof(path), "C:\\Temp");
}
strncat( path, "\\AndroidEmulator", sizeof(path)-1 );
_mkdir(path);
return bufprint(buff, end, "%s", path);
#else
const char* tmppath = "/tmp/android";
mkdir(tmppath, 0744);
return bufprint(buff, end, "%s", tmppath );
#endif
}
char*
bufprint_temp_file(char* buff, char* end, const char* suffix)
{
char* p;
p = bufprint_temp_dir(buff, end);
p = bufprint(p, end, PATH_SEP "%s", suffix);
return p;
}
/** FILE LOCKS SUPPORT
**
** a FileLock is useful to prevent several emulator instances from using the same
** writable file (e.g. the userdata.img disk images).
**
** create a FileLock object with filelock_create(), ithis function should return NULL
** only if the corresponding file path could not be locked.
**
** all file locks are automatically released and destroyed when the program exits.
** the filelock_lock() function can also detect stale file locks that can linger
** when the emulator crashes unexpectedly, and will happily clean them for you
**
** here's how it works, three files are used:
** file - the data file accessed by the emulator
** lock - a lock file (file + '.lock')
** temp - a temporary file make unique with mkstemp
**
** when locking:
** create 'temp' and store our pid in it
** attemp to link 'lock' to 'temp'
** if the link succeeds, we obtain the lock
** unlink 'temp'
**
** when unlocking:
** unlink 'lock'
**
**
** on Windows, 'lock' is a directory name. locking is equivalent to
** creating it...
**
**/
struct FileLock
{
const char* file;
const char* lock;
char* temp;
int locked;
FileLock* next;
};
/* used to cleanup all locks at emulator exit */
static FileLock* _all_filelocks;
#define LOCK_NAME ".lock"
#define TEMP_NAME ".tmp-XXXXXX"
#ifdef _WIN32
#define PIDFILE_NAME "pid"
#endif
/* returns 0 on success, -1 on failure */
static int
filelock_lock( FileLock* lock )
{
int ret;
#ifdef _WIN32
int pidfile_fd = -1;
ret = _mkdir( lock->lock );
if (ret < 0) {
if (errno == ENOENT) {
D( "could not access directory '%s', check path elements", lock->lock );
return -1;
} else if (errno != EEXIST) {
D( "_mkdir(%s): %s", lock->lock, strerror(errno) );
return -1;
}
/* if we get here, it's because the .lock directory already exists */
/* check to see if there is a pid file in it */
D("directory '%s' already exist, waiting a bit to ensure that no other emulator instance is starting", lock->lock );
{
int _sleep = 200;
int tries;
for ( tries = 4; tries > 0; tries-- )
{
pidfile_fd = open( lock->temp, O_RDONLY );
if (pidfile_fd >= 0)
break;
Sleep( _sleep );
_sleep *= 2;
}
}
if (pidfile_fd < 0) {
D( "no pid file in '%s', assuming stale directory", lock->lock );
}
else
{
/* read the pidfile, and check wether the corresponding process is still running */
char buf[16];
int len, lockpid;
HANDLE processSnapshot;
PROCESSENTRY32 pe32;
int is_locked = 0;
len = read( pidfile_fd, buf, sizeof(buf)-1 );
if (len < 0) {
D( "could not read pid file '%s'", lock->temp );
close( pidfile_fd );
return -1;
}
buf[len] = 0;
lockpid = atoi(buf);
/* PID 0 is the IDLE process, and 0 is returned in case of invalid input */
if (lockpid == 0)
lockpid = -1;
close( pidfile_fd );
pe32.dwSize = sizeof( PROCESSENTRY32 );
processSnapshot = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );
if ( processSnapshot == INVALID_HANDLE_VALUE ) {
D( "could not retrieve the list of currently active processes\n" );
is_locked = 1;
}
else if ( !Process32First( processSnapshot, &pe32 ) )
{
D( "could not retrieve first process id\n" );
CloseHandle( processSnapshot );
is_locked = 1;
}
else
{
do {
if (pe32.th32ProcessID == lockpid) {
is_locked = 1;
break;
}
} while (Process32Next( processSnapshot, &pe32 ) );
CloseHandle( processSnapshot );
}
if (is_locked) {
D( "the file '%s' is locked by process ID %d\n", lock->file, lockpid );
return -1;
}
}
}
/* write our PID into the pid file */
pidfile_fd = open( lock->temp, O_WRONLY | O_CREAT | O_TRUNC );
if (pidfile_fd < 0) {
if (errno == EACCES) {
if ( unlink_file( lock->temp ) < 0 ) {
D( "could not remove '%s': %s\n", lock->temp, strerror(errno) );
return -1;
}
pidfile_fd = open( lock->temp, O_WRONLY | O_CREAT | O_TRUNC );
}
if (pidfile_fd < 0) {
D( "could not create '%s': %s\n", lock->temp, strerror(errno) );
return -1;
}
}
{
char buf[16];
sprintf( buf, "%ld", GetCurrentProcessId() );
ret = write( pidfile_fd, buf, strlen(buf) );
close(pidfile_fd);
if (ret < 0) {
D( "could not write PID to '%s'\n", lock->temp );
return -1;
}
}
lock->locked = 1;
return 0;
#else
int temp_fd = -1;
int lock_fd = -1;
int rc, tries, _sleep;
FILE* f = NULL;
char pid[8];
struct stat st_temp;
strcpy( lock->temp, lock->file );
strcat( lock->temp, TEMP_NAME );
temp_fd = mkstemp( lock->temp );
if (temp_fd < 0) {
D("cannot create locking temp file '%s'", lock->temp );
goto Fail;
}
sprintf( pid, "%d", getpid() );
ret = write( temp_fd, pid, strlen(pid)+1 );
if (ret < 0) {
D( "cannot write to locking temp file '%s'", lock->temp);
goto Fail;
}
close( temp_fd );
temp_fd = -1;
CHECKED(rc, lstat( lock->temp, &st_temp ));
if (rc < 0) {
D( "can't properly stat our locking temp file '%s'", lock->temp );
goto Fail;
}
/* now attempt to link the temp file to the lock file */
_sleep = 0;
for ( tries = 4; tries > 0; tries-- )
{
struct stat st_lock;
int rc;
if (_sleep > 0) {
if (_sleep > 2000000) {
D( "cannot acquire lock file '%s'", lock->lock );
goto Fail;
}
usleep( _sleep );
}
_sleep += 200000;
/* the return value of link() is buggy on NFS */
CHECKED(rc, link( lock->temp, lock->lock ));
CHECKED(rc, lstat( lock->lock, &st_lock ));
if (rc == 0 &&
st_temp.st_rdev == st_lock.st_rdev &&
st_temp.st_ino == st_lock.st_ino )
{
/* SUCCESS */
lock->locked = 1;
CHECKED(rc, unlink( lock->temp ));
return 0;
}
/* if we get there, it means that the link() call failed */
/* check the lockfile to see if it is stale */
if (rc == 0) {
char buf[16];
time_t now;
int lockpid = 0;
int lockfd;
int stale = 2; /* means don't know */
struct stat st;
CHECKED(rc, time( &now));
st.st_mtime = now - 120;
CHECKED(lockfd, open( lock->lock,O_RDONLY ));
if ( lockfd >= 0 ) {
int len;
CHECKED(len, read( lockfd, buf, sizeof(buf)-1 ));
buf[len] = 0;
lockpid = atoi(buf);
CHECKED(rc, fstat( lockfd, &st ));
if (rc == 0)
now = st.st_atime;
CHECKED(rc, close(lockfd));
}
/* if there is a PID, check that it is still alive */
if (lockpid > 0) {
CHECKED(rc, kill( lockpid, 0 ));
if (rc == 0 || errno == EPERM) {
stale = 0;
} else if (rc < 0 && errno == ESRCH) {
stale = 1;
}
}
if (stale == 2) {
/* no pid, stale if the file is older than 1 minute */
stale = (now >= st.st_mtime + 60);
}
if (stale) {
D( "removing stale lockfile '%s'", lock->lock );
CHECKED(rc, unlink( lock->lock ));
_sleep = 0;
tries++;
}
}
}
D("file '%s' is already in use by another process", lock->file );
Fail:
if (f)
fclose(f);
if (temp_fd >= 0) {
close(temp_fd);
}
if (lock_fd >= 0) {
close(lock_fd);
}
unlink( lock->lock );
unlink( lock->temp );
return -1;
#endif
}
void
filelock_release( FileLock* lock )
{
if (lock->locked) {
#ifdef _WIN32
unlink_file( (char*)lock->temp );
rmdir( (char*)lock->lock );
#else
unlink( (char*)lock->lock );
#endif
lock->locked = 0;
}
}
static void
filelock_atexit( void )
{
FileLock* lock;
for (lock = _all_filelocks; lock != NULL; lock = lock->next)
filelock_release( lock );
}
/* create a file lock */
FileLock*
filelock_create( const char* file )
{
int file_len = strlen(file);
int lock_len = file_len + sizeof(LOCK_NAME);
#ifdef _WIN32
int temp_len = lock_len + 1 + sizeof(PIDFILE_NAME);
#else
int temp_len = file_len + sizeof(TEMP_NAME);
#endif
int total_len = sizeof(FileLock) + file_len + lock_len + temp_len + 3;
FileLock* lock = malloc(total_len);
lock->file = (const char*)(lock + 1);
memcpy( (char*)lock->file, file, file_len+1 );
lock->lock = lock->file + file_len + 1;
memcpy( (char*)lock->lock, file, file_len+1 );
strcat( (char*)lock->lock, LOCK_NAME );
lock->temp = (char*)lock->lock + lock_len + 1;
#ifdef _WIN32
snprintf( (char*)lock->temp, temp_len, "%s\\" PIDFILE_NAME, lock->lock );
#else
lock->temp[0] = 0;
#endif
lock->locked = 0;
if (filelock_lock(lock) < 0) {
free(lock);
return NULL;
}
lock->next = _all_filelocks;
_all_filelocks = lock;
if (lock->next == NULL)
atexit( filelock_atexit );
return lock;
}
/** TEMP FILE SUPPORT
**
** simple interface to create an empty temporary file on the system.
**
** create the file with tempfile_create(), which returns a reference to a TempFile
** object, or NULL if your system is so weird it doesn't have a temporary directory.
**
** you can then call tempfile_path() to retrieve the TempFile's real path to open
** it. the returned path is owned by the TempFile object and should not be freed.
**
** all temporary files are destroyed when the program quits, unless you explicitely
** close them before that with tempfile_close()
**/
struct TempFile
{
const char* name;
TempFile* next;
};
static void tempfile_atexit();
static TempFile* _all_tempfiles;
TempFile*
tempfile_create( void )
{
TempFile* tempfile;
const char* tempname = NULL;
#ifdef _WIN32
char temp_namebuff[MAX_PATH];
char temp_dir[MAX_PATH];
char *p = temp_dir, *end = p + sizeof(temp_dir);
UINT retval;
p = bufprint_temp_dir( p, end );
if (p >= end) {
D( "TEMP directory path is too long" );
return NULL;
}
retval = GetTempFileName(temp_dir, "TMP", 0, temp_namebuff);
if (retval == 0) {
D( "can't create temporary file in '%s'", temp_dir );
return NULL;
}
tempname = temp_namebuff;
#else
#define TEMPLATE "/tmp/.android-emulator-XXXXXX"
int tempfd = -1;
char template[512];
char *p = template, *end = p + sizeof(template);
p = bufprint_temp_file( p, end, "emulator-XXXXXX" );
if (p >= end) {
D( "Xcannot create temporary file in /tmp/android !!" );
return NULL;
}
D( "template: %s", template );
tempfd = mkstemp( template );
if (tempfd < 0) {
D("cannot create temporary file in /tmp/android !!");
return NULL;
}
close(tempfd);
tempname = template;
#endif
tempfile = malloc( sizeof(*tempfile) + strlen(tempname) + 1 );
tempfile->name = (char*)(tempfile + 1);
strcpy( (char*)tempfile->name, tempname );
tempfile->next = _all_tempfiles;
_all_tempfiles = tempfile;
if ( !tempfile->next ) {
atexit( tempfile_atexit );
}
return tempfile;
}
const char*
tempfile_path(TempFile* temp)
{
return temp ? temp->name : NULL;
}
void
tempfile_close(TempFile* tempfile)
{
#ifdef _WIN32
DeleteFile(tempfile->name);
#else
unlink(tempfile->name);
#endif
}
/** TEMP FILE CLEANUP
**
**/
/* we don't expect to use many temporary files */
#define MAX_ATEXIT_FDS 16
typedef struct {
int count;
int fds[ MAX_ATEXIT_FDS ];
} AtExitFds;
static void
atexit_fds_add( AtExitFds* t, int fd )
{
if (t->count < MAX_ATEXIT_FDS)
t->fds[t->count++] = fd;
else {
dwarning("%s: over %d calls. Program exit may not cleanup all temporary files",
__FUNCTION__, MAX_ATEXIT_FDS);
}
}
static void
atexit_fds_del( AtExitFds* t, int fd )
{
int nn;
for (nn = 0; nn < t->count; nn++)
if (t->fds[nn] == fd) {
/* move the last element to the current position */
t->count -= 1;
t->fds[nn] = t->fds[t->count];
break;
}
}
static void
atexit_fds_close_all( AtExitFds* t )
{
int nn;
for (nn = 0; nn < t->count; nn++)
close(t->fds[nn]);
}
static AtExitFds _atexit_fds[1];
void
atexit_close_fd(int fd)
{
if (fd >= 0)
atexit_fds_add(_atexit_fds, fd);
}
void
atexit_close_fd_remove(int fd)
{
if (fd >= 0)
atexit_fds_del(_atexit_fds, fd);
}
static void
tempfile_atexit( void )
{
TempFile* tempfile;
atexit_fds_close_all( _atexit_fds );
for (tempfile = _all_tempfiles; tempfile; tempfile = tempfile->next)
tempfile_close(tempfile);
}
/** OTHER FILE UTILITIES
**
** make_empty_file() creates an empty file at a given path location.
** if the file already exists, it is truncated without warning
**
** copy_file() copies one file into another.
**
** both functions return 0 on success, and -1 on error
**/
int
make_empty_file( const char* path )
{
#ifdef _WIN32
int fd = _creat( path, S_IWRITE );
#else
/* on Unix, only allow the owner to read/write, since the file *
* may contain some personal data we don't want to see exposed */
int fd = creat(path, S_IRUSR | S_IWUSR);
#endif
if (fd >= 0) {
close(fd);
return 0;
}
return -1;
}
int
copy_file( const char* dest, const char* source )
{
int fd, fs, result = -1;
if ( access(source, F_OK) < 0 ||
make_empty_file(dest) < 0) {
return -1;
}
#ifdef _WIN32
fd = _open(dest, _O_RDWR | _O_BINARY);
fs = _open(source, _O_RDONLY | _O_BINARY);
#else
fd = creat(dest, S_IRUSR | S_IWUSR);
fs = open(source, S_IREAD);
#endif
if (fs >= 0 && fd >= 0) {
char buf[4096];
ssize_t total = 0;
ssize_t n;
result = 0; /* success */
while ((n = read(fs, buf, 4096)) > 0) {
if (write(fd, buf, n) != n) {
/* write failed. Make it return -1 so that an
* empty file be created. */
D("Failed to copy '%s' to '%s': %s (%d)",
source, dest, strerror(errno), errno);
result = -1;
break;
}
total += n;
}
}
if (fs >= 0) {
close(fs);
}
if (fd >= 0) {
close(fd);
}
return result;
}
int
unlink_file( const char* path )
{
#ifdef _WIN32
int ret = _unlink( path );
if (ret == -1 && errno == EACCES) {
/* a first call to _unlink will fail if the file is set read-only */
/* we can however try to change its mode first and call unlink */
/* again... */
ret = _chmod( path, _S_IREAD | _S_IWRITE );
if (ret == 0)
ret = _unlink( path );
}
return ret;
#else
return unlink(path);
#endif
}
void*
load_text_file(const char *fn)
{
char *data;
int sz;
int fd;
data = NULL;
fd = open(fn, O_BINARY | O_RDONLY);
if(fd < 0) return NULL;
sz = lseek(fd, 0, SEEK_END);
if(sz < 0) goto oops;
if(lseek(fd, 0, SEEK_SET) != 0) goto oops;
data = (char*) malloc(sz + 1);
if(data == 0) goto oops;
if(read(fd, data, sz) != sz) goto oops;
close(fd);
data[sz] = 0;
return data;
oops:
close(fd);
if(data != 0)
free(data);
return NULL;
}
/** HOST RESOLUTION SETTINGS
**
** return the main monitor's DPI resolution according to the host device
** beware: this is not always reliable or even obtainable.
**
** returns 0 on success, or -1 in case of error (e.g. the system returns funky values)
**/
/** NOTE: the following code assumes that we exclusively use X11 on Linux, and Quartz on OS X
**/
#ifdef _WIN32
int
get_monitor_resolution( int *px_dpi, int *py_dpi )
{
HDC displayDC = CreateDC( "DISPLAY", NULL, NULL, NULL );
int xdpi, ydpi;
if (displayDC == NULL) {
D( "%s: could not get display DC\n", __FUNCTION__ );
return -1;
}
xdpi = GetDeviceCaps( displayDC, LOGPIXELSX );
ydpi = GetDeviceCaps( displayDC, LOGPIXELSY );
/* sanity checks */
if (xdpi < 20 || xdpi > 400 || ydpi < 20 || ydpi > 400) {
D( "%s: bad resolution: xpi=%d ydpi=%d", __FUNCTION__,
xdpi, ydpi );
return -1;
}
*px_dpi = xdpi;
*py_dpi = ydpi;
return 0;
}
#elif defined __APPLE__
int
get_monitor_resolution( int *px_dpi, int *py_dpi )
{
fprintf(stderr, "emulator: FIXME: implement get_monitor_resolution on OS X\n" );
return -1;
}
#else /* Linux and others */
#include <SDL.h>
#include <SDL_syswm.h>
#include <dlfcn.h>
#include <X11/Xlib.h>
#define MM_PER_INCH 25.4
#define DYNLINK_FUNCTIONS \
DYNLINK_FUNC(int,XDefaultScreen,(Display*)) \
DYNLINK_FUNC(int,XDisplayWidth,(Display*,int)) \
DYNLINK_FUNC(int,XDisplayWidthMM,(Display*,int)) \
DYNLINK_FUNC(int,XDisplayHeight,(Display*,int)) \
DYNLINK_FUNC(int,XDisplayHeightMM,(Display*,int)) \
#define DYNLINK_FUNCTIONS_INIT \
x11_dynlink_init
#include "dynlink.h"
static int x11_lib_inited;
static void* x11_lib;
int
x11_lib_init( void )
{
if (!x11_lib_inited) {
x11_lib_inited = 1;
x11_lib = dlopen( "libX11.so", RTLD_NOW );
if (x11_lib == NULL) {
x11_lib = dlopen( "libX11.so.6", RTLD_NOW );
}
if (x11_lib == NULL) {
D("%s: Could not find libX11.so on this machine",
__FUNCTION__);
return -1;
}
if (x11_dynlink_init(x11_lib) < 0) {
D("%s: didn't find necessary symbols in libX11.so",
__FUNCTION__);
dlclose(x11_lib);
x11_lib = NULL;
}
}
return x11_lib ? 0 : -1;
}
int
get_monitor_resolution( int *px_dpi, int *py_dpi )
{
SDL_SysWMinfo info;
Display* display;
int screen;
int width, width_mm, height, height_mm, xdpi, ydpi;
SDL_VERSION(&info.version);
if ( !SDL_GetWMInfo(&info) ) {
D( "%s: SDL_GetWMInfo() failed: %s", __FUNCTION__, SDL_GetError());
return -1;
}
if (x11_lib_init() < 0)
return -1;
display = info.info.x11.display;
screen = FF(XDefaultScreen)(display);
width = FF(XDisplayWidth)(display, screen);
width_mm = FF(XDisplayWidthMM)(display, screen);
height = FF(XDisplayHeight)(display, screen);
height_mm = FF(XDisplayHeightMM)(display, screen);
if (width_mm <= 0 || height_mm <= 0) {
D( "%s: bad screen dimensions: width_mm = %d, height_mm = %d",
__FUNCTION__, width_mm, height_mm);
return -1;
}
D( "%s: found screen width=%d height=%d width_mm=%d height_mm=%d",
__FUNCTION__, width, height, width_mm, height_mm );
xdpi = width * MM_PER_INCH / width_mm;
ydpi = height * MM_PER_INCH / height_mm;
if (xdpi < 20 || xdpi > 400 || ydpi < 20 || ydpi > 400) {
D( "%s: bad resolution: xpi=%d ydpi=%d", __FUNCTION__,
xdpi, ydpi );
return -1;
}
*px_dpi = xdpi;
*py_dpi = ydpi;
return 0;
}
#endif
void
disable_sigalrm( signal_state_t *state )
{
#ifdef _WIN32
(void)state;
#else
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGALRM);
pthread_sigmask (SIG_BLOCK, &set, &state->old);
#endif
}
void
restore_sigalrm( signal_state_t *state )
{
#ifdef _WIN32
(void)state;
#else
pthread_sigmask (SIG_SETMASK, &state->old, NULL);
#endif
}
void
sleep_ms( int timeout_ms )
{
#ifdef _WIN32
if (timeout_ms <= 0)
return;
Sleep( timeout_ms );
#else
if (timeout_ms <= 0)
return;
BEGIN_NOSIGALRM
usleep( timeout_ms*1000 );
END_NOSIGALRM
#endif
}
extern void
print_tabular( const char** strings, int count,
const char* prefix, int width )
{
int nrows, ncols, r, c, n, maxw = 0;
for (n = 0; n < count; n++) {
int len = strlen(strings[n]);
if (len > maxw)
maxw = len;
}
maxw += 2;
ncols = width/maxw;
nrows = (count + ncols-1)/ncols;
for (r = 0; r < nrows; r++) {
printf( "%s", prefix );
for (c = 0; c < ncols; c++) {
int index = c*nrows + r;
if (index >= count) {
break;
}
printf( "%-*s", maxw, strings[index] );
}
printf( "\n" );
}
}
extern void
stralloc_tabular( stralloc_t* out,
const char** strings, int count,
const char* prefix, int width )
{
int nrows, ncols, r, c, n, maxw = 0;
for (n = 0; n < count; n++) {
int len = strlen(strings[n]);
if (len > maxw)
maxw = len;
}
maxw += 2;
ncols = width/maxw;
nrows = (count + ncols-1)/ncols;
for (r = 0; r < nrows; r++) {
stralloc_add_str( out, prefix );
for (c = 0; c < ncols; c++) {
int index = c*nrows + r;
if (index >= count) {
break;
}
stralloc_add_format( out, "%-*s", maxw, strings[index] );
}
stralloc_add_str( out, "\n" );
}
}
extern void
string_translate_char( char* str, char from, char to )
{
char* p = str;
while (p != NULL && (p = strchr(p, from)) != NULL)
*p++ = to;
}
extern void
buffer_translate_char( char* buff,
unsigned buffLen,
const char* src,
char fromChar,
char toChar )
{
int len = strlen(src);
if (len >= buffLen)
len = buffLen-1;
memcpy(buff, src, len);
buff[len] = 0;
string_translate_char( buff, fromChar, toChar );
}
/** DYNAMIC STRINGS
**/
extern void
stralloc_reset( stralloc_t* s )
{
free(s->s);
s->s = NULL;
s->n = 0;
s->a = 0;
}
extern void
stralloc_ready( stralloc_t* s, unsigned int len )
{
unsigned old_max = s->a;
unsigned new_max = old_max;
while (new_max < len) {
unsigned new_max2 = new_max + (new_max >> 1) + 16;
if (new_max2 < new_max)
new_max2 = UINT_MAX;
new_max = new_max2;
}
s->s = realloc( s->s, new_max );
if (s->s == NULL) {
derror( "%s: not enough memory to reallocate %ld bytes",
__FUNCTION__, new_max );
exit(1);
}
s->a = new_max;
}
extern void
stralloc_readyplus( stralloc_t* s, unsigned int len )
{
unsigned len2 = s->n + len;
if (len2 < s->n) { /* overflow ? */
derror("%s: trying to grow by too many bytes: %ld",
__FUNCTION__, len);
exit(1);
}
stralloc_ready( s, len2 );
}
extern void
stralloc_copy( stralloc_t* s, stralloc_t* from )
{
stralloc_ready(s, from->n);
memcpy( s->s, from->s, from->n );
s->n = from->n;
}
extern void
stralloc_append( stralloc_t* s, stralloc_t* from )
{
stralloc_readyplus( s, from->n );
memcpy( s->s + s->n, from->s, from->n );
s->n += from->n;
}
extern void
stralloc_add_c( stralloc_t* s, int c )
{
stralloc_add_bytes( s, (char*)&c, 1 );
}
extern void
stralloc_add_str( stralloc_t* s, const char* str )
{
stralloc_add_bytes( s, str, strlen(str) );
}
extern void
stralloc_add_bytes( stralloc_t* s, const void* from, unsigned len )
{
stralloc_readyplus( s, len );
memcpy( s->s + s->n, from, len );
s->n += len;
}
extern char*
stralloc_cstr( stralloc_t* s )
{
stralloc_readyplus( s, 1 );
s->s[s->n] = 0;
return s->s;
}
extern void
stralloc_format( stralloc_t* s, const char* fmt, ... )
{
stralloc_reset(s);
stralloc_ready(s, 10);
while (1) {
int n;
va_list args;
va_start(args, fmt);
n = vsnprintf( s->s, s->a, fmt, args );
va_end(args);
/* funky old C libraries returns -1 when truncation occurs */
if (n > -1 && n < s->a) {
s->n = n;
break;
}
if (n > -1) { /* we now precisely what we need */
stralloc_ready( s, n+1 );
} else {
stralloc_ready( s, s->a*2 );
}
}
}
extern void
stralloc_add_format( stralloc_t* s, const char* fmt, ... )
{
STRALLOC_DEFINE(s2);
stralloc_ready(s, 10);
while (1) {
int n;
va_list args;
va_start(args, fmt);
n = vsnprintf( s2->s, s2->a, fmt, args );
va_end(args);
/* some C libraries return -1 when truncation occurs */
if (n > -1 && n < s2->a) {
s2->n = n;
break;
}
if (n > -1) { /* we now precisely what we need */
stralloc_ready( s2, n+1 );
} else {
stralloc_ready( s2, s2->a*2 );
}
}
stralloc_append( s, s2 );
stralloc_reset( s2 );
}
extern void
stralloc_add_quote_c( stralloc_t* s, int c )
{
stralloc_add_quote_bytes( s, (char*)&c, 1 );
}
extern void
stralloc_add_quote_str( stralloc_t* s, const char* str )
{
stralloc_add_quote_bytes( s, str, strlen(str) );
}
extern void
stralloc_add_quote_bytes( stralloc_t* s, const void* from, unsigned len )
{
uint8_t* p = (uint8_t*) from;
uint8_t* end = p + len;
for ( ; p < end; p++ ) {
int c = p[0];
if (c == '\\') {
stralloc_add_str( s, "\\\\" );
} else if (c >= ' ' && c < 128) {
stralloc_add_c( s, c );
} else if (c == '\n') {
stralloc_add_str( s, "\\n" );
} else if (c == '\t') {
stralloc_add_str( s, "\\t" );
} else if (c == '\r') {
stralloc_add_str( s, "\\r" );
} else {
stralloc_add_format( s, "\\x%02x", c );
}
}
}
extern void
stralloc_add_hex( stralloc_t* s, unsigned value, int num_digits )
{
const char hexdigits[16] = "0123456789abcdef";
int nn;
if (num_digits <= 0)
return;
stralloc_readyplus(s, num_digits);
for (nn = num_digits-1; nn >= 0; nn--) {
s->s[s->n+nn] = hexdigits[value & 15];
value >>= 4;
}
s->n += num_digits;
}
extern void
stralloc_add_hexdump( stralloc_t* s, void* base, int size, const char* prefix )
{
uint8_t* p = (uint8_t*)base;
const int max_count = 16;
int prefix_len = strlen(prefix);
while (size > 0) {
int count = size > max_count ? max_count : size;
int count2;
int n;
stralloc_add_bytes( s, prefix, prefix_len );
stralloc_add_hex( s, p[0], 2 );
for (n = 1; n < count; n++) {
stralloc_add_c( s, ' ' );
stralloc_add_hex( s, p[n], 2 );
}
count2 = 4 + 3*(max_count - count);
stralloc_readyplus( s, count2 );
memset( s->s + s->n, ' ', count2 );
s->n += count2;
stralloc_readyplus(s, count+1);
for (n = 0; n < count; n++) {
int c = p[n];
if (c < 32 || c > 127)
c = '.';
s->s[s->n++] = c;
}
s->s[s->n++] = '\n';
size -= count;
p += count;
}
}
/** TEMP CHAR STRINGS
**
** implement a circular ring of temporary string buffers
**/
typedef struct Temptring {
struct TempString* next;
char* buffer;
int size;
} TempString;
#define MAX_TEMP_STRINGS 16
static TempString _temp_strings[ MAX_TEMP_STRINGS ];
static int _temp_string_n;
extern char*
tempstr_get( int size )
{
TempString* t = &_temp_strings[_temp_string_n];
if ( ++_temp_string_n >= MAX_TEMP_STRINGS )
_temp_string_n = 0;
size += 1; /* reserve 1 char for terminating zero */
if (t->size < size) {
t->buffer = realloc( t->buffer, size );
if (t->buffer == NULL) {
derror( "%s: could not allocate %d bytes",
__FUNCTION__, size );
exit(1);
}
t->size = size;
}
return t->buffer;
}
extern char*
tempstr_from_stralloc( stralloc_t* s )
{
char* q = tempstr_get( s->n );
memcpy( q, s->s, s->n );
q[s->n] = 0;
return q;
}
/** QUOTING
**
** dumps a human-readable version of a string. this replaces
** newlines with \n, etc...
**/
extern const char*
quote_bytes( const char* str, int len )
{
STRALLOC_DEFINE(s);
char* q;
stralloc_add_quote_bytes( s, str, len );
q = tempstr_from_stralloc( s );
stralloc_reset(s);
return q;
}
extern const char*
quote_str( const char* str )
{
int len = strlen(str);
return quote_bytes( str, len );
}
/** DYNAMIC ARRAYS OF POINTERS
**/
void
qvector_init( qvector_t* v )
{
v->i = NULL;
v->n = v->a = 0;
}
void
qvector_reset( qvector_t* v )
{
free(v->i);
v->i = NULL;
v->n = v->a = 0;
}
void
qvector_ready( qvector_t* v, unsigned len )
{
unsigned old_a = v->a;
unsigned new_a = old_a;
unsigned max_a = UINT_MAX/sizeof(v->i[0]);
if (len <= old_a)
return;
if (len > max_a) {
derror("panic: %s: length too long (%d)", __FUNCTION__, len);
exit(1);
}
while (new_a < len) {
unsigned new_a2 = new_a + (new_a >> 1) + 8;
if (new_a2 < new_a || new_a2 > max_a)
new_a2 = max_a;
new_a = max_a;
}
v->i = realloc( v->i, new_a*sizeof(v->i[0]) );
v->a = new_a;
}
void
qvector_readyplus( qvector_t* v, unsigned len )
{
unsigned len2 = len + v->n;
if (len2 < len) {
derror("panic: %s: length too long (%d)", __FUNCTION__, len);
exit(1);
}
qvector_ready(v, len2);
}
void
qvector_add( qvector_t* v, void* item )
{
qvector_readyplus(v, 1);
v->i[v->n] = item;
v->n += 1;
}
int
qvector_del( qvector_t* v, void* item )
{
int index = qvector_index(v, item);
if (index < 0) return 0;
qvector_remove(v, index);
return 1;
}
extern void*
qvector_get( qvector_t* v, int index )
{
if ((unsigned)index >= (unsigned)v->n)
return NULL;
return v->i[index];
}
extern void
qvector_set( qvector_t* v, int index, void* item )
{
if ((unsigned)index < (unsigned)v->n)
v->i[index] = item;
}
int
qvector_len( qvector_t* v )
{
return v->n;
}
int
qvector_index( qvector_t* v, void* item )
{
int nn;
for (nn = 0; nn < v->n; nn++)
if (v->i[nn] == item)
return nn;
return -1;
}
void
qvector_insert( qvector_t* v, int index, void* item )
{
if (index < 0) index = 0;
if (index > v->n) index = v->n;
memmove( v->i + index, v->i + index + 1, sizeof(v->i[0]) );
v->i[index] = item;
v->n += 1;
}
void
qvector_remove( qvector_t* v, int index )
{
if (index < 0 || index >= v->n )
return;
memmove( v->i + index + 1, v->i + index, v->n - index - 1 );
v->n -= 1;
}
void
qvector_remove_n( qvector_t* v, int index, int count )
{
int end = index + count;
if (index < 0 || index >= v->n || end <= index)
return;
if (end > v->n) {
end = v->n;
count = end - index;
}
memmove( v->i + index + count, v->i + index, v->n - index - count );
v->n -= count;
}
/** HEXADECIMAL CHARACTER SEQUENCES
**/
static int
hexdigit( int c )
{
unsigned d;
d = (unsigned)(c - '0');
if (d < 10) return d;
d = (unsigned)(c - 'a');
if (d < 6) return d+10;
d = (unsigned)(c - 'A');
if (d < 6) return d+10;
return -1;
}
int
hex2int( const uint8_t* hex, int len )
{
int result = 0;
while (len > 0) {
int c = hexdigit(*hex++);
if (c < 0)
return -1;
result = (result << 4) | c;
len --;
}
return result;
}
void
int2hex( uint8_t* hex, int len, int val )
{
static const uint8_t hexchars[16] = "0123456789abcdef";
while ( --len >= 0 )
*hex++ = hexchars[(val >> (len*4)) & 15];
}