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 <utils/ZipFileRO.h> 24#include <ScopedUtfChars.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 54// These match PackageManager.java install codes 55typedef enum { 56 INSTALL_SUCCEEDED = 1, 57 INSTALL_FAILED_INVALID_APK = -2, 58 INSTALL_FAILED_INSUFFICIENT_STORAGE = -4, 59 INSTALL_FAILED_CONTAINER_ERROR = -18, 60 INSTALL_FAILED_INTERNAL_ERROR = -110, 61} install_status_t; 62 63typedef install_status_t (*iterFunc)(JNIEnv*, void*, ZipFileRO*, ZipEntryRO, const char*); 64 65// Equivalent to isFilenameSafe 66static bool 67isFilenameSafe(const char* filename) 68{ 69 off_t offset = 0; 70 for (;;) { 71 switch (*(filename + offset)) { 72 case 0: 73 // Null. 74 // If we've reached the end, all the other characters are good. 75 return true; 76 77 case 'A' ... 'Z': 78 case 'a' ... 'z': 79 case '0' ... '9': 80 case '+': 81 case ',': 82 case '-': 83 case '.': 84 case '/': 85 case '=': 86 case '_': 87 offset++; 88 break; 89 90 default: 91 // We found something that is not good. 92 return false; 93 } 94 } 95 // Should not reach here. 96} 97 98static bool 99isFileDifferent(const char* filePath, size_t fileSize, time_t modifiedTime, 100 long zipCrc, struct stat64* st) 101{ 102 if (lstat64(filePath, st) < 0) { 103 // File is not found or cannot be read. 104 ALOGV("Couldn't stat %s, copying: %s\n", filePath, strerror(errno)); 105 return true; 106 } 107 108 if (!S_ISREG(st->st_mode)) { 109 return true; 110 } 111 112 if (st->st_size != fileSize) { 113 return true; 114 } 115 116 // For some reason, bionic doesn't define st_mtime as time_t 117 if (time_t(st->st_mtime) != modifiedTime) { 118 ALOGV("mod time doesn't match: %ld vs. %ld\n", st->st_mtime, modifiedTime); 119 return true; 120 } 121 122 int fd = TEMP_FAILURE_RETRY(open(filePath, O_RDONLY)); 123 if (fd < 0) { 124 ALOGV("Couldn't open file %s: %s", filePath, strerror(errno)); 125 return true; 126 } 127 128 long crc = crc32(0L, Z_NULL, 0); 129 unsigned char crcBuffer[16384]; 130 ssize_t numBytes; 131 while ((numBytes = TEMP_FAILURE_RETRY(read(fd, crcBuffer, sizeof(crcBuffer)))) > 0) { 132 crc = crc32(crc, crcBuffer, numBytes); 133 } 134 close(fd); 135 136 ALOGV("%s: crc = %lx, zipCrc = %lx\n", filePath, crc, zipCrc); 137 138 if (crc != zipCrc) { 139 return true; 140 } 141 142 return false; 143} 144 145static install_status_t 146sumFiles(JNIEnv* env, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntry, const char* fileName) 147{ 148 size_t* total = (size_t*) arg; 149 size_t uncompLen; 150 151 if (!zipFile->getEntryInfo(zipEntry, NULL, &uncompLen, NULL, NULL, NULL, NULL)) { 152 return INSTALL_FAILED_INVALID_APK; 153 } 154 155 *total += uncompLen; 156 157 return INSTALL_SUCCEEDED; 158} 159 160/* 161 * Copy the native library if needed. 162 * 163 * This function assumes the library and path names passed in are considered safe. 164 */ 165static install_status_t 166copyFileIfChanged(JNIEnv *env, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntry, const char* fileName) 167{ 168 jstring* javaNativeLibPath = (jstring*) arg; 169 ScopedUtfChars nativeLibPath(env, *javaNativeLibPath); 170 171 size_t uncompLen; 172 long when; 173 long crc; 174 time_t modTime; 175 176 if (!zipFile->getEntryInfo(zipEntry, NULL, &uncompLen, NULL, NULL, &when, &crc)) { 177 ALOGD("Couldn't read zip entry info\n"); 178 return INSTALL_FAILED_INVALID_APK; 179 } else { 180 struct tm t; 181 ZipFileRO::zipTimeToTimespec(when, &t); 182 modTime = mktime(&t); 183 } 184 185 // Build local file path 186 const size_t fileNameLen = strlen(fileName); 187 char localFileName[nativeLibPath.size() + fileNameLen + 2]; 188 189 if (strlcpy(localFileName, nativeLibPath.c_str(), sizeof(localFileName)) != nativeLibPath.size()) { 190 ALOGD("Couldn't allocate local file name for library"); 191 return INSTALL_FAILED_INTERNAL_ERROR; 192 } 193 194 *(localFileName + nativeLibPath.size()) = '/'; 195 196 if (strlcpy(localFileName + nativeLibPath.size() + 1, fileName, sizeof(localFileName) 197 - nativeLibPath.size() - 1) != fileNameLen) { 198 ALOGD("Couldn't allocate local file name for library"); 199 return INSTALL_FAILED_INTERNAL_ERROR; 200 } 201 202 // Only copy out the native file if it's different. 203 struct stat st; 204 if (!isFileDifferent(localFileName, uncompLen, modTime, crc, &st)) { 205 return INSTALL_SUCCEEDED; 206 } 207 208 char localTmpFileName[nativeLibPath.size() + TMP_FILE_PATTERN_LEN + 2]; 209 if (strlcpy(localTmpFileName, nativeLibPath.c_str(), sizeof(localTmpFileName)) 210 != nativeLibPath.size()) { 211 ALOGD("Couldn't allocate local file name for library"); 212 return INSTALL_FAILED_INTERNAL_ERROR; 213 } 214 215 *(localFileName + nativeLibPath.size()) = '/'; 216 217 if (strlcpy(localTmpFileName + nativeLibPath.size(), TMP_FILE_PATTERN, 218 TMP_FILE_PATTERN_LEN - nativeLibPath.size()) != TMP_FILE_PATTERN_LEN) { 219 ALOGI("Couldn't allocate temporary file name for library"); 220 return INSTALL_FAILED_INTERNAL_ERROR; 221 } 222 223 int fd = mkstemp(localTmpFileName); 224 if (fd < 0) { 225 ALOGI("Couldn't open temporary file name: %s: %s\n", localTmpFileName, strerror(errno)); 226 return INSTALL_FAILED_CONTAINER_ERROR; 227 } 228 229 if (!zipFile->uncompressEntry(zipEntry, fd)) { 230 ALOGI("Failed uncompressing %s to %s\n", fileName, localTmpFileName); 231 close(fd); 232 unlink(localTmpFileName); 233 return INSTALL_FAILED_CONTAINER_ERROR; 234 } 235 236 close(fd); 237 238 // Set the modification time for this file to the ZIP's mod time. 239 struct timeval times[2]; 240 times[0].tv_sec = st.st_atime; 241 times[1].tv_sec = modTime; 242 times[0].tv_usec = times[1].tv_usec = 0; 243 if (utimes(localTmpFileName, times) < 0) { 244 ALOGI("Couldn't change modification time on %s: %s\n", localTmpFileName, strerror(errno)); 245 unlink(localTmpFileName); 246 return INSTALL_FAILED_CONTAINER_ERROR; 247 } 248 249 // Set the mode to 755 250 static const mode_t mode = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; 251 if (chmod(localTmpFileName, mode) < 0) { 252 ALOGI("Couldn't change permissions on %s: %s\n", localTmpFileName, strerror(errno)); 253 unlink(localTmpFileName); 254 return INSTALL_FAILED_CONTAINER_ERROR; 255 } 256 257 // Finally, rename it to the final name. 258 if (rename(localTmpFileName, localFileName) < 0) { 259 ALOGI("Couldn't rename %s to %s: %s\n", localTmpFileName, localFileName, strerror(errno)); 260 unlink(localTmpFileName); 261 return INSTALL_FAILED_CONTAINER_ERROR; 262 } 263 264 ALOGV("Successfully moved %s to %s\n", localTmpFileName, localFileName); 265 266 return INSTALL_SUCCEEDED; 267} 268 269static install_status_t 270iterateOverNativeFiles(JNIEnv *env, jstring javaFilePath, jstring javaCpuAbi, jstring javaCpuAbi2, 271 iterFunc callFunc, void* callArg) { 272 ScopedUtfChars filePath(env, javaFilePath); 273 ScopedUtfChars cpuAbi(env, javaCpuAbi); 274 ScopedUtfChars cpuAbi2(env, javaCpuAbi2); 275 276 ZipFileRO zipFile; 277 278 if (zipFile.open(filePath.c_str()) != NO_ERROR) { 279 ALOGI("Couldn't open APK %s\n", filePath.c_str()); 280 return INSTALL_FAILED_INVALID_APK; 281 } 282 283 const int N = zipFile.getNumEntries(); 284 285 char fileName[PATH_MAX]; 286 bool hasPrimaryAbi = false; 287 288 for (int i = 0; i < N; i++) { 289 const ZipEntryRO entry = zipFile.findEntryByIndex(i); 290 if (entry == NULL) { 291 continue; 292 } 293 294 // Make sure this entry has a filename. 295 if (zipFile.getEntryFileName(entry, fileName, sizeof(fileName))) { 296 continue; 297 } 298 299 // Make sure we're in the lib directory of the ZIP. 300 if (strncmp(fileName, APK_LIB, APK_LIB_LEN)) { 301 continue; 302 } 303 304 // Make sure the filename is at least to the minimum library name size. 305 const size_t fileNameLen = strlen(fileName); 306 static const size_t minLength = APK_LIB_LEN + 2 + LIB_PREFIX_LEN + 1 + LIB_SUFFIX_LEN; 307 if (fileNameLen < minLength) { 308 continue; 309 } 310 311 const char* lastSlash = strrchr(fileName, '/'); 312 ALOG_ASSERT(lastSlash != NULL, "last slash was null somehow for %s\n", fileName); 313 314 // Check to make sure the CPU ABI of this file is one we support. 315 const char* cpuAbiOffset = fileName + APK_LIB_LEN; 316 const size_t cpuAbiRegionSize = lastSlash - cpuAbiOffset; 317 318 ALOGV("Comparing ABIs %s and %s versus %s\n", cpuAbi.c_str(), cpuAbi2.c_str(), cpuAbiOffset); 319 if (cpuAbi.size() == cpuAbiRegionSize 320 && *(cpuAbiOffset + cpuAbi.size()) == '/' 321 && !strncmp(cpuAbiOffset, cpuAbi.c_str(), cpuAbiRegionSize)) { 322 ALOGV("Using primary ABI %s\n", cpuAbi.c_str()); 323 hasPrimaryAbi = true; 324 } else if (cpuAbi2.size() == cpuAbiRegionSize 325 && *(cpuAbiOffset + cpuAbi2.size()) == '/' 326 && !strncmp(cpuAbiOffset, cpuAbi2.c_str(), cpuAbiRegionSize)) { 327 328 /* 329 * If this library matches both the primary and secondary ABIs, 330 * only use the primary ABI. 331 */ 332 if (hasPrimaryAbi) { 333 ALOGV("Already saw primary ABI, skipping secondary ABI %s\n", cpuAbi2.c_str()); 334 continue; 335 } else { 336 ALOGV("Using secondary ABI %s\n", cpuAbi2.c_str()); 337 } 338 } else { 339 ALOGV("abi didn't match anything: %s (end at %zd)\n", cpuAbiOffset, cpuAbiRegionSize); 340 continue; 341 } 342 343 // If this is a .so file, check to see if we need to copy it. 344 if ((!strncmp(fileName + fileNameLen - LIB_SUFFIX_LEN, LIB_SUFFIX, LIB_SUFFIX_LEN) 345 && !strncmp(lastSlash, LIB_PREFIX, LIB_PREFIX_LEN) 346 && isFilenameSafe(lastSlash + 1)) 347 || !strncmp(lastSlash + 1, GDBSERVER, GDBSERVER_LEN)) { 348 349 install_status_t ret = callFunc(env, callArg, &zipFile, entry, lastSlash + 1); 350 351 if (ret != INSTALL_SUCCEEDED) { 352 ALOGV("Failure for entry %s", lastSlash + 1); 353 return ret; 354 } 355 } 356 } 357 358 return INSTALL_SUCCEEDED; 359} 360 361static jint 362com_android_internal_content_NativeLibraryHelper_copyNativeBinaries(JNIEnv *env, jclass clazz, 363 jstring javaFilePath, jstring javaNativeLibPath, jstring javaCpuAbi, jstring javaCpuAbi2) 364{ 365 return (jint) iterateOverNativeFiles(env, javaFilePath, javaCpuAbi, javaCpuAbi2, 366 copyFileIfChanged, &javaNativeLibPath); 367} 368 369static jlong 370com_android_internal_content_NativeLibraryHelper_sumNativeBinaries(JNIEnv *env, jclass clazz, 371 jstring javaFilePath, jstring javaCpuAbi, jstring javaCpuAbi2) 372{ 373 size_t totalSize = 0; 374 375 iterateOverNativeFiles(env, javaFilePath, javaCpuAbi, javaCpuAbi2, sumFiles, &totalSize); 376 377 return totalSize; 378} 379 380static JNINativeMethod gMethods[] = { 381 {"nativeCopyNativeBinaries", 382 "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I", 383 (void *)com_android_internal_content_NativeLibraryHelper_copyNativeBinaries}, 384 {"nativeSumNativeBinaries", 385 "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)J", 386 (void *)com_android_internal_content_NativeLibraryHelper_sumNativeBinaries}, 387}; 388 389 390int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env) 391{ 392 return AndroidRuntime::registerNativeMethods(env, 393 "com/android/internal/content/NativeLibraryHelper", gMethods, NELEM(gMethods)); 394} 395 396}; 397