diff options
Diffstat (limited to 'android_utils.c')
-rw-r--r-- | android_utils.c | 1764 |
1 files changed, 1764 insertions, 0 deletions
diff --git a/android_utils.c b/android_utils.c new file mode 100644 index 0000000..26518cd --- /dev/null +++ b/android_utils.c @@ -0,0 +1,1764 @@ +/* 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__) + +/** 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 + +#ifdef _WIN32 +#define _ANDROID_PATH "Android" +#else +#define _ANDROID_PATH ".android" +#endif + +char* +bufprint_config_path(char* buff, char* end) +{ +#ifdef _WIN32 + char path[MAX_PATH]; + + SHGetFolderPath( NULL, CSIDL_LOCAL_APPDATA|CSIDL_FLAG_CREATE, + NULL, 0, path); + + return bufprint(buff, end, "%s\\%s\\%s", path, _ANDROID_PATH, ANDROID_SDK_VERSION); +#else + const char* home = getenv("HOME"); + if (home == NULL) + home = "/tmp"; + return bufprint(buff, end, "%s/%s/%s", home, _ANDROID_PATH, ANDROID_SDK_VERSION); +#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 thee file doesn't exist, or if you don't have enough memory. + * + * then call filelock_lock() to try to acquire a lock for the corresponding file. + ** returns 0 on success, or -1 in case of error, which means that another program + ** is using the file or that the directory containing the file is read-only. + ** + ** 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; +}; + +#define LOCK_NAME ".lock" +#define TEMP_NAME ".tmp-XXXXXX" + +#ifdef _WIN32 +#define PIDFILE_NAME "pid" +#endif + +/* returns 0 on success, -1 on failure */ +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_unlock( FileLock* lock ) +{ +#ifdef _WIN32 + unlink_file( (char*)lock->temp ); + rmdir( (char*)lock->lock ); + lock->locked = 0; +#else + unlink( (char*)lock->lock ); + lock->locked = 0; +#endif +} + + +/* used to cleanup all locks at emulator exit */ +static FileLock* _all_filelocks; + +static void +filelock_atexit( void ) +{ + FileLock* lock; + + for (lock = _all_filelocks; lock != NULL; lock = lock->next) + { + if (lock->locked) + filelock_unlock( 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); + if (lock == NULL) + goto Exit; + + 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 + sprintf( (char*)lock->temp, "%s\\" PIDFILE_NAME, lock->lock ); +#else + lock->temp[0] = 0; +#endif + lock->locked = 0; + + lock->next = _all_filelocks; + _all_filelocks = lock; + + if (lock->next == NULL) + atexit( filelock_atexit ); +Exit: + 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 <X11/Xlib.h> +#define MM_PER_INCH 25.4 + +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; + } + + display = info.info.x11.display; + screen = XDefaultScreen(display); + + width = XDisplayWidth(display, screen); + width_mm = XDisplayWidthMM(display, screen); + height = XDisplayHeight(display, screen); + height_mm = 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 +buffer_translate_char( char* buff, unsigned len, + char from, char to ) +{ + char* p = buff; + char* end = p + len; + + while (p != NULL && (p = memchr(p, from, (size_t)(end - p))) != NULL) + *p++ = to; +} + +extern void +string_translate_char( char* str, char from, char to ) +{ + char* p = str; + while (p != NULL && (p = strchr(p, from)) != NULL) + *p++ = to; +} + +/** 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 ); + } + } +} + +/** 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; +} + |