Source/PLCrashAsync.c (237 lines of code) (raw):

/* * Author: Landon Fuller <landonf@plausible.coop> * * Copyright (c) 2008-2013 Plausible Labs Cooperative, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "PLCrashAsync.h" #include <stdint.h> #include <errno.h> #include <string.h> #include <inttypes.h> /** * @internal * @defgroup plcrash_async Async Safe Utilities * @ingroup plcrash_internal * * Implements async-safe utility functions * * @{ */ /* Simple byteswap wrappers */ static uint16_t plcr_swap16 (uint16_t input) { return OSSwapInt16(input); } static uint16_t plcr_nswap16 (uint16_t input) { return input; } static uint32_t plcr_swap32 (uint32_t input) { return OSSwapInt32(input); } static uint32_t plcr_nswap32 (uint32_t input) { return input; } static uint64_t plcr_swap64 (uint64_t input) { return OSSwapInt64(input); } static uint64_t plcr_nswap64 (uint64_t input) { return input; } /** * Byte swap functions for a target using the reverse of the host's byte order. */ const plcrash_async_byteorder_t plcrash_async_byteorder_swapped = { .swap16 = plcr_swap16, .swap32 = plcr_swap32, .swap64 = plcr_swap64 }; /** * Byte swap functions for a target using the host's byte order. No swapping will be performed. */ const plcrash_async_byteorder_t plcrash_async_byteorder_direct = { .swap16 = plcr_nswap16, .swap32 = plcr_nswap32, .swap64 = plcr_nswap64 }; /** * Return byte order functions that may be used to swap to/from little endian to host byte order. */ extern const plcrash_async_byteorder_t *plcrash_async_byteorder_little_endian (void) { #if defined(__LITTLE_ENDIAN__) return &plcrash_async_byteorder_direct; #elif defined(__BIG_ENDIAN__) return &plcrash_async_byteorder_swapped; #else #error Unknown byte order #endif } /** * Return byte order functions that may be used to swap to/from big endian to host byte order. */ extern const plcrash_async_byteorder_t *plcrash_async_byteorder_big_endian (void) { #if defined(__LITTLE_ENDIAN__) return &plcrash_async_byteorder_swapped; #elif defined(__BIG_ENDIAN__) return &plcrash_async_byteorder_direct; #else #error Unknown byte order #endif } /** * Return an error description for the given plcrash_error_t. */ const char *plcrash_async_strerror (plcrash_error_t error) { switch (error) { case PLCRASH_ESUCCESS: return "No error"; case PLCRASH_EUNKNOWN: return "Unknown error"; case PLCRASH_OUTPUT_ERR: return "Output file can not be opened (or written to)"; case PLCRASH_ENOMEM: return "No memory available"; case PLCRASH_ENOTSUP: return "Operation not supported"; case PLCRASH_EINVAL: return "Invalid argument"; case PLCRASH_EINTERNAL: return "Internal error"; case PLCRASH_EACCESS: return "Access denied"; case PLCRASH_ENOTFOUND: return "Not found"; case PLCRASH_EINVALID_DATA: return "The input data is in an unknown or invalid format."; } /* Should be unreachable */ return "Unhandled error code"; } /** * Safely add @a offset to @a base_address, returning the result in @a result. If an overflow would occur, false is returned. * * @param base_address The base address from which @a result will be computed. * @param offset The offset to apply to @a base_address. * @param result The location in which to store the result. */ bool plcrash_async_address_apply_offset (pl_vm_address_t base_address, pl_vm_off_t offset, pl_vm_address_t *result) { /* Check for overflow */ if (offset > 0 && PL_VM_ADDRESS_MAX - offset < base_address) { return false; } else if (offset < 0 && (offset * -1) > base_address) { return false; } if (result != NULL) *result = base_address + offset; return true; } /** * Return a borrowed reference to the current thread's mach port. This differs * from mach_thread_self(), which acquires a new reference to the backing thread. * * @note The mach_thread_self() reference counting semantics differ from mach_task_self(); * mach_task_self() returns a borrowed reference, and will not leak -- a wrapper * function such as this is not required for mach_task_self(). */ thread_t pl_mach_thread_self (void) { thread_t result = mach_thread_self(); mach_port_deallocate(mach_task_self(), result); return result; } /** * Copy @a len bytes from @a task, at @a address + @a offset, storing in @a dest. If the page(s) at the * given @a address + @a offset are unmapped or unreadable, no copy will be performed and an error will * be returned. * * @param task The task from which data from address @a source will be read. * @param address The base address within @a task from which the data will be read. * @param offset The offset from @a address at which data will be read. * @param dest The destination address to which copied data will be written. * @param len The number of bytes to be read. * * @return On success, returns PLCRASH_ESUCCESS. If the pages containing @a source + len are unmapped, PLCRASH_ENOTFOUND * will be returned. If the pages can not be read due to access restrictions, PLCRASH_EACCESS will be returned. If * the proivded address + offset would overflow pl_vm_address_t, PLCRASH_ENOMEM is returned. */ plcrash_error_t plcrash_async_task_memcpy (mach_port_t task, pl_vm_address_t address, pl_vm_off_t offset, void *dest, pl_vm_size_t len) { pl_vm_address_t target; kern_return_t kt; /* Compute the target address and check for overflow */ if (!plcrash_async_address_apply_offset(address, offset, &target)) return PLCRASH_ENOMEM; #ifdef PL_HAVE_MACH_VM pl_vm_size_t read_size = len; kt = mach_vm_read_overwrite(task, target, len, (pointer_t) dest, &read_size); #else vm_size_t read_size = len; kt = vm_read_overwrite(task, target, len, (pointer_t) dest, &read_size); #endif switch (kt) { case KERN_SUCCESS: return PLCRASH_ESUCCESS; case KERN_INVALID_ADDRESS: return PLCRASH_ENOTFOUND; case KERN_PROTECTION_FAILURE: return PLCRASH_EACCESS; default: PLCF_DEBUG("Unexpected error from vm_read_overwrite: %d", kt); return PLCRASH_EUNKNOWN; } } /** * Read an 8-bit value from @a task, at @a address + @a offset, storing in @a dest. If the page(s) at the * given @a address + @a offset are unmapped or unreadable, no copy will be performed and an error will * be returned. * * @param task Task from which to read the value. * @param address The base address to be read. This address should be relative to the target task's address space. * @param offset An offset to be applied to @a address. * @param result The destination to which the data will be written, after @a byteorder has been applied. * * @return Returns PLCRASH_ESUCCESS on success, PLCRASH_EINVAL if the target address does not fall within the @a mobj address * range, or one of the plcrash_error_t constants for other error conditions. */ plcrash_error_t plcrash_async_task_read_uint8 (task_t task, pl_vm_address_t address, pl_vm_off_t offset, uint8_t *result) { return plcrash_async_task_memcpy(task, address, offset, result, sizeof(*result)); } /** * Read a 16-bit value from @a task, at @a address + @a offset, performing byte-swapping using @a byteorder, * and store in @a dest. * * If the page(s) at the given @a address + @a offset are unmapped or unreadable, no copy will be performed and an error will * be returned. * * @param task Task from which to read the value. * @param byteorder Byte order of the target value. * @param address The base address to be read. This address should be relative to the target task's address space. * @param offset An offset to be applied to @a address. * @param result The destination to which the data will be written, after @a byteorder has been applied. * * @return Returns PLCRASH_ESUCCESS on success, PLCRASH_EINVAL if the target address does not fall within the @a mobj address * range, or one of the plcrash_error_t constants for other error conditions. */ plcrash_error_t plcrash_async_task_read_uint16 (task_t task, const plcrash_async_byteorder_t *byteorder, pl_vm_address_t address, pl_vm_off_t offset, uint16_t *result) { plcrash_error_t err = plcrash_async_task_memcpy(task, address, offset, result, sizeof(*result)); *result = byteorder->swap16(*result); return err; } /** * Read a 32-bit value from @a task, at @a address + @a offset, performing byte-swapping using @a byteorder, * and store in @a dest. * * If the page(s) at the given @a address + @a offset are unmapped or unreadable, no copy will be performed and an error will * be returned. * * @param task Task from which to read the value. * @param byteorder Byte order of the target value. * @param address The base address to be read. This address should be relative to the target task's address space. * @param offset An offset to be applied to @a address. * @param result The destination to which the data will be written, after @a byteorder has been applied. * * @return Returns PLCRASH_ESUCCESS on success, PLCRASH_EINVAL if the target address does not fall within the @a mobj address * range, or one of the plcrash_error_t constants for other error conditions. */ plcrash_error_t plcrash_async_task_read_uint32 (task_t task, const plcrash_async_byteorder_t *byteorder, pl_vm_address_t address, pl_vm_off_t offset, uint32_t *result) { plcrash_error_t err = plcrash_async_task_memcpy(task, address, offset, result, sizeof(*result)); *result = byteorder->swap32(*result); return err; } /** * Read a 64-bit value from @a task, at @a address + @a offset, performing byte-swapping using @a byteorder, * and store in @a dest. * * If the page(s) at the given @a address + @a offset are unmapped or unreadable, no copy will be performed and an error will * be returned. * * @param task Task from which to read the value. * @param byteorder Byte order of the target value. * @param address The base address to be read. This address should be relative to the target task's address space. * @param offset An offset to be applied to @a address. * @param result The destination to which the data will be written, after @a byteorder has been applied. * * @return Returns PLCRASH_ESUCCESS on success, PLCRASH_EINVAL if the target address does not fall within the @a mobj address * range, or one of the plcrash_error_t constants for other error conditions. */ plcrash_error_t plcrash_async_task_read_uint64 (task_t task, const plcrash_async_byteorder_t *byteorder, pl_vm_address_t address, pl_vm_off_t offset, uint64_t *result) { plcrash_error_t err = plcrash_async_task_memcpy(task, address, offset, result, sizeof(*result)); *result = byteorder->swap64(*result); return err; } /** * An intentionally naive async-safe implementation of strcmp(). strcmp() itself is not declared to be async-safe, * though in reality, it is. * * @param s1 First string. * @param s2 Second string. * @return Return an integer greater than, equal to, or less than 0, according as the string @a s1 is greater than, * equal to, or less than the string @a s2. */ int plcrash_async_strcmp(const char *s1, const char *s2) { while (*s1 == *s2++) { if (*s1++ == 0) return (0); } return (*(const unsigned char *)s1 - *(const unsigned char *)(s2 - 1)); } /** * An intentionally naive async-safe implementation of strncmp(). strncmp() itself is not declared to be async-safe, * though in reality, it is. * * @param s1 First string. * @param s2 Second string. * @param n No more than n characters will be compared. * @return Return an integer greater than, equal to, or less than 0, according as the string @a s1 is greater than, * equal to, or less than the string @a s2. */ int plcrash_async_strncmp(const char *s1, const char *s2, size_t n) { while (*s1 == *s2++ && n-- > 0) { if (*s1++ == 0) return (0); } if (n == 0) return 0; return (*(const unsigned char *)s1 - *(const unsigned char *)(s2 - 1)); } /** * An intentionally naive async-safe implementation of memcpy(). memcpy() itself is not declared to be async-safe, * though in reality, it is. * * @param dest Destination. * @param source Source. * @param n Number of bytes to copy. */ void *plcrash_async_memcpy (void *dest, const void *source, size_t n) { uint8_t *s = (uint8_t *) source; uint8_t *d = (uint8_t *) dest; for (size_t count = 0; count < n; count++) *d++ = *s++; return (void *) source; } /** * An intentionally naive async-safe implementation of memset(). memset() itself is not declared to be async-safe, * though in reality, it is. * * @param dest Destination. * @param value Value to write to @a dest. * @param n Number of bytes to copy. */ void *plcrash_async_memset(void *dest, uint8_t value, size_t n) { uint8_t *d = (uint8_t *) dest; for (size_t count = 0; count < n; count++) *d++ = value; return (void *) dest; } /** * @internal * @ingroup plcrash_async * @defgroup plcrash_async_bufio Async-safe Buffered IO * @{ */ /** * * Write len bytes to fd, looping until all bytes are written * or an error occurs. For the local file system, only one call to write() * should be necessary */ ssize_t plcrash_async_writen (int fd, const void *data, size_t len) { const uint8_t *p; size_t left; ssize_t written = 0; /* Loop until all bytes are written */ p = (const uint8_t *) data; left = len; while (left > 0) { if ((written = write(fd, p, left)) <= 0) { if (errno == EINTR) { // Try again written = 0; } else { return -1; } } left -= written; p += written; } return written; } /** * Initialize the plcrash_async_file_t instance. * * @param file File structure to initialize. * @param output_limit Maximum number of bytes that will be written to disk. Intended as a * safety measure prevent a run-away crash log writer from filling the disk. Specify * 0 to disable any limits. Once the limit is reached, all data will be dropped. * @param fd Open file descriptor. */ void plcrash_async_file_init (plcrash_async_file_t *file, int fd, off_t output_limit) { file->fd = fd; file->buflen = 0; file->total_bytes = 0; file->limit_bytes = output_limit; } /** * Write all bytes from @a data to the file buffer. Returns true on success, * or false if an error occurs. */ bool plcrash_async_file_write (plcrash_async_file_t *file, const void *data, size_t len) { /* Check and update output limit */ if (file->limit_bytes != 0 && len + file->total_bytes > file->limit_bytes) { return false; } else if (file->limit_bytes != 0) { file->total_bytes += len; } /* Check if the buffer will fill */ if (file->buflen + len > sizeof(file->buffer)) { /* Flush the buffer */ if (plcrash_async_writen(file->fd, file->buffer, file->buflen) < 0) { PLCF_DEBUG("Error occured writing to crash log: %s", strerror(errno)); return false; } file->buflen = 0; } /* Check if the new data fits within the buffer, if so, buffer it */ if (len + file->buflen <= sizeof(file->buffer)) { plcrash_async_memcpy(file->buffer + file->buflen, data, len); file->buflen += len; return true; } else { /* Won't fit in the buffer, just write it */ if (plcrash_async_writen(file->fd, data, len) < 0) { PLCF_DEBUG("Error occured writing to crash log: %s", strerror(errno)); return false; } return true; } } /** * Flush all buffered bytes from the file buffer. */ bool plcrash_async_file_flush (plcrash_async_file_t *file) { /* Anything to do? */ if (file->buflen == 0) return true; /* Write remaining */ if (plcrash_async_writen(file->fd, file->buffer, file->buflen) < 0) { PLCF_DEBUG("Error occured writing to crash log: %s", strerror(errno)); return false; } file->buflen = 0; return true; } /** * Close the backing file descriptor. */ bool plcrash_async_file_close (plcrash_async_file_t *file) { /* Flush any pending data */ if (!plcrash_async_file_flush(file)) return false; /* Close the file descriptor */ if (close(file->fd) != 0) { PLCF_DEBUG("Error closing file: %s", strerror(errno)); return false; } return true; } /* * @} plcrash_async_bufio */ /* * @} plcrash_async */