1// fileio.cpp --
2// $Id: fileio.cpp 1230 2007-03-09 15:58:53Z jcw $
3// This is part of Metakit, see http://www.equi4.com/metakit.html
4
5/** @file
6 * Implementation of c4_FileStrategy and c4_FileStream
7 */
8
9#include "header.h"
10#include "mk4io.h"
11
12#if q4_WIN32
13#if q4_MSVC && !q4_STRICT
14#pragma warning(disable: 4201) // nonstandard extension used : ...
15#endif
16#define WIN32_LEAN_AND_MEAN
17#include <windows.h>
18#include <io.h>
19#include <fcntl.h>
20#include <sys/stat.h>
21#endif
22
23#if q4_UNIX && HAVE_MMAP
24#include <sys/types.h>
25#include <sys/mman.h>
26#endif
27
28#if q4_UNIX
29#include <unistd.h>
30#include <fcntl.h>
31#endif
32
33#if q4_WINCE
34#define _get_osfhandle(x) x
35#endif
36
37#ifndef _O_NOINHERIT
38#define _O_NOINHERIT 0
39#endif
40
41/////////////////////////////////////////////////////////////////////////////
42//
43//  The "Carbon" version of a build on Macintosh supports running under
44//  either MacOS 7..9 (which has no mmap), or MacOS X (which has mmap).
45//  The logic below was adapted from a contribution by Paul Snively, it
46//  decides at run time which case it is, and switches I/O calls to match.
47
48#if defined (q4_CARBON) && q4_CARBON
49//#if q4_MAC && !defined (__MACH__) && (!q4_MWCW || __MWERKS__ >= 0x3000)
50#undef HAVE_MMAP
51#define HAVE_MMAP 1
52
53#include <CFBundle.h>
54#include <Folders.h>
55
56#define PROT_NONE        0x00
57#define PROT_READ        0x01
58#define PROT_WRITE       0x02
59#define PROT_EXEC        0x04
60
61#define MAP_SHARED       0x0001
62#define MAP_PRIVATE      0x0002
63
64#define MAP_FIXED        0x0010
65#define MAP_RENAME       0x0020
66#define MAP_NORESERVE    0x0040
67#define MAP_INHERIT      0x0080
68#define MAP_NOEXTEND     0x0100
69#define MAP_HASSEMAPHORE 0x0200
70
71typedef unsigned long t4_u32;
72
73static t4_u32 sfwRefCount = 0;
74static CFBundleRef systemFramework = NULL;
75
76static char *fake_mmap(char *, t4_u32, int, int, int, long long) {
77  return (char*) - 1L;
78}
79
80static int fake_munmap(char *, t4_u32) {
81  return 0;
82}
83
84static FILE *(*my_fopen)(const char *, const char*) = fopen;
85static int(*my_fclose)(FILE*) = fclose;
86static long(*my_ftell)(FILE*) = ftell;
87static int(*my_fseek)(FILE *, long, int) = fseek;
88static t4_u32(*my_fread)(void *ptr, t4_u32, t4_u32, FILE*) = fread;
89static t4_u32(*my_fwrite)(const void *ptr, t4_u32, t4_u32, FILE*) = fwrite;
90static int(*my_ferror)(FILE*) = ferror;
91static int(*my_fflush)(FILE*) = fflush;
92static int(*my_fileno)(FILE*) = fileno;
93static char *(*my_mmap)(char *, t4_u32, int, int, int, long long) = fake_mmap;
94static int(*my_munmap)(char *, t4_u32) = fake_munmap;
95
96static void InitializeIO() {
97  if (sfwRefCount++)
98    return ;
99  // race condition, infinitesimal risk
100
101  FSRef theRef;
102  if (FSFindFolder(kOnAppropriateDisk, kFrameworksFolderType, false, &theRef)
103    == noErr) {
104    CFURLRef fw = CFURLCreateFromFSRef(kCFAllocatorSystemDefault, &theRef);
105    if (fw) {
106      CFURLRef bd = CFURLCreateCopyAppendingPathComponent
107        (kCFAllocatorSystemDefault, fw, CFSTR("System.framework"), false);
108      CFRelease(fw);
109      if (bd) {
110        systemFramework = CFBundleCreate(kCFAllocatorSystemDefault, bd);
111        CFRelease(bd);
112      }
113    }
114    if (!systemFramework || !CFBundleLoadExecutable(systemFramework))
115      return ;
116#define F(x) CFBundleGetFunctionPointerForName(systemFramework, CFSTR(#x))
117    my_fopen = (FILE *(*)(const char *, const char*))F(fopen);
118    my_fclose = (int(*)(FILE*))F(fclose);
119    my_ftell = (long(*)(FILE*))F(ftell);
120    my_fseek = (int(*)(FILE *, long, int))F(fseek);
121    my_fread = (t4_u32(*)(void *ptr, t4_u32, t4_u32, FILE*))F(fread);
122    my_fwrite = (t4_u32(*)(const void *ptr, t4_u32, t4_u32, FILE*))F(fwrite);
123    my_ferror = (int(*)(FILE*))F(ferror);
124    my_fflush = (int(*)(FILE*))F(fflush);
125    my_fileno = (int(*)(FILE*))F(fileno);
126    my_mmap = (char *(*)(char *, t4_u32, int, int, int, long long))F(mmap);
127    my_munmap = (int(*)(char *, t4_u32))F(munmap);
128#undef F
129    d4_assert(my_fopen && my_fclose && my_ftell && my_fseek && my_fread &&
130      my_fwrite && my_ferror && my_fflush && my_fileno && my_mmap && my_munmap);
131  }
132}
133
134static void FinalizeIO() {
135  if (--sfwRefCount)
136    return ;
137  // race condition, infinitesimal risk
138
139  if (systemFramework) {
140    CFBundleUnloadExecutable(systemFramework);
141    CFRelease(systemFramework);
142    systemFramework = 0;
143  }
144}
145
146#define fopen my_fopen
147#define fclose  my_fclose
148#define ftell my_ftell
149#define fseek my_fseek
150#define fread my_fread
151#define fwrite  my_fwrite
152#define ferror  my_ferror
153#define fflush  my_fflush
154#define fileno  my_fileno
155#define mmap  my_mmap
156#define munmap  my_munmap
157
158#else
159
160#define InitializeIO()
161#define FinalizeIO()
162
163#endif
164
165/////////////////////////////////////////////////////////////////////////////
166
167#if q4_CHECK
168#include <stdlib.h>
169
170void f4_AssertionFailed(const char *cond_, const char *file_, int line_) {
171  fprintf(stderr, "Assertion failed: %s (file %s, line %d)\n", cond_, file_,
172    line_);
173  abort();
174}
175
176#endif //q4_CHECK
177
178/////////////////////////////////////////////////////////////////////////////
179// c4_FileStream
180
181c4_FileStream::c4_FileStream(FILE *stream_, bool owned_): _stream(stream_),
182  _owned(owned_){}
183
184c4_FileStream::~c4_FileStream() {
185  if (_owned)
186    fclose(_stream);
187}
188
189int c4_FileStream::Read(void *buffer_, int length_) {
190  d4_assert(_stream != 0);
191
192  return (int)fread(buffer_, 1, length_, _stream);
193}
194
195bool c4_FileStream::Write(const void *buffer_, int length_) {
196  d4_assert(_stream != 0);
197
198  return (int)fwrite(buffer_, 1, length_, _stream) == length_;
199}
200
201/////////////////////////////////////////////////////////////////////////////
202// c4_FileStrategy
203
204c4_FileStrategy::c4_FileStrategy(FILE *file_): _file(file_), _cleanup(0) {
205  InitializeIO();
206  ResetFileMapping();
207}
208
209c4_FileStrategy::~c4_FileStrategy() {
210  _file = 0;
211  ResetFileMapping();
212
213  if (_cleanup)
214    fclose(_cleanup);
215
216  d4_assert(_mapStart == 0);
217  FinalizeIO();
218}
219
220bool c4_FileStrategy::IsValid()const {
221  return _file != 0;
222}
223
224t4_i32 c4_FileStrategy::FileSize() {
225  d4_assert(_file != 0);
226
227  long size =  - 1;
228
229  long old = ftell(_file);
230  if (old >= 0 && fseek(_file, 0, 2) == 0) {
231    long pos = ftell(_file);
232    if (fseek(_file, old, 0) == 0)
233      size = pos;
234  }
235
236  if (size < 0)
237    _failure = ferror(_file);
238
239  return size;
240}
241
242t4_i32 c4_FileStrategy::FreshGeneration() {
243  d4_assert(false);
244  return 0;
245}
246
247void c4_FileStrategy::ResetFileMapping() {
248#if q4_WIN32
249  if (_mapStart != 0) {
250    _mapStart -= _baseOffset;
251    d4_dbgdef(BOOL g = )::UnmapViewOfFile((char*)_mapStart);
252    d4_assert(g);
253    _mapStart = 0;
254    _dataSize = 0;
255  }
256
257  if (_file != 0) {
258    t4_i32 len = FileSize();
259
260    if (len > 0) {
261      FlushFileBuffers((HANDLE)_get_osfhandle(_fileno(_file)));
262      HANDLE h = ::CreateFileMapping((HANDLE)_get_osfhandle(_fileno(_file)), 0,
263        PAGE_READONLY, 0, len, 0);
264
265      if (h) {
266        _mapStart = (t4_byte*)::MapViewOfFile(h, FILE_MAP_READ, 0, 0, len);
267
268        if (_mapStart != 0) {
269          _mapStart += _baseOffset;
270          _dataSize = len - _baseOffset;
271        }
272
273        d4_dbgdef(BOOL f = )::CloseHandle(h);
274        d4_assert(f);
275      }
276    }
277  }
278#elif HAVE_MMAP && !NO_MMAP
279  if (_mapStart != 0) {
280    _mapStart -= _baseOffset;
281    munmap((char*)_mapStart, _baseOffset + _dataSize); // also loses const
282    _mapStart = 0;
283    _dataSize = 0;
284  }
285
286  if (_file != 0) {
287    t4_i32 len = FileSize();
288
289    if (len > 0) {
290      _mapStart = (const t4_byte*)mmap(0, len, PROT_READ, MAP_SHARED, fileno
291        (_file), 0);
292      if (_mapStart != (void*) - 1L) {
293        _mapStart += _baseOffset;
294        _dataSize = len - _baseOffset;
295      } else
296        _mapStart = 0;
297    }
298  }
299#endif
300}
301
302#if q4_WIN32 && !q4_BORC && !q4_WINCE
303static DWORD GetPlatformId() {
304  static OSVERSIONINFO os;
305  if (os.dwPlatformId == 0) {
306    os.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
307    GetVersionEx(&os);
308  }
309  return os.dwPlatformId;
310}
311
312#endif
313
314bool c4_FileStrategy::DataOpen(const char *fname_, int mode_) {
315  d4_assert(!_file);
316
317#if q4_WIN32 && !q4_BORC && !q4_WINCE
318  int flags = _O_BINARY | _O_NOINHERIT | (mode_ > 0 ? _O_RDWR : _O_RDONLY);
319  int fd =  - 1;
320
321  if (GetPlatformId() != VER_PLATFORM_WIN32_NT)
322    fd = _open(fname_, flags);
323  if (fd ==  - 1) {
324    WCHAR wName[MAX_PATH];
325    MultiByteToWideChar(CP_UTF8, 0, fname_,  - 1, wName, MAX_PATH);
326    fd = _wopen(wName, flags);
327  }
328  if (fd !=  - 1)
329    _cleanup = _file = _fdopen(fd, mode_ > 0 ? "r+b" : "rb");
330#else
331  _cleanup = _file = fopen(fname_, mode_ > 0 ? "r+b" : "rb");
332#if q4_UNIX
333  if (_file != 0)
334    fcntl(fileno(_file), F_SETFD, FD_CLOEXEC);
335#endif //q4_UNIX
336#endif //q4_WIN32 && !q4_BORC && !q4_WINCE
337
338  if (_file != 0) {
339    ResetFileMapping();
340    return true;
341  }
342
343  if (mode_ > 0) {
344#if q4_WIN32 && !q4_BORC && !q4_WINCE
345    WCHAR wName[MAX_PATH];
346    MultiByteToWideChar(CP_UTF8, 0, fname_,  - 1, wName, MAX_PATH);
347    fd = _wopen(wName, flags | _O_CREAT, _S_IREAD | _S_IWRITE);
348    if (fd !=  - 1)
349      _cleanup = _file = _fdopen(fd, "w+b");
350#else
351    _cleanup = _file = fopen(fname_, "w+b");
352#if q4_UNIX
353    if (_file != 0)
354      fcntl(fileno(_file), F_SETFD, FD_CLOEXEC);
355#endif //q4_UNIX
356#endif //q4_WIN32 && !q4_BORC && !q4_WINCE
357  }
358
359  //d4_assert(_file != 0);
360  return false;
361}
362
363int c4_FileStrategy::DataRead(t4_i32 pos_, void *buf_, int len_) {
364  d4_assert(_baseOffset + pos_ >= 0);
365  d4_assert(_file != 0);
366
367  //printf("DataRead at %d len %d\n", pos_, len_);
368  return fseek(_file, _baseOffset + pos_, 0) != 0 ?  - 1: (int)fread(buf_, 1,
369    len_, _file);
370}
371
372void c4_FileStrategy::DataWrite(t4_i32 pos_, const void *buf_, int len_) {
373  d4_assert(_baseOffset + pos_ >= 0);
374  d4_assert(_file != 0);
375#if 0
376  if (_mapStart <= buf_ && buf_ < _mapStart + _dataSize) {
377    printf("DataWrite %08x at %d len %d (map %d)\n", buf_, pos_, len_, (const
378      t4_byte*)buf_ - _mapStart + _baseOffset);
379  } else {
380    printf("DataWrite %08x at %d len %d\n", buf_, pos_, len_);
381  }
382  fprintf(stderr,
383    "  _mapStart %08x _dataSize %d buf_ %08x len_ %d _baseOffset %d\n",
384    _mapStart, _dataSize, buf_, len_, _baseOffset);
385  printf("  _mapStart %08x _dataSize %d buf_ %08x len_ %d _baseOffset %d\n",
386    _mapStart, _dataSize, buf_, len_, _baseOffset);
387  fflush(stdout);
388#endif
389
390#if q4_WIN32 || __hpux || __MACH__
391  // if (buf_ >= _mapStart && buf_ <= _mapLimit - len_)
392
393  // a horrendous hack to allow file mapping for Win95 on network drive
394  // must use a temp buf to avoid write from mapped file to same file
395  //
396  //  6-Feb-1999  --  this workaround is not thread safe
397  // 30-Nov-2001  --  changed to use the stack so now it is
398  // 28-Oct-2002  --  added HP/UX to the mix, to avoid hard lockup
399  char tempBuf[4096];
400  d4_assert(len_ <= sizeof tempBuf);
401  buf_ = memcpy(tempBuf, buf_, len_);
402#endif
403
404  if (fseek(_file, _baseOffset + pos_, 0) != 0 || (int)fwrite(buf_, 1, len_,
405    _file) != len_) {
406    _failure = ferror(_file);
407    d4_assert(_failure != 0);
408    d4_assert(true); // always force an assertion failure in debug mode
409  }
410}
411
412void c4_FileStrategy::DataCommit(t4_i32 limit_) {
413  d4_assert(_file != 0);
414
415  if (fflush(_file) < 0) {
416    _failure = ferror(_file);
417    d4_assert(_failure != 0);
418    d4_assert(true); // always force an assertion failure in debug mode
419    return ;
420  }
421
422  if (limit_ > 0) {
423#if 0 // can't truncate file in a portable way!
424    // unmap the file first, WinNT is more picky about this than Win95
425    FILE *save = _file;
426
427    _file = 0;
428    ResetFileMapping();
429    _file = save;
430
431    _file->SetLength(limit_); // now we can resize the file
432#endif
433    ResetFileMapping(); // remap, since file length may have changed
434  }
435}
436
437/////////////////////////////////////////////////////////////////////////////
438