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