com_android_internal_content_NativeLibraryHelper.cpp revision 66269ea6f68f2f25888ce1080c94ac782742fafc
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#define LOG_TAG "NativeLibraryHelper" 18//#define LOG_NDEBUG 0 19 20#include <android_runtime/AndroidRuntime.h> 21 22#include <utils/Log.h> 23#include <ScopedUtfChars.h> 24#include <utils/ZipFileRO.h> 25 26#include <zlib.h> 27 28#include <fcntl.h> 29#include <stdlib.h> 30#include <string.h> 31#include <time.h> 32#include <unistd.h> 33#include <sys/stat.h> 34#include <sys/types.h> 35 36 37#define APK_LIB "lib/" 38#define APK_LIB_LEN (sizeof(APK_LIB) - 1) 39 40#define LIB_PREFIX "/lib" 41#define LIB_PREFIX_LEN (sizeof(LIB_PREFIX) - 1) 42 43#define LIB_SUFFIX ".so" 44#define LIB_SUFFIX_LEN (sizeof(LIB_SUFFIX) - 1) 45 46#define GDBSERVER "gdbserver" 47#define GDBSERVER_LEN (sizeof(GDBSERVER) - 1) 48 49#define TMP_FILE_PATTERN "/tmp.XXXXXX" 50#define TMP_FILE_PATTERN_LEN (sizeof(TMP_FILE_PATTERN) - 1) 51 52namespace android { 53 54typedef void (*iterFunc)(JNIEnv*, void*, ZipFileRO*, ZipEntryRO, const char*); 55 56// These match PackageManager.java install codes 57typedef enum { 58 INSTALL_SUCCEEDED = 0, 59 INSTALL_FAILED_INVALID_APK = -2, 60 INSTALL_FAILED_INSUFFICIENT_STORAGE = -4, 61} install_status_t; 62 63// Equivalent to isFilenameSafe 64static bool 65isFilenameSafe(const char* filename) 66{ 67 off_t offset = 0; 68 for (;;) { 69 switch (*(filename + offset)) { 70 case 0: 71 // Null. 72 // If we've reached the end, all the other characters are good. 73 return true; 74 75 case 'A' ... 'Z': 76 case 'a' ... 'z': 77 case '0' ... '9': 78 case '+': 79 case ',': 80 case '-': 81 case '.': 82 case '/': 83 case '=': 84 case '_': 85 offset++; 86 break; 87 88 default: 89 // We found something that is not good. 90 return false; 91 } 92 } 93 // Should not reach here. 94} 95 96static bool 97isFileDifferent(const char* filePath, size_t fileSize, time_t modifiedTime, 98 long zipCrc, struct stat64* st) 99{ 100 if (lstat64(filePath, st) < 0) { 101 // File is not found or cannot be read. 102 LOGV("Couldn't stat %s, copying: %s\n", filePath, strerror(errno)); 103 return true; 104 } 105 106 if (!S_ISREG(st->st_mode)) { 107 return true; 108 } 109 110 if (st->st_size != fileSize) { 111 return true; 112 } 113 114 // For some reason, bionic doesn't define st_mtime as time_t 115 if (time_t(st->st_mtime) != modifiedTime) { 116 LOGV("mod time doesn't match: %ld vs. %ld\n", st->st_mtime, modifiedTime); 117 return true; 118 } 119 120 int fd = TEMP_FAILURE_RETRY(open(filePath, O_RDONLY)); 121 if (fd < 0) { 122 LOGV("Couldn't open file %s: %s", filePath, strerror(errno)); 123 return true; 124 } 125 126 long crc = crc32(0L, Z_NULL, 0); 127 unsigned char crcBuffer[16384]; 128 ssize_t numBytes; 129 while ((numBytes = TEMP_FAILURE_RETRY(read(fd, crcBuffer, sizeof(crcBuffer)))) > 0) { 130 crc = crc32(crc, crcBuffer, numBytes); 131 } 132 close(fd); 133 134 LOGV("%s: crc = %lx, zipCrc = %lx\n", filePath, crc, zipCrc); 135 136 if (crc != zipCrc) { 137 return true; 138 } 139 140 return false; 141} 142 143static void 144sumFiles(JNIEnv* env, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntry, const char* fileName) 145{ 146 size_t* total = (size_t*) arg; 147 size_t uncompLen; 148 149 if (!zipFile->getEntryInfo(zipEntry, NULL, &uncompLen, NULL, NULL, NULL, NULL)) { 150 return; 151 } 152 153 *total += uncompLen; 154} 155 156/* 157 * Copy the native library if needed. 158 * 159 * This function assumes the library and path names passed in are considered safe. 160 */ 161static void 162copyFileIfChanged(JNIEnv *env, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntry, const char* fileName) 163{ 164 jstring* javaNativeLibPath = (jstring*) arg; 165 ScopedUtfChars nativeLibPath(env, *javaNativeLibPath); 166 167 size_t uncompLen; 168 long when; 169 long crc; 170 time_t modTime; 171 172 if (!zipFile->getEntryInfo(zipEntry, NULL, &uncompLen, NULL, NULL, &when, &crc)) { 173 return; 174 } else { 175 struct tm t; 176 ZipFileRO::zipTimeToTimespec(when, &t); 177 modTime = mktime(&t); 178 } 179 180 // Build local file path 181 const size_t fileNameLen = strlen(fileName); 182 char localFileName[nativeLibPath.size() + fileNameLen + 2]; 183 184 if (strlcpy(localFileName, nativeLibPath.c_str(), sizeof(localFileName)) != nativeLibPath.size()) { 185 LOGD("Couldn't allocate local file name for library: %s", strerror(errno)); 186 return; 187 } 188 189 *(localFileName + nativeLibPath.size()) = '/'; 190 191 if (strlcpy(localFileName + nativeLibPath.size() + 1, fileName, sizeof(localFileName) 192 - nativeLibPath.size() - 1) != fileNameLen) { 193 LOGD("Couldn't allocate local file name for library: %s", strerror(errno)); 194 return; 195 } 196 197 // Only copy out the native file if it's different. 198 struct stat st; 199 if (!isFileDifferent(localFileName, uncompLen, modTime, crc, &st)) { 200 return; 201 } 202 203 char localTmpFileName[nativeLibPath.size() + TMP_FILE_PATTERN_LEN + 2]; 204 if (strlcpy(localTmpFileName, nativeLibPath.c_str(), sizeof(localTmpFileName)) 205 != nativeLibPath.size()) { 206 LOGD("Couldn't allocate local file name for library: %s", strerror(errno)); 207 return; 208 } 209 210 *(localFileName + nativeLibPath.size()) = '/'; 211 212 if (strlcpy(localTmpFileName + nativeLibPath.size(), TMP_FILE_PATTERN, 213 TMP_FILE_PATTERN_LEN - nativeLibPath.size()) != TMP_FILE_PATTERN_LEN) { 214 LOGI("Couldn't allocate temporary file name for library: %s", strerror(errno)); 215 return; 216 } 217 218 int fd = mkstemp(localTmpFileName); 219 if (fd < 0) { 220 LOGI("Couldn't open temporary file name: %s: %s\n", localTmpFileName, strerror(errno)); 221 return; 222 } 223 224 if (!zipFile->uncompressEntry(zipEntry, fd)) { 225 LOGI("Failed uncompressing %s to %s: %s", fileName, localTmpFileName, strerror(errno)); 226 close(fd); 227 unlink(localTmpFileName); 228 return; 229 } 230 231 close(fd); 232 233 // Set the modification time for this file to the ZIP's mod time. 234 struct timeval times[2]; 235 times[0].tv_sec = st.st_atime; 236 times[1].tv_sec = modTime; 237 times[0].tv_usec = times[1].tv_usec = 0; 238 if (utimes(localTmpFileName, times) < 0) { 239 LOGI("Couldn't change modification time on %s: %s\n", localTmpFileName, strerror(errno)); 240 unlink(localTmpFileName); 241 return; 242 } 243 244 // Set the mode to 755 245 static const mode_t mode = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; 246 if (chmod(localTmpFileName, mode) < 0) { 247 LOGI("Couldn't change permissions on %s: %s\n", localTmpFileName, strerror(errno)); 248 unlink(localTmpFileName); 249 return; 250 } 251 252 // Finally, rename it to the final name. 253 if (rename(localTmpFileName, localFileName) < 0) { 254 LOGI("Couldn't rename %s to %s: %s\n", localTmpFileName, localFileName, strerror(errno)); 255 unlink(localTmpFileName); 256 return; 257 } 258 259 LOGV("Successfully moved %s to %s\n", localTmpFileName, localFileName); 260} 261 262static install_status_t 263iterateOverNativeFiles(JNIEnv *env, jstring javaFilePath, jstring javaCpuAbi, jstring javaCpuAbi2, 264 iterFunc callFunc, void* callArg) { 265 ScopedUtfChars filePath(env, javaFilePath); 266 ScopedUtfChars cpuAbi(env, javaCpuAbi); 267 ScopedUtfChars cpuAbi2(env, javaCpuAbi2); 268 269 ZipFileRO zipFile; 270 271 if (zipFile.open(filePath.c_str()) != NO_ERROR) { 272 LOGI("Couldn't open APK %s\n", filePath.c_str()); 273 return INSTALL_FAILED_INVALID_APK; 274 } 275 276 const int N = zipFile.getNumEntries(); 277 278 char fileName[PATH_MAX]; 279 280 for (int i = 0; i < N; i++) { 281 const ZipEntryRO entry = zipFile.findEntryByIndex(i); 282 if (entry == NULL) { 283 continue; 284 } 285 286 // Make sure this entry has a filename. 287 if (zipFile.getEntryFileName(entry, fileName, sizeof(fileName))) { 288 continue; 289 } 290 291 // Make sure we're in the lib directory of the ZIP. 292 if (strncmp(fileName, APK_LIB, APK_LIB_LEN)) { 293 continue; 294 } 295 296 // Make sure the filename is at least to the minimum library name size. 297 const size_t fileNameLen = strlen(fileName); 298 static const size_t minLength = APK_LIB_LEN + 2 + LIB_PREFIX_LEN + 1 + LIB_SUFFIX_LEN; 299 if (fileNameLen < minLength) { 300 continue; 301 } 302 303 const char* lastSlash = strrchr(fileName, '/'); 304 if (lastSlash == NULL) { 305 LOG_ASSERT("last slash was null somehow for %s\n", fileName); 306 continue; 307 } 308 309 // Check to make sure the CPU ABI of this file is one we support. 310 const char* cpuAbiOffset = fileName + APK_LIB_LEN; 311 const size_t cpuAbiRegionSize = lastSlash - cpuAbiOffset; 312 313 LOGV("Comparing ABIs %s and %s versus %s\n", cpuAbi.c_str(), cpuAbi2.c_str(), cpuAbiOffset); 314 if (cpuAbi.size() == cpuAbiRegionSize 315 && *(cpuAbiOffset + cpuAbi.size()) == '/' 316 && !strncmp(cpuAbiOffset, cpuAbi.c_str(), cpuAbiRegionSize)) { 317 LOGV("Using ABI %s\n", cpuAbi.c_str()); 318 } else if (cpuAbi2.size() == cpuAbiRegionSize 319 && *(cpuAbiOffset + cpuAbi2.size()) == '/' 320 && !strncmp(cpuAbiOffset, cpuAbi2.c_str(), cpuAbiRegionSize)) { 321 LOGV("Using ABI %s\n", cpuAbi2.c_str()); 322 } else { 323 LOGV("abi didn't match anything: %s (end at %zd)\n", cpuAbiOffset, cpuAbiRegionSize); 324 continue; 325 } 326 327 // If this is a .so file, check to see if we need to copy it. 328 if (!strncmp(fileName + fileNameLen - LIB_SUFFIX_LEN, LIB_SUFFIX, LIB_SUFFIX_LEN) 329 && !strncmp(lastSlash, LIB_PREFIX, LIB_PREFIX_LEN) 330 && isFilenameSafe(lastSlash + 1)) { 331 callFunc(env, callArg, &zipFile, entry, lastSlash + 1); 332 } else if (!strncmp(lastSlash + 1, GDBSERVER, GDBSERVER_LEN)) { 333 callFunc(env, callArg, &zipFile, entry, lastSlash + 1); 334 } 335 } 336 337 return INSTALL_SUCCEEDED; 338} 339 340static jint 341com_android_internal_content_NativeLibraryHelper_copyNativeBinaries(JNIEnv *env, jclass clazz, 342 jstring javaFilePath, jstring javaNativeLibPath, jstring javaCpuAbi, jstring javaCpuAbi2) 343{ 344 return iterateOverNativeFiles(env, javaFilePath, javaCpuAbi, javaCpuAbi2, 345 copyFileIfChanged, &javaNativeLibPath); 346} 347 348static jlong 349com_android_internal_content_NativeLibraryHelper_sumNativeBinaries(JNIEnv *env, jclass clazz, 350 jstring javaFilePath, jstring javaCpuAbi, jstring javaCpuAbi2) 351{ 352 size_t totalSize = 0; 353 354 iterateOverNativeFiles(env, javaFilePath, javaCpuAbi, javaCpuAbi2, sumFiles, &totalSize); 355 356 return totalSize; 357} 358 359static JNINativeMethod gMethods[] = { 360 {"nativeCopyNativeBinaries", 361 "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I", 362 (void *)com_android_internal_content_NativeLibraryHelper_copyNativeBinaries}, 363 {"nativeSumNativeBinaries", 364 "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)J", 365 (void *)com_android_internal_content_NativeLibraryHelper_sumNativeBinaries}, 366}; 367 368 369int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env) 370{ 371 return AndroidRuntime::registerNativeMethods(env, 372 "com/android/internal/content/NativeLibraryHelper", gMethods, NELEM(gMethods)); 373} 374 375}; 376