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