1/* 2 Copyright (C) 2000-2005 SKYRIX Software AG 3 4 This file is part of SOPE. 5 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 9 later version. 10 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. 15 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 19 02111-1307, USA. 20*/ 21 22#include "config.h" 23 24#if HAVE_UNISTD_H || __APPLE__ 25# include <unistd.h> 26#endif 27#if HAVE_SYS_STAT_H 28# include <sys/stat.h> 29#endif 30#if HAVE_SYS_FCNTL_H 31# include <sys/fcntl.h> 32#endif 33#if HAVE_FCNTL_H || __APPLE__ 34# include <fcntl.h> 35#endif 36 37#include <NGStreams/NGFileStream.h> 38#include <NGStreams/NGBufferedStream.h> 39#include <NGStreams/NGConcreteStreamFileHandle.h> 40#include <NGStreams/NGLockingStream.h> 41#include <NGStreams/NGStreamExceptions.h> 42#include <NGStreams/NGDescriptorFunctions.h> 43#include "common.h" 44#import <Foundation/NSThread.h> 45 46#if !defined(POLLRDNORM) 47# define POLLRDNORM POLLIN 48#endif 49 50// TODO: NGFileStream needs to be changed to operate without throwing 51// exceptions 52 53NGStreams_DECLARE NSString *NGFileReadOnly = @"r"; 54NGStreams_DECLARE NSString *NGFileWriteOnly = @"w"; 55NGStreams_DECLARE NSString *NGFileReadWrite = @"rw"; 56NGStreams_DECLARE NSString *NGFileAppend = @"a"; 57NGStreams_DECLARE NSString *NGFileReadAppend = @"ra"; 58 59static const int NGInvalidUnixDescriptor = -1; 60static const int NGFileCreationMask = 0666; // rw-rw-rw- 61 62@interface _NGConcreteFileStreamFileHandle : NGConcreteStreamFileHandle 63@end 64 65NGStreams_DECLARE id<NGInputStream> NGIn = nil; 66NGStreams_DECLARE id<NGOutputStream> NGOut = nil; 67NGStreams_DECLARE id<NGOutputStream> NGErr = nil; 68 69@implementation NGFileStream 70 71// stdio stream 72 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); 79 /* 80 self->fh = CreateFile("CONIN$", GENERIC_READ, FILE_SHARE_READ, 81 NULL, 82 OPEN_EXISTING, 83 0, 84 NULL); 85 */ 86 } 87 return self; 88} 89- (id)__initWithOutConsole { 90 if ((self = [self init])) { 91 DWORD written; 92 self->systemPath = @"CONOUT$"; 93 self->streamMode = NGStreamMode_readWrite; 94 self->fh = GetStdHandle(STD_OUTPUT_HANDLE); 95 /* 96 self->fh = CreateFile("CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE, 97 NULL, 98 OPEN_EXISTING, 99 0, 100 NULL); 101 */ 102 FlushFileBuffers(self->fh); 103 } 104 return self; 105} 106#else 107- (id)__initWithDescriptor:(int)_fd mode:(NGStreamMode)_mode { 108 if ((self = [self init])) { 109 self->fd = _fd; 110 self->streamMode = _mode; 111 } 112 return self; 113} 114#endif 115 116void NGInitStdio(void) { 117 static BOOL isInitialized = NO; 118 if (!isInitialized) { 119 NGFileStream *ti = nil, *to = nil, *te = nil; 120 121 isInitialized = YES; 122 123#if defined(__MINGW32__) 124 ti = [[NGFileStream alloc] __initWithInConsole]; 125 to = [[NGFileStream alloc] __initWithOutConsole]; 126 te = [to retain]; 127#else 128 ti = [[NGFileStream alloc] __initWithDescriptor:0 129 mode:NGStreamMode_readOnly]; 130 to = [[NGFileStream alloc] __initWithDescriptor:1 131 mode:NGStreamMode_writeOnly]; 132 te = [[NGFileStream alloc] __initWithDescriptor:2 133 mode:NGStreamMode_writeOnly]; 134#endif 135 136 NGIn = [[NGBufferedStream alloc] initWithSource:(id)ti]; 137 NGOut = [[NGBufferedStream alloc] initWithSource:(id)to]; 138 NGErr = [[NGBufferedStream alloc] initWithSource:(id)te]; 139 140 [ti release]; ti = nil; 141 [to release]; to = nil; 142 [te release]; te = nil; 143 } 144} 145 146+ (void)_makeThreadSafe:(NSNotification *)_notification { 147 NGLockingStream *li = nil, *lo = nil, *le = nil; 148 149 if ([NGIn isKindOfClass:[NGLockingStream class]]) 150 return; 151 152 li = [[NGLockingStream alloc] initWithSource:(id)NGIn]; 153 [NGIn release]; NGIn = li; 154 lo = [[NGLockingStream alloc] initWithSource:(id)NGOut]; 155 [NGOut release]; NGOut = lo; 156 le = [[NGLockingStream alloc] initWithSource:(id)NGErr]; 157 [NGErr release]; NGErr = le; 158} 159 160+ (void)_flushForExit:(NSNotification *)_notification { 161 //[NGIn flush]; 162 [NGOut flush]; 163 [NGErr flush]; 164} 165 166static void _flushForExit(void) { 167 //[NGIn flush]; 168 [NGOut flush]; 169 [NGErr flush]; 170} 171 172+ (void)initialize { 173 BOOL isInitialized = NO; 174 if (!isInitialized) { 175 isInitialized = YES; 176 177 if ([NSThread isMultiThreaded]) 178 [self _makeThreadSafe:nil]; 179 else { 180 [[NSNotificationCenter defaultCenter] 181 addObserver:self 182 selector:@selector(_makeThreadSafe:) 183 name:NSWillBecomeMultiThreadedNotification 184 object:nil]; 185 } 186 atexit(_flushForExit); 187 } 188} 189 190/* normal file stream */ 191 192- (id)init { 193 if ((self = [super init])) { 194 self->streamMode = NGStreamMode_undefined; 195 self->systemPath = nil; 196 self->markDelta = -1; 197 self->handle = nil; 198#if defined(__MINGW32__) 199 self->fh = INVALID_HANDLE_VALUE; 200#else 201 self->fd = NGInvalidUnixDescriptor; 202#endif 203 } 204 return self; 205} 206 207- (id)initWithPath:(NSString *)_path { 208 if ((self = [self init])) { 209 self->systemPath = [_path copy]; 210 } 211 return self; 212} 213 214- (id)initWithFileHandle:(NSFileHandle *)_handle { 215 if ((self = [self init])) { 216#if defined(__MINGW32__) 217 self->fh = [_handle nativeHandle]; 218#else 219 self->fd = [_handle fileDescriptor]; 220#endif 221 } 222 return self; 223} 224 225- (void)gcFinalize { 226 if ([self isOpen]) { 227#if DEBUG && 0 228 NSLog(@"NGFileStream(gcFinalize): closing %@", self); 229#endif 230 [self close]; 231 } 232} 233- (void)dealloc { 234 [self gcFinalize]; 235 self->streamMode = NGStreamMode_undefined; 236 [self->systemPath release]; self->systemPath = nil; 237 self->handle = nil; 238 [super dealloc]; 239} 240 241// opening 242 243- (BOOL)openInMode:(NSString *)_mode { 244 // throws 245 // NGUnknownStreamModeException when _mode is invalid 246 // NGCouldNotOpenStreamException when the file could not be opened 247#if defined(__MINGW32__) 248 DWORD openFlags; 249 DWORD shareMode; 250 251 if (self->fh != INVALID_HANDLE_VALUE) 252 [self close]; // if stream is open, close and reopen 253 254 if ([_mode isEqualToString:NGFileReadOnly]) { 255 self->streamMode = NGStreamMode_readOnly; 256 openFlags = GENERIC_READ; 257 shareMode = FILE_SHARE_READ; 258 } 259 else if ([_mode isEqualToString:NGFileWriteOnly]) { 260 self->streamMode = NGStreamMode_writeOnly; 261 openFlags = GENERIC_WRITE; 262 shareMode = FILE_SHARE_WRITE; 263 } 264 else if ([_mode isEqualToString:NGFileReadWrite]) { 265 self->streamMode = NGStreamMode_readWrite; 266 openFlags = GENERIC_READ | GENERIC_WRITE; 267 shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; 268 } 269 else { 270 [[[NGUnknownStreamModeException alloc] 271 initWithStream:self mode:_mode] raise]; 272 return NO; 273 } 274 275 self->fh = CreateFile([self->systemPath fileSystemRepresentation], 276 openFlags, shareMode, NULL, 277 OPEN_ALWAYS, // same as the Unix O_CREAT flag 278 0, // security flags ? 279 NULL); 280 281 if (self->fh == INVALID_HANDLE_VALUE) 282 [NGCouldNotOpenStreamException raiseWithStream:self]; 283 284#else 285 int openFlags; // flags passed to open() call 286 287 if (self->fd != NGInvalidUnixDescriptor) 288 [self close]; // if stream is open, close and reopen 289 290 if ([_mode isEqualToString:NGFileReadOnly]) { 291 self->streamMode = NGStreamMode_readOnly; 292 openFlags = O_RDONLY; 293 } 294 else if ([_mode isEqualToString:NGFileWriteOnly]) { 295 self->streamMode = NGStreamMode_writeOnly; 296 openFlags = O_WRONLY | O_CREAT; 297 } 298 else if ([_mode isEqualToString:NGFileReadWrite]) { 299 self->streamMode = NGStreamMode_readWrite; 300 openFlags = O_RDWR | O_CREAT; 301 } 302 else if ([_mode isEqualToString:NGFileAppend]) { 303 self->streamMode = NGStreamMode_writeOnly; 304 openFlags = O_WRONLY | O_CREAT | O_APPEND; 305 } 306 else if ([_mode isEqualToString:NGFileReadAppend]) { 307 self->streamMode = NGStreamMode_readWrite; 308 openFlags = O_RDWR | O_CREAT | O_APPEND; 309 } 310 else { 311 [[[NGUnknownStreamModeException alloc] 312 initWithStream:self mode:_mode] raise]; 313 return NO; 314 } 315 316 self->fd = open([self->systemPath fileSystemRepresentation], 317 openFlags, 318 NGFileCreationMask); 319 320 if (self->fd == -1) { 321 self->fd = NGInvalidUnixDescriptor; 322 323 [NGCouldNotOpenStreamException raiseWithStream:self]; 324 return NO; 325 } 326#endif 327 328 self->markDelta = -1; // not marked 329 return YES; 330} 331 332- (BOOL)isOpen { 333#if defined(__MINGW32__) 334 return (self->fh != INVALID_HANDLE_VALUE) ? YES : NO; 335#else 336 return (self->fd != NGInvalidUnixDescriptor) ? YES : NO; 337#endif 338} 339 340// Foundation file handles 341 342- (void)resetFileHandle { // called by NSFileHandle on dealloc 343 self->handle = nil; 344} 345- (NSFileHandle *)fileHandle { 346 if (self->handle == nil) 347 self->handle = [[_NGConcreteFileStreamFileHandle allocWithZone:[self zone]] 348 initWithStream:self]; 349 return [self->handle autorelease]; 350} 351 352#if defined(__MINGW32__) 353- (HANDLE)windowsFileHandle { 354 return self->fh; 355} 356#endif 357 358- (int)fileDescriptor { 359#if defined(__MINGW32__) 360 return (int)[self fileHandle]; 361#else 362 return self->fd; 363#endif 364} 365 366// primitives 367 368static void _checkOpen(NGFileStream *self, NSString *_reason) { 369#if defined(__MINGW32__) 370 if (self->fh == INVALID_HANDLE_VALUE) 371 [NGStreamNotOpenException raiseWithStream:self reason:_reason]; 372#else 373 if (self->fd == NGInvalidUnixDescriptor) 374 [NGStreamNotOpenException raiseWithStream:self reason:_reason]; 375#endif 376} 377 378- (unsigned)readBytes:(void *)_buf count:(unsigned)_len { 379 // throws 380 // NGWriteOnlyStreamException when the stream is not readable 381 // NGStreamNotOpenException when the stream is not open 382 // NGEndOfStreamException when the end of the stream is reached 383 // NGStreamReadErrorException when the read call failed 384 385 _checkOpen(self, @"tried to read from a file stream which is closed"); 386 387 if (!NGCanReadInStreamMode(streamMode)) 388 [NGWriteOnlyStreamException raiseWithStream:self]; 389 390 { 391#if defined(__MINGW32__) 392 DWORD readResult = 0; 393 394 if (ReadFile(self->fh, _buf, _len, &readResult, NULL) == FALSE) { 395 DWORD lastErr = GetLastError(); 396 397 if (lastErr == ERROR_HANDLE_EOF) 398 [NGEndOfStreamException raiseWithStream:self]; 399 else 400 [NGStreamReadErrorException raiseWithStream:self errorCode:lastErr]; 401 } 402 if (readResult == 0) 403 [NGEndOfStreamException raiseWithStream:self]; 404#else 405 int readResult; 406 int retryCount = 0; 407 408 do { 409 readResult = read(self->fd, _buf, _len); 410 411 if (readResult == 0) 412 [NGEndOfStreamException raiseWithStream:self]; 413 else if (readResult == -1) { 414 int errCode = errno; 415 416 if (errCode == EINTR) 417 // system call was interrupted 418 retryCount++; 419 else 420 [NGStreamReadErrorException raiseWithStream:self errorCode:errCode]; 421 } 422 } 423 while ((readResult <= 0) && (retryCount < 10)); 424 425 if (retryCount >= 10) 426 [NGStreamReadErrorException raiseWithStream:self errorCode:EINTR]; 427#endif 428 429 NSAssert(readResult > 0, @"invalid read method state"); 430 431 // adjust mark 432 if (self->markDelta != -1) 433 self->markDelta += readResult; // increase delta 434 435 return readResult; 436 } 437} 438 439- (unsigned)writeBytes:(const void *)_buf count:(unsigned)_len { 440 // throws 441 // NGReadOnlyStreamException when the stream is not writeable 442 // NGStreamNotOpenException when the stream is not open 443 // NGStreamWriteErrorException when the write call failed 444 445 _checkOpen(self, @"tried to write to a file stream which is closed"); 446 447 if (!NGCanWriteInStreamMode(streamMode)) 448 [NGReadOnlyStreamException raiseWithStream:self]; 449 450 { 451#if defined(__MINGW32__) 452 DWORD writeResult = 0; 453 454 if (WriteFile(self->fh, _buf, _len, &writeResult, NULL) == FALSE) { 455 DWORD errorCode = GetLastError(); 456 457 switch (errorCode) { 458 case ERROR_INVALID_HANDLE: 459 [NGStreamWriteErrorException raiseWithStream:self 460 reason:@"incorrect file handle"]; 461 break; 462 case ERROR_WRITE_PROTECT: 463 [NGStreamWriteErrorException raiseWithStream:self 464 reason:@"disk write protected"]; 465 break; 466 case ERROR_NOT_READY: 467 [NGStreamWriteErrorException raiseWithStream:self 468 reason:@"the drive is not ready"]; 469 break; 470 case ERROR_HANDLE_EOF: 471 [NGStreamWriteErrorException raiseWithStream:self 472 reason:@"reached end of file"]; 473 break; 474 case ERROR_DISK_FULL: 475 [NGStreamWriteErrorException raiseWithStream:self 476 reason:@"disk is full"]; 477 break; 478 479 default: 480 [NGStreamWriteErrorException raiseWithStream:self 481 errorCode:GetLastError()]; 482 } 483 484 NSLog(@"invalid program state, aborting"); 485 abort(); 486 } 487#else 488 int writeResult; 489 int retryCount = 0; 490 491 do { 492 writeResult = write(self->fd, _buf, _len); 493 494 if (writeResult == -1) { 495 int errCode = errno; 496 497 if (errCode == EINTR) 498 // system call was interrupted 499 retryCount++; 500 else 501 [NGStreamWriteErrorException raiseWithStream:self errorCode:errno]; 502 } 503 } 504 while ((writeResult == -1) && (retryCount < 10)); 505 506 if (retryCount >= 10) 507 [NGStreamWriteErrorException raiseWithStream:self errorCode:EINTR]; 508#endif 509 510 return writeResult; 511 } 512} 513 514- (BOOL)close { 515#if defined(__MINGW32__) 516 if (self->fh == INVALID_HANDLE_VALUE) { 517 NSLog(@"tried to close already closed stream %@", self); 518 return YES; /* not signaled as an error .. */ 519 } 520 521 if (CloseHandle(self->fh) == FALSE) { 522 [NGCouldNotCloseStreamException raiseWithStream:self]; 523 return NO; 524 } 525 526 self->fh = INVALID_HANDLE_VALUE; 527#else 528 if (self->fd == NGInvalidUnixDescriptor) { 529 NSLog(@"tried to close already closed stream %@", self); 530 return YES; /* not signaled as an error .. */ 531 } 532 533 if (close(self->fd) != 0) { 534 [NGCouldNotCloseStreamException raiseWithStream:self]; 535 return NO; 536 } 537 538 self->fd = NGInvalidUnixDescriptor; 539#endif 540 self->markDelta = -1; 541 return YES; 542} 543 544- (NGStreamMode)mode { 545 return self->streamMode; 546} 547- (BOOL)isRootStream { 548 return YES; 549} 550 551#if defined(__MINGW32__) 552- (BOOL)flush { 553 if (self->fh != INVALID_HANDLE_VALUE) 554 FlushFileBuffers(self->fh); 555 return YES; 556} 557#endif 558 559// blocking 560 561#if defined(__MINGW32__) 562- (BOOL)wouldBlockInMode:(NGStreamMode)_mode { 563 NSLog(@"%@ not supported in Windows environment !", 564 NSStringFromSelector(_cmd)); 565 return YES; 566} 567#else 568- (BOOL)wouldBlockInMode:(NGStreamMode)_mode { 569 short events = 0; 570 571 if (self->fd == NGInvalidUnixDescriptor) 572 return NO; 573 574 if (NGCanReadInStreamMode(_mode)) events |= POLLRDNORM; 575 if (NGCanWriteInStreamMode(_mode)) events |= POLLWRNORM; 576 577 // timeout of 0 means return immediatly 578 return (NGPollDescriptor(self->fd, events, 0) == 1 ? NO : YES); 579} 580#endif 581 582// marking 583 584- (BOOL)mark { 585 self->markDelta = 0; 586 return YES; 587} 588- (BOOL)rewind { 589 if (![self moveByOffset:-(self->markDelta)]) 590 return NO; 591 self->markDelta = -1; 592 return YES; 593} 594- (BOOL)markSupported { 595 return YES; 596} 597 598// NGPositionableStream 599 600- (BOOL)moveToLocation:(unsigned)_location { 601 self->markDelta = -1; 602 603#if defined(__MINGW32__) 604 if (SetFilePointer(self->fh, _location, NULL, FILE_BEGIN) == -1) { 605 [NGStreamSeekErrorException raiseWithStream:self errorCode:GetLastError()]; 606 return NO; 607 } 608#else 609 if (lseek(self->fd, _location, SEEK_SET) == -1) { 610 [NGStreamSeekErrorException raiseWithStream:self errorCode:errno]; 611 return NO; 612 } 613#endif 614 return YES; 615} 616- (BOOL)moveByOffset:(int)_delta { 617 self->markDelta += _delta; 618 619#if defined(__MINGW32__) 620 if (SetFilePointer(self->fh, _delta, NULL, FILE_CURRENT) == -1) { 621 [NGStreamSeekErrorException raiseWithStream:self errorCode:GetLastError()]; 622 return NO; 623 } 624#else 625 if (lseek(self->fd, _delta, SEEK_CUR) == -1) { 626 [NGStreamSeekErrorException raiseWithStream:self errorCode:errno]; 627 return NO; 628 } 629#endif 630 return YES; 631} 632 633/* description */ 634 635- (NSString *)description { 636 return [NSString stringWithFormat: 637 @"<%@[0x%p] path=%@ mode=%@>", 638 NSStringFromClass([self class]), self, 639 self->systemPath ? self->systemPath : (NSString *)@"nil", 640 [self modeDescription]]; 641} 642 643@end /* NGFileStream */ 644 645 646@implementation _NGConcreteFileStreamFileHandle 647 648// accessors 649 650#if defined(__MINGW32__) 651- (HANDLE)fileHandle { 652 return [(NGFileStream *)stream windowsFileHandle]; 653} 654#endif 655 656- (int)fileDescriptor { 657 return [(NGFileStream *)stream fileDescriptor]; 658} 659 660@end 661