/* Copyright (C) 2007-2009 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/path.h" #include #include #include #include #include #ifdef _WIN32 #include #include #include #include #include #include #include #include #include #else #include #include #include #include #endif #include "android/utils/debug.h" #define D(...) VERBOSE_PRINT(init,__VA_ARGS__) #ifndef CHECKED # ifdef _WIN32 # define CHECKED(ret, call) (ret) = (call) # else # define CHECKED(ret, call) do { (ret) = (call); } while ((ret) < 0 && errno == EINTR) # endif #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; } static char* substring_dup( const char* start, const char* end ) { int len = end - start; char* result = android_alloc(len+1); memcpy(result, start, len); result[len] = 0; return result; } int path_split( const char* path, char* *pdirname, char* *pbasename ) { const char* end = path + strlen(path); const char* last; char* basename; /* prepare for errors */ if (pdirname) *pdirname = NULL; if (pbasename) *pbasename = NULL; /* handle empty path case */ if (end == path) { return -1; } /* strip trailing path separators */ while (end > path && ispathsep(end[-1])) end -= 1; /* handle "/" and degenerate cases like "////" */ if (end == path) { return -1; } /* find last separator */ last = end; while (last > path && !ispathsep(last[-1])) last -= 1; /* handle cases where there is no path separator */ if (last == path) { if (pdirname) *pdirname = ASTRDUP("."); if (pbasename) *pbasename = substring_dup(path,end); return 0; } /* handle "/foo" */ if (last == path+1) { if (pdirname) *pdirname = ASTRDUP("/"); if (pbasename) *pbasename = substring_dup(path+1,end); return 0; } /* compute basename */ basename = substring_dup(last,end); if (strcmp(basename, ".") == 0 || strcmp(basename, "..") == 0) { AFREE(basename); return -1; } if (pbasename) *pbasename = basename; else { AFREE(basename); } /* compute dirname */ if (pdirname != NULL) *pdirname = substring_dup(path,last-1); return 0; } char* path_basename( const char* path ) { char* basename; if (path_split(path, NULL, &basename) < 0) return NULL; return basename; } char* path_dirname( const char* path ) { char* dirname; if (path_split(path, &dirname, NULL) < 0) return NULL; return dirname; } /** MISC FILE AND DIRECTORY HANDLING **/ ABool 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 */ ABool 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 */ ABool 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 */ ABool path_can_read( const char* path ) { int ret; CHECKED(ret, access(path, R_OK)); return (ret == 0); } ABool path_can_write( const char* path ) { int ret; CHECKED(ret, access(path, R_OK)); return (ret == 0); } ABool path_can_exec( const char* path ) { int ret; CHECKED(ret, access(path, X_OK)); return (ret == 0); } /* try to make a directory. returns 0 on success, -1 on failure * (error code in errno) */ APosixStatus 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 APosixStatus 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) */ APosixStatus 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) */ APosixStatus 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 } ABool path_is_absolute( const char* path ) { #ifdef _WIN32 if (path == NULL) return 0; if (path[0] == '/' || path[0] == '\\') return 1; /* 'C:' is always considered to be absolute * even if used with a relative path like C:foo which * is different from C:\foo */ if (path[0] != 0 && path[1] == ':') return 1; return 0; #else return (path != NULL && path[0] == '/'); #endif } char* path_get_absolute( const char* path ) { if (path_is_absolute(path)) { return ASTRDUP(path); } #ifdef _WIN32 { char* result; int pathLen = strlen(path); int currentLen = GetCurrentDirectory(0, NULL); if (currentLen <= 0) { /* Could not get size of working directory. something is * really fishy here, return a simple copy */ return ASTRDUP(path); } result = malloc(currentLen + pathLen + 2); GetCurrentDirectory(currentLen+1, result); if (currentLen == 0 || result[currentLen-1] != '\\') { result[currentLen++] = '\\'; } memcpy(result + currentLen, path, pathLen+1); return result; } #else { int pathLen = strlen(path); char currentDir[PATH_MAX]; int currentLen; char* result; if (getcwd(currentDir, sizeof(currentDir)) == NULL) { /* Could not get the current working directory. something is really * fishy here, so don't do anything and return a copy */ return ASTRDUP(path); } /* Make a new path with / */ currentLen = strlen(currentDir); result = malloc(currentLen + pathLen + 2); memcpy(result, currentDir, currentLen); if (currentLen == 0 || result[currentLen-1] != '/') { result[currentLen++] = '/'; } memcpy(result + currentLen, path, pathLen+1); return result; } #endif } /** OTHER FILE UTILITIES ** ** path_empty_file() creates an empty file at a given path location. ** if the file already exists, it is truncated without warning ** ** path_copy_file() copies one file into another. ** ** both functions return 0 on success, and -1 on error **/ APosixStatus path_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; } APosixStatus path_copy_file( const char* dest, const char* source ) { int fd, fs, result = -1; /* if the destination doesn't exist, create it */ if ( access(source, F_OK) < 0 || path_empty_file(dest) < 0) { return -1; } if ( access(source, R_OK) < 0 ) { D("%s: source file is un-readable: %s\n", __FUNCTION__, source); 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; } APosixStatus path_delete_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* path_load_file(const char *fn, size_t *pSize) { char* data; int sz; int fd; if (pSize) *pSize = 0; data = NULL; fd = open(fn, O_BINARY | O_RDONLY); if(fd < 0) return NULL; do { sz = lseek(fd, 0, SEEK_END); if(sz < 0) break; if (pSize) *pSize = (size_t) sz; if (lseek(fd, 0, SEEK_SET) != 0) break; data = (char*) malloc(sz + 1); if(data == NULL) break; if (read(fd, data, sz) != sz) break; close(fd); data[sz] = 0; return data; } while (0); close(fd); if(data != NULL) free(data); return NULL; } #ifdef _WIN32 # define DIR_SEP ';' #else # define DIR_SEP ':' #endif char* path_search_exec( const char* filename ) { const char* sysPath = getenv("PATH"); char temp[PATH_MAX]; int count; int slen; const char* p; /* If the file contains a directory separator, don't search */ #ifdef _WIN32 if (strchr(filename, '/') != NULL || strchr(filename, '\\') != NULL) { #else if (strchr(filename, '/') != NULL) { #endif if (path_exists(filename)) { return strdup(filename); } else { return NULL; } } /* If system path is empty, don't search */ if (sysPath == NULL || sysPath[0] == '\0') { return NULL; } /* Count the number of non-empty items in the system path * Items are separated by DIR_SEP, and two successive separators * correspond to an empty item that will be ignored. * Also compute the required string storage length. */ count = 0; slen = 0; p = sysPath; while (*p) { char* p2 = strchr(p, DIR_SEP); int len; if (p2 == NULL) { len = strlen(p); } else { len = p2 - p; } do { if (len <= 0) break; snprintf(temp, sizeof(temp), "%.*s/%s", len, p, filename); if (path_exists(temp) && path_can_exec(temp)) { return strdup(temp); } } while (0); p += len; if (*p == DIR_SEP) p++; } /* Nothing, really */ return NULL; }