/* * Copyright (c) 2008-2023 Jonathan Schleifer <js@nil.im> * * All rights reserved. * * This file is part of ObjFW. It may be distributed under the terms of the * Q Public License 1.0, which can be found in the file LICENSE.QPL included in * the packaging of this file. * * Alternatively, it may be distributed under the terms of the GNU General * Public License, either version 2 or 3, which can be found in the file * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this * file. */ #include "config.h" #define _LARGEFILE64_SOURCE #include <errno.h> #ifdef HAVE_FCNTL_H # include <fcntl.h> #endif #include "unistd_wrapper.h" #ifdef HAVE_SYS_STAT_H # include <sys/stat.h> #endif #import "OFFile.h" #import "OFLocale.h" #import "OFString.h" #import "OFSystemInfo.h" #import "OFInitializationFailedException.h" #import "OFInvalidArgumentException.h" #import "OFNotOpenException.h" #import "OFOpenItemFailedException.h" #import "OFOutOfMemoryException.h" #import "OFOutOfRangeException.h" #import "OFReadFailedException.h" #import "OFSeekFailedException.h" #import "OFWriteFailedException.h" #ifdef OF_WINDOWS # include <windows.h> #endif #ifdef OF_AMIGAOS # include <proto/exec.h> # include <proto/dos.h> #endif #ifdef OF_WII # include <fat.h> #endif #ifdef OF_NINTENDO_DS # include <stdbool.h> # include <filesystem.h> #endif #ifdef OF_NINTENDO_SWITCH # define id nx_id # include <switch.h> # undef id #endif #ifndef O_BINARY # define O_BINARY 0 #endif #ifndef O_CLOEXEC # define O_CLOEXEC 0 #endif #ifndef O_EXCL # define O_EXCL 0 #endif #ifndef O_EXLOCK # define O_EXLOCK 0 #endif #ifndef OF_AMIGAOS # define closeHandle(h) close(h) #else static struct _OFFileHandle { struct _OFFileHandle *previous, *next; BPTR handle; bool append; } *firstHandle = NULL; static void closeHandle(OFFileHandle handle) { Close(handle->handle); Forbid(); if (handle->previous != NULL) handle->previous->next = handle->next; if (handle->next != NULL) handle->next->previous = handle->previous; if (firstHandle == handle) firstHandle = handle->next; Permit(); OFFreeMemory(handle); } OF_DESTRUCTOR() { for (OFFileHandle iter = firstHandle; iter != NULL; iter = iter->next) Close(iter->handle); } #endif #ifndef OF_AMIGAOS static int parseMode(const char *mode) { if (strcmp(mode, "r") == 0) return O_RDONLY; if (strcmp(mode, "r+") == 0) return O_RDWR; if (strcmp(mode, "w") == 0) return O_WRONLY | O_CREAT | O_TRUNC; if (strcmp(mode, "wx") == 0) return O_WRONLY | O_CREAT | O_EXCL | O_EXLOCK; if (strcmp(mode, "w+") == 0) return O_RDWR | O_CREAT | O_TRUNC; if (strcmp(mode, "w+x") == 0) return O_RDWR | O_CREAT | O_EXCL | O_EXLOCK; if (strcmp(mode, "a") == 0) return O_WRONLY | O_CREAT | O_APPEND; if (strcmp(mode, "a+") == 0) return O_RDWR | O_CREAT | O_APPEND; return -1; } #else static int parseMode(const char *mode, bool *append) { *append = false; if (strcmp(mode, "r") == 0) return MODE_OLDFILE; if (strcmp(mode, "r+") == 0) return MODE_OLDFILE; if (strcmp(mode, "w") == 0) return MODE_NEWFILE; if (strcmp(mode, "wx") == 0) return MODE_NEWFILE; if (strcmp(mode, "w+") == 0) return MODE_NEWFILE; if (strcmp(mode, "w+x") == 0) return MODE_NEWFILE; if (strcmp(mode, "a") == 0) { *append = true; return MODE_READWRITE; } if (strcmp(mode, "a+") == 0) { *append = true; return MODE_READWRITE; } return -1; } #endif @implementation OFFile + (void)initialize { if (self != [OFFile class]) return; #ifdef OF_WII if (!fatInitDefault()) @throw [OFInitializationFailedException exceptionWithClass: self]; #endif #ifdef OF_NINTENDO_DS if (!nitroFSInit(NULL)) @throw [OFInitializationFailedException exceptionWithClass: self]; #endif #ifdef OF_NINTENDO_SWITCH if (R_SUCCEEDED(romfsInit())) /* * Errors are intentionally ignored, as it's possible we just * have no romfs. */ atexit((void (*)(void))romfsExit); #endif } + (instancetype)fileWithPath: (OFString *)path mode: (OFString *)mode { return [[[self alloc] initWithPath: path mode: mode] autorelease]; } + (instancetype)fileWithHandle: (OFFileHandle)handle { return [[[self alloc] initWithHandle: handle] autorelease]; } - (instancetype)initWithPath: (OFString *)path mode: (OFString *)mode { OFFileHandle handle; @try { void *pool = objc_autoreleasePoolPush(); int flags; #ifndef OF_AMIGAOS if ((flags = parseMode(mode.UTF8String)) == -1) @throw [OFInvalidArgumentException exception]; flags |= O_BINARY | O_CLOEXEC; # ifdef OF_WINDOWS if ([OFSystemInfo isWindowsNT]) handle = _wopen(path.UTF16String, flags, _S_IREAD | _S_IWRITE); else # endif # ifdef HAVE_OPEN64 handle = open64( [path cStringWithEncoding: [OFLocale encoding]], flags, 0666); # else handle = open( [path cStringWithEncoding: [OFLocale encoding]], flags, 0666); # endif if (handle == -1) @throw [OFOpenItemFailedException exceptionWithPath: path mode: mode errNo: errno]; #else handle = OFAllocMemory(1, sizeof(*handle)); @try { if ((flags = parseMode(mode.UTF8String, &handle->append)) == -1) @throw [OFInvalidArgumentException exception]; if ((handle->handle = Open([path cStringWithEncoding: [OFLocale encoding]], flags)) == 0) { int errNo; switch (IoErr()) { case ERROR_OBJECT_IN_USE: case ERROR_DISK_NOT_VALIDATED: errNo = EBUSY; break; case ERROR_OBJECT_NOT_FOUND: errNo = ENOENT; break; case ERROR_DISK_WRITE_PROTECTED: errNo = EROFS; break; case ERROR_WRITE_PROTECTED: case ERROR_READ_PROTECTED: errNo = EACCES; break; default: errNo = 0; break; } @throw [OFOpenItemFailedException exceptionWithPath: path mode: mode errNo: errNo]; } if (handle->append) { # if defined(OF_MORPHOS) if (Seek64(handle->handle, 0, OFFSET_END) == -1) { # elif defined(OF_AMIGAOS4) if (ChangeFilePosition(handle->handle, 0, OFFSET_END) == -1) { # else if (Seek(handle->handle, 0, OFFSET_END) == -1) { # endif Close(handle->handle); @throw [OFOpenItemFailedException exceptionWithPath: path mode: mode errNo: EIO]; } } Forbid(); handle->previous = NULL; handle->next = firstHandle; if (firstHandle != NULL) firstHandle->previous = handle; firstHandle = handle; Permit(); } @catch (id e) { OFFreeMemory(handle); @throw e; } #endif objc_autoreleasePoolPop(pool); } @catch (id e) { [self release]; @throw e; } @try { self = [self initWithHandle: handle]; } @catch (id e) { closeHandle(handle); @throw e; } return self; } - (instancetype)initWithHandle: (OFFileHandle)handle { self = [super init]; _handle = handle; return self; } - (instancetype)init { OF_INVALID_INIT_METHOD } - (bool)lowlevelIsAtEndOfStream { if (_handle == OFInvalidFileHandle) @throw [OFNotOpenException exceptionWithObject: self]; return _atEndOfStream; } - (size_t)lowlevelReadIntoBuffer: (void *)buffer length: (size_t)length { ssize_t ret; if (_handle == OFInvalidFileHandle) @throw [OFNotOpenException exceptionWithObject: self]; #if defined(OF_WINDOWS) if (length > UINT_MAX) @throw [OFOutOfRangeException exception]; if ((ret = read(_handle, buffer, (unsigned int)length)) < 0) @throw [OFReadFailedException exceptionWithObject: self requestedLength: length errNo: errno]; #elif defined(OF_AMIGAOS) if (length > LONG_MAX) @throw [OFOutOfRangeException exception]; if ((ret = Read(_handle->handle, buffer, length)) < 0) @throw [OFReadFailedException exceptionWithObject: self requestedLength: length errNo: EIO]; #else if ((ret = read(_handle, buffer, length)) < 0) @throw [OFReadFailedException exceptionWithObject: self requestedLength: length errNo: errno]; #endif if (ret == 0) _atEndOfStream = true; return ret; } - (size_t)lowlevelWriteBuffer: (const void *)buffer length: (size_t)length { if (_handle == OFInvalidFileHandle) @throw [OFNotOpenException exceptionWithObject: self]; #if defined(OF_WINDOWS) int bytesWritten; if (length > INT_MAX) @throw [OFOutOfRangeException exception]; if ((bytesWritten = write(_handle, buffer, (int)length)) < 0) @throw [OFWriteFailedException exceptionWithObject: self requestedLength: length bytesWritten: 0 errNo: errno]; #elif defined(OF_AMIGAOS) LONG bytesWritten; if (length > LONG_MAX) @throw [OFOutOfRangeException exception]; if (_handle->append) { # if defined(OF_MORPHOS) if (Seek64(_handle->handle, 0, OFFSET_END) == -1) # elif defined(OF_AMIGAOS4) if (ChangeFilePosition(_handle->handle, 0, OFFSET_END) == -1) # else if (Seek(_handle->handle, 0, OFFSET_END) == -1) # endif @throw [OFWriteFailedException exceptionWithObject: self requestedLength: length bytesWritten: 0 errNo: EIO]; } if ((bytesWritten = Write(_handle->handle, (void *)buffer, length)) < 0) @throw [OFWriteFailedException exceptionWithObject: self requestedLength: length bytesWritten: 0 errNo: EIO]; #else ssize_t bytesWritten; if (length > SSIZE_MAX) @throw [OFOutOfRangeException exception]; if ((bytesWritten = write(_handle, buffer, length)) < 0) @throw [OFWriteFailedException exceptionWithObject: self requestedLength: length bytesWritten: 0 errNo: errno]; #endif return (size_t)bytesWritten; } - (OFStreamOffset)lowlevelSeekToOffset: (OFStreamOffset)offset whence: (OFSeekWhence)whence { OFStreamOffset ret; if (_handle == OFInvalidFileHandle) @throw [OFNotOpenException exceptionWithObject: self]; #ifndef OF_AMIGAOS int translatedWhence; switch (whence) { case OFSeekSet: translatedWhence = SEEK_SET; break; case OFSeekCurrent: translatedWhence = SEEK_CUR; break; case OFSeekEnd: translatedWhence = SEEK_END; break; default: @throw [OFSeekFailedException exceptionWithStream: self offset: offset whence: whence errNo: EINVAL]; } # if defined(OF_WINDOWS) ret = _lseeki64(_handle, offset, translatedWhence); # elif defined(HAVE_LSEEK64) ret = lseek64(_handle, offset, translatedWhence); # else ret = lseek(_handle, offset, translatedWhence); # endif if (ret == -1) @throw [OFSeekFailedException exceptionWithStream: self offset: offset whence: whence errNo: errno]; #else LONG translatedWhence; switch (whence) { case OFSeekSet: translatedWhence = OFFSET_BEGINNING; break; case OFSeekCurrent: translatedWhence = OFFSET_CURRENT; break; case OFSeekEnd: translatedWhence = OFFSET_END; break; default: @throw [OFSeekFailedException exceptionWithStream: self offset: offset whence: whence errNo: EINVAL]; } # if defined(OF_MORPHOS) if ((ret = Seek64(_handle->handle, offset, translatedWhence)) == 1) # elif defined(OF_AMIGAOS4) if ((ret = ChangeFilePosition(_handle->handle, offset, translatedWhence)) == 1) # else if ((ret = Seek(_handle->handle, offset, translatedWhence)) == 1) # endif @throw [OFSeekFailedException exceptionWithStream: self offset: offset whence: whence errNo: EINVAL]; #endif _atEndOfStream = false; return ret; } #ifdef OF_FILE_HANDLE_IS_FD - (int)fileDescriptorForReading { return _handle; } - (int)fileDescriptorForWriting { return _handle; } #endif - (void)close { if (_handle == OFInvalidFileHandle) @throw [OFNotOpenException exceptionWithObject: self]; closeHandle(_handle); _handle = OFInvalidFileHandle; [super close]; } - (void)dealloc { if (_handle != OFInvalidFileHandle) [self close]; [super dealloc]; } @end