AutorÃa | Ultima modificación | Ver Log |
// Copyright 2019 Google//// 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.#include "Crashlytics/Crashlytics/Helpers/FIRCLSFile.h"#include "Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h"#include "Crashlytics/Shared/FIRCLSByteUtility.h"#if TARGET_OS_MAC#include <Foundation/Foundation.h>#endif#include <sys/stat.h>#include <stdio.h>#include <string.h>#include <unistd.h>// uint64_t should only have max 19 chars in base 10, and less in base 16static const size_t FIRCLSUInt64StringBufferLength = 21;static const size_t FIRCLSStringBufferLength = 16;const size_t FIRCLSWriteBufferLength = 1000;static bool FIRCLSFileInit(FIRCLSFile* file, int fdm, bool appendMode, bool bufferWrites);static void FIRCLSFileWriteToFileDescriptorOrBuffer(FIRCLSFile* file,const char* string,size_t length);static void FIRCLSFileWriteToBuffer(FIRCLSFile* file, const char* string, size_t length);static void FIRCLSFileWriteToFileDescriptor(FIRCLSFile* file, const char* string, size_t length);short FIRCLSFilePrepareUInt64(char* buffer, uint64_t number, bool hex);static void FIRCLSFileWriteString(FIRCLSFile* file, const char* string);static void FIRCLSFileWriteHexEncodedString(FIRCLSFile* file, const char* string);static void FIRCLSFileWriteBool(FIRCLSFile* file, bool value);static void FIRCLSFileWriteCollectionStart(FIRCLSFile* file, const char openingChar);static void FIRCLSFileWriteCollectionEnd(FIRCLSFile* file, const char closingChar);static void FIRCLSFileWriteCollectionEntryProlog(FIRCLSFile* file);static void FIRCLSFileWriteCollectionEntryEpilog(FIRCLSFile* file);#define CLS_FILE_DEBUG_LOGGING 0#pragma mark - File Structurestatic bool FIRCLSFileInit(FIRCLSFile* file, int fd, bool appendMode, bool bufferWrites) {if (!file) {FIRCLSSDKLog("Error: file is null\n");return false;}if (fd < 0) {FIRCLSSDKLog("Error: file descriptor invalid\n");return false;}memset(file, 0, sizeof(FIRCLSFile));file->fd = fd;file->bufferWrites = bufferWrites;if (bufferWrites) {file->writeBuffer = malloc(FIRCLSWriteBufferLength * sizeof(char));if (!file->writeBuffer) {FIRCLSErrorLog(@"Unable to malloc in FIRCLSFileInit");return false;}file->writeBufferLength = 0;}file->writtenLength = 0;if (appendMode) {struct stat fileStats;fstat(fd, &fileStats);off_t currentFileSize = fileStats.st_size;if (currentFileSize > 0) {file->writtenLength += currentFileSize;}}return true;}bool FIRCLSFileInitWithPath(FIRCLSFile* file, const char* path, bool bufferWrites) {return FIRCLSFileInitWithPathMode(file, path, true, bufferWrites);}bool FIRCLSFileInitWithPathMode(FIRCLSFile* file,const char* path,bool appendMode,bool bufferWrites) {if (!file) {FIRCLSSDKLog("Error: file is null\n");return false;}int mask = O_WRONLY | O_CREAT;if (appendMode) {mask |= O_APPEND;} else {mask |= O_TRUNC;}// make sure to call FIRCLSFileInit no matter whatint fd = -1;if (path) {#if TARGET_OS_IPHONE/** data-protected non-portable open(2) :* int open_dprotected_np(user_addr_t path, int flags, int class, int dpflags, int mode)*/fd = open_dprotected_np(path, mask, 4, 0, 0644);#elsefd = open(path, mask, 0644);#endifif (fd < 0) {FIRCLSSDKLog("Error: Unable to open file %s\n", strerror(errno));}}return FIRCLSFileInit(file, fd, appendMode, bufferWrites);}bool FIRCLSFileClose(FIRCLSFile* file) {return FIRCLSFileCloseWithOffset(file, NULL);}bool FIRCLSFileCloseWithOffset(FIRCLSFile* file, off_t* finalSize) {if (!FIRCLSIsValidPointer(file)) {return false;}if (file->bufferWrites && FIRCLSIsValidPointer(file->writeBuffer)) {if (file->writeBufferLength > 0) {FIRCLSFileFlushWriteBuffer(file);}free(file->writeBuffer);}if (FIRCLSIsValidPointer(finalSize)) {*finalSize = file->writtenLength;}if (close(file->fd) != 0) {FIRCLSSDKLog("Error: Unable to close file %s\n", strerror(errno));return false;}memset(file, 0, sizeof(FIRCLSFile));file->fd = -1;return true;}bool FIRCLSFileIsOpen(FIRCLSFile* file) {if (!FIRCLSIsValidPointer(file)) {return false;}return file->fd > -1;}#pragma mark - Core Writing APIvoid FIRCLSFileFlushWriteBuffer(FIRCLSFile* file) {if (!FIRCLSIsValidPointer(file)) {return;}if (!file->bufferWrites) {return;}FIRCLSFileWriteToFileDescriptor(file, file->writeBuffer, file->writeBufferLength);file->writeBufferLength = 0;}static void FIRCLSFileWriteToFileDescriptorOrBuffer(FIRCLSFile* file,const char* string,size_t length) {if (file->bufferWrites) {if (file->writeBufferLength + length > FIRCLSWriteBufferLength - 1) {// fill remaining space in buffersize_t remainingSpace = FIRCLSWriteBufferLength - file->writeBufferLength - 1;FIRCLSFileWriteToBuffer(file, string, remainingSpace);FIRCLSFileFlushWriteBuffer(file);// write remainder of string to newly-emptied buffersize_t remainingLength = length - remainingSpace;FIRCLSFileWriteToFileDescriptorOrBuffer(file, string + remainingSpace, remainingLength);} else {FIRCLSFileWriteToBuffer(file, string, length);}} else {FIRCLSFileWriteToFileDescriptor(file, string, length);}}void FIRCLSFileWriteStringUnquoted(FIRCLSFile* file, const char* string) {size_t length = strlen(string);FIRCLSFileWriteToFileDescriptorOrBuffer(file, string, length);}static void FIRCLSFileWriteToFileDescriptor(FIRCLSFile* file, const char* string, size_t length) {if (!FIRCLSFileWriteWithRetries(file->fd, string, length)) {return;}file->writtenLength += length;}// Beware calling this method directly: it will truncate the input string if it's longer// than the remaining space in the buffer. It's safer to call through// FIRCLSFileWriteToFileDescriptorOrBuffer.static void FIRCLSFileWriteToBuffer(FIRCLSFile* file, const char* string, size_t length) {size_t writeLength = length;if (file->writeBufferLength + writeLength > FIRCLSWriteBufferLength - 1) {writeLength = FIRCLSWriteBufferLength - file->writeBufferLength - 1;}strncpy(file->writeBuffer + file->writeBufferLength, string, writeLength);file->writeBufferLength += writeLength;file->writeBuffer[file->writeBufferLength] = '\0';}bool FIRCLSFileLoopWithWriteBlock(const void* buffer,size_t length,ssize_t (^writeBlock)(const void* buf, size_t len)) {for (size_t count = 0; length > 0 && count < CLS_FILE_MAX_WRITE_ATTEMPTS; ++count) {// try to write all that is leftssize_t ret = writeBlock(buffer, length);if (length > SIZE_MAX) {// if this happens we can't convert it to a signed version due to overflowreturn false;}const ssize_t signedLength = (ssize_t)length;if (ret >= 0 && ret == signedLength) {return true;}// Write was unsuccessful (out of space, etc)if (ret < 0) {return false;}// We wrote more bytes than we expected, abortif (ret > signedLength) {return false;}// wrote a portion of the data, adjust and keep tryingif (ret > 0) {length -= ret;buffer += ret;continue;}// return value is <= 0, which is an errorbreak;}return false;}bool FIRCLSFileWriteWithRetries(int fd, const void* buffer, size_t length) {return FIRCLSFileLoopWithWriteBlock(buffer, length,^ssize_t(const void* partialBuffer, size_t partialLength) {return write(fd, partialBuffer, partialLength);});}#pragma mark - Stringsstatic void FIRCLSFileWriteUnbufferedStringWithSuffix(FIRCLSFile* file,const char* string,size_t length,char suffix) {char suffixBuffer[2];// collaspe the quote + suffix into one single write call, for a small performance winsuffixBuffer[0] = '"';suffixBuffer[1] = suffix;FIRCLSFileWriteToFileDescriptorOrBuffer(file, "\"", 1);FIRCLSFileWriteToFileDescriptorOrBuffer(file, string, length);FIRCLSFileWriteToFileDescriptorOrBuffer(file, suffixBuffer, suffix == 0 ? 1 : 2);}static void FIRCLSFileWriteStringWithSuffix(FIRCLSFile* file,const char* string,size_t length,char suffix) {// 2 for quotes, 1 for suffix (if present) and 1 more for null characterconst size_t maxStringSize = FIRCLSStringBufferLength - (suffix == 0 ? 3 : 4);if (length >= maxStringSize) {FIRCLSFileWriteUnbufferedStringWithSuffix(file, string, length, suffix);return;}// we are trying to achieve this in one write call// <"><string contents><"><suffix>char buffer[FIRCLSStringBufferLength];buffer[0] = '"';strncpy(buffer + 1, string, length);buffer[length + 1] = '"';length += 2;if (suffix) {buffer[length] = suffix;length += 1;}// Always add the terminator. strncpy above would copy the terminator, if we supplied length + 1,// but since we do this suffix adjustment here, it's easier to just fix it up in both cases.buffer[length + 1] = 0;FIRCLSFileWriteToFileDescriptorOrBuffer(file, buffer, length);}void FIRCLSFileWriteString(FIRCLSFile* file, const char* string) {if (!string) {FIRCLSFileWriteToFileDescriptorOrBuffer(file, "null", 4);return;}FIRCLSFileWriteStringWithSuffix(file, string, strlen(string), 0);}void FIRCLSFileWriteHexEncodedString(FIRCLSFile* file, const char* string) {if (!file) {return;}if (!string) {FIRCLSFileWriteToFileDescriptorOrBuffer(file, "null", 4);return;}char buffer[CLS_FILE_HEX_BUFFER];memset(buffer, 0, sizeof(buffer));size_t length = strlen(string);FIRCLSFileWriteToFileDescriptorOrBuffer(file, "\"", 1);int bufferIndex = 0;for (int i = 0; i < length; ++i) {FIRCLSHexFromByte(string[i], &buffer[bufferIndex]);bufferIndex += 2; // 1 char => 2 hex values at a time// we can continue only if we have enough space for two more hex// characters *and* a terminator. So, we need three total chars// of spaceif (bufferIndex >= CLS_FILE_HEX_BUFFER) {FIRCLSFileWriteToFileDescriptorOrBuffer(file, buffer, CLS_FILE_HEX_BUFFER);bufferIndex = 0;}}// Copy the remainder, which could even be the entire string, if it// fit into the buffer completely. Be careful with bounds checking here.// The string needs to be non-empty, and we have to have copied at least// one pair of hex characters in.if (bufferIndex > 0 && length > 0) {FIRCLSFileWriteToFileDescriptorOrBuffer(file, buffer, bufferIndex);}FIRCLSFileWriteToFileDescriptorOrBuffer(file, "\"", 1);}#pragma mark - Integersvoid FIRCLSFileWriteUInt64(FIRCLSFile* file, uint64_t number, bool hex) {char buffer[FIRCLSUInt64StringBufferLength];short i = FIRCLSFilePrepareUInt64(buffer, number, hex);char* beginning = &buffer[i]; // Write from a pointer to the begining of the string.FIRCLSFileWriteToFileDescriptorOrBuffer(file, beginning, strlen(beginning));}void FIRCLSFileFDWriteUInt64(int fd, uint64_t number, bool hex) {char buffer[FIRCLSUInt64StringBufferLength];short i = FIRCLSFilePrepareUInt64(buffer, number, hex);char* beginning = &buffer[i]; // Write from a pointer to the begining of the string.FIRCLSFileWriteWithRetries(fd, beginning, strlen(beginning));}void FIRCLSFileWriteInt64(FIRCLSFile* file, int64_t number) {if (number < 0) {FIRCLSFileWriteToFileDescriptorOrBuffer(file, "-", 1);number *= -1; // make it positive}FIRCLSFileWriteUInt64(file, number, false);}void FIRCLSFileFDWriteInt64(int fd, int64_t number) {if (number < 0) {FIRCLSFileWriteWithRetries(fd, "-", 1);number *= -1; // make it positive}FIRCLSFileFDWriteUInt64(fd, number, false);}short FIRCLSFilePrepareUInt64(char* buffer, uint64_t number, bool hex) {uint32_t base = hex ? 16 : 10;// zero it out, which will add a terminatormemset(buffer, 0, FIRCLSUInt64StringBufferLength);// TODO: look at this closer// I'm pretty sure there is a bug in this code that// can result in numbers with leading zeros. Technically,// those are not valid json.// Set current index.short i = FIRCLSUInt64StringBufferLength - 1;// Loop through filling in the chars from the end.do {char value = number % base + '0';if (value > '9') {value += 'a' - '9' - 1;}buffer[--i] = value;} while ((number /= base) > 0 && i > 0);// returns index pointing to the beginning of the string.return i;}void FIRCLSFileWriteBool(FIRCLSFile* file, bool value) {if (value) {FIRCLSFileWriteToFileDescriptorOrBuffer(file, "true", 4);} else {FIRCLSFileWriteToFileDescriptorOrBuffer(file, "false", 5);}}void FIRCLSFileWriteSectionStart(FIRCLSFile* file, const char* name) {FIRCLSFileWriteHashStart(file);FIRCLSFileWriteHashKey(file, name);}void FIRCLSFileWriteSectionEnd(FIRCLSFile* file) {FIRCLSFileWriteHashEnd(file);FIRCLSFileWriteToFileDescriptorOrBuffer(file, "\n", 1);}void FIRCLSFileWriteCollectionStart(FIRCLSFile* file, const char openingChar) {char string[2];string[0] = ',';string[1] = openingChar;if (file->needComma) {FIRCLSFileWriteToFileDescriptorOrBuffer(file, string, 2); // write the seperator + opening char} else {FIRCLSFileWriteToFileDescriptorOrBuffer(file, &string[1], 1); // write only the opening char}file->collectionDepth++;file->needComma = false;}void FIRCLSFileWriteCollectionEnd(FIRCLSFile* file, const char closingChar) {FIRCLSFileWriteToFileDescriptorOrBuffer(file, &closingChar, 1);if (file->collectionDepth <= 0) {// FIRCLSSafeLog("Collection depth invariant violated\n");return;}file->collectionDepth--;file->needComma = file->collectionDepth > 0;}void FIRCLSFileWriteCollectionEntryProlog(FIRCLSFile* file) {if (file->needComma) {FIRCLSFileWriteToFileDescriptorOrBuffer(file, ",", 1);}}void FIRCLSFileWriteCollectionEntryEpilog(FIRCLSFile* file) {file->needComma = true;}void FIRCLSFileWriteHashStart(FIRCLSFile* file) {FIRCLSFileWriteCollectionStart(file, '{');}void FIRCLSFileWriteHashEnd(FIRCLSFile* file) {FIRCLSFileWriteCollectionEnd(file, '}');}void FIRCLSFileWriteHashKey(FIRCLSFile* file, const char* key) {FIRCLSFileWriteCollectionEntryProlog(file);FIRCLSFileWriteStringWithSuffix(file, key, strlen(key), ':');file->needComma = false;}void FIRCLSFileWriteHashEntryUint64(FIRCLSFile* file, const char* key, uint64_t value) {// no prolog needed because it comes from the keyFIRCLSFileWriteHashKey(file, key);FIRCLSFileWriteUInt64(file, value, false);FIRCLSFileWriteCollectionEntryEpilog(file);}void FIRCLSFileWriteHashEntryInt64(FIRCLSFile* file, const char* key, int64_t value) {// prolog from keyFIRCLSFileWriteHashKey(file, key);FIRCLSFileWriteInt64(file, value);FIRCLSFileWriteCollectionEntryEpilog(file);}void FIRCLSFileWriteHashEntryString(FIRCLSFile* file, const char* key, const char* value) {FIRCLSFileWriteHashKey(file, key);FIRCLSFileWriteString(file, value);FIRCLSFileWriteCollectionEntryEpilog(file);}void FIRCLSFileWriteHashEntryNSString(FIRCLSFile* file, const char* key, NSString* string) {FIRCLSFileWriteHashEntryString(file, key, [string UTF8String]);}void FIRCLSFileWriteHashEntryNSStringUnlessNilOrEmpty(FIRCLSFile* file,const char* key,NSString* string) {if ([string length] > 0) {FIRCLSFileWriteHashEntryString(file, key, [string UTF8String]);}}void FIRCLSFileWriteHashEntryHexEncodedString(FIRCLSFile* file,const char* key,const char* value) {FIRCLSFileWriteHashKey(file, key);FIRCLSFileWriteHexEncodedString(file, value);FIRCLSFileWriteCollectionEntryEpilog(file);}void FIRCLSFileWriteHashEntryBoolean(FIRCLSFile* file, const char* key, bool value) {FIRCLSFileWriteHashKey(file, key);FIRCLSFileWriteBool(file, value);FIRCLSFileWriteCollectionEntryEpilog(file);}void FIRCLSFileWriteArrayStart(FIRCLSFile* file) {FIRCLSFileWriteCollectionStart(file, '[');}void FIRCLSFileWriteArrayEnd(FIRCLSFile* file) {FIRCLSFileWriteCollectionEnd(file, ']');}void FIRCLSFileWriteArrayEntryUint64(FIRCLSFile* file, uint64_t value) {FIRCLSFileWriteCollectionEntryProlog(file);FIRCLSFileWriteUInt64(file, value, false);FIRCLSFileWriteCollectionEntryEpilog(file);}void FIRCLSFileWriteArrayEntryString(FIRCLSFile* file, const char* value) {FIRCLSFileWriteCollectionEntryProlog(file);FIRCLSFileWriteString(file, value);FIRCLSFileWriteCollectionEntryEpilog(file);}void FIRCLSFileWriteArrayEntryHexEncodedString(FIRCLSFile* file, const char* value) {FIRCLSFileWriteCollectionEntryProlog(file);FIRCLSFileWriteHexEncodedString(file, value);FIRCLSFileWriteCollectionEntryEpilog(file);}NSArray* FIRCLSFileReadSections(const char* path,bool deleteOnFailure,NSObject* (^transformer)(id obj)) {if (!FIRCLSIsValidPointer(path)) {FIRCLSSDKLogError("Error: input path is invalid\n");return nil;}NSString* pathString = [NSString stringWithUTF8String:path];NSString* contents = [NSString stringWithContentsOfFile:pathStringencoding:NSUTF8StringEncodingerror:nil];NSArray* components = [contents componentsSeparatedByString:@"\n"];if (!components) {if (deleteOnFailure) {unlink(path);}FIRCLSSDKLog("Unable to read file %s\n", path);return nil;}NSMutableArray* array = [NSMutableArray array];// loop through all the entires, andfor (NSString* component in components) {NSData* data = [component dataUsingEncoding:NSUTF8StringEncoding];id obj = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];if (!obj) {continue;}if (transformer) {obj = transformer(obj);}if (!obj) {continue;}[array addObject:obj];}return array;}NSString* FIRCLSFileHexEncodeString(const char* string) {size_t length = strlen(string);char* encodedBuffer = malloc(length * 2 + 1);if (!encodedBuffer) {FIRCLSErrorLog(@"Unable to malloc in FIRCLSFileHexEncodeString");return nil;}memset(encodedBuffer, 0, length * 2 + 1);int bufferIndex = 0;for (int i = 0; i < length; ++i) {FIRCLSHexFromByte(string[i], &encodedBuffer[bufferIndex]);bufferIndex += 2; // 1 char => 2 hex values at a time}NSString* stringObject = [NSString stringWithUTF8String:encodedBuffer];free(encodedBuffer);return stringObject;}NSString* FIRCLSFileHexDecodeString(const char* string) {size_t length = strlen(string);char* decodedBuffer = malloc(length); // too long, but safeif (!decodedBuffer) {FIRCLSErrorLog(@"Unable to malloc in FIRCLSFileHexDecodeString");return nil;}memset(decodedBuffer, 0, length);for (int i = 0; i < length / 2; ++i) {size_t index = i * 2;uint8_t hiNybble = FIRCLSNybbleFromChar(string[index]);uint8_t lowNybble = FIRCLSNybbleFromChar(string[index + 1]);if (hiNybble == FIRCLSInvalidCharNybble || lowNybble == FIRCLSInvalidCharNybble) {// char is invalid, abort loopbreak;}decodedBuffer[i] = (hiNybble << 4) | lowNybble;}NSString* strObject = [NSString stringWithUTF8String:decodedBuffer];free(decodedBuffer);return strObject;}