| /* |
| * Copyright (c) 2008-2009 Brent Fulgham <bfulgham@gmail.org>. All rights reserved. |
| * |
| * This source code is a modified version of the CoreFoundation sources released by Apple Inc. under |
| * the terms of the APSL version 2.0 (see below). |
| * |
| * For information about changes from the original Apple source release can be found by reviewing the |
| * source control system for the project at https://sourceforge.net/svn/?group_id=246198. |
| * |
| * The original license information is as follows: |
| * |
| * Copyright (c) 2008 Apple Inc. All rights reserved. |
| * |
| * @APPLE_LICENSE_HEADER_START@ |
| * |
| * This file contains Original Code and/or Modifications of Original Code |
| * as defined in and that are subject to the Apple Public Source License |
| * Version 2.0 (the 'License'). You may not use this file except in |
| * compliance with the License. Please obtain a copy of the License at |
| * http://www.opensource.apple.com/apsl/ and read it before using this |
| * file. |
| * |
| * The Original Code and all software distributed under the License are |
| * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER |
| * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, |
| * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. |
| * Please see the License for the specific language governing rights and |
| * limitations under the License. |
| * |
| * @APPLE_LICENSE_HEADER_END@ |
| */ |
| /* CFConcreteStreams.c |
| Copyright 2000-2002, Apple, Inc. All rights reserved. |
| Responsibility: Becky Willrich |
| */ |
| |
| #define _DARWIN_UNLIMITED_SELECT 1 |
| |
| #include "CFStreamInternal.h" |
| #include "CFInternal.h" |
| #include "CFPriv.h" |
| #include <CoreFoundation/CFNumber.h> |
| #include <sys/types.h> |
| #include <stdlib.h> |
| #include <fcntl.h> |
| #include <string.h> |
| #include <stdio.h> |
| #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_LINUX |
| #include <sys/time.h> |
| #include <unistd.h> |
| #elif DEPLOYMENT_TARGET_WINDOWS |
| #include <io.h> |
| #define lseek _lseek |
| #define open _open |
| #define read _read |
| #define write _write |
| #define close _close |
| #endif |
| |
| // On Unix, you can schedule an fd with the RunLoop by creating a CFSocket around it. On Win32 |
| // files and sockets are not interchangeable, and we do cheapo scheduling, where the file is |
| // always readable and writable until we hit EOF (similar to the way CFData streams are scheduled). |
| #if DEPLOYMENT_TARGET_MACOSX |
| #define REAL_FILE_SCHEDULING (1) |
| #endif |
| |
| #define SCHEDULE_AFTER_WRITE (0) |
| #define SCHEDULE_AFTER_READ (1) |
| #define APPEND (3) |
| #define AT_EOF (4) |
| #define USE_RUNLOOP_ARRAY (5) |
| |
| |
| /* File callbacks */ |
| typedef struct { |
| CFURLRef url; |
| int fd; |
| #ifdef REAL_FILE_SCHEDULING |
| union { |
| CFSocketRef sock; // socket created once we open and have an fd |
| CFMutableArrayRef rlArray; // scheduling information prior to open |
| } rlInfo; // If fd > 0, sock exists. Otherwise, rlArray. |
| #else |
| uint16_t scheduled; // ref count of how many times we've been scheduled |
| #endif |
| CFOptionFlags flags; |
| off_t offset; |
| } _CFFileStreamContext; |
| |
| |
| CONST_STRING_DECL(kCFStreamPropertyFileCurrentOffset, "kCFStreamPropertyFileCurrentOffset"); |
| |
| |
| #ifdef REAL_FILE_SCHEDULING |
| static void fileCallBack(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void *data, void *info); |
| |
| static void constructCFSocket(_CFFileStreamContext *fileStream, Boolean forRead, struct _CFStream *stream) { |
| CFSocketContext context = {0, stream, NULL, NULL, CFCopyDescription}; |
| CFSocketRef sock = CFSocketCreateWithNative(CFGetAllocator(stream), fileStream->fd, forRead ? kCFSocketReadCallBack : kCFSocketWriteCallBack, fileCallBack, &context); |
| CFSocketSetSocketFlags(sock, 0); |
| if (fileStream->rlInfo.rlArray) { |
| CFIndex i, c = CFArrayGetCount(fileStream->rlInfo.rlArray); |
| CFRunLoopSourceRef src = CFSocketCreateRunLoopSource(CFGetAllocator(stream), sock, 0); |
| for (i = 0; i+1 < c; i += 2) { |
| CFRunLoopRef rl = (CFRunLoopRef)CFArrayGetValueAtIndex(fileStream->rlInfo.rlArray, i); |
| CFStringRef mode = (CFStringRef)CFArrayGetValueAtIndex(fileStream->rlInfo.rlArray, i+1); |
| CFRunLoopAddSource(rl, src, mode); |
| } |
| CFRelease(fileStream->rlInfo.rlArray); |
| CFRelease(src); |
| } |
| fileStream->rlInfo.sock = sock; |
| } |
| #endif |
| |
| static Boolean constructFD(_CFFileStreamContext *fileStream, CFStreamError *error, Boolean forRead, struct _CFStream *stream) { |
| UInt8 path[1024]; |
| int flags = forRead ? O_RDONLY : (O_CREAT | O_TRUNC | O_WRONLY); |
| |
| if (CFURLGetFileSystemRepresentation(fileStream->url, TRUE, path, 1024) == FALSE) { |
| error->error = ENOENT; |
| error->domain = kCFStreamErrorDomainPOSIX; |
| return FALSE; |
| } |
| if (__CFBitIsSet(fileStream->flags, APPEND)) { |
| flags |= O_APPEND; |
| if(_CFExecutableLinkedOnOrAfter(CFSystemVersionPanther)) flags &= ~O_TRUNC; |
| } |
| |
| do { |
| fileStream->fd = open((const char *)path, flags, 0666); |
| |
| if (fileStream->fd < 0) |
| break; |
| |
| if ((fileStream->offset != -1) && (lseek(fileStream->fd, fileStream->offset, SEEK_SET) == -1)) |
| break; |
| |
| #ifdef REAL_FILE_SCHEDULING |
| if (fileStream->rlInfo.rlArray != NULL) { |
| constructCFSocket(fileStream, forRead, stream); |
| } |
| #endif |
| |
| return TRUE; |
| } while (1); |
| |
| __CFBitSet(fileStream->flags, USE_RUNLOOP_ARRAY); |
| error->error = errno; |
| error->domain = kCFStreamErrorDomainPOSIX; |
| |
| return FALSE; |
| } |
| |
| static Boolean fileOpen(struct _CFStream *stream, CFStreamError *errorCode, Boolean *openComplete, void *info) { |
| _CFFileStreamContext *ctxt = (_CFFileStreamContext *)info; |
| Boolean forRead = (CFGetTypeID(stream) == CFReadStreamGetTypeID()); |
| *openComplete = TRUE; |
| if (ctxt->url) { |
| if (constructFD(ctxt, errorCode, forRead, stream)) { |
| #ifndef REAL_FILE_SCHEDULING |
| if (ctxt->scheduled > 0) { |
| if (forRead) |
| CFReadStreamSignalEvent((CFReadStreamRef)stream, kCFStreamEventHasBytesAvailable, NULL); |
| else |
| CFWriteStreamSignalEvent((CFWriteStreamRef)stream, kCFStreamEventCanAcceptBytes, NULL); |
| } |
| #endif |
| return TRUE; |
| } else { |
| return FALSE; |
| } |
| #ifdef REAL_FILE_SCHEDULING |
| } else if (ctxt->rlInfo.rlArray != NULL) { |
| constructCFSocket(ctxt, forRead, stream); |
| #endif |
| } |
| return TRUE; |
| } |
| |
| __private_extern__ CFIndex fdRead(int fd, UInt8 *buffer, CFIndex bufferLength, CFStreamError *errorCode, Boolean *atEOF) { |
| CFIndex bytesRead = read(fd, buffer, bufferLength); |
| if (bytesRead < 0) { |
| errorCode->error = errno; |
| errorCode->domain = kCFStreamErrorDomainPOSIX; |
| return -1; |
| } else { |
| *atEOF = (bytesRead == 0) ? TRUE : FALSE; |
| errorCode->error = 0; |
| return bytesRead; |
| } |
| } |
| |
| static CFIndex fileRead(CFReadStreamRef stream, UInt8 *buffer, CFIndex bufferLength, CFStreamError *errorCode, Boolean *atEOF, void *info) { |
| _CFFileStreamContext *ctxt = (_CFFileStreamContext *)info; |
| CFIndex result; |
| result = fdRead(ctxt->fd, buffer, bufferLength, errorCode, atEOF); |
| #ifdef REAL_FILE_SCHEDULING |
| if (__CFBitIsSet(ctxt->flags, SCHEDULE_AFTER_READ)) { |
| __CFBitClear(ctxt->flags, SCHEDULE_AFTER_READ); |
| if (ctxt->rlInfo.sock) { |
| CFSocketEnableCallBacks(ctxt->rlInfo.sock, kCFSocketReadCallBack); |
| } |
| } |
| #else |
| if (*atEOF) |
| __CFBitSet(ctxt->flags, AT_EOF); |
| if (ctxt->scheduled > 0 && !*atEOF) { |
| CFReadStreamSignalEvent(stream, kCFStreamEventHasBytesAvailable, NULL); |
| } |
| #endif |
| return result; |
| } |
| |
| #ifdef REAL_FILE_SCHEDULING |
| __private_extern__ Boolean fdCanRead(int fd) { |
| struct timeval timeout = {0, 0}; |
| fd_set *readSetPtr; |
| fd_set readSet; |
| Boolean result; |
| // fd_set is not a mask in Win32, so checking for an fd that's too big is not relevant |
| if (fd < FD_SETSIZE) { |
| FD_ZERO(&readSet); |
| readSetPtr = &readSet; |
| } else { |
| int size = howmany(fd+1, NFDBITS) * sizeof(uint32_t); |
| uint32_t *fds_bits = (uint32_t *)malloc(size); |
| memset(fds_bits, 0, size); |
| readSetPtr = (fd_set *)fds_bits; |
| } |
| FD_SET(fd, readSetPtr); |
| result = (select(fd + 1, readSetPtr, NULL, NULL, &timeout) == 1) ? TRUE : FALSE; |
| if (readSetPtr != &readSet) { |
| free(readSetPtr); |
| } |
| return result; |
| } |
| #endif |
| |
| static Boolean fileCanRead(CFReadStreamRef stream, void *info) { |
| _CFFileStreamContext *ctxt = (_CFFileStreamContext *)info; |
| #ifdef REAL_FILE_SCHEDULING |
| return fdCanRead(ctxt->fd); |
| #else |
| return !__CFBitIsSet(ctxt->flags, AT_EOF); |
| #endif |
| } |
| |
| __private_extern__ CFIndex fdWrite(int fd, const UInt8 *buffer, CFIndex bufferLength, CFStreamError *errorCode) { |
| CFIndex bytesWritten = write(fd, buffer, bufferLength); |
| if (bytesWritten < 0) { |
| errorCode->error = errno; |
| errorCode->domain = kCFStreamErrorDomainPOSIX; |
| return -1; |
| } else { |
| errorCode->error = 0; |
| return bytesWritten; |
| } |
| } |
| |
| static CFIndex fileWrite(CFWriteStreamRef stream, const UInt8 *buffer, CFIndex bufferLength, CFStreamError *errorCode, void *info) { |
| _CFFileStreamContext *fileStream = ((_CFFileStreamContext *)info); |
| CFIndex result = fdWrite(fileStream->fd, buffer, bufferLength, errorCode); |
| #ifdef REAL_FILE_SCHEDULING |
| if (__CFBitIsSet(fileStream->flags, SCHEDULE_AFTER_WRITE)) { |
| __CFBitClear(fileStream->flags, SCHEDULE_AFTER_WRITE); |
| if (fileStream->rlInfo.sock) { |
| CFSocketEnableCallBacks(fileStream->rlInfo.sock, kCFSocketWriteCallBack); |
| } |
| } |
| #else |
| if (fileStream->scheduled > 0) { |
| CFWriteStreamSignalEvent(stream, kCFStreamEventCanAcceptBytes, NULL); |
| } |
| #endif |
| return result; |
| } |
| |
| #ifdef REAL_FILE_SCHEDULING |
| __private_extern__ Boolean fdCanWrite(int fd) { |
| struct timeval timeout = {0, 0}; |
| fd_set *writeSetPtr; |
| fd_set writeSet; |
| Boolean result; |
| if (fd < FD_SETSIZE) { |
| FD_ZERO(&writeSet); |
| writeSetPtr = &writeSet; |
| } else { |
| int size = howmany(fd+1, NFDBITS) * sizeof(uint32_t); |
| uint32_t *fds_bits = (uint32_t *)malloc(size); |
| memset(fds_bits, 0, size); |
| writeSetPtr = (fd_set *)fds_bits; |
| } |
| FD_SET(fd, writeSetPtr); |
| result = (select(fd + 1, NULL, writeSetPtr, NULL, &timeout) == 1) ? TRUE : FALSE; |
| if (writeSetPtr != &writeSet) { |
| free(writeSetPtr); |
| } |
| return result; |
| } |
| #endif |
| |
| static Boolean fileCanWrite(CFWriteStreamRef stream, void *info) { |
| #ifdef REAL_FILE_SCHEDULING |
| return fdCanWrite(((_CFFileStreamContext *)info)->fd); |
| #else |
| return TRUE; |
| #endif |
| } |
| |
| static void fileClose(struct _CFStream *stream, void *info) { |
| _CFFileStreamContext *ctxt = (_CFFileStreamContext *)info; |
| if (ctxt->fd >= 0) { |
| close(ctxt->fd); |
| ctxt->fd = -1; |
| #ifdef REAL_FILE_SCHEDULING |
| if (ctxt->rlInfo.sock) { |
| CFSocketInvalidate(ctxt->rlInfo.sock); |
| CFRelease(ctxt->rlInfo.sock); |
| ctxt->rlInfo.sock = NULL; |
| } |
| } else if (ctxt->rlInfo.rlArray) { |
| CFRelease(ctxt->rlInfo.rlArray); |
| ctxt->rlInfo.rlArray = NULL; |
| #endif |
| } |
| } |
| |
| #ifdef REAL_FILE_SCHEDULING |
| static void fileCallBack(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) { |
| struct _CFStream *stream = (struct _CFStream *)info; |
| Boolean isReadStream = (CFGetTypeID(stream) == CFReadStreamGetTypeID()); |
| _CFFileStreamContext *fileStream = (_CFFileStreamContext *)((isReadStream) ? CFReadStreamGetInfoPointer((CFReadStreamRef)stream) : CFWriteStreamGetInfoPointer((CFWriteStreamRef)stream)); |
| if (type == kCFSocketWriteCallBack) { |
| __CFBitSet(fileStream->flags, SCHEDULE_AFTER_WRITE); |
| CFWriteStreamSignalEvent((CFWriteStreamRef)stream, kCFStreamEventCanAcceptBytes, NULL); |
| } else { |
| // type == kCFSocketReadCallBack |
| __CFBitSet(fileStream->flags, SCHEDULE_AFTER_READ); |
| CFReadStreamSignalEvent((CFReadStreamRef)stream, kCFStreamEventHasBytesAvailable, NULL); |
| } |
| } |
| #endif |
| |
| static void fileSchedule(struct _CFStream *stream, CFRunLoopRef runLoop, CFStringRef runLoopMode, void *info) { |
| _CFFileStreamContext *fileStream = (_CFFileStreamContext *)info; |
| Boolean isReadStream = (CFGetTypeID(stream) == CFReadStreamGetTypeID()); |
| CFStreamStatus status = isReadStream ? CFReadStreamGetStatus((CFReadStreamRef)stream) : CFWriteStreamGetStatus((CFWriteStreamRef)stream); |
| if (fileStream->fd < 0 && status != kCFStreamStatusNotOpen) { |
| // Stream's already closed or error-ed out |
| return; |
| } |
| #ifdef REAL_FILE_SCHEDULING |
| if (status == kCFStreamStatusNotOpen) { |
| if (!fileStream->rlInfo.rlArray) { |
| fileStream->rlInfo.rlArray = CFArrayCreateMutable(CFGetAllocator(stream), 0, &kCFTypeArrayCallBacks); |
| } |
| CFArrayAppendValue(fileStream->rlInfo.rlArray, runLoop); |
| CFArrayAppendValue(fileStream->rlInfo.rlArray, runLoopMode); |
| } else { |
| CFRunLoopSourceRef rlSrc; |
| if (!fileStream->rlInfo.sock) { |
| constructCFSocket(fileStream, isReadStream, stream); |
| } |
| rlSrc = CFSocketCreateRunLoopSource(CFGetAllocator(stream), fileStream->rlInfo.sock, 0); |
| CFRunLoopAddSource(runLoop, rlSrc, runLoopMode); |
| CFRelease(rlSrc); |
| } |
| #else |
| fileStream->scheduled++; |
| if (fileStream->scheduled == 1 && fileStream->fd > 0 && status == kCFStreamStatusOpen) { |
| if (isReadStream) |
| CFReadStreamSignalEvent((CFReadStreamRef)stream, kCFStreamEventHasBytesAvailable, NULL); |
| else |
| CFWriteStreamSignalEvent((CFWriteStreamRef)stream, kCFStreamEventCanAcceptBytes, NULL); |
| } |
| #endif |
| } |
| |
| static void fileUnschedule(struct _CFStream *stream, CFRunLoopRef runLoop, CFStringRef runLoopMode, void *info) { |
| _CFFileStreamContext *fileStream = (_CFFileStreamContext *)info; |
| #ifdef REAL_FILE_SCHEDULING |
| Boolean isReadStream = (CFGetTypeID(stream) == CFReadStreamGetTypeID()); |
| CFStreamStatus status = isReadStream ? CFReadStreamGetStatus((CFReadStreamRef)stream) : CFWriteStreamGetStatus((CFWriteStreamRef)stream); |
| if (status == kCFStreamStatusNotOpen) { |
| // Not opened yet |
| if (fileStream->rlInfo.rlArray) { |
| CFMutableArrayRef runloops = fileStream->rlInfo.rlArray; |
| CFIndex i, c; |
| for (i = 0, c = CFArrayGetCount(runloops); i+1 < c; i += 2) { |
| if (CFEqual(CFArrayGetValueAtIndex(runloops, i), runLoop) && CFEqual(CFArrayGetValueAtIndex(runloops, i+1), runLoopMode)) { |
| CFArrayRemoveValueAtIndex(runloops, i); |
| CFArrayRemoveValueAtIndex(runloops, i); |
| break; |
| } |
| } |
| } |
| } else if (fileStream->rlInfo.sock) { |
| if (__CFBitIsSet(fileStream->flags, USE_RUNLOOP_ARRAY)) { |
| // we know that fileStream->rlInfo.rlArray is non-NULL because it is in a union with fileStream->rlInfo.sock |
| CFMutableArrayRef runloops = fileStream->rlInfo.rlArray; |
| CFIndex i, c; |
| for (i = 0, c = CFArrayGetCount(runloops); i+1 < c; i += 2) { |
| if (CFEqual(CFArrayGetValueAtIndex(runloops, i), runLoop) && CFEqual(CFArrayGetValueAtIndex(runloops, i+1), runLoopMode)) { |
| CFArrayRemoveValueAtIndex(runloops, i); |
| CFArrayRemoveValueAtIndex(runloops, i); |
| break; |
| } |
| } |
| } else { |
| CFRunLoopSourceRef sockSource = CFSocketCreateRunLoopSource(CFGetAllocator(stream), fileStream->rlInfo.sock, 0); |
| CFRunLoopRemoveSource(runLoop, sockSource, runLoopMode); |
| CFRelease(sockSource); |
| } |
| } |
| #else |
| if (fileStream->scheduled > 0) |
| fileStream->scheduled--; |
| #endif |
| } |
| |
| static CFTypeRef fileCopyProperty(struct _CFStream *stream, CFStringRef propertyName, void *info) { |
| |
| CFTypeRef result = NULL; |
| _CFFileStreamContext *fileStream = (_CFFileStreamContext *)info; |
| |
| if (CFEqual(propertyName, kCFStreamPropertyFileCurrentOffset)) { |
| |
| // NOTE that this does a lseek of 0 from the current location in |
| // order to populate the offset field which will then be used to |
| // create the resulting value. |
| if (!__CFBitIsSet(fileStream->flags, APPEND) && fileStream->fd != -1) { |
| fileStream->offset = lseek(fileStream->fd, 0, SEEK_CUR); |
| } |
| |
| if (fileStream->offset != -1) { |
| result = CFNumberCreate(CFGetAllocator((CFTypeRef)stream), kCFNumberSInt64Type, &(fileStream->offset)); |
| } |
| } |
| |
| return result; |
| } |
| |
| static Boolean fileSetProperty(struct _CFStream *stream, CFStringRef prop, CFTypeRef val, void *info) { |
| |
| Boolean result = FALSE; |
| _CFFileStreamContext *fileStream = (_CFFileStreamContext *)info; |
| |
| if (CFEqual(prop, kCFStreamPropertyAppendToFile) && CFGetTypeID(stream) == CFWriteStreamGetTypeID() && |
| CFWriteStreamGetStatus((CFWriteStreamRef)stream) == kCFStreamStatusNotOpen) |
| { |
| if (val == kCFBooleanTrue) { |
| __CFBitSet(fileStream->flags, APPEND); |
| fileStream->offset = -1; // Can't offset and append on the stream |
| } else { |
| __CFBitClear(fileStream->flags, APPEND); |
| } |
| result = TRUE; |
| } |
| |
| else if (CFEqual(prop, kCFStreamPropertyFileCurrentOffset)) { |
| |
| if (!__CFBitIsSet(fileStream->flags, APPEND)) |
| { |
| result = CFNumberGetValue((CFNumberRef)val, kCFNumberSInt64Type, &(fileStream->offset)); |
| } |
| |
| if ((fileStream->fd != -1) && (lseek(fileStream->fd, fileStream->offset, SEEK_SET) == -1)) { |
| result = FALSE; |
| } |
| } |
| |
| return result; |
| } |
| |
| static void *fileCreate(struct _CFStream *stream, void *info) { |
| _CFFileStreamContext *ctxt = (_CFFileStreamContext *)info; |
| _CFFileStreamContext *newCtxt = (_CFFileStreamContext *)CFAllocatorAllocate(CFGetAllocator(stream), sizeof(_CFFileStreamContext), 0); |
| if (!newCtxt) return NULL; |
| newCtxt->url = ctxt->url; |
| if (newCtxt->url) { |
| CFRetain(newCtxt->url); |
| } |
| newCtxt->fd = ctxt->fd; |
| #ifdef REAL_FILE_SCHEDULING |
| newCtxt->rlInfo.sock = NULL; |
| #else |
| newCtxt->scheduled = 0; |
| #endif |
| newCtxt->flags = 0; |
| newCtxt->offset = -1; |
| return newCtxt; |
| } |
| |
| static void fileFinalize(struct _CFStream *stream, void *info) { |
| _CFFileStreamContext *ctxt = (_CFFileStreamContext *)info; |
| if (ctxt->fd > 0) { |
| #ifdef REAL_FILE_SCHEDULING |
| if (ctxt->rlInfo.sock) { |
| CFSocketInvalidate(ctxt->rlInfo.sock); |
| CFRelease(ctxt->rlInfo.sock); |
| } |
| #endif |
| close(ctxt->fd); |
| #ifdef REAL_FILE_SCHEDULING |
| } else if (ctxt->rlInfo.rlArray) { |
| CFRelease(ctxt->rlInfo.rlArray); |
| #endif |
| } |
| if (ctxt->url) { |
| CFRelease(ctxt->url); |
| } |
| CFAllocatorDeallocate(CFGetAllocator(stream), ctxt); |
| } |
| |
| static CFStringRef fileCopyDescription(struct _CFStream *stream, void *info) { |
| // This needs work |
| _CFFileStreamContext *ctxt = (_CFFileStreamContext *)info; |
| if (ctxt->url) { |
| return CFCopyDescription(ctxt->url); |
| } else { |
| return CFStringCreateWithFormat(CFGetAllocator(stream), NULL, CFSTR("fd = %d"), ctxt->fd); |
| } |
| } |
| |
| /* CFData stream callbacks */ |
| typedef struct { |
| CFDataRef data; // Mutable if the stream was constructed writable |
| const UInt8 *loc; // Current location in the file |
| Boolean scheduled; |
| char _padding[3]; |
| } _CFReadDataStreamContext; |
| |
| #define BUF_SIZE 1024 |
| typedef struct _CFStreamByteBuffer { |
| UInt8 *bytes; |
| CFIndex capacity, length; |
| struct _CFStreamByteBuffer *next; |
| } _CFStreamByteBuffer; |
| |
| typedef struct { |
| _CFStreamByteBuffer *firstBuf, *currentBuf; |
| CFAllocatorRef bufferAllocator; |
| Boolean scheduled; |
| char _padding[3]; |
| } _CFWriteDataStreamContext; |
| |
| static Boolean readDataOpen(struct _CFStream *stream, CFStreamError *errorCode, Boolean *openComplete, void *info) { |
| _CFReadDataStreamContext *dataStream = (_CFReadDataStreamContext *)info; |
| if (dataStream->scheduled) { |
| if (CFDataGetLength(dataStream->data) != 0) { |
| CFReadStreamSignalEvent((CFReadStreamRef)stream, kCFStreamEventHasBytesAvailable, NULL); |
| } else { |
| CFReadStreamSignalEvent((CFReadStreamRef)stream, kCFStreamEventEndEncountered, NULL); |
| } |
| } |
| errorCode->error = 0; |
| *openComplete = TRUE; |
| return TRUE; |
| } |
| |
| static void readDataSchedule(struct _CFStream *stream, CFRunLoopRef rl, CFStringRef rlMode, void *info) { |
| _CFReadDataStreamContext *dataStream = (_CFReadDataStreamContext *)info; |
| if (dataStream->scheduled == FALSE) { |
| dataStream->scheduled = TRUE; |
| if (CFReadStreamGetStatus((CFReadStreamRef)stream) != kCFStreamStatusOpen) |
| return; |
| if (CFDataGetBytePtr(dataStream->data) + CFDataGetLength(dataStream->data) > dataStream->loc) { |
| CFReadStreamSignalEvent((CFReadStreamRef)stream, kCFStreamEventHasBytesAvailable, NULL); |
| } else { |
| CFReadStreamSignalEvent((CFReadStreamRef)stream, kCFStreamEventEndEncountered, NULL); |
| } |
| } |
| } |
| |
| static CFIndex dataRead(CFReadStreamRef stream, UInt8 *buffer, CFIndex bufferLength, CFStreamError *error, Boolean *atEOF, void *info) { |
| _CFReadDataStreamContext *dataCtxt = (_CFReadDataStreamContext *)info; |
| const UInt8 *bytePtr = CFDataGetBytePtr(dataCtxt->data); |
| CFIndex length = CFDataGetLength(dataCtxt->data); |
| CFIndex bytesToCopy = bytePtr + length - dataCtxt->loc; |
| if (bytesToCopy > bufferLength) { |
| bytesToCopy = bufferLength; |
| } |
| if (bytesToCopy < 0) { |
| bytesToCopy = 0; |
| } |
| if (bytesToCopy != 0) { |
| memmove(buffer, dataCtxt->loc, bytesToCopy); |
| dataCtxt->loc += bytesToCopy; |
| } |
| error->error = 0; |
| *atEOF = (dataCtxt->loc < bytePtr + length) ? FALSE : TRUE; |
| if (dataCtxt->scheduled && !*atEOF) { |
| CFReadStreamSignalEvent(stream, kCFStreamEventHasBytesAvailable, NULL); |
| } |
| return bytesToCopy; |
| } |
| |
| static const UInt8 *dataGetBuffer(CFReadStreamRef stream, CFIndex maxBytesToRead, CFIndex *numBytesRead, CFStreamError *error, Boolean *atEOF, void *info) { |
| _CFReadDataStreamContext *dataCtxt = (_CFReadDataStreamContext *)info; |
| const UInt8 *bytes = CFDataGetBytePtr(dataCtxt->data); |
| if (dataCtxt->loc - bytes > maxBytesToRead) { |
| *numBytesRead = maxBytesToRead; |
| *atEOF = FALSE; |
| } else { |
| *numBytesRead = dataCtxt->loc - bytes; |
| *atEOF = TRUE; |
| } |
| error->error = 0; |
| bytes = dataCtxt->loc; |
| dataCtxt->loc += *numBytesRead; |
| if (dataCtxt->scheduled && !*atEOF) { |
| CFReadStreamSignalEvent(stream, kCFStreamEventHasBytesAvailable, NULL); |
| } |
| return bytes; |
| } |
| |
| static Boolean dataCanRead(CFReadStreamRef stream, void *info) { |
| _CFReadDataStreamContext *dataCtxt = (_CFReadDataStreamContext *)info; |
| return (CFDataGetBytePtr(dataCtxt->data) + CFDataGetLength(dataCtxt->data) > dataCtxt->loc) ? TRUE : FALSE; |
| } |
| |
| static Boolean writeDataOpen(struct _CFStream *stream, CFStreamError *errorCode, Boolean *openComplete, void *info) { |
| _CFWriteDataStreamContext *dataStream = (_CFWriteDataStreamContext *)info; |
| if (dataStream->scheduled) { |
| if (dataStream->bufferAllocator != kCFAllocatorNull || dataStream->currentBuf->capacity > dataStream->currentBuf->length) { |
| CFWriteStreamSignalEvent((CFWriteStreamRef)stream, kCFStreamEventCanAcceptBytes, NULL); |
| } else { |
| CFWriteStreamSignalEvent((CFWriteStreamRef)stream, kCFStreamEventEndEncountered, NULL); |
| } |
| } |
| errorCode->error = 0; |
| *openComplete = TRUE; |
| return TRUE; |
| } |
| |
| static void writeDataSchedule(struct _CFStream *stream, CFRunLoopRef rl, CFStringRef rlMode, void *info) { |
| _CFWriteDataStreamContext *dataStream = (_CFWriteDataStreamContext *)info; |
| if (dataStream->scheduled == FALSE) { |
| dataStream->scheduled = TRUE; |
| if (CFWriteStreamGetStatus((CFWriteStreamRef)stream) != kCFStreamStatusOpen) |
| return; |
| if (dataStream->bufferAllocator != kCFAllocatorNull || dataStream->currentBuf->capacity > dataStream->currentBuf->length) { |
| CFWriteStreamSignalEvent((CFWriteStreamRef)stream, kCFStreamEventCanAcceptBytes, NULL); |
| } else { |
| CFWriteStreamSignalEvent((CFWriteStreamRef)stream, kCFStreamEventEndEncountered, NULL); |
| } |
| } |
| } |
| |
| static CFIndex dataWrite(CFWriteStreamRef stream, const UInt8 *buffer, CFIndex bufferLength, CFStreamError *errorCode, void *info) { |
| _CFWriteDataStreamContext *dataStream = (_CFWriteDataStreamContext *)info; |
| CFIndex result; |
| CFIndex freeSpace = dataStream->currentBuf->capacity - dataStream->currentBuf->length; |
| if (dataStream->bufferAllocator == kCFAllocatorNull && bufferLength > freeSpace) { |
| errorCode->error = ENOMEM; |
| errorCode->domain = kCFStreamErrorDomainPOSIX; |
| return -1; |
| } else { |
| result = bufferLength; |
| while (bufferLength > 0) { |
| CFIndex amountToCopy = (bufferLength > freeSpace) ? freeSpace : bufferLength; |
| if (freeSpace > 0) { |
| memmove(dataStream->currentBuf->bytes + dataStream->currentBuf->length, buffer, amountToCopy); |
| buffer += amountToCopy; |
| bufferLength -= amountToCopy; |
| dataStream->currentBuf->length += amountToCopy; |
| } |
| if (bufferLength > 0) { |
| CFIndex bufSize = BUF_SIZE > bufferLength ? BUF_SIZE : bufferLength; |
| _CFStreamByteBuffer *newBuf = (_CFStreamByteBuffer *)CFAllocatorAllocate(dataStream->bufferAllocator, sizeof(_CFStreamByteBuffer) + bufSize, 0); |
| newBuf->bytes = (UInt8 *)(newBuf + 1); |
| newBuf->capacity = bufSize; |
| newBuf->length = 0; |
| newBuf->next = NULL; |
| dataStream->currentBuf->next = newBuf; |
| dataStream->currentBuf = newBuf; |
| freeSpace = bufSize; |
| } |
| } |
| errorCode->error = 0; |
| } |
| if (dataStream->scheduled && (dataStream->bufferAllocator != kCFAllocatorNull || dataStream->currentBuf->capacity > dataStream->currentBuf->length)) { |
| CFWriteStreamSignalEvent(stream, kCFStreamEventCanAcceptBytes, NULL); |
| } |
| return result; |
| } |
| |
| static Boolean dataCanWrite(CFWriteStreamRef stream, void *info) { |
| _CFWriteDataStreamContext *dataStream = (_CFWriteDataStreamContext *)info; |
| if (dataStream->bufferAllocator != kCFAllocatorNull) return TRUE; |
| if (dataStream->currentBuf->capacity > dataStream->currentBuf->length) return TRUE; |
| return FALSE; |
| } |
| |
| static CFPropertyListRef dataCopyProperty(struct _CFStream *stream, CFStringRef propertyName, void *info) { |
| _CFWriteDataStreamContext *dataStream = (_CFWriteDataStreamContext *)info; |
| CFIndex size = 0; |
| _CFStreamByteBuffer *buf; |
| CFAllocatorRef alloc; |
| UInt8 *bytes, *currByte; |
| if (!CFEqual(propertyName, kCFStreamPropertyDataWritten)) return NULL; |
| if (dataStream->bufferAllocator == kCFAllocatorNull) return NULL; |
| alloc = dataStream->bufferAllocator; |
| for (buf = dataStream->firstBuf; buf != NULL; buf = buf->next) { |
| size += buf->length; |
| } |
| if (size == 0) return NULL; |
| bytes = (UInt8 *)CFAllocatorAllocate(alloc, size, 0); |
| currByte = bytes; |
| for (buf = dataStream->firstBuf; buf != NULL; buf = buf->next) { |
| memmove(currByte, buf->bytes, buf->length); |
| currByte += buf->length; |
| } |
| return CFDataCreateWithBytesNoCopy(alloc, bytes, size, alloc); |
| } |
| |
| static void *readDataCreate(struct _CFStream *stream, void *info) { |
| _CFReadDataStreamContext *ctxt = (_CFReadDataStreamContext *)info; |
| _CFReadDataStreamContext *newCtxt = (_CFReadDataStreamContext *)CFAllocatorAllocate(CFGetAllocator(stream), sizeof(_CFReadDataStreamContext), 0); |
| if (!newCtxt) return NULL; |
| newCtxt->data = (CFDataRef)CFRetain(ctxt->data); |
| newCtxt->loc = CFDataGetBytePtr(newCtxt->data); |
| newCtxt->scheduled = FALSE; |
| return (void *)newCtxt; |
| } |
| |
| static void readDataFinalize(struct _CFStream *stream, void *info) { |
| _CFReadDataStreamContext *ctxt = (_CFReadDataStreamContext *)info; |
| CFRelease(ctxt->data); |
| CFAllocatorDeallocate(CFGetAllocator(stream), ctxt); |
| } |
| |
| static CFStringRef readDataCopyDescription(struct _CFStream *stream, void *info) { |
| return CFCopyDescription(((_CFReadDataStreamContext *)info)->data); |
| } |
| |
| static void *writeDataCreate(struct _CFStream *stream, void *info) { |
| _CFWriteDataStreamContext *ctxt = (_CFWriteDataStreamContext *)info; |
| _CFWriteDataStreamContext *newCtxt; |
| if (ctxt->bufferAllocator != kCFAllocatorNull) { |
| if (ctxt->bufferAllocator == NULL) ctxt->bufferAllocator = CFAllocatorGetDefault(); |
| CFRetain(ctxt->bufferAllocator); |
| newCtxt = (_CFWriteDataStreamContext *)CFAllocatorAllocate(CFGetAllocator(stream), sizeof(_CFWriteDataStreamContext) + sizeof(_CFStreamByteBuffer) + BUF_SIZE, 0); |
| newCtxt->firstBuf = (_CFStreamByteBuffer *)(newCtxt + 1); |
| newCtxt->firstBuf->bytes = (UInt8 *)(newCtxt->firstBuf + 1); |
| newCtxt->firstBuf->capacity = BUF_SIZE; |
| newCtxt->firstBuf->length = 0; |
| newCtxt->firstBuf->next = NULL; |
| newCtxt->currentBuf = newCtxt->firstBuf; |
| newCtxt->bufferAllocator = ctxt->bufferAllocator; |
| newCtxt->scheduled = FALSE; |
| } else { |
| newCtxt = (_CFWriteDataStreamContext *)CFAllocatorAllocate(CFGetAllocator(stream), sizeof(_CFWriteDataStreamContext) + sizeof(_CFStreamByteBuffer), 0); |
| newCtxt->firstBuf = (_CFStreamByteBuffer *)(newCtxt+1); |
| newCtxt->firstBuf->bytes = ctxt->firstBuf->bytes; |
| newCtxt->firstBuf->capacity = ctxt->firstBuf->capacity; |
| newCtxt->firstBuf->length = 0; |
| newCtxt->firstBuf->next = NULL; |
| newCtxt->currentBuf = newCtxt->firstBuf; |
| newCtxt->bufferAllocator = kCFAllocatorNull; |
| newCtxt->scheduled = FALSE; |
| } |
| return (void *)newCtxt; |
| } |
| |
| static void writeDataFinalize(struct _CFStream *stream, void *info) { |
| _CFWriteDataStreamContext *ctxt = (_CFWriteDataStreamContext *)info; |
| if (ctxt->bufferAllocator != kCFAllocatorNull) { |
| _CFStreamByteBuffer *buf = ctxt->firstBuf->next, *next; |
| while (buf != NULL) { |
| next = buf->next; |
| CFAllocatorDeallocate(ctxt->bufferAllocator, buf); |
| buf = next; |
| } |
| CFRelease(ctxt->bufferAllocator); |
| } |
| CFAllocatorDeallocate(CFGetAllocator(stream), ctxt); |
| } |
| |
| static CFStringRef writeDataCopyDescription(struct _CFStream *stream, void *info) { |
| return CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("<CFWriteDataContext %p>"), info); |
| } |
| |
| static const struct _CFStreamCallBacksV1 fileCallBacks = {1, fileCreate, fileFinalize, fileCopyDescription, fileOpen, NULL, fileRead, NULL, fileCanRead, fileWrite, fileCanWrite, fileClose, fileCopyProperty, fileSetProperty, NULL, fileSchedule, fileUnschedule}; |
| |
| static struct _CFStream *_CFStreamCreateWithFile(CFAllocatorRef alloc, CFURLRef fileURL, Boolean forReading) { |
| _CFFileStreamContext fileContext; |
| CFStringRef scheme = fileURL ? CFURLCopyScheme(fileURL) : NULL; |
| if (!scheme || !CFEqual(scheme, CFSTR("file"))) { |
| if (scheme) CFRelease(scheme); |
| return NULL; |
| } |
| CFRelease(scheme); |
| fileContext.url = fileURL; |
| fileContext.fd = -1; |
| return _CFStreamCreateWithConstantCallbacks(alloc, &fileContext, (struct _CFStreamCallBacks *)(&fileCallBacks), forReading); |
| } |
| |
| CF_EXPORT CFReadStreamRef CFReadStreamCreateWithFile(CFAllocatorRef alloc, CFURLRef fileURL) { |
| return (CFReadStreamRef)_CFStreamCreateWithFile(alloc, fileURL, TRUE); |
| } |
| |
| CF_EXPORT CFWriteStreamRef CFWriteStreamCreateWithFile(CFAllocatorRef alloc, CFURLRef fileURL) { |
| return (CFWriteStreamRef)_CFStreamCreateWithFile(alloc, fileURL, FALSE); |
| } |
| |
| CFReadStreamRef _CFReadStreamCreateFromFileDescriptor(CFAllocatorRef alloc, int fd) { |
| _CFFileStreamContext fileContext; |
| fileContext.url = NULL; |
| fileContext.fd = fd; |
| return (CFReadStreamRef)_CFStreamCreateWithConstantCallbacks(alloc, &fileContext, (struct _CFStreamCallBacks *)(&fileCallBacks), TRUE); |
| } |
| |
| CFWriteStreamRef _CFWriteStreamCreateFromFileDescriptor(CFAllocatorRef alloc, int fd) { |
| _CFFileStreamContext fileContext; |
| fileContext.url = NULL; |
| fileContext.fd = fd; |
| return (CFWriteStreamRef)_CFStreamCreateWithConstantCallbacks(alloc, &fileContext, (struct _CFStreamCallBacks *)(&fileCallBacks), FALSE); |
| } |
| |
| |
| |
| static const struct _CFStreamCallBacksV1 readDataCallBacks = {1, readDataCreate, readDataFinalize, readDataCopyDescription, readDataOpen, NULL, dataRead, dataGetBuffer, dataCanRead, NULL, NULL, NULL, NULL, NULL, NULL, readDataSchedule, NULL}; |
| static const struct _CFStreamCallBacksV1 writeDataCallBacks = {1, writeDataCreate, writeDataFinalize, writeDataCopyDescription, writeDataOpen, NULL, NULL, NULL, NULL, dataWrite, dataCanWrite, NULL, dataCopyProperty, NULL, NULL, writeDataSchedule, NULL}; |
| |
| CF_EXPORT CFReadStreamRef CFReadStreamCreateWithBytesNoCopy(CFAllocatorRef alloc, const UInt8 *bytes, CFIndex length, CFAllocatorRef bytesDeallocator) { |
| _CFReadDataStreamContext ctxt; |
| CFReadStreamRef result; |
| ctxt.data = CFDataCreateWithBytesNoCopy(alloc, bytes, length, bytesDeallocator); |
| result = (CFReadStreamRef)_CFStreamCreateWithConstantCallbacks(alloc, &ctxt, (struct _CFStreamCallBacks *)(&readDataCallBacks), TRUE); |
| CFRelease(ctxt.data); |
| return result; |
| } |
| |
| /* This needs to be exported to make it callable from Foundation. */ |
| CF_EXPORT CFReadStreamRef CFReadStreamCreateWithData(CFAllocatorRef alloc, CFDataRef data) { |
| _CFReadDataStreamContext ctxt; |
| CFReadStreamRef result = NULL; |
| |
| ctxt.data = (CFDataRef)CFRetain(data); |
| result = (CFReadStreamRef)_CFStreamCreateWithConstantCallbacks(alloc, &ctxt, (struct _CFStreamCallBacks *)(&readDataCallBacks), TRUE); |
| CFRelease(data); |
| return result; |
| } |
| |
| CFWriteStreamRef CFWriteStreamCreateWithBuffer(CFAllocatorRef alloc, UInt8 *buffer, CFIndex bufferCapacity) { |
| _CFStreamByteBuffer buf; |
| _CFWriteDataStreamContext ctxt; |
| buf.bytes = buffer; |
| buf.capacity = bufferCapacity; |
| buf.length = 0; |
| buf.next = NULL; |
| ctxt.firstBuf = &buf; |
| ctxt.currentBuf = ctxt.firstBuf; |
| ctxt.bufferAllocator = kCFAllocatorNull; |
| return (CFWriteStreamRef)_CFStreamCreateWithConstantCallbacks(alloc, &ctxt, (struct _CFStreamCallBacks *)(&writeDataCallBacks), FALSE); |
| } |
| |
| CF_EXPORT CFWriteStreamRef CFWriteStreamCreateWithAllocatedBuffers(CFAllocatorRef alloc, CFAllocatorRef bufferAllocator) { |
| _CFWriteDataStreamContext ctxt; |
| ctxt.firstBuf = NULL; |
| ctxt.currentBuf = NULL; |
| ctxt.bufferAllocator = bufferAllocator; |
| return (CFWriteStreamRef)_CFStreamCreateWithConstantCallbacks(alloc, &ctxt, (struct _CFStreamCallBacks *)(&writeDataCallBacks), FALSE); |
| } |
| |
| #undef BUF_SIZE |
| |