1 package org.robolectric.res.android; 2 3 import static org.robolectric.res.android.Asset.toIntExact; 4 import static org.robolectric.res.android.Util.ALOGV; 5 6 import java.io.FileInputStream; 7 import java.io.IOException; 8 import java.io.InputStream; 9 import java.util.Enumeration; 10 import java.util.zip.ZipEntry; 11 import java.util.zip.ZipFile; 12 13 public class FileMap { 14 15 private ZipFile zipFile; 16 private ZipEntry zipEntry; 17 private boolean readOnly; 18 private int fd; 19 private boolean isFromZip; 20 21 // Create a new mapping on an open file. 22 // 23 // Closing the file descriptor does not unmap the pages, so we don't 24 // claim ownership of the fd. 25 // 26 // Returns "false" on failure. create(String origFileName, int fd, long offset, int length, boolean readOnly)27 boolean create(String origFileName, int fd, long offset, int length, 28 boolean readOnly) 29 { 30 this.mFileName = origFileName; 31 this.fd = fd; 32 this.mDataOffset = offset; 33 this.readOnly = readOnly; 34 return true; 35 } 36 // #if defined(__MINGW32__) 37 // int adjust; 38 // off64_t adjOffset; 39 // size_t adjLength; 40 // 41 // if (mPageSize == -1) { 42 // SYSTEM_INFO si; 43 // 44 // GetSystemInfo( &si ); 45 // mPageSize = si.dwAllocationGranularity; 46 // } 47 // 48 // DWORD protect = readOnly ? PAGE_READONLY : PAGE_READWRITE; 49 // 50 // mFileHandle = (HANDLE) _get_osfhandle(fd); 51 // mFileMapping = CreateFileMapping( mFileHandle, NULL, protect, 0, 0, NULL); 52 // if (mFileMapping == NULL) { 53 // ALOGE("CreateFileMapping(%s, %" PRIx32 ") failed with error %" PRId32 "\n", 54 // mFileHandle, protect, GetLastError() ); 55 // return false; 56 // } 57 // 58 // adjust = offset % mPageSize; 59 // adjOffset = offset - adjust; 60 // adjLength = length + adjust; 61 // 62 // mBasePtr = MapViewOfFile( mFileMapping, 63 // readOnly ? FILE_MAP_READ : FILE_MAP_ALL_ACCESS, 64 // 0, 65 // (DWORD)(adjOffset), 66 // adjLength ); 67 // if (mBasePtr == NULL) { 68 // ALOGE("MapViewOfFile(%" PRId64 ", 0x%x) failed with error %" PRId32 "\n", 69 // adjOffset, adjLength, GetLastError() ); 70 // CloseHandle(mFileMapping); 71 // mFileMapping = INVALID_HANDLE_VALUE; 72 // return false; 73 // } 74 // #else // !defined(__MINGW32__) 75 // int prot, flags, adjust; 76 // off64_t adjOffset; 77 // size_t adjLength; 78 // 79 // void* ptr; 80 // 81 // assert(fd >= 0); 82 // assert(offset >= 0); 83 // assert(length > 0); 84 // 85 // // init on first use 86 // if (mPageSize == -1) { 87 // mPageSize = sysconf(_SC_PAGESIZE); 88 // if (mPageSize == -1) { 89 // ALOGE("could not get _SC_PAGESIZE\n"); 90 // return false; 91 // } 92 // } 93 // 94 // adjust = offset % mPageSize; 95 // adjOffset = offset - adjust; 96 // adjLength = length + adjust; 97 // 98 // flags = MAP_SHARED; 99 // prot = PROT_READ; 100 // if (!readOnly) 101 // prot |= PROT_WRITE; 102 // 103 // ptr = mmap(NULL, adjLength, prot, flags, fd, adjOffset); 104 // if (ptr == MAP_FAILED) { 105 // ALOGE("mmap(%lld,0x%x) failed: %s\n", 106 // (long long)adjOffset, adjLength, strerror(errno)); 107 // return false; 108 // } 109 // mBasePtr = ptr; 110 // #endif // !defined(__MINGW32__) 111 // 112 // mFileName = origFileName != NULL ? strdup(origFileName) : NULL; 113 // mBaseLength = adjLength; 114 // mDataOffset = offset; 115 // mDataPtr = (char*) mBasePtr + adjust; 116 // mDataLength = length; 117 // 118 // assert(mBasePtr != NULL); 119 // 120 // ALOGV("MAP: base %s/0x%x data %s/0x%x\n", 121 // mBasePtr, mBaseLength, mDataPtr, mDataLength); 122 // 123 // return true; 124 // } 125 126 createFromZip(String origFileName, ZipFile zipFile, ZipEntry entry, int length, boolean readOnly)127 public boolean createFromZip(String origFileName, ZipFile zipFile, ZipEntry entry, int length, 128 boolean readOnly) { 129 isFromZip = true; 130 this.zipFile = zipFile; 131 this.zipEntry = entry; 132 133 int prot, flags, adjust; 134 long adjOffset; 135 int adjLength; 136 137 int ptr; 138 long offset = guessOffsetFor(zipFile, entry); 139 140 assert(fd >= 0); 141 assert(offset >= 0); 142 // assert(length > 0); 143 144 // init on first use 145 // if (mPageSize == -1) { 146 // mPageSize = sysconf(_SC_PAGESIZE); 147 // if (mPageSize == -1) { 148 // ALOGE("could not get _SC_PAGESIZE\n"); 149 // return false; 150 // } 151 // } 152 153 // adjust = Math.toIntExact(offset % mPageSize); 154 // adjOffset = offset - adjust; 155 // adjLength = length + adjust; 156 157 //flags = MAP_SHARED; 158 //prot = PROT_READ; 159 //if (!readOnly) 160 // prot |= PROT_WRITE; 161 162 // ptr = mmap(null, adjLength, prot, flags, fd, adjOffset); 163 // if (ptr == MAP_FAILED) { 164 // ALOGE("mmap(%lld,0x%x) failed: %s\n", 165 // (long long)adjOffset, adjLength, strerror(errno)); 166 // return false; 167 // } 168 // mBasePtr = ptr; 169 170 mFileName = origFileName != null ? origFileName : null; 171 //mBaseLength = adjLength; 172 mDataOffset = offset; 173 //mDataPtr = mBasePtr + adjust; 174 mDataLength = toIntExact(entry.getSize()); 175 176 //assert(mBasePtr != 0); 177 178 ALOGV("MAP: base %s/0x%x data %s/0x%x\n", 179 mBasePtr, mBaseLength, mDataPtr, mDataLength); 180 181 return true; 182 } 183 guessOffsetFor(ZipFile zipFile, ZipEntry zipEntry)184 long guessOffsetFor(ZipFile zipFile, ZipEntry zipEntry) { 185 Enumeration<? extends ZipEntry> zipEntries = zipFile.entries(); 186 long offset = 0; 187 while (zipEntries.hasMoreElements()) 188 { 189 ZipEntry entry = zipEntries.nextElement(); 190 long fileSize = 0; 191 long extra = entry.getExtra() == null ? 0 : entry.getExtra().length; 192 offset += 30 + entry.getName().length() + extra; 193 194 if (entry.getName().equals(zipEntry.getName())) { 195 return offset; 196 } 197 198 if(!entry.isDirectory()) 199 { 200 fileSize = entry.getCompressedSize(); 201 202 // Do stuff here with fileSize & offset 203 } 204 offset += fileSize; 205 } 206 throw new IllegalStateException("'" + zipEntry.getName() + "' not found"); 207 } 208 /* 209 * This represents a memory-mapped file. It might be the entire file or 210 * only part of it. This requires a little bookkeeping because the mapping 211 * needs to be aligned on page boundaries, and in some cases we'd like to 212 * have multiple references to the mapped area without creating additional 213 * maps. 214 * 215 * This always uses MAP_SHARED. 216 * 217 * TODO: we should be able to create a new FileMap that is a subset of 218 * an existing FileMap and shares the underlying mapped pages. Requires 219 * completing the refcounting stuff and possibly introducing the notion 220 * of a FileMap hierarchy. 221 */ 222 // class FileMap { 223 // public: 224 // FileMap(void); 225 // 226 // FileMap(FileMap&& f); 227 // FileMap& operator=(FileMap&& f); 228 229 /* 230 * Create a new mapping on an open file. 231 * 232 * Closing the file descriptor does not unmap the pages, so we don't 233 * claim ownership of the fd. 234 * 235 * Returns "false" on failure. 236 */ 237 // boolean create(String origFileName, int fd, 238 // long offset, int length, boolean readOnly) { 239 // } 240 241 // ~FileMap(void); 242 243 /* 244 * Return the name of the file this map came from, if known. 245 */ getFileName()246 String getFileName() { return mFileName; } 247 248 /* 249 * Get a pointer to the piece of the file we requested. 250 */ getDataPtr()251 synchronized byte[] getDataPtr() { 252 if (mDataPtr == null) { 253 mDataPtr = new byte[mDataLength]; 254 255 InputStream is; 256 try { 257 if (isFromZip) { 258 is = zipFile.getInputStream(zipEntry); 259 } else { 260 is = new FileInputStream(getFileName()); 261 } 262 try { 263 readFully(is, mDataPtr); 264 } finally { 265 is.close(); 266 } 267 } catch (IOException e) { 268 throw new RuntimeException(e); 269 } 270 } 271 return mDataPtr; 272 } 273 readFully(InputStream is, byte[] bytes)274 public static void readFully(InputStream is, byte[] bytes) throws IOException { 275 int size = bytes.length; 276 int remaining = size; 277 while (remaining > 0) { 278 int location = size - remaining; 279 int bytesRead = is.read(bytes, location, remaining); 280 if (bytesRead == -1) { 281 break; 282 } 283 remaining -= bytesRead; 284 } 285 286 if (remaining > 0) { 287 throw new RuntimeException("failed to read " + size + " (" + remaining + " bytes unread)"); 288 } 289 } 290 291 /* 292 * Get the length we requested. 293 */ getDataLength()294 int getDataLength() { return mDataLength; } 295 296 /* 297 * Get the data offset used to create this map. 298 */ getDataOffset()299 long getDataOffset() { return mDataOffset; } 300 getZipEntry()301 public ZipEntry getZipEntry() { 302 return zipEntry; 303 } 304 305 // /* 306 // * This maps directly to madvise() values, but allows us to avoid 307 // * including <sys/mman.h> everywhere. 308 // */ 309 // enum MapAdvice { 310 // NORMAL, RANDOM, SEQUENTIAL, WILLNEED, DONTNEED 311 // }; 312 // 313 // /* 314 // * Apply an madvise() call to the entire file. 315 // * 316 // * Returns 0 on success, -1 on failure. 317 // */ 318 // int advise(MapAdvice advice); 319 // 320 // protected: 321 // 322 // private: 323 // // these are not implemented 324 // FileMap(const FileMap& src); 325 // const FileMap& operator=(const FileMap& src); 326 // 327 String mFileName; // original file name, if known 328 int mBasePtr; // base of mmap area; page aligned 329 int mBaseLength; // length, measured from "mBasePtr" 330 long mDataOffset; // offset used when map was created 331 byte[] mDataPtr; // start of requested data, offset from base 332 int mDataLength; // length, measured from "mDataPtr" 333 static long mPageSize; 334 335 @Override toString()336 public String toString() { 337 if (isFromZip) { 338 return "FileMap{" + 339 "zipFile=" + zipFile.getName() + 340 ", zipEntry=" + zipEntry + 341 '}'; 342 } else { 343 return "FileMap{" + 344 "mFileName='" + mFileName + '\'' + 345 '}'; 346 } 347 } 348 } 349