| /* 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]; |
| } |