| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| /* |
| * Miscellaneous utility functions. |
| */ |
| #include "Dalvik.h" |
| |
| #include <stdlib.h> |
| #include <stddef.h> |
| #include <string.h> |
| #include <strings.h> |
| #include <ctype.h> |
| #include <time.h> |
| #include <sys/time.h> |
| #include <fcntl.h> |
| #include <cutils/ashmem.h> |
| #include <sys/mman.h> |
| |
| #define ALIGN_UP_TO_PAGE_SIZE(p) \ |
| (((size_t)(p) + (SYSTEM_PAGE_SIZE - 1)) & ~(SYSTEM_PAGE_SIZE - 1)) |
| |
| /* |
| * Print a hex dump in this format: |
| * |
| 01234567: 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 0123456789abcdef\n |
| * |
| * If "mode" is kHexDumpLocal, we start at offset zero, and show a full |
| * 16 bytes on the first line. If it's kHexDumpMem, we make this look |
| * like a memory dump, using the actual address, outputting a partial line |
| * if "vaddr" isn't aligned on a 16-byte boundary. |
| * |
| * "priority" and "tag" determine the values passed to the log calls. |
| * |
| * Does not use printf() or other string-formatting calls. |
| */ |
| void dvmPrintHexDumpEx(int priority, const char* tag, const void* vaddr, |
| size_t length, HexDumpMode mode) |
| { |
| static const char gHexDigit[] = "0123456789abcdef"; |
| const unsigned char* addr = vaddr; |
| char out[77]; /* exact fit */ |
| unsigned int offset; /* offset to show while printing */ |
| char* hex; |
| char* asc; |
| int gap; |
| //int trickle = 0; |
| |
| if (mode == kHexDumpLocal) |
| offset = 0; |
| else |
| offset = (int) addr; |
| |
| memset(out, ' ', sizeof(out)-1); |
| out[8] = ':'; |
| out[sizeof(out)-2] = '\n'; |
| out[sizeof(out)-1] = '\0'; |
| |
| gap = (int) offset & 0x0f; |
| while (length) { |
| unsigned int lineOffset = offset & ~0x0f; |
| int i, count; |
| |
| hex = out; |
| asc = out + 59; |
| |
| for (i = 0; i < 8; i++) { |
| *hex++ = gHexDigit[lineOffset >> 28]; |
| lineOffset <<= 4; |
| } |
| hex++; |
| hex++; |
| |
| count = ((int)length > 16-gap) ? 16-gap : (int)length; /* cap length */ |
| assert(count != 0); |
| assert(count+gap <= 16); |
| |
| if (gap) { |
| /* only on first line */ |
| hex += gap * 3; |
| asc += gap; |
| } |
| |
| for (i = gap ; i < count+gap; i++) { |
| *hex++ = gHexDigit[*addr >> 4]; |
| *hex++ = gHexDigit[*addr & 0x0f]; |
| hex++; |
| if (*addr >= 0x20 && *addr < 0x7f /*isprint(*addr)*/) |
| *asc++ = *addr; |
| else |
| *asc++ = '.'; |
| addr++; |
| } |
| for ( ; i < 16; i++) { |
| /* erase extra stuff; only happens on last line */ |
| *hex++ = ' '; |
| *hex++ = ' '; |
| hex++; |
| *asc++ = ' '; |
| } |
| |
| LOG_PRI(priority, tag, "%s", out); |
| #if 0 //def HAVE_ANDROID_OS |
| /* |
| * We can overrun logcat easily by writing at full speed. On the |
| * other hand, we can make Eclipse time out if we're showing |
| * packet dumps while debugging JDWP. |
| */ |
| { |
| if (trickle++ == 8) { |
| trickle = 0; |
| usleep(20000); |
| } |
| } |
| #endif |
| |
| gap = 0; |
| length -= count; |
| offset += count; |
| } |
| } |
| |
| |
| /* |
| * Fill out a DebugOutputTarget, suitable for printing to the log. |
| */ |
| void dvmCreateLogOutputTarget(DebugOutputTarget* target, int priority, |
| const char* tag) |
| { |
| assert(target != NULL); |
| assert(tag != NULL); |
| |
| target->which = kDebugTargetLog; |
| target->data.log.priority = priority; |
| target->data.log.tag = tag; |
| } |
| |
| /* |
| * Fill out a DebugOutputTarget suitable for printing to a file pointer. |
| */ |
| void dvmCreateFileOutputTarget(DebugOutputTarget* target, FILE* fp) |
| { |
| assert(target != NULL); |
| assert(fp != NULL); |
| |
| target->which = kDebugTargetFile; |
| target->data.file.fp = fp; |
| } |
| |
| /* |
| * Free "target" and any associated data. |
| */ |
| void dvmFreeOutputTarget(DebugOutputTarget* target) |
| { |
| free(target); |
| } |
| |
| /* |
| * Print a debug message, to either a file or the log. |
| */ |
| void dvmPrintDebugMessage(const DebugOutputTarget* target, const char* format, |
| ...) |
| { |
| va_list args; |
| |
| va_start(args, format); |
| |
| switch (target->which) { |
| case kDebugTargetLog: |
| LOG_PRI_VA(target->data.log.priority, target->data.log.tag, |
| format, args); |
| break; |
| case kDebugTargetFile: |
| vfprintf(target->data.file.fp, format, args); |
| break; |
| default: |
| LOGE("unexpected 'which' %d\n", target->which); |
| break; |
| } |
| |
| va_end(args); |
| } |
| |
| |
| /* |
| * Allocate a bit vector with enough space to hold at least the specified |
| * number of bits. |
| */ |
| BitVector* dvmAllocBitVector(int startBits, bool expandable) |
| { |
| BitVector* bv; |
| int count; |
| |
| assert(sizeof(bv->storage[0]) == 4); /* assuming 32-bit units */ |
| assert(startBits >= 0); |
| |
| bv = (BitVector*) malloc(sizeof(BitVector)); |
| |
| count = (startBits + 31) >> 5; |
| |
| bv->storageSize = count; |
| bv->expandable = expandable; |
| bv->storage = (u4*) malloc(count * sizeof(u4)); |
| memset(bv->storage, 0x00, count * sizeof(u4)); |
| return bv; |
| } |
| |
| /* |
| * Free a BitVector. |
| */ |
| void dvmFreeBitVector(BitVector* pBits) |
| { |
| if (pBits == NULL) |
| return; |
| |
| free(pBits->storage); |
| free(pBits); |
| } |
| |
| /* |
| * "Allocate" the first-available bit in the bitmap. |
| * |
| * This is not synchronized. The caller is expected to hold some sort of |
| * lock that prevents multiple threads from executing simultaneously in |
| * dvmAllocBit/dvmFreeBit. |
| */ |
| int dvmAllocBit(BitVector* pBits) |
| { |
| int word, bit; |
| |
| retry: |
| for (word = 0; word < pBits->storageSize; word++) { |
| if (pBits->storage[word] != 0xffffffff) { |
| /* |
| * There are unallocated bits in this word. Return the first. |
| */ |
| bit = ffs(~(pBits->storage[word])) -1; |
| assert(bit >= 0 && bit < 32); |
| pBits->storage[word] |= 1 << bit; |
| return (word << 5) | bit; |
| } |
| } |
| |
| /* |
| * Ran out of space, allocate more if we're allowed to. |
| */ |
| if (!pBits->expandable) |
| return -1; |
| |
| pBits->storage = realloc(pBits->storage, |
| (pBits->storageSize + kBitVectorGrowth) * sizeof(u4)); |
| memset(&pBits->storage[pBits->storageSize], 0x00, |
| kBitVectorGrowth * sizeof(u4)); |
| pBits->storageSize += kBitVectorGrowth; |
| goto retry; |
| } |
| |
| /* |
| * Mark the specified bit as "set". |
| * |
| * Returns "false" if the bit is outside the range of the vector and we're |
| * not allowed to expand. |
| */ |
| bool dvmSetBit(BitVector* pBits, int num) |
| { |
| assert(num >= 0); |
| if (num >= pBits->storageSize * (int)sizeof(u4) * 8) { |
| if (!pBits->expandable) |
| return false; |
| |
| int newSize = (num + 31) >> 5; |
| assert(newSize > pBits->storageSize); |
| pBits->storage = realloc(pBits->storage, newSize * sizeof(u4)); |
| memset(&pBits->storage[pBits->storageSize], 0x00, |
| (newSize - pBits->storageSize) * sizeof(u4)); |
| pBits->storageSize = newSize; |
| } |
| |
| pBits->storage[num >> 5] |= 1 << (num & 0x1f); |
| return true; |
| } |
| |
| /* |
| * Mark the specified bit as "clear". |
| */ |
| void dvmClearBit(BitVector* pBits, int num) |
| { |
| assert(num >= 0 && num < (int) pBits->storageSize * (int)sizeof(u4) * 8); |
| |
| pBits->storage[num >> 5] &= ~(1 << (num & 0x1f)); |
| } |
| |
| /* |
| * Mark all bits bit as "clear". |
| */ |
| void dvmClearAllBits(BitVector* pBits) |
| { |
| int count = pBits->storageSize; |
| memset(pBits->storage, 0, count * sizeof(u4)); |
| } |
| |
| /* |
| * Determine whether or not the specified bit is set. |
| */ |
| bool dvmIsBitSet(const BitVector* pBits, int num) |
| { |
| assert(num >= 0 && num < (int) pBits->storageSize * (int)sizeof(u4) * 8); |
| |
| int val = pBits->storage[num >> 5] & (1 << (num & 0x1f)); |
| return (val != 0); |
| } |
| |
| /* |
| * Count the number of bits that are set. |
| */ |
| int dvmCountSetBits(const BitVector* pBits) |
| { |
| int word; |
| int count = 0; |
| |
| for (word = 0; word < pBits->storageSize; word++) { |
| u4 val = pBits->storage[word]; |
| |
| if (val != 0) { |
| if (val == 0xffffffff) { |
| count += 32; |
| } else { |
| /* count the number of '1' bits */ |
| while (val != 0) { |
| val &= val - 1; |
| count++; |
| } |
| } |
| } |
| } |
| |
| return count; |
| } |
| |
| /* |
| * Copy a whole vector to the other. Only do that when the both vectors have |
| * the same size and attribute. |
| */ |
| bool dvmCopyBitVector(BitVector *dest, const BitVector *src) |
| { |
| if (dest->storageSize != src->storageSize || |
| dest->expandable != src->expandable) |
| return false; |
| memcpy(dest->storage, src->storage, sizeof(u4) * dest->storageSize); |
| return true; |
| } |
| |
| /* |
| * Intersect two bit vectores and merge the result on top of the pre-existing |
| * value in the dest vector. |
| */ |
| bool dvmIntersectBitVectors(BitVector *dest, const BitVector *src1, |
| const BitVector *src2) |
| { |
| if (dest->storageSize != src1->storageSize || |
| dest->storageSize != src2->storageSize || |
| dest->expandable != src1->expandable || |
| dest->expandable != src2->expandable) |
| return false; |
| |
| int i; |
| for (i = 0; i < dest->storageSize; i++) { |
| dest->storage[i] |= src1->storage[i] & src2->storage[i]; |
| } |
| return true; |
| } |
| |
| /* |
| * Return a newly-allocated string in which all occurrences of '.' have |
| * been changed to '/'. If we find a '/' in the original string, NULL |
| * is returned to avoid ambiguity. |
| */ |
| char* dvmDotToSlash(const char* str) |
| { |
| char* newStr = strdup(str); |
| char* cp = newStr; |
| |
| if (newStr == NULL) |
| return NULL; |
| |
| while (*cp != '\0') { |
| if (*cp == '/') { |
| assert(false); |
| return NULL; |
| } |
| if (*cp == '.') |
| *cp = '/'; |
| cp++; |
| } |
| |
| return newStr; |
| } |
| |
| /* |
| * Return a newly-allocated string for the "dot version" of the class |
| * name for the given type descriptor. That is, The initial "L" and |
| * final ";" (if any) have been removed and all occurrences of '/' |
| * have been changed to '.'. |
| */ |
| char* dvmDescriptorToDot(const char* str) |
| { |
| size_t at = strlen(str); |
| char* newStr; |
| |
| if ((at >= 2) && (str[0] == 'L') && (str[at - 1] == ';')) { |
| at -= 2; /* Two fewer chars to copy. */ |
| str++; /* Skip the 'L'. */ |
| } |
| |
| newStr = malloc(at + 1); /* Add one for the '\0'. */ |
| if (newStr == NULL) |
| return NULL; |
| |
| newStr[at] = '\0'; |
| |
| while (at > 0) { |
| at--; |
| newStr[at] = (str[at] == '/') ? '.' : str[at]; |
| } |
| |
| return newStr; |
| } |
| |
| /* |
| * Return a newly-allocated string for the type descriptor |
| * corresponding to the "dot version" of the given class name. That |
| * is, non-array names are surrounded by "L" and ";", and all |
| * occurrences of '.' are changed to '/'. |
| */ |
| char* dvmDotToDescriptor(const char* str) |
| { |
| size_t length = strlen(str); |
| int wrapElSemi = 0; |
| char* newStr; |
| char* at; |
| |
| if (str[0] != '[') { |
| length += 2; /* for "L" and ";" */ |
| wrapElSemi = 1; |
| } |
| |
| newStr = at = malloc(length + 1); /* + 1 for the '\0' */ |
| |
| if (newStr == NULL) { |
| return NULL; |
| } |
| |
| if (wrapElSemi) { |
| *(at++) = 'L'; |
| } |
| |
| while (*str) { |
| char c = *(str++); |
| if (c == '.') { |
| c = '/'; |
| } |
| *(at++) = c; |
| } |
| |
| if (wrapElSemi) { |
| *(at++) = ';'; |
| } |
| |
| *at = '\0'; |
| return newStr; |
| } |
| |
| /* |
| * Return a newly-allocated string for the internal-form class name for |
| * the given type descriptor. That is, the initial "L" and final ";" (if |
| * any) have been removed. |
| */ |
| char* dvmDescriptorToName(const char* str) |
| { |
| if (str[0] == 'L') { |
| size_t length = strlen(str) - 1; |
| char* newStr = malloc(length); |
| |
| if (newStr == NULL) { |
| return NULL; |
| } |
| |
| strlcpy(newStr, str + 1, length); |
| return newStr; |
| } |
| |
| return strdup(str); |
| } |
| |
| /* |
| * Return a newly-allocated string for the type descriptor for the given |
| * internal-form class name. That is, a non-array class name will get |
| * surrounded by "L" and ";", while array names are left as-is. |
| */ |
| char* dvmNameToDescriptor(const char* str) |
| { |
| if (str[0] != '[') { |
| size_t length = strlen(str); |
| char* descriptor = malloc(length + 3); |
| |
| if (descriptor == NULL) { |
| return NULL; |
| } |
| |
| descriptor[0] = 'L'; |
| strcpy(descriptor + 1, str); |
| descriptor[length + 1] = ';'; |
| descriptor[length + 2] = '\0'; |
| |
| return descriptor; |
| } |
| |
| return strdup(str); |
| } |
| |
| /* |
| * Get a notion of the current time, in nanoseconds. This is meant for |
| * computing durations (e.g. "operation X took 52nsec"), so the result |
| * should not be used to get the current date/time. |
| */ |
| u8 dvmGetRelativeTimeNsec(void) |
| { |
| #ifdef HAVE_POSIX_CLOCKS |
| struct timespec now; |
| clock_gettime(CLOCK_MONOTONIC, &now); |
| return (u8)now.tv_sec*1000000000LL + now.tv_nsec; |
| #else |
| struct timeval now; |
| gettimeofday(&now, NULL); |
| return (u8)now.tv_sec*1000000000LL + now.tv_usec * 1000LL; |
| #endif |
| } |
| |
| /* |
| * Get the per-thread CPU time, in nanoseconds. |
| * |
| * Only useful for time deltas. |
| */ |
| u8 dvmGetThreadCpuTimeNsec(void) |
| { |
| #ifdef HAVE_POSIX_CLOCKS |
| struct timespec now; |
| clock_gettime(CLOCK_THREAD_CPUTIME_ID, &now); |
| return (u8)now.tv_sec*1000000000LL + now.tv_nsec; |
| #else |
| return (u8) -1; |
| #endif |
| } |
| |
| /* |
| * Get the per-thread CPU time, in nanoseconds, for the specified thread. |
| */ |
| u8 dvmGetOtherThreadCpuTimeNsec(pthread_t thread) |
| { |
| #if 0 /*def HAVE_POSIX_CLOCKS*/ |
| int clockId; |
| |
| if (pthread_getcpuclockid(thread, &clockId) != 0) |
| return (u8) -1; |
| |
| struct timespec now; |
| clock_gettime(clockId, &now); |
| return (u8)now.tv_sec*1000000000LL + now.tv_nsec; |
| #else |
| return (u8) -1; |
| #endif |
| } |
| |
| |
| /* |
| * Call this repeatedly, with successively higher values for "iteration", |
| * to sleep for a period of time not to exceed "maxTotalSleep". |
| * |
| * For example, when called with iteration==0 we will sleep for a very |
| * brief time. On the next call we will sleep for a longer time. When |
| * the sum total of all sleeps reaches "maxTotalSleep", this returns false. |
| * |
| * The initial start time value for "relStartTime" MUST come from the |
| * dvmGetRelativeTimeUsec call. On the device this must come from the |
| * monotonic clock source, not the wall clock. |
| * |
| * This should be used wherever you might be tempted to call sched_yield() |
| * in a loop. The problem with sched_yield is that, for a high-priority |
| * thread, the kernel might not actually transfer control elsewhere. |
| * |
| * Returns "false" if we were unable to sleep because our time was up. |
| */ |
| bool dvmIterativeSleep(int iteration, int maxTotalSleep, u8 relStartTime) |
| { |
| const int minSleep = 10000; |
| u8 curTime; |
| int curDelay; |
| |
| /* |
| * Get current time, and see if we've already exceeded the limit. |
| */ |
| curTime = dvmGetRelativeTimeUsec(); |
| if (curTime >= relStartTime + maxTotalSleep) { |
| LOGVV("exsl: sleep exceeded (start=%llu max=%d now=%llu)\n", |
| relStartTime, maxTotalSleep, curTime); |
| return false; |
| } |
| |
| /* |
| * Compute current delay. We're bounded by "maxTotalSleep", so no |
| * real risk of overflow assuming "usleep" isn't returning early. |
| * (Besides, 2^30 usec is about 18 minutes by itself.) |
| * |
| * For iteration==0 we just call sched_yield(), so the first sleep |
| * at iteration==1 is actually (minSleep * 2). |
| */ |
| curDelay = minSleep; |
| while (iteration-- > 0) |
| curDelay *= 2; |
| assert(curDelay > 0); |
| |
| if (curTime + curDelay >= relStartTime + maxTotalSleep) { |
| LOGVV("exsl: reduced delay from %d to %d\n", |
| curDelay, (int) ((relStartTime + maxTotalSleep) - curTime)); |
| curDelay = (int) ((relStartTime + maxTotalSleep) - curTime); |
| } |
| |
| if (iteration == 0) { |
| LOGVV("exsl: yield\n"); |
| sched_yield(); |
| } else { |
| LOGVV("exsl: sleep for %d\n", curDelay); |
| usleep(curDelay); |
| } |
| return true; |
| } |
| |
| |
| /* |
| * Set the "close on exec" flag so we don't expose our file descriptors |
| * to processes launched by us. |
| */ |
| bool dvmSetCloseOnExec(int fd) |
| { |
| int flags; |
| |
| /* |
| * There's presently only one flag defined, so getting the previous |
| * value of the fd flags is probably unnecessary. |
| */ |
| flags = fcntl(fd, F_GETFD); |
| if (flags < 0) { |
| LOGW("Unable to get fd flags for fd %d\n", fd); |
| return false; |
| } |
| if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0) { |
| LOGW("Unable to set close-on-exec for fd %d\n", fd); |
| return false; |
| } |
| return true; |
| } |
| |
| #if (!HAVE_STRLCPY) |
| /* Implementation of strlcpy() for platforms that don't already have it. */ |
| size_t strlcpy(char *dst, const char *src, size_t size) { |
| size_t srcLength = strlen(src); |
| size_t copyLength = srcLength; |
| |
| if (srcLength > (size - 1)) { |
| copyLength = size - 1; |
| } |
| |
| if (size != 0) { |
| strncpy(dst, src, copyLength); |
| dst[copyLength] = '\0'; |
| } |
| |
| return srcLength; |
| } |
| #endif |
| |
| /* |
| * Allocates a memory region using ashmem and mmap, initialized to |
| * zero. Actual allocation rounded up to page multiple. Returns |
| * NULL on failure. |
| */ |
| void *dvmAllocRegion(size_t size, int prot, const char *name) { |
| void *base; |
| int fd, ret; |
| |
| size = ALIGN_UP_TO_PAGE_SIZE(size); |
| fd = ashmem_create_region(name, size); |
| if (fd == -1) { |
| return NULL; |
| } |
| base = mmap(NULL, size, prot, MAP_PRIVATE, fd, 0); |
| ret = close(fd); |
| if (base == MAP_FAILED) { |
| return NULL; |
| } |
| if (ret == -1) { |
| return NULL; |
| } |
| return base; |
| } |
| |
| /* |
| * Get some per-thread stats. |
| * |
| * This is currently generated by opening the appropriate "stat" file |
| * in /proc and reading the pile of stuff that comes out. |
| */ |
| bool dvmGetThreadStats(ProcStatData* pData, pid_t tid) |
| { |
| /* |
| int pid; |
| char comm[128]; |
| char state; |
| int ppid, pgrp, session, tty_nr, tpgid; |
| unsigned long flags, minflt, cminflt, majflt, cmajflt, utime, stime; |
| long cutime, cstime, priority, nice, zero, itrealvalue; |
| unsigned long starttime, vsize; |
| long rss; |
| unsigned long rlim, startcode, endcode, startstack, kstkesp, kstkeip; |
| unsigned long signal, blocked, sigignore, sigcatch, wchan, nswap, cnswap; |
| int exit_signal, processor; |
| unsigned long rt_priority, policy; |
| |
| scanf("%d %s %c %d %d %d %d %d %lu %lu %lu %lu %lu %lu %lu %ld %ld %ld " |
| "%ld %ld %ld %lu %lu %ld %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu " |
| "%lu %lu %lu %d %d %lu %lu", |
| &pid, comm, &state, &ppid, &pgrp, &session, &tty_nr, &tpgid, |
| &flags, &minflt, &cminflt, &majflt, &cmajflt, &utime, &stime, |
| &cutime, &cstime, &priority, &nice, &zero, &itrealvalue, |
| &starttime, &vsize, &rss, &rlim, &startcode, &endcode, |
| &startstack, &kstkesp, &kstkeip, &signal, &blocked, &sigignore, |
| &sigcatch, &wchan, &nswap, &cnswap, &exit_signal, &processor, |
| &rt_priority, &policy); |
| |
| (new: delayacct_blkio_ticks %llu (since Linux 2.6.18)) |
| */ |
| |
| char nameBuf[64]; |
| int i, fd; |
| |
| /* |
| * Open and read the appropriate file. This is expected to work on |
| * Linux but will fail on other platforms (e.g. Mac sim). |
| */ |
| sprintf(nameBuf, "/proc/self/task/%d/stat", (int) tid); |
| fd = open(nameBuf, O_RDONLY); |
| if (fd < 0) { |
| LOGV("Unable to open '%s': %s\n", nameBuf, strerror(errno)); |
| return false; |
| } |
| |
| char lineBuf[512]; /* > 2x typical */ |
| int cc = read(fd, lineBuf, sizeof(lineBuf)-1); |
| if (cc <= 0) { |
| const char* msg = (cc == 0) ? "unexpected EOF" : strerror(errno); |
| LOGI("Unable to read '%s': %s\n", nameBuf, msg); |
| close(fd); |
| return false; |
| } |
| close(fd); |
| lineBuf[cc] = '\0'; |
| |
| /* |
| * Skip whitespace-separated tokens. For the most part we can assume |
| * that tokens do not contain spaces, and are separated by exactly one |
| * space character. The only exception is the second field ("comm") |
| * which may contain spaces but is surrounded by parenthesis. |
| */ |
| char* cp = strchr(lineBuf, ')'); |
| if (cp == NULL) |
| goto parse_fail; |
| cp++; |
| for (i = 2; i < 13; i++) { |
| cp = strchr(cp+1, ' '); |
| if (cp == NULL) |
| goto parse_fail; |
| } |
| |
| /* |
| * Grab utime/stime. |
| */ |
| char* endp; |
| pData->utime = strtoul(cp+1, &endp, 10); |
| if (endp == cp+1) |
| LOGI("Warning: strtoul failed on utime ('%.30s...')\n", cp); |
| |
| cp = strchr(cp+1, ' '); |
| if (cp == NULL) |
| goto parse_fail; |
| |
| pData->stime = strtoul(cp+1, &endp, 10); |
| if (endp == cp+1) |
| LOGI("Warning: strtoul failed on stime ('%.30s...')\n", cp); |
| |
| /* |
| * Skip more stuff we don't care about. |
| */ |
| for (i = 14; i < 38; i++) { |
| cp = strchr(cp+1, ' '); |
| if (cp == NULL) |
| goto parse_fail; |
| } |
| |
| /* |
| * Grab processor number. |
| */ |
| pData->processor = strtol(cp+1, &endp, 10); |
| if (endp == cp+1) |
| LOGI("Warning: strtoul failed on processor ('%.30s...')\n", cp); |
| |
| return true; |
| |
| parse_fail: |
| LOGI("stat parse failed (%s)\n", lineBuf); |
| return false; |
| } |
| |
| /* documented in header file */ |
| const char* dvmPathToAbsolutePortion(const char* path) { |
| if (path == NULL) { |
| return NULL; |
| } |
| |
| if (path[0] == '/') { |
| /* It's a regular absolute path. Return it. */ |
| return path; |
| } |
| |
| const char* sentinel = strstr(path, "/./"); |
| |
| if (sentinel != NULL) { |
| /* It's got the sentinel. Return a pointer to the second slash. */ |
| return sentinel + 2; |
| } |
| |
| return NULL; |
| } |