]> err.no Git - sope/blob - sope-core/NGStreams/NGFileStream.m
Drop apache 1 build-dependency
[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 <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
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
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
166 static 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
368 static 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