1/*
2 * Copyright (c) 2014 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24/*	CFConcreteStreams.c
25	Copyright (c) 2000-2013, Apple Inc. All rights reserved.
26	Responsibility: John Iarocci
27*/
28
29#include "CFStreamInternal.h"
30#include "CFInternal.h"
31#include <CoreFoundation/CFPriv.h>
32#include <CoreFoundation/CFNumber.h>
33#include <sys/types.h>
34#include <stdlib.h>
35#include <fcntl.h>
36#include <string.h>
37#include <stdio.h>
38#include <sys/stat.h>
39#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI || DEPLOYMENT_TARGET_LINUX
40#include <sys/time.h>
41#include <unistd.h>
42#endif
43
44
45#define SCHEDULE_AFTER_WRITE  (0)
46#define SCHEDULE_AFTER_READ   (1)
47#define APPEND                (3)
48#define AT_EOF                (4)
49#define USE_RUNLOOP_ARRAY     (5)
50
51
52/* File callbacks */
53typedef struct {
54    CFURLRef url;
55    int fd;
56#ifdef REAL_FILE_SCHEDULING
57    union {
58        CFFileDescriptorRef cffd;	// ref created once we open and have an fd
59        CFMutableArrayRef rlArray;	// scheduling information prior to open
60    } rlInfo; // If fd > 0, cffd exists.  Otherwise, rlArray.
61#else
62    uint16_t scheduled;	// ref count of how many times we've been scheduled
63#endif
64    CFOptionFlags flags;
65    off_t offset;
66} _CFFileStreamContext;
67
68
69CONST_STRING_DECL(kCFStreamPropertyFileCurrentOffset, "kCFStreamPropertyFileCurrentOffset");
70#if DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
71CONST_STRING_DECL(_kCFStreamPropertyFileNativeHandle, "_kCFStreamPropertyFileNativeHandle");
72#endif
73
74#ifdef REAL_FILE_SCHEDULING
75extern void _CFFileDescriptorInduceFakeReadCallBack(CFFileDescriptorRef);
76static void fileCallBack(CFFileDescriptorRef f, CFOptionFlags callBackTypes, void *info);
77
78static void constructCFFD(_CFFileStreamContext *fileStream, Boolean forRead, struct _CFStream *stream) {
79    CFFileDescriptorContext context = {0, stream, NULL, NULL, (void *)CFCopyDescription};
80    CFFileDescriptorRef cffd = CFFileDescriptorCreate(CFGetAllocator(stream), fileStream->fd, false, fileCallBack, &context);
81    CFFileDescriptorEnableCallBacks(cffd, forRead ? kCFFileDescriptorReadCallBack : kCFFileDescriptorWriteCallBack);
82    if (fileStream->rlInfo.rlArray) {
83        CFIndex i, c = CFArrayGetCount(fileStream->rlInfo.rlArray);
84        CFRunLoopSourceRef src = CFFileDescriptorCreateRunLoopSource(CFGetAllocator(stream), cffd, 0);
85        for (i = 0; i+1 < c; i += 2) {
86            CFRunLoopRef rl = (CFRunLoopRef)CFArrayGetValueAtIndex(fileStream->rlInfo.rlArray, i);
87            CFStringRef mode = CFArrayGetValueAtIndex(fileStream->rlInfo.rlArray, i+1);
88            CFRunLoopAddSource(rl, src, mode);
89        }
90        CFRelease(fileStream->rlInfo.rlArray);
91        CFRelease(src);
92    }
93    fileStream->rlInfo.cffd = cffd;
94}
95#endif
96
97static Boolean constructFD(_CFFileStreamContext *fileStream, CFStreamError *error, Boolean forRead, struct _CFStream *stream) {
98    int flags = forRead ? O_RDONLY : (O_CREAT | O_TRUNC | O_WRONLY);
99#if DEPLOYMENT_TARGET_WINDOWS
100    wchar_t path[CFMaxPathSize];
101    flags |= (_O_BINARY|_O_NOINHERIT);
102    if (_CFURLGetWideFileSystemRepresentation(fileStream->url, TRUE, path, CFMaxPathSize) == FALSE)
103#elif DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
104    char path[CFMaxPathSize];
105    if (CFURLGetFileSystemRepresentation(fileStream->url, TRUE, (UInt8 *)path, CFMaxPathSize) == FALSE)
106#endif
107     {
108        error->error = ENOENT;
109        error->domain = kCFStreamErrorDomainPOSIX;
110        return FALSE;
111    }
112    if (__CFBitIsSet(fileStream->flags, APPEND)) {
113        flags |= O_APPEND;
114        flags &= ~O_TRUNC;
115    }
116
117    do {
118#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
119        fileStream->fd = open((const char *)path, flags, 0666);
120#elif DEPLOYMENT_TARGET_WINDOWS
121	fileStream->fd = _wopen(path, flags, 0666);
122#endif
123        if (fileStream->fd < 0)
124            break;
125
126        if ((fileStream->offset != -1) && (lseek(fileStream->fd, fileStream->offset, SEEK_SET) == -1))
127            break;
128
129#ifdef REAL_FILE_SCHEDULING
130        if (fileStream->rlInfo.rlArray != NULL) {
131            constructCFFD(fileStream, forRead, stream);
132        }
133#endif
134
135        return TRUE;
136    } while (1);
137
138    __CFBitSet(fileStream->flags, USE_RUNLOOP_ARRAY);
139    error->error = errno;
140    error->domain = kCFStreamErrorDomainPOSIX;
141
142    return FALSE;
143}
144
145static Boolean fileOpen(struct _CFStream *stream, CFStreamError *errorCode, Boolean *openComplete, void *info) {
146    _CFFileStreamContext *ctxt = (_CFFileStreamContext *)info;
147    Boolean forRead = (CFGetTypeID(stream) == CFReadStreamGetTypeID());
148    *openComplete = TRUE;
149    if (ctxt->url) {
150        if (constructFD(ctxt, errorCode, forRead, stream)) {
151#ifndef REAL_FILE_SCHEDULING
152            if (ctxt->scheduled > 0) {
153                if (forRead)
154                    CFReadStreamSignalEvent((CFReadStreamRef)stream, kCFStreamEventHasBytesAvailable, NULL);
155                else
156                    CFWriteStreamSignalEvent((CFWriteStreamRef)stream, kCFStreamEventCanAcceptBytes, NULL);
157            }
158#endif
159            return TRUE;
160        } else {
161            return FALSE;
162        }
163#ifdef REAL_FILE_SCHEDULING
164    } else if (ctxt->rlInfo.rlArray != NULL) {
165        constructCFFD(ctxt, forRead, stream);
166#endif
167    }
168    return TRUE;
169}
170
171CF_PRIVATE CFIndex fdRead(int fd, UInt8 *buffer, CFIndex bufferLength, CFStreamError *errorCode, Boolean *atEOF) {
172    CFIndex bytesRead = read(fd, buffer, bufferLength);
173    if (bytesRead < 0) {
174        errorCode->error = errno;
175        errorCode->domain = kCFStreamErrorDomainPOSIX;
176        return -1;
177    } else {
178        *atEOF = (bytesRead == 0) ? TRUE : FALSE;
179        errorCode->error = 0;
180        return bytesRead;
181    }
182}
183
184static CFIndex fileRead(CFReadStreamRef stream, UInt8 *buffer, CFIndex bufferLength, CFStreamError *errorCode, Boolean *atEOF, void *info) {
185    _CFFileStreamContext *ctxt = (_CFFileStreamContext *)info;
186    CFIndex result;
187    result = fdRead(ctxt->fd, buffer, bufferLength, errorCode, atEOF);
188#ifdef REAL_FILE_SCHEDULING
189    if (__CFBitIsSet(ctxt->flags, SCHEDULE_AFTER_READ)) {
190        __CFBitClear(ctxt->flags, SCHEDULE_AFTER_READ);
191        if (!*atEOF && ctxt->rlInfo.cffd) {
192            struct stat statbuf;
193            int ret = fstat(ctxt->fd, &statbuf);
194            if (0 <= ret && (S_IFREG == (statbuf.st_mode & S_IFMT))) {
195                off_t offset = lseek(ctxt->fd, 0, SEEK_CUR);
196                if (statbuf.st_size == offset) {
197                    _CFFileDescriptorInduceFakeReadCallBack(ctxt->rlInfo.cffd);
198                }
199            }
200        }
201        if (ctxt->rlInfo.cffd) {
202            CFFileDescriptorEnableCallBacks(ctxt->rlInfo.cffd, kCFFileDescriptorReadCallBack);
203        }
204    }
205#else
206    if (*atEOF)
207        __CFBitSet(ctxt->flags, AT_EOF);
208    if (ctxt->scheduled > 0 && !*atEOF) {
209        CFReadStreamSignalEvent(stream, kCFStreamEventHasBytesAvailable, NULL);
210    }
211#endif
212    return result;
213}
214
215#ifdef REAL_FILE_SCHEDULING
216CF_PRIVATE Boolean fdCanRead(int fd) {
217    struct timeval timeout = {0, 0};
218    fd_set *readSetPtr;
219    fd_set readSet;
220    Boolean result;
221// fd_set is not a mask in Win32, so checking for an fd that's too big is not relevant
222    if (fd < FD_SETSIZE) {
223        FD_ZERO(&readSet);
224        readSetPtr = &readSet;
225    } else {
226        int size = howmany(fd+1, NFDBITS) * sizeof(uint32_t);
227        uint32_t *fds_bits = (uint32_t *)malloc(size);
228        memset(fds_bits, 0, size);
229        readSetPtr = (fd_set *)fds_bits;
230    }
231    FD_SET(fd, readSetPtr);
232    result = (select(fd + 1, readSetPtr, NULL, NULL, &timeout) == 1) ? TRUE : FALSE;
233    if (readSetPtr != &readSet) {
234        free(readSetPtr);
235    }
236    return result;
237}
238#endif
239
240static Boolean fileCanRead(CFReadStreamRef stream, void *info) {
241    _CFFileStreamContext *ctxt = (_CFFileStreamContext *)info;
242#ifdef REAL_FILE_SCHEDULING
243    return fdCanRead(ctxt->fd);
244#else
245    return !__CFBitIsSet(ctxt->flags, AT_EOF);
246#endif
247}
248
249CF_PRIVATE CFIndex fdWrite(int fd, const UInt8 *buffer, CFIndex bufferLength, CFStreamError *errorCode) {
250    CFIndex bytesWritten = write(fd, buffer, bufferLength);
251    if (bytesWritten < 0) {
252        errorCode->error = errno;
253        errorCode->domain = kCFStreamErrorDomainPOSIX;
254        return -1;
255    } else {
256        errorCode->error = 0;
257        return bytesWritten;
258    }
259}
260
261static CFIndex fileWrite(CFWriteStreamRef stream, const UInt8 *buffer, CFIndex bufferLength, CFStreamError *errorCode, void *info) {
262    _CFFileStreamContext *fileStream = ((_CFFileStreamContext *)info);
263    CFIndex result = fdWrite(fileStream->fd, buffer, bufferLength, errorCode);
264#ifdef REAL_FILE_SCHEDULING
265    if (__CFBitIsSet(fileStream->flags, SCHEDULE_AFTER_WRITE)) {
266        __CFBitClear(fileStream->flags, SCHEDULE_AFTER_WRITE);
267        if (fileStream->rlInfo.cffd) {
268            CFFileDescriptorEnableCallBacks(fileStream->rlInfo.cffd, kCFFileDescriptorWriteCallBack);
269        }
270    }
271#else
272    if (fileStream->scheduled > 0) {
273        CFWriteStreamSignalEvent(stream, kCFStreamEventCanAcceptBytes, NULL);
274    }
275#endif
276    return result;
277}
278
279#ifdef REAL_FILE_SCHEDULING
280CF_PRIVATE Boolean fdCanWrite(int fd) {
281    struct timeval timeout = {0, 0};
282    fd_set *writeSetPtr;
283    fd_set writeSet;
284    Boolean result;
285    if (fd < FD_SETSIZE) {
286        FD_ZERO(&writeSet);
287        writeSetPtr = &writeSet;
288    } else {
289        int size = howmany(fd+1, NFDBITS) * sizeof(uint32_t);
290        uint32_t *fds_bits = (uint32_t *)malloc(size);
291        memset(fds_bits, 0, size);
292        writeSetPtr = (fd_set *)fds_bits;
293    }
294    FD_SET(fd, writeSetPtr);
295    result = (select(fd + 1, NULL, writeSetPtr, NULL, &timeout) == 1) ? TRUE : FALSE;
296    if (writeSetPtr != &writeSet) {
297        free(writeSetPtr);
298    }
299    return result;
300}
301#endif
302
303static Boolean fileCanWrite(CFWriteStreamRef stream, void *info) {
304#ifdef REAL_FILE_SCHEDULING
305    return fdCanWrite(((_CFFileStreamContext *)info)->fd);
306#else
307    return TRUE;
308#endif
309}
310
311static void fileClose(struct _CFStream *stream, void *info) {
312    _CFFileStreamContext *ctxt = (_CFFileStreamContext *)info;
313    if (ctxt->fd >= 0) {
314        close(ctxt->fd);
315        ctxt->fd = -1;
316#ifdef REAL_FILE_SCHEDULING
317        if (ctxt->rlInfo.cffd) {
318            CFFileDescriptorInvalidate(ctxt->rlInfo.cffd);
319            CFRelease(ctxt->rlInfo.cffd);
320            ctxt->rlInfo.cffd = NULL;
321        }
322    } else if (ctxt->rlInfo.rlArray) {
323        CFRelease(ctxt->rlInfo.rlArray);
324        ctxt->rlInfo.rlArray = NULL;
325#endif
326    }
327}
328
329#ifdef REAL_FILE_SCHEDULING
330static void fileCallBack(CFFileDescriptorRef f, CFOptionFlags type, void *info) {
331    struct _CFStream *stream = (struct _CFStream *)info;
332    Boolean isReadStream = (CFGetTypeID(stream) == CFReadStreamGetTypeID());
333    _CFFileStreamContext *fileStream = isReadStream ? CFReadStreamGetInfoPointer((CFReadStreamRef)stream) : CFWriteStreamGetInfoPointer((CFWriteStreamRef)stream);
334    if (type == kCFFileDescriptorWriteCallBack) {
335        __CFBitSet(fileStream->flags, SCHEDULE_AFTER_WRITE);
336        CFWriteStreamSignalEvent((CFWriteStreamRef)stream, kCFStreamEventCanAcceptBytes, NULL);
337    } else {
338        __CFBitSet(fileStream->flags, SCHEDULE_AFTER_READ);
339        CFReadStreamSignalEvent((CFReadStreamRef)stream, kCFStreamEventHasBytesAvailable, NULL);
340    }
341}
342#endif
343
344static void fileSchedule(struct _CFStream *stream, CFRunLoopRef runLoop, CFStringRef runLoopMode, void *info) {
345    _CFFileStreamContext *fileStream = (_CFFileStreamContext *)info;
346    Boolean isReadStream = (CFGetTypeID(stream) == CFReadStreamGetTypeID());
347    CFStreamStatus status = isReadStream ? CFReadStreamGetStatus((CFReadStreamRef)stream) : CFWriteStreamGetStatus((CFWriteStreamRef)stream);
348    if (fileStream->fd < 0 && status != kCFStreamStatusNotOpen) {
349        // Stream's already closed or error-ed out
350        return;
351    }
352#ifdef REAL_FILE_SCHEDULING
353    if (status == kCFStreamStatusNotOpen) {
354        if (!fileStream->rlInfo.rlArray) {
355            fileStream->rlInfo.rlArray = CFArrayCreateMutable(CFGetAllocator(stream), 0, &kCFTypeArrayCallBacks);
356        }
357        CFArrayAppendValue(fileStream->rlInfo.rlArray, runLoop);
358        CFArrayAppendValue(fileStream->rlInfo.rlArray, runLoopMode);
359    } else {
360        CFRunLoopSourceRef rlSrc;
361        if (!fileStream->rlInfo.cffd) {
362            constructCFFD(fileStream, isReadStream, stream);
363        }
364        rlSrc = CFFileDescriptorCreateRunLoopSource(CFGetAllocator(stream), fileStream->rlInfo.cffd, 0);
365        CFRunLoopAddSource(runLoop, rlSrc, runLoopMode);
366        CFRelease(rlSrc);
367    }
368#else
369    fileStream->scheduled++;
370    if (fileStream->scheduled == 1 && fileStream->fd > 0 && status == kCFStreamStatusOpen) {
371        if (isReadStream)
372            CFReadStreamSignalEvent((CFReadStreamRef)stream, kCFStreamEventHasBytesAvailable, NULL);
373        else
374            CFWriteStreamSignalEvent((CFWriteStreamRef)stream, kCFStreamEventCanAcceptBytes, NULL);
375    }
376#endif
377}
378
379static void fileUnschedule(struct _CFStream *stream, CFRunLoopRef runLoop, CFStringRef runLoopMode, void *info) {
380    _CFFileStreamContext *fileStream = (_CFFileStreamContext *)info;
381#ifdef REAL_FILE_SCHEDULING
382    Boolean isReadStream = (CFGetTypeID(stream) == CFReadStreamGetTypeID());
383    CFStreamStatus status = isReadStream ? CFReadStreamGetStatus((CFReadStreamRef)stream) : CFWriteStreamGetStatus((CFWriteStreamRef)stream);
384    if (status == kCFStreamStatusNotOpen) {
385        // Not opened yet
386        if (fileStream->rlInfo.rlArray) {
387            CFMutableArrayRef runloops = fileStream->rlInfo.rlArray;
388            CFIndex i, c;
389            for (i = 0, c = CFArrayGetCount(runloops); i+1 < c; i += 2) {
390                if (CFEqual(CFArrayGetValueAtIndex(runloops, i), runLoop) && CFEqual(CFArrayGetValueAtIndex(runloops, i+1), runLoopMode)) {
391                    CFArrayRemoveValueAtIndex(runloops, i);
392                    CFArrayRemoveValueAtIndex(runloops, i);
393                    break;
394                }
395            }
396        }
397    } else if (fileStream->rlInfo.cffd) {
398		if (__CFBitIsSet(fileStream->flags, USE_RUNLOOP_ARRAY)) {
399			// we know that fileStream->rlInfo.rlArray is non-NULL because it is in a union with fileStream->rlInfo.cffd
400            CFMutableArrayRef runloops = fileStream->rlInfo.rlArray;
401            CFIndex i, c;
402            for (i = 0, c = CFArrayGetCount(runloops); i+1 < c; i += 2) {
403                if (CFEqual(CFArrayGetValueAtIndex(runloops, i), runLoop) && CFEqual(CFArrayGetValueAtIndex(runloops, i+1), runLoopMode)) {
404                    CFArrayRemoveValueAtIndex(runloops, i);
405                    CFArrayRemoveValueAtIndex(runloops, i);
406                    break;
407                }
408            }
409        } else {
410			CFRunLoopSourceRef rlSrc = CFFileDescriptorCreateRunLoopSource(CFGetAllocator(stream), fileStream->rlInfo.cffd, 0);
411			CFRunLoopRemoveSource(runLoop, rlSrc, runLoopMode);
412			CFRelease(rlSrc);
413		}
414    }
415#else
416    if (fileStream->scheduled > 0)
417        fileStream->scheduled--;
418#endif
419}
420
421static CFTypeRef fileCopyProperty(struct _CFStream *stream, CFStringRef propertyName, void *info) {
422
423    CFTypeRef result = NULL;
424    _CFFileStreamContext *fileStream = (_CFFileStreamContext *)info;
425
426    if (CFEqual(propertyName, kCFStreamPropertyFileCurrentOffset)) {
427
428        // NOTE that this does a lseek of 0 from the current location in
429        // order to populate the offset field which will then be used to
430        // create the resulting value.
431        if (!__CFBitIsSet(fileStream->flags, APPEND) && fileStream->fd != -1) {
432            fileStream->offset = lseek(fileStream->fd, 0, SEEK_CUR);
433        }
434
435        if (fileStream->offset != -1) {
436            result = CFNumberCreate(CFGetAllocator((CFTypeRef)stream), kCFNumberSInt64Type, &(fileStream->offset));
437        }
438#if DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
439    } else if (CFEqual(propertyName, _kCFStreamPropertyFileNativeHandle)) {
440		int fd = fileStream->fd;
441		if (fd != -1) {
442			result = CFDataCreate(CFGetAllocator((CFTypeRef) stream), (const uint8_t *)&fd, sizeof(fd));
443		}
444#endif
445	}
446
447    return result;
448}
449
450static Boolean fileSetProperty(struct _CFStream *stream, CFStringRef prop, CFTypeRef val, void *info) {
451
452    Boolean result = FALSE;
453    _CFFileStreamContext *fileStream = (_CFFileStreamContext *)info;
454
455    if (CFEqual(prop, kCFStreamPropertyAppendToFile) && CFGetTypeID(stream) == CFWriteStreamGetTypeID() &&
456        CFWriteStreamGetStatus((CFWriteStreamRef)stream) == kCFStreamStatusNotOpen)
457    {
458        if (val == kCFBooleanTrue) {
459            __CFBitSet(fileStream->flags, APPEND);
460            fileStream->offset = -1;				// Can't offset and append on the stream
461        } else {
462            __CFBitClear(fileStream->flags, APPEND);
463        }
464        result = TRUE;
465    }
466
467    else if (CFEqual(prop, kCFStreamPropertyFileCurrentOffset)) {
468
469        if (!__CFBitIsSet(fileStream->flags, APPEND))
470        {
471            result = CFNumberGetValue((CFNumberRef)val, kCFNumberSInt64Type, &(fileStream->offset));
472        }
473
474        if ((fileStream->fd != -1) && (lseek(fileStream->fd, fileStream->offset, SEEK_SET) == -1)) {
475            result = FALSE;
476        }
477    }
478
479    return result;
480}
481
482static void *fileCreate(struct _CFStream *stream, void *info) {
483    _CFFileStreamContext *ctxt = (_CFFileStreamContext *)info;
484    _CFFileStreamContext *newCtxt = (_CFFileStreamContext *)CFAllocatorAllocate(CFGetAllocator(stream), sizeof(_CFFileStreamContext), 0);
485    if (!newCtxt) return NULL;
486    newCtxt->url = ctxt->url;
487    if (newCtxt->url) {
488        CFRetain(newCtxt->url);
489    }
490    newCtxt->fd = ctxt->fd;
491#ifdef REAL_FILE_SCHEDULING
492    newCtxt->rlInfo.cffd = NULL;
493#else
494    newCtxt->scheduled = 0;
495#endif
496    newCtxt->flags = 0;
497    newCtxt->offset = -1;
498    return newCtxt;
499}
500
501static void	fileFinalize(struct _CFStream *stream, void *info) {
502    _CFFileStreamContext *ctxt = (_CFFileStreamContext *)info;
503    if (ctxt->fd > 0) {
504#ifdef REAL_FILE_SCHEDULING
505        if (ctxt->rlInfo.cffd) {
506            CFFileDescriptorInvalidate(ctxt->rlInfo.cffd);
507            CFRelease(ctxt->rlInfo.cffd);
508            ctxt->rlInfo.cffd = NULL;
509        }
510#endif
511        close(ctxt->fd);
512#ifdef REAL_FILE_SCHEDULING
513    } else if (ctxt->rlInfo.rlArray) {
514        CFRelease(ctxt->rlInfo.rlArray);
515#endif
516    }
517    if (ctxt->url) {
518        CFRelease(ctxt->url);
519    }
520    CFAllocatorDeallocate(CFGetAllocator(stream), ctxt);
521}
522
523static CFStringRef fileCopyDescription(struct _CFStream *stream, void *info) {
524    // This needs work
525    _CFFileStreamContext *ctxt = (_CFFileStreamContext *)info;
526    if (ctxt->url) {
527        return CFCopyDescription(ctxt->url);
528    } else {
529        return CFStringCreateWithFormat(CFGetAllocator(stream), NULL, CFSTR("fd = %d"), ctxt->fd);
530    }
531}
532
533/* CFData stream callbacks */
534typedef struct {
535    CFDataRef data; // Mutable if the stream was constructed writable
536    const UInt8 *loc; // Current location in the file
537    Boolean scheduled;
538    char _padding[3];
539} _CFReadDataStreamContext;
540
541#define BUF_SIZE 1024
542typedef struct _CFStreamByteBuffer {
543    UInt8 *bytes;
544    CFIndex capacity, length;
545    struct _CFStreamByteBuffer *next;
546} _CFStreamByteBuffer;
547
548typedef struct {
549    _CFStreamByteBuffer *firstBuf, *currentBuf;
550    CFAllocatorRef bufferAllocator;
551    Boolean scheduled;
552    char _padding[3];
553} _CFWriteDataStreamContext;
554
555static Boolean readDataOpen(struct _CFStream *stream, CFStreamError *errorCode, Boolean *openComplete, void *info) {
556    _CFReadDataStreamContext *dataStream = (_CFReadDataStreamContext *)info;
557    if (dataStream->scheduled) {
558        if (CFDataGetLength(dataStream->data) != 0) {
559            CFReadStreamSignalEvent((CFReadStreamRef)stream, kCFStreamEventHasBytesAvailable, NULL);
560        } else {
561            CFReadStreamSignalEvent((CFReadStreamRef)stream, kCFStreamEventEndEncountered, NULL);
562        }
563    }
564    errorCode->error = 0;
565    *openComplete = TRUE;
566    return TRUE;
567}
568
569static void readDataSchedule(struct _CFStream *stream, CFRunLoopRef rl, CFStringRef rlMode, void *info) {
570    _CFReadDataStreamContext *dataStream = (_CFReadDataStreamContext *)info;
571    if (dataStream->scheduled == FALSE) {
572        dataStream->scheduled = TRUE;
573		if (CFReadStreamGetStatus((CFReadStreamRef)stream) != kCFStreamStatusOpen)
574			return;
575        if (CFDataGetBytePtr(dataStream->data) + CFDataGetLength(dataStream->data) > dataStream->loc) {
576            CFReadStreamSignalEvent((CFReadStreamRef)stream, kCFStreamEventHasBytesAvailable, NULL);
577        } else {
578            CFReadStreamSignalEvent((CFReadStreamRef)stream, kCFStreamEventEndEncountered, NULL);
579        }
580    }
581}
582
583static CFIndex dataRead(CFReadStreamRef stream, UInt8 *buffer, CFIndex bufferLength, CFStreamError *error, Boolean *atEOF, void *info) {
584    _CFReadDataStreamContext *dataCtxt = (_CFReadDataStreamContext *)info;
585    const UInt8 *bytePtr = CFDataGetBytePtr(dataCtxt->data);
586    CFIndex length = CFDataGetLength(dataCtxt->data);
587    CFIndex bytesToCopy = bytePtr + length - dataCtxt->loc;
588    if (bytesToCopy > bufferLength) {
589        bytesToCopy = bufferLength;
590    }
591    if (bytesToCopy < 0) {
592        bytesToCopy = 0;
593    }
594    if (bytesToCopy != 0) {
595        memmove(buffer, dataCtxt->loc, bytesToCopy);
596        dataCtxt->loc += bytesToCopy;
597    }
598    error->error = 0;
599    *atEOF = (dataCtxt->loc < bytePtr + length) ? FALSE : TRUE;
600    if (dataCtxt->scheduled && !*atEOF) {
601        CFReadStreamSignalEvent(stream, kCFStreamEventHasBytesAvailable, NULL);
602    }
603    return bytesToCopy;
604}
605
606static const UInt8 *dataGetBuffer(CFReadStreamRef stream, CFIndex maxBytesToRead, CFIndex *numBytesRead, CFStreamError *error, Boolean *atEOF, void *info) {
607    _CFReadDataStreamContext *dataCtxt = (_CFReadDataStreamContext *)info;
608    const UInt8 *bytes = CFDataGetBytePtr(dataCtxt->data);
609    if (dataCtxt->loc - bytes > maxBytesToRead) {
610        *numBytesRead = maxBytesToRead;
611        *atEOF = FALSE;
612    } else {
613        *numBytesRead = dataCtxt->loc - bytes;
614        *atEOF = TRUE;
615    }
616    error->error = 0;
617    bytes = dataCtxt->loc;
618    dataCtxt->loc += *numBytesRead;
619    if (dataCtxt->scheduled && !*atEOF) {
620        CFReadStreamSignalEvent(stream, kCFStreamEventHasBytesAvailable, NULL);
621    }
622    return bytes;
623}
624
625static Boolean dataCanRead(CFReadStreamRef stream, void *info) {
626    _CFReadDataStreamContext *dataCtxt = (_CFReadDataStreamContext *)info;
627    return (CFDataGetBytePtr(dataCtxt->data) + CFDataGetLength(dataCtxt->data) > dataCtxt->loc) ? TRUE : FALSE;
628}
629
630static Boolean writeDataOpen(struct _CFStream *stream, CFStreamError *errorCode, Boolean *openComplete, void *info) {
631    _CFWriteDataStreamContext *dataStream = (_CFWriteDataStreamContext *)info;
632    if (dataStream->scheduled) {
633        if (dataStream->bufferAllocator != kCFAllocatorNull || dataStream->currentBuf->capacity > dataStream->currentBuf->length) {
634            CFWriteStreamSignalEvent((CFWriteStreamRef)stream, kCFStreamEventCanAcceptBytes, NULL);
635        } else {
636            CFWriteStreamSignalEvent((CFWriteStreamRef)stream, kCFStreamEventEndEncountered, NULL);
637        }
638    }
639    errorCode->error = 0;
640    *openComplete = TRUE;
641    return TRUE;
642}
643
644static void writeDataSchedule(struct _CFStream *stream, CFRunLoopRef rl, CFStringRef rlMode, void *info) {
645    _CFWriteDataStreamContext *dataStream = (_CFWriteDataStreamContext *)info;
646    if (dataStream->scheduled == FALSE) {
647        dataStream->scheduled = TRUE;
648		if (CFWriteStreamGetStatus((CFWriteStreamRef)stream) != kCFStreamStatusOpen)
649			return;
650        if (dataStream->bufferAllocator != kCFAllocatorNull || dataStream->currentBuf->capacity > dataStream->currentBuf->length) {
651            CFWriteStreamSignalEvent((CFWriteStreamRef)stream, kCFStreamEventCanAcceptBytes, NULL);
652        } else {
653            CFWriteStreamSignalEvent((CFWriteStreamRef)stream, kCFStreamEventEndEncountered, NULL);
654        }
655    }
656}
657
658static CFIndex dataWrite(CFWriteStreamRef stream, const UInt8 *buffer, CFIndex bufferLength, CFStreamError *errorCode, void *info) {
659    _CFWriteDataStreamContext *dataStream = (_CFWriteDataStreamContext *)info;
660    CFIndex result;
661    CFIndex freeSpace = dataStream->currentBuf->capacity - dataStream->currentBuf->length;
662    if (dataStream->bufferAllocator == kCFAllocatorNull && bufferLength > freeSpace) {
663        errorCode->error = ENOMEM;
664        errorCode->domain = kCFStreamErrorDomainPOSIX;
665        return -1;
666    } else {
667        result = bufferLength;
668        while (bufferLength > 0) {
669            CFIndex amountToCopy = (bufferLength > freeSpace) ? freeSpace : bufferLength;
670            if (freeSpace > 0) {
671                memmove(dataStream->currentBuf->bytes + dataStream->currentBuf->length, buffer, amountToCopy);
672                buffer += amountToCopy;
673                bufferLength -= amountToCopy;
674                dataStream->currentBuf->length += amountToCopy;
675            }
676            if (bufferLength > 0) {
677                CFIndex bufSize = BUF_SIZE > bufferLength ? BUF_SIZE : bufferLength;
678                _CFStreamByteBuffer *newBuf = (_CFStreamByteBuffer *)CFAllocatorAllocate(dataStream->bufferAllocator, sizeof(_CFStreamByteBuffer) + bufSize, 0);
679                if (newBuf == NULL) {
680                    errorCode->error = ENOMEM;
681                    errorCode->domain = kCFStreamErrorDomainPOSIX;
682                    return -1;
683                } else {
684                    newBuf->bytes = (UInt8 *)(newBuf + 1);
685                    newBuf->capacity = bufSize;
686                    newBuf->length = 0;
687                    newBuf->next = NULL;
688                    dataStream->currentBuf->next = newBuf;
689                    dataStream->currentBuf = newBuf;
690                    freeSpace = bufSize;
691                }
692            }
693        }
694        errorCode->error = 0;
695    }
696    if (dataStream->scheduled && (dataStream->bufferAllocator != kCFAllocatorNull || dataStream->currentBuf->capacity > dataStream->currentBuf->length)) {
697        CFWriteStreamSignalEvent(stream, kCFStreamEventCanAcceptBytes, NULL);
698    }
699    return result;
700}
701
702static  Boolean dataCanWrite(CFWriteStreamRef stream, void *info) {
703    _CFWriteDataStreamContext *dataStream = (_CFWriteDataStreamContext *)info;
704    if (dataStream->bufferAllocator != kCFAllocatorNull) return TRUE;
705    if (dataStream->currentBuf->capacity  > dataStream->currentBuf->length) return TRUE;
706    return FALSE;
707}
708
709static CFPropertyListRef dataCopyProperty(struct _CFStream *stream, CFStringRef propertyName, void *info) {
710    _CFWriteDataStreamContext *dataStream = (_CFWriteDataStreamContext *)info;
711    CFIndex size = 0;
712    _CFStreamByteBuffer *buf;
713    CFAllocatorRef alloc;
714    UInt8 *bytes, *currByte;
715    if (!CFEqual(propertyName, kCFStreamPropertyDataWritten)) return NULL;
716    if (dataStream->bufferAllocator == kCFAllocatorNull)  return NULL;
717    alloc = dataStream->bufferAllocator;
718    for (buf = dataStream->firstBuf; buf != NULL; buf = buf->next) {
719        size += buf->length;
720    }
721    bytes = (UInt8 *)CFAllocatorAllocate(alloc, size, 0);
722    currByte = bytes;
723    for (buf = dataStream->firstBuf; buf != NULL; buf = buf->next) {
724        memmove(currByte, buf->bytes, buf->length);
725        currByte += buf->length;
726    }
727    return CFDataCreateWithBytesNoCopy(alloc, bytes, size, alloc);
728}
729
730static void *readDataCreate(struct _CFStream *stream, void *info) {
731    _CFReadDataStreamContext *ctxt = (_CFReadDataStreamContext *)info;
732    _CFReadDataStreamContext *newCtxt = (_CFReadDataStreamContext *)CFAllocatorAllocate(CFGetAllocator(stream), sizeof(_CFReadDataStreamContext), 0);
733    if (!newCtxt) return NULL;
734    newCtxt->data = (CFDataRef)CFRetain(ctxt->data);
735    newCtxt->loc = CFDataGetBytePtr(newCtxt->data);
736    newCtxt->scheduled = FALSE;
737    return (void *)newCtxt;
738}
739
740static void readDataFinalize(struct _CFStream *stream, void *info) {
741    _CFReadDataStreamContext *ctxt = (_CFReadDataStreamContext *)info;
742    CFRelease(ctxt->data);
743    CFAllocatorDeallocate(CFGetAllocator(stream), ctxt);
744}
745
746static CFStringRef readDataCopyDescription(struct _CFStream *stream, void *info) {
747    return CFCopyDescription(((_CFReadDataStreamContext *)info)->data);
748}
749
750static void *writeDataCreate(struct _CFStream *stream, void *info) {
751    _CFWriteDataStreamContext *ctxt = (_CFWriteDataStreamContext *)info;
752    _CFWriteDataStreamContext *newCtxt;
753    if (ctxt->bufferAllocator != kCFAllocatorNull) {
754        if (ctxt->bufferAllocator == NULL) ctxt->bufferAllocator = CFAllocatorGetDefault();
755        CFRetain(ctxt->bufferAllocator);
756        newCtxt = (_CFWriteDataStreamContext *)CFAllocatorAllocate(CFGetAllocator(stream), sizeof(_CFWriteDataStreamContext) + sizeof(_CFStreamByteBuffer) + BUF_SIZE, 0);
757        newCtxt->firstBuf = (_CFStreamByteBuffer *)(newCtxt + 1);
758        newCtxt->firstBuf->bytes = (UInt8 *)(newCtxt->firstBuf + 1);
759        newCtxt->firstBuf->capacity = BUF_SIZE;
760        newCtxt->firstBuf->length = 0;
761        newCtxt->firstBuf->next = NULL;
762        newCtxt->currentBuf = newCtxt->firstBuf;
763        newCtxt->bufferAllocator = ctxt->bufferAllocator;
764        newCtxt->scheduled = FALSE;
765    } else {
766        newCtxt = (_CFWriteDataStreamContext *)CFAllocatorAllocate(CFGetAllocator(stream), sizeof(_CFWriteDataStreamContext) + sizeof(_CFStreamByteBuffer), 0);
767        newCtxt->firstBuf = (_CFStreamByteBuffer *)(newCtxt+1);
768        newCtxt->firstBuf->bytes = ctxt->firstBuf->bytes;
769        newCtxt->firstBuf->capacity = ctxt->firstBuf->capacity;
770        newCtxt->firstBuf->length = 0;
771        newCtxt->firstBuf->next = NULL;
772        newCtxt->currentBuf = newCtxt->firstBuf;
773        newCtxt->bufferAllocator = kCFAllocatorNull;
774        newCtxt->scheduled = FALSE;
775    }
776    return (void *)newCtxt;
777}
778
779static void writeDataFinalize(struct _CFStream *stream, void *info) {
780    _CFWriteDataStreamContext *ctxt = (_CFWriteDataStreamContext *)info;
781    if (ctxt->bufferAllocator != kCFAllocatorNull) {
782        _CFStreamByteBuffer *buf = ctxt->firstBuf->next, *next;
783        while (buf != NULL) {
784            next = buf->next;
785            CFAllocatorDeallocate(ctxt->bufferAllocator, buf);
786            buf = next;
787        }
788        CFRelease(ctxt->bufferAllocator);
789    }
790    CFAllocatorDeallocate(CFGetAllocator(stream), ctxt);
791}
792
793static CFStringRef writeDataCopyDescription(struct _CFStream *stream, void *info) {
794    return CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("<CFWriteDataContext %p>"), info);
795}
796
797static const struct _CFStreamCallBacksV1 fileCallBacks = {1, fileCreate, fileFinalize, fileCopyDescription, fileOpen, NULL, fileRead, NULL, fileCanRead, fileWrite, fileCanWrite, fileClose, fileCopyProperty, fileSetProperty, NULL, fileSchedule, fileUnschedule};
798
799static struct _CFStream *_CFStreamCreateWithFile(CFAllocatorRef alloc, CFURLRef fileURL, Boolean forReading) {
800    _CFFileStreamContext fileContext;
801    CFStringRef scheme = fileURL ? CFURLCopyScheme(fileURL) : NULL;
802    if (!scheme || !CFEqual(scheme, CFSTR("file"))) {
803        if (scheme) CFRelease(scheme);
804        return NULL;
805    }
806    CFRelease(scheme);
807    fileContext.url = fileURL;
808    fileContext.fd = -1;
809    return _CFStreamCreateWithConstantCallbacks(alloc, &fileContext, (struct _CFStreamCallBacks *)(&fileCallBacks), forReading);
810}
811
812CF_EXPORT CFReadStreamRef CFReadStreamCreateWithFile(CFAllocatorRef alloc, CFURLRef fileURL) {
813    return (CFReadStreamRef)_CFStreamCreateWithFile(alloc, fileURL, TRUE);
814}
815
816CF_EXPORT CFWriteStreamRef CFWriteStreamCreateWithFile(CFAllocatorRef alloc, CFURLRef fileURL) {
817    return (CFWriteStreamRef)_CFStreamCreateWithFile(alloc, fileURL, FALSE);
818}
819
820// CFReadStreamRef takes ownership of the fd, and will close() it
821CFReadStreamRef _CFReadStreamCreateFromFileDescriptor(CFAllocatorRef alloc, int fd) {
822    _CFFileStreamContext fileContext;
823    fileContext.url = NULL;
824    fileContext.fd = fd;
825    return (CFReadStreamRef)_CFStreamCreateWithConstantCallbacks(alloc, &fileContext, (struct _CFStreamCallBacks *)(&fileCallBacks), TRUE);
826}
827
828// CFWriteStreamRef takes ownership of the fd, and will close() it
829CFWriteStreamRef _CFWriteStreamCreateFromFileDescriptor(CFAllocatorRef alloc, int fd) {
830    _CFFileStreamContext fileContext;
831    fileContext.url = NULL;
832    fileContext.fd = fd;
833    return (CFWriteStreamRef)_CFStreamCreateWithConstantCallbacks(alloc, &fileContext, (struct _CFStreamCallBacks *)(&fileCallBacks), FALSE);
834}
835
836
837
838static const struct _CFStreamCallBacksV1 readDataCallBacks = {1, readDataCreate, readDataFinalize, readDataCopyDescription, readDataOpen, NULL, dataRead, dataGetBuffer, dataCanRead, NULL, NULL, NULL, NULL, NULL, NULL, readDataSchedule, NULL};
839static const struct _CFStreamCallBacksV1 writeDataCallBacks = {1, writeDataCreate, writeDataFinalize, writeDataCopyDescription, writeDataOpen, NULL, NULL, NULL, NULL, dataWrite, dataCanWrite, NULL, dataCopyProperty, NULL, NULL, writeDataSchedule, NULL};
840
841CF_EXPORT CFReadStreamRef CFReadStreamCreateWithBytesNoCopy(CFAllocatorRef alloc, const UInt8 *bytes, CFIndex length, CFAllocatorRef bytesDeallocator) {
842    _CFReadDataStreamContext ctxt;
843    CFReadStreamRef result;
844    ctxt.data = CFDataCreateWithBytesNoCopy(alloc, bytes, length, bytesDeallocator);
845    result = (CFReadStreamRef)_CFStreamCreateWithConstantCallbacks(alloc, &ctxt, (struct _CFStreamCallBacks *)(&readDataCallBacks), TRUE);
846    CFRelease(ctxt.data);
847    return result;
848}
849
850/* This needs to be exported to make it callable from Foundation. */
851CF_EXPORT CFReadStreamRef CFReadStreamCreateWithData(CFAllocatorRef alloc, CFDataRef data) {
852    _CFReadDataStreamContext ctxt;
853    CFReadStreamRef result = NULL;
854
855    ctxt.data = (CFDataRef)CFRetain(data);
856    result = (CFReadStreamRef)_CFStreamCreateWithConstantCallbacks(alloc, &ctxt, (struct _CFStreamCallBacks *)(&readDataCallBacks), TRUE);
857    CFRelease(data);
858    return result;
859}
860
861CFWriteStreamRef CFWriteStreamCreateWithBuffer(CFAllocatorRef alloc, UInt8 *buffer, CFIndex bufferCapacity) {
862    _CFStreamByteBuffer buf;
863    _CFWriteDataStreamContext ctxt;
864    buf.bytes = buffer;
865    buf.capacity = bufferCapacity;
866    buf.length = 0;
867    buf.next = NULL;
868    ctxt.firstBuf = &buf;
869    ctxt.currentBuf = ctxt.firstBuf;
870    ctxt.bufferAllocator = kCFAllocatorNull;
871    return (CFWriteStreamRef)_CFStreamCreateWithConstantCallbacks(alloc, &ctxt, (struct _CFStreamCallBacks *)(&writeDataCallBacks), FALSE);
872}
873
874CF_EXPORT CFWriteStreamRef CFWriteStreamCreateWithAllocatedBuffers(CFAllocatorRef alloc, CFAllocatorRef bufferAllocator) {
875    _CFWriteDataStreamContext ctxt;
876    ctxt.firstBuf = NULL;
877    ctxt.currentBuf = NULL;
878    ctxt.bufferAllocator = bufferAllocator;
879    return (CFWriteStreamRef)_CFStreamCreateWithConstantCallbacks(alloc, &ctxt, (struct _CFStreamCallBacks *)(&writeDataCallBacks), FALSE);
880}
881
882#undef BUF_SIZE
883
884