]> err.no Git - sope/blob - sope-core/NGStreams/NGFileStream.m
fixed copyrights for 2005
[sope] / sope-core / NGStreams / NGFileStream.m
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 "common.h"
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"
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
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";
58
59 static const int NGInvalidUnixDescriptor = -1;
60 static const int NGFileCreationMask      = 0666; // rw-rw-rw-
61
62 @interface _NGConcreteFileStreamFileHandle : NGConcreteStreamFileHandle
63 @end
64
65 NGStreams_DECLARE id<NGInputStream>  NGIn  = nil;
66 NGStreams_DECLARE id<NGOutputStream> NGOut = nil;
67 NGStreams_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
116 void 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 mode:NGStreamMode_readOnly];
129     to = [[NGFileStream alloc] __initWithDescriptor:1 mode:NGStreamMode_writeOnly];
130     te = [[NGFileStream alloc] __initWithDescriptor:2 mode:NGStreamMode_writeOnly];
131 #endif
132
133     NGIn  = [[NGBufferedStream alloc] initWithSource:(id)ti];
134     NGOut = [[NGBufferedStream alloc] initWithSource:(id)to];
135     NGErr = [[NGBufferedStream alloc] initWithSource:(id)te];
136
137     [ti release]; ti = nil;
138     [to release]; to = nil;
139     [te release]; te = nil;
140   }
141 }
142
143 + (void)_makeThreadSafe:(NSNotification *)_notification {
144   NGLockingStream *li = nil, *lo = nil, *le = nil;
145   
146   if ([NGIn isKindOfClass:[NGLockingStream class]])
147     return;
148
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;
152 }
153
154 + (void)_flushForExit:(NSNotification *)_notification {
155   [NGIn  flush];
156   [NGOut flush];
157   [NGErr flush];
158 }
159
160 static void _flushForExit(void) {
161   [NGIn  flush];
162   [NGOut flush];
163   [NGErr flush];
164 }
165
166 + (void)initialize {
167   BOOL isInitialized = NO;
168   if (!isInitialized) {
169     isInitialized = YES;
170
171     if ([NSThread isMultiThreaded])
172       [self _makeThreadSafe:nil];
173     else {
174       [[NSNotificationCenter defaultCenter]
175                              addObserver:self
176                              selector:@selector(_makeThreadSafe:)
177                              name:NSWillBecomeMultiThreadedNotification
178                              object:nil];
179     }
180     atexit(_flushForExit);
181   }
182 }
183
184 /* normal file stream */
185
186 - (id)init {
187   if ((self = [super init])) {
188     self->streamMode = NGStreamMode_undefined;
189     self->systemPath = nil;
190     self->markDelta  = -1;
191     self->handle     = nil;
192 #if defined(__MINGW32__)
193     self->fh         = INVALID_HANDLE_VALUE;
194 #else
195     self->fd         = NGInvalidUnixDescriptor;
196 #endif
197   }
198   return self;
199 }
200
201 - (id)initWithPath:(NSString *)_path {
202   if ((self = [self init])) {
203     self->systemPath = [_path copy];
204   }
205   return self;
206 }
207
208 - (id)initWithFileHandle:(NSFileHandle *)_handle {
209   if ((self = [self init])) {
210 #if defined(__MINGW32__)
211     self->fh = [_handle nativeHandle];
212 #else
213     self->fd = [_handle fileDescriptor];
214 #endif
215   }
216   return self;
217 }
218
219 - (void)gcFinalize {
220   if ([self isOpen]) {
221 #if DEBUG && 0
222     NSLog(@"NGFileStream(gcFinalize): closing %@", self);
223 #endif
224     [self close];
225   }
226 }
227 - (void)dealloc {
228   [self gcFinalize];
229   self->streamMode = NGStreamMode_undefined;
230   [self->systemPath release]; self->systemPath = nil;
231   self->handle = nil;
232   [super dealloc];
233 }
234
235 // opening
236
237 - (BOOL)openInMode:(NSString *)_mode {
238   // throws
239   //   NGUnknownStreamModeException  when _mode is invalid
240   //   NGCouldNotOpenStreamException when the file could not be opened
241 #if defined(__MINGW32__)
242   DWORD openFlags;
243   DWORD shareMode;
244
245   if (self->fh != INVALID_HANDLE_VALUE)
246     [self close]; // if stream is open, close and reopen
247
248   if ([_mode isEqualToString:NGFileReadOnly]) {
249     self->streamMode = NGStreamMode_readOnly;
250     openFlags = GENERIC_READ;
251     shareMode = FILE_SHARE_READ;
252   }
253   else if ([_mode isEqualToString:NGFileWriteOnly]) {
254     self->streamMode = NGStreamMode_writeOnly;
255     openFlags = GENERIC_WRITE;
256     shareMode = FILE_SHARE_WRITE;
257   }
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;
262   }
263   else {
264     [[[NGUnknownStreamModeException alloc]
265                                     initWithStream:self mode:_mode] raise];
266     return NO;
267   }
268
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 ?
273                         NULL);
274
275   if (self->fh == INVALID_HANDLE_VALUE)
276     [NGCouldNotOpenStreamException raiseWithStream:self];
277
278 #else
279   int openFlags; // flags passed to open() call
280
281   if (self->fd != NGInvalidUnixDescriptor)
282     [self close]; // if stream is open, close and reopen
283
284   if ([_mode isEqualToString:NGFileReadOnly]) {
285     self->streamMode = NGStreamMode_readOnly;
286     openFlags = O_RDONLY;
287   }
288   else if ([_mode isEqualToString:NGFileWriteOnly]) {
289     self->streamMode = NGStreamMode_writeOnly;
290     openFlags = O_WRONLY | O_CREAT;
291   }
292   else if ([_mode isEqualToString:NGFileReadWrite]) {
293     self->streamMode = NGStreamMode_readWrite;
294     openFlags = O_RDWR | O_CREAT;
295   }
296   else if ([_mode isEqualToString:NGFileAppend]) {
297     self->streamMode = NGStreamMode_writeOnly;
298     openFlags = O_WRONLY | O_CREAT | O_APPEND;
299   }
300   else if ([_mode isEqualToString:NGFileReadAppend]) {
301     self->streamMode = NGStreamMode_readWrite;
302     openFlags = O_RDWR | O_CREAT | O_APPEND;
303   }
304   else {
305     [[[NGUnknownStreamModeException alloc]
306               initWithStream:self mode:_mode] raise];
307     return NO;
308   }
309
310   self->fd = open([self->systemPath fileSystemRepresentation],
311                   openFlags,
312                   NGFileCreationMask);
313
314   if (self->fd == -1) {
315     self->fd = NGInvalidUnixDescriptor;
316
317     [NGCouldNotOpenStreamException raiseWithStream:self];
318     return NO;
319   }
320 #endif
321   
322   self->markDelta = -1; // not marked
323   return YES;
324 }
325
326 - (BOOL)isOpen {
327 #if defined(__MINGW32__)
328   return (self->fh != INVALID_HANDLE_VALUE) ? YES : NO;
329 #else
330   return (self->fd != NGInvalidUnixDescriptor) ? YES : NO;
331 #endif
332 }
333
334 // Foundation file handles
335
336 - (void)resetFileHandle { // called by NSFileHandle on dealloc
337   self->handle = nil;
338 }
339 - (NSFileHandle *)fileHandle {
340   if (self->handle == nil)
341     self->handle = [[_NGConcreteFileStreamFileHandle allocWithZone:[self zone]]
342                                                      initWithStream:self];
343   return [self->handle autorelease];
344 }
345
346 #if defined(__MINGW32__)
347 - (HANDLE)windowsFileHandle {
348   return self->fh;
349 }
350 #endif
351
352 - (int)fileDescriptor {
353 #if defined(__MINGW32__)
354   return (int)[self fileHandle];
355 #else
356   return self->fd;
357 #endif
358 }
359
360 // primitives
361
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];
366 #else
367   if (self->fd == NGInvalidUnixDescriptor)
368     [NGStreamNotOpenException raiseWithStream:self reason:_reason];
369 #endif
370 }
371
372 - (unsigned)readBytes:(void *)_buf count:(unsigned)_len {
373   // throws
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
378
379   _checkOpen(self, @"tried to read from a file stream which is closed");
380
381   if (!NGCanReadInStreamMode(streamMode))
382     [NGWriteOnlyStreamException raiseWithStream:self];
383
384   {
385 #if defined(__MINGW32__)
386     DWORD readResult = 0;
387
388     if (ReadFile(self->fh, _buf, _len, &readResult, NULL) == FALSE) {
389       DWORD lastErr = GetLastError();
390
391       if (lastErr == ERROR_HANDLE_EOF)
392         [NGEndOfStreamException raiseWithStream:self];
393       else
394         [NGStreamReadErrorException raiseWithStream:self errorCode:lastErr];
395     }
396     if (readResult == 0)
397       [NGEndOfStreamException raiseWithStream:self];
398 #else
399     int readResult;
400     int retryCount = 0;
401     
402     do {
403       readResult = read(self->fd, _buf, _len);
404       
405       if (readResult == 0)
406         [NGEndOfStreamException raiseWithStream:self];
407       else if (readResult == -1) {
408         int errCode = errno;
409
410         if (errCode == EINTR)
411           // system call was interrupted
412           retryCount++;
413         else
414           [NGStreamReadErrorException raiseWithStream:self errorCode:errCode];
415       }
416     }
417     while ((readResult <= 0) && (retryCount < 10));
418
419     if (retryCount >= 10)
420       [NGStreamReadErrorException raiseWithStream:self errorCode:EINTR];
421 #endif
422     
423     NSAssert(readResult > 0, @"invalid read method state");
424
425     // adjust mark
426     if (self->markDelta != -1)
427       self->markDelta += readResult; // increase delta
428     
429     return readResult;
430   }
431 }
432
433 - (unsigned)writeBytes:(const void *)_buf count:(unsigned)_len {
434   // throws
435   //   NGReadOnlyStreamException   when the stream is not writeable
436   //   NGStreamNotOpenException    when the stream is not open
437   //   NGStreamWriteErrorException when the write call failed
438   
439   _checkOpen(self, @"tried to write to a file stream which is closed");
440   
441   if (!NGCanWriteInStreamMode(streamMode))
442     [NGReadOnlyStreamException raiseWithStream:self];
443
444   {
445 #if defined(__MINGW32__)
446     DWORD writeResult = 0;
447
448     if (WriteFile(self->fh, _buf, _len, &writeResult, NULL) == FALSE) {
449       DWORD errorCode = GetLastError();
450
451       switch (errorCode) {
452         case ERROR_INVALID_HANDLE:
453           [NGStreamWriteErrorException raiseWithStream:self
454                                        reason:@"incorrect file handle"];
455           break;
456         case ERROR_WRITE_PROTECT:
457           [NGStreamWriteErrorException raiseWithStream:self
458                                        reason:@"disk write protected"];
459           break;
460         case ERROR_NOT_READY:
461           [NGStreamWriteErrorException raiseWithStream:self
462                                        reason:@"the drive is not ready"];
463           break;
464         case ERROR_HANDLE_EOF:
465           [NGStreamWriteErrorException raiseWithStream:self
466                                        reason:@"reached end of file"];
467           break;
468         case ERROR_DISK_FULL:
469           [NGStreamWriteErrorException raiseWithStream:self
470                                        reason:@"disk is full"];
471           break;
472         
473         default:
474           [NGStreamWriteErrorException raiseWithStream:self
475                                        errorCode:GetLastError()];
476       }
477       
478       NSLog(@"invalid program state, aborting");
479       abort();
480     }
481 #else
482     int writeResult;
483     int retryCount = 0;
484
485     do {
486       writeResult = write(self->fd, _buf, _len);
487
488       if (writeResult == -1) {
489         int errCode = errno;
490
491         if (errCode == EINTR)
492           // system call was interrupted
493           retryCount++;
494         else
495           [NGStreamWriteErrorException raiseWithStream:self errorCode:errno];
496       }
497     }
498     while ((writeResult == -1) && (retryCount < 10));
499
500     if (retryCount >= 10)
501       [NGStreamWriteErrorException raiseWithStream:self errorCode:EINTR];
502 #endif
503     
504     return writeResult;
505   }
506 }
507
508 - (BOOL)close {
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 .. */
513   }
514
515   if (CloseHandle(self->fh) == FALSE) {
516     [NGCouldNotCloseStreamException raiseWithStream:self];
517     return NO;
518   }
519   
520   self->fh = INVALID_HANDLE_VALUE;
521 #else
522   if (self->fd == NGInvalidUnixDescriptor) {
523     NSLog(@"tried to close already closed stream %@", self);
524     return YES; /* not signaled as an error .. */
525   }
526
527   if (close(self->fd) != 0) {
528     [NGCouldNotCloseStreamException raiseWithStream:self];
529     return NO;
530   }
531   
532   self->fd = NGInvalidUnixDescriptor;
533 #endif
534   self->markDelta = -1;
535   return YES;
536 }
537
538 - (NGStreamMode)mode {
539   return self->streamMode;
540 }
541 - (BOOL)isRootStream {
542   return YES;
543 }
544
545 #if defined(__MINGW32__)
546 - (BOOL)flush {
547   if (self->fh != INVALID_HANDLE_VALUE)
548     FlushFileBuffers(self->fh);
549   return YES;
550 }
551 #endif
552
553 // blocking
554
555 #if defined(__MINGW32__)
556 - (BOOL)wouldBlockInMode:(NGStreamMode)_mode {
557   NSLog(@"%@ not supported in Windows environment !",
558         NSStringFromSelector(_cmd));
559   return YES;
560 }
561 #else
562 - (BOOL)wouldBlockInMode:(NGStreamMode)_mode {
563   short events = 0;
564
565   if (self->fd == NGInvalidUnixDescriptor)
566     return NO;
567
568   if (NGCanReadInStreamMode(_mode))  events |= POLLRDNORM;
569   if (NGCanWriteInStreamMode(_mode)) events |= POLLWRNORM;
570
571   // timeout of 0 means return immediatly
572   return (NGPollDescriptor(self->fd, events, 0) == 1 ? NO : YES);
573 }
574 #endif
575
576 // marking
577
578 - (BOOL)mark {
579   self->markDelta = 0;
580   return YES;
581 }
582 - (BOOL)rewind {
583   if (![self moveByOffset:-(self->markDelta)])
584     return NO;
585   self->markDelta = -1;
586   return YES;
587 }
588 - (BOOL)markSupported {
589   return YES;
590 }
591
592 // NGPositionableStream
593
594 - (BOOL)moveToLocation:(unsigned)_location {
595   self->markDelta = -1;
596
597 #if defined(__MINGW32__)
598   if (SetFilePointer(self->fh, _location, NULL, FILE_BEGIN) == -1) {
599     [NGStreamSeekErrorException raiseWithStream:self errorCode:GetLastError()];
600     return NO;
601   }
602 #else
603   if (lseek(self->fd, _location, SEEK_SET) == -1) {
604     [NGStreamSeekErrorException raiseWithStream:self errorCode:errno];
605     return NO;
606   }
607 #endif
608   return YES;
609 }
610 - (BOOL)moveByOffset:(int)_delta {
611   self->markDelta += _delta;
612   
613 #if defined(__MINGW32__)
614   if (SetFilePointer(self->fh, _delta, NULL, FILE_CURRENT) == -1) {
615     [NGStreamSeekErrorException raiseWithStream:self errorCode:GetLastError()];
616     return NO;
617   }
618 #else
619   if (lseek(self->fd, _delta, SEEK_CUR) == -1) {
620     [NGStreamSeekErrorException raiseWithStream:self errorCode:errno];
621     return NO;
622   }
623 #endif
624   return YES;
625 }
626
627 /* description */
628
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]];
635 }
636
637 @end
638
639 @implementation _NGConcreteFileStreamFileHandle
640
641 // accessors
642
643 #if defined(__MINGW32__)
644 - (HANDLE)fileHandle {
645   return [(NGFileStream *)stream windowsFileHandle];
646 }
647 #endif
648
649 - (int)fileDescriptor {
650   return [(NGFileStream *)stream fileDescriptor];
651 }
652
653 @end