2 Copyright (C) 2000-2005 SKYRIX Software AG
4 This file is part of SOPE.
6 SOPE is free software; you can redistribute it and/or modify it under
7 the terms of the GNU Lesser General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
11 SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with SOPE; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
24 #if HAVE_UNISTD_H || __APPLE__
28 # include <sys/stat.h>
31 # include <sys/fcntl.h>
33 #if HAVE_FCNTL_H || __APPLE__
38 #import <Foundation/NSThread.h>
39 #include "NGFileStream.h"
40 #include "NGBufferedStream.h"
41 #include "NGConcreteStreamFileHandle.h"
42 #include "NGLockingStream.h"
43 #include "NGStreamExceptions.h"
44 #include "NGDescriptorFunctions.h"
46 #if !defined(POLLRDNORM)
47 # define POLLRDNORM POLLIN
50 // TODO: NGFileStream needs to be changed to operate without throwing
53 NGStreams_DECLARE NSString *NGFileReadOnly = @"r";
54 NGStreams_DECLARE NSString *NGFileWriteOnly = @"w";
55 NGStreams_DECLARE NSString *NGFileReadWrite = @"rw";
56 NGStreams_DECLARE NSString *NGFileAppend = @"a";
57 NGStreams_DECLARE NSString *NGFileReadAppend = @"ra";
59 static const int NGInvalidUnixDescriptor = -1;
60 static const int NGFileCreationMask = 0666; // rw-rw-rw-
62 @interface _NGConcreteFileStreamFileHandle : NGConcreteStreamFileHandle
65 NGStreams_DECLARE id<NGInputStream> NGIn = nil;
66 NGStreams_DECLARE id<NGOutputStream> NGOut = nil;
67 NGStreams_DECLARE id<NGOutputStream> NGErr = nil;
69 @implementation NGFileStream
73 #if defined(__MINGW32__)
74 - (id)__initWithInConsole {
75 if ((self = [self init])) {
76 self->systemPath = @"CONIN$";
77 self->streamMode = NGStreamMode_readWrite;
78 self->fh = GetStdHandle(STD_INPUT_HANDLE);
80 self->fh = CreateFile("CONIN$", GENERIC_READ, FILE_SHARE_READ,
89 - (id)__initWithOutConsole {
90 if ((self = [self init])) {
92 self->systemPath = @"CONOUT$";
93 self->streamMode = NGStreamMode_readWrite;
94 self->fh = GetStdHandle(STD_OUTPUT_HANDLE);
96 self->fh = CreateFile("CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE,
102 FlushFileBuffers(self->fh);
107 - (id)__initWithDescriptor:(int)_fd mode:(NGStreamMode)_mode {
108 if ((self = [self init])) {
110 self->streamMode = _mode;
116 void NGInitStdio(void) {
117 static BOOL isInitialized = NO;
118 if (!isInitialized) {
119 NGFileStream *ti = nil, *to = nil, *te = nil;
123 #if defined(__MINGW32__)
124 ti = [[NGFileStream alloc] __initWithInConsole];
125 to = [[NGFileStream alloc] __initWithOutConsole];
128 ti = [[NGFileStream alloc] __initWithDescriptor:0 mode:NGStreamMode_readOnly];
129 to = [[NGFileStream alloc] __initWithDescriptor:1 mode:NGStreamMode_writeOnly];
130 te = [[NGFileStream alloc] __initWithDescriptor:2 mode:NGStreamMode_writeOnly];
133 NGIn = [[NGBufferedStream alloc] initWithSource:(id)ti];
134 NGOut = [[NGBufferedStream alloc] initWithSource:(id)to];
135 NGErr = [[NGBufferedStream alloc] initWithSource:(id)te];
137 [ti release]; ti = nil;
138 [to release]; to = nil;
139 [te release]; te = nil;
143 + (void)_makeThreadSafe:(NSNotification *)_notification {
144 NGLockingStream *li = nil, *lo = nil, *le = nil;
146 if ([NGIn isKindOfClass:[NGLockingStream class]])
149 li = [[NGLockingStream alloc] initWithSource:NGIn]; [NGIn release]; NGIn = li;
150 lo = [[NGLockingStream alloc] initWithSource:NGOut]; [NGOut release]; NGOut = lo;
151 le = [[NGLockingStream alloc] initWithSource:NGErr]; [NGErr release]; NGErr = le;
154 + (void)_flushForExit:(NSNotification *)_notification {
160 static void _flushForExit(void) {
167 BOOL isInitialized = NO;
168 if (!isInitialized) {
171 if ([NSThread isMultiThreaded])
172 [self _makeThreadSafe:nil];
174 [[NSNotificationCenter defaultCenter]
176 selector:@selector(_makeThreadSafe:)
177 name:NSWillBecomeMultiThreadedNotification
180 atexit(_flushForExit);
184 /* normal file stream */
187 if ((self = [super init])) {
188 self->streamMode = NGStreamMode_undefined;
189 self->systemPath = nil;
190 self->markDelta = -1;
192 #if defined(__MINGW32__)
193 self->fh = INVALID_HANDLE_VALUE;
195 self->fd = NGInvalidUnixDescriptor;
201 - (id)initWithPath:(NSString *)_path {
202 if ((self = [self init])) {
203 self->systemPath = [_path copy];
208 - (id)initWithFileHandle:(NSFileHandle *)_handle {
209 if ((self = [self init])) {
210 #if defined(__MINGW32__)
211 self->fh = [_handle nativeHandle];
213 self->fd = [_handle fileDescriptor];
222 NSLog(@"NGFileStream(gcFinalize): closing %@", self);
229 self->streamMode = NGStreamMode_undefined;
230 [self->systemPath release]; self->systemPath = nil;
237 - (BOOL)openInMode:(NSString *)_mode {
239 // NGUnknownStreamModeException when _mode is invalid
240 // NGCouldNotOpenStreamException when the file could not be opened
241 #if defined(__MINGW32__)
245 if (self->fh != INVALID_HANDLE_VALUE)
246 [self close]; // if stream is open, close and reopen
248 if ([_mode isEqualToString:NGFileReadOnly]) {
249 self->streamMode = NGStreamMode_readOnly;
250 openFlags = GENERIC_READ;
251 shareMode = FILE_SHARE_READ;
253 else if ([_mode isEqualToString:NGFileWriteOnly]) {
254 self->streamMode = NGStreamMode_writeOnly;
255 openFlags = GENERIC_WRITE;
256 shareMode = FILE_SHARE_WRITE;
258 else if ([_mode isEqualToString:NGFileReadWrite]) {
259 self->streamMode = NGStreamMode_readWrite;
260 openFlags = GENERIC_READ | GENERIC_WRITE;
261 shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
264 [[[NGUnknownStreamModeException alloc]
265 initWithStream:self mode:_mode] raise];
269 self->fh = CreateFile([self->systemPath fileSystemRepresentation],
270 openFlags, shareMode, NULL,
271 OPEN_ALWAYS, // same as the Unix O_CREAT flag
272 0, // security flags ?
275 if (self->fh == INVALID_HANDLE_VALUE)
276 [NGCouldNotOpenStreamException raiseWithStream:self];
279 int openFlags; // flags passed to open() call
281 if (self->fd != NGInvalidUnixDescriptor)
282 [self close]; // if stream is open, close and reopen
284 if ([_mode isEqualToString:NGFileReadOnly]) {
285 self->streamMode = NGStreamMode_readOnly;
286 openFlags = O_RDONLY;
288 else if ([_mode isEqualToString:NGFileWriteOnly]) {
289 self->streamMode = NGStreamMode_writeOnly;
290 openFlags = O_WRONLY | O_CREAT;
292 else if ([_mode isEqualToString:NGFileReadWrite]) {
293 self->streamMode = NGStreamMode_readWrite;
294 openFlags = O_RDWR | O_CREAT;
296 else if ([_mode isEqualToString:NGFileAppend]) {
297 self->streamMode = NGStreamMode_writeOnly;
298 openFlags = O_WRONLY | O_CREAT | O_APPEND;
300 else if ([_mode isEqualToString:NGFileReadAppend]) {
301 self->streamMode = NGStreamMode_readWrite;
302 openFlags = O_RDWR | O_CREAT | O_APPEND;
305 [[[NGUnknownStreamModeException alloc]
306 initWithStream:self mode:_mode] raise];
310 self->fd = open([self->systemPath fileSystemRepresentation],
314 if (self->fd == -1) {
315 self->fd = NGInvalidUnixDescriptor;
317 [NGCouldNotOpenStreamException raiseWithStream:self];
322 self->markDelta = -1; // not marked
327 #if defined(__MINGW32__)
328 return (self->fh != INVALID_HANDLE_VALUE) ? YES : NO;
330 return (self->fd != NGInvalidUnixDescriptor) ? YES : NO;
334 // Foundation file handles
336 - (void)resetFileHandle { // called by NSFileHandle on dealloc
339 - (NSFileHandle *)fileHandle {
340 if (self->handle == nil)
341 self->handle = [[_NGConcreteFileStreamFileHandle allocWithZone:[self zone]]
342 initWithStream:self];
343 return [self->handle autorelease];
346 #if defined(__MINGW32__)
347 - (HANDLE)windowsFileHandle {
352 - (int)fileDescriptor {
353 #if defined(__MINGW32__)
354 return (int)[self fileHandle];
362 static void _checkOpen(NGFileStream *self, NSString *_reason) {
363 #if defined(__MINGW32__)
364 if (self->fh == INVALID_HANDLE_VALUE)
365 [NGStreamNotOpenException raiseWithStream:self reason:_reason];
367 if (self->fd == NGInvalidUnixDescriptor)
368 [NGStreamNotOpenException raiseWithStream:self reason:_reason];
372 - (unsigned)readBytes:(void *)_buf count:(unsigned)_len {
374 // NGWriteOnlyStreamException when the stream is not readable
375 // NGStreamNotOpenException when the stream is not open
376 // NGEndOfStreamException when the end of the stream is reached
377 // NGStreamReadErrorException when the read call failed
379 _checkOpen(self, @"tried to read from a file stream which is closed");
381 if (!NGCanReadInStreamMode(streamMode))
382 [NGWriteOnlyStreamException raiseWithStream:self];
385 #if defined(__MINGW32__)
386 DWORD readResult = 0;
388 if (ReadFile(self->fh, _buf, _len, &readResult, NULL) == FALSE) {
389 DWORD lastErr = GetLastError();
391 if (lastErr == ERROR_HANDLE_EOF)
392 [NGEndOfStreamException raiseWithStream:self];
394 [NGStreamReadErrorException raiseWithStream:self errorCode:lastErr];
397 [NGEndOfStreamException raiseWithStream:self];
403 readResult = read(self->fd, _buf, _len);
406 [NGEndOfStreamException raiseWithStream:self];
407 else if (readResult == -1) {
410 if (errCode == EINTR)
411 // system call was interrupted
414 [NGStreamReadErrorException raiseWithStream:self errorCode:errCode];
417 while ((readResult <= 0) && (retryCount < 10));
419 if (retryCount >= 10)
420 [NGStreamReadErrorException raiseWithStream:self errorCode:EINTR];
423 NSAssert(readResult > 0, @"invalid read method state");
426 if (self->markDelta != -1)
427 self->markDelta += readResult; // increase delta
433 - (unsigned)writeBytes:(const void *)_buf count:(unsigned)_len {
435 // NGReadOnlyStreamException when the stream is not writeable
436 // NGStreamNotOpenException when the stream is not open
437 // NGStreamWriteErrorException when the write call failed
439 _checkOpen(self, @"tried to write to a file stream which is closed");
441 if (!NGCanWriteInStreamMode(streamMode))
442 [NGReadOnlyStreamException raiseWithStream:self];
445 #if defined(__MINGW32__)
446 DWORD writeResult = 0;
448 if (WriteFile(self->fh, _buf, _len, &writeResult, NULL) == FALSE) {
449 DWORD errorCode = GetLastError();
452 case ERROR_INVALID_HANDLE:
453 [NGStreamWriteErrorException raiseWithStream:self
454 reason:@"incorrect file handle"];
456 case ERROR_WRITE_PROTECT:
457 [NGStreamWriteErrorException raiseWithStream:self
458 reason:@"disk write protected"];
460 case ERROR_NOT_READY:
461 [NGStreamWriteErrorException raiseWithStream:self
462 reason:@"the drive is not ready"];
464 case ERROR_HANDLE_EOF:
465 [NGStreamWriteErrorException raiseWithStream:self
466 reason:@"reached end of file"];
468 case ERROR_DISK_FULL:
469 [NGStreamWriteErrorException raiseWithStream:self
470 reason:@"disk is full"];
474 [NGStreamWriteErrorException raiseWithStream:self
475 errorCode:GetLastError()];
478 NSLog(@"invalid program state, aborting");
486 writeResult = write(self->fd, _buf, _len);
488 if (writeResult == -1) {
491 if (errCode == EINTR)
492 // system call was interrupted
495 [NGStreamWriteErrorException raiseWithStream:self errorCode:errno];
498 while ((writeResult == -1) && (retryCount < 10));
500 if (retryCount >= 10)
501 [NGStreamWriteErrorException raiseWithStream:self errorCode:EINTR];
509 #if defined(__MINGW32__)
510 if (self->fh == INVALID_HANDLE_VALUE) {
511 NSLog(@"tried to close already closed stream %@", self);
512 return YES; /* not signaled as an error .. */
515 if (CloseHandle(self->fh) == FALSE) {
516 [NGCouldNotCloseStreamException raiseWithStream:self];
520 self->fh = INVALID_HANDLE_VALUE;
522 if (self->fd == NGInvalidUnixDescriptor) {
523 NSLog(@"tried to close already closed stream %@", self);
524 return YES; /* not signaled as an error .. */
527 if (close(self->fd) != 0) {
528 [NGCouldNotCloseStreamException raiseWithStream:self];
532 self->fd = NGInvalidUnixDescriptor;
534 self->markDelta = -1;
538 - (NGStreamMode)mode {
539 return self->streamMode;
541 - (BOOL)isRootStream {
545 #if defined(__MINGW32__)
547 if (self->fh != INVALID_HANDLE_VALUE)
548 FlushFileBuffers(self->fh);
555 #if defined(__MINGW32__)
556 - (BOOL)wouldBlockInMode:(NGStreamMode)_mode {
557 NSLog(@"%@ not supported in Windows environment !",
558 NSStringFromSelector(_cmd));
562 - (BOOL)wouldBlockInMode:(NGStreamMode)_mode {
565 if (self->fd == NGInvalidUnixDescriptor)
568 if (NGCanReadInStreamMode(_mode)) events |= POLLRDNORM;
569 if (NGCanWriteInStreamMode(_mode)) events |= POLLWRNORM;
571 // timeout of 0 means return immediatly
572 return (NGPollDescriptor(self->fd, events, 0) == 1 ? NO : YES);
583 if (![self moveByOffset:-(self->markDelta)])
585 self->markDelta = -1;
588 - (BOOL)markSupported {
592 // NGPositionableStream
594 - (BOOL)moveToLocation:(unsigned)_location {
595 self->markDelta = -1;
597 #if defined(__MINGW32__)
598 if (SetFilePointer(self->fh, _location, NULL, FILE_BEGIN) == -1) {
599 [NGStreamSeekErrorException raiseWithStream:self errorCode:GetLastError()];
603 if (lseek(self->fd, _location, SEEK_SET) == -1) {
604 [NGStreamSeekErrorException raiseWithStream:self errorCode:errno];
610 - (BOOL)moveByOffset:(int)_delta {
611 self->markDelta += _delta;
613 #if defined(__MINGW32__)
614 if (SetFilePointer(self->fh, _delta, NULL, FILE_CURRENT) == -1) {
615 [NGStreamSeekErrorException raiseWithStream:self errorCode:GetLastError()];
619 if (lseek(self->fd, _delta, SEEK_CUR) == -1) {
620 [NGStreamSeekErrorException raiseWithStream:self errorCode:errno];
629 - (NSString *)description {
630 return [NSString stringWithFormat:
631 @"<%@[0x%08X] path=%@ mode=%@>",
632 NSStringFromClass([self class]), (unsigned)self,
633 self->systemPath ? self->systemPath : @"nil",
634 [self modeDescription]];
639 @implementation _NGConcreteFileStreamFileHandle
643 #if defined(__MINGW32__)
644 - (HANDLE)fileHandle {
645 return [(NGFileStream *)stream windowsFileHandle];
649 - (int)fileDescriptor {
650 return [(NGFileStream *)stream fileDescriptor];