/* -*- tab-width: 4; -*- */1/* vi: set sw=2 ts=4 expandtab: */23/*4* Copyright 2010-2020 The Khronos Group Inc.5* SPDX-License-Identifier: Apache-2.06*/78/**9* @file10* @~English11*12* @brief Implementation of ktxStream for FILE.13*14* @author Maksim Kolesin, Under Development15* @author Georg Kolling, Imagination Technology16* @author Mark Callow, HI Corporation17*/1819#include <assert.h>20#include <errno.h>21#include <inttypes.h>22#include <string.h>23/* I need these on Linux. Why? */24#define __USE_LARGEFILE 1 // For declaration of ftello, etc.25#define __USE_POSIX 1 // For declaration of fileno.26#define _POSIX_SOURCE 1 // For both the above in Emscripten.27#include <stdio.h>28#include <stdlib.h>29#include <sys/types.h> // For stat.h on Windows30#define __USE_MISC 1 // For declaration of S_IF...31#include <sys/stat.h>3233#include "ktx.h"34#include "ktxint.h"35#include "filestream.h"3637// Gotta love Windows :-(38#if defined(_MSC_VER)39#if defined(_WIN64)40#define ftello _ftelli6441#define fseeko _fseeki6442#else43#define ftello ftell44#define fseeko fseek45#endif46#define fileno _fileno47#define fstat _fstat48#define stat _stat49#define S_IFIFO _S_IFIFO50#define S_IFSOCK 0xC00051typedef unsigned short mode_t;52#endif5354#if defined(__MINGW32__)55#define S_IFSOCK 0xC00056#endif5758#define KTX_FILE_STREAM_MAX (1 << (sizeof(ktx_off_t) - 1) - 1)5960/**61* @~English62* @brief Read bytes from a ktxFileStream.63*64* @param [in] str pointer to the ktxStream from which to read.65* @param [out] dst pointer to a block of memory with a size66* of at least @p size bytes, converted to a void*.67* @param [in,out] count pointer to total count of bytes to be read.68* On completion set to number of bytes read.69*70* @return KTX_SUCCESS on success, other KTX_* enum values on error.71*72* @exception KTX_INVALID_VALUE @p dst is @c NULL or @p src is @c NULL.73* @exception KTX_FILE_READ_ERROR an error occurred while reading the file.74* @exception KTX_FILE_UNEXPECTED_EOF not enough data to satisfy the request.75*/76static77KTX_error_code ktxFileStream_read(ktxStream* str, void* dst, const ktx_size_t count)78{79ktx_size_t nread;8081if (!str || !dst)82return KTX_INVALID_VALUE;8384assert(str->type == eStreamTypeFile);8586if ((nread = fread(dst, 1, count, str->data.file)) != count) {87if (feof(str->data.file)) {88return KTX_FILE_UNEXPECTED_EOF;89} else {90return KTX_FILE_READ_ERROR;91}92}93str->readpos += count;9495return KTX_SUCCESS;96}9798/**99* @~English100* @brief Skip bytes in a ktxFileStream.101*102* @param [in] str pointer to a ktxStream object.103* @param [in] count number of bytes to be skipped.104*105* In order to support applications reading from stdin, read characters106* rather than using seek functions.107*108* @return KTX_SUCCESS on success, other KTX_* enum values on error.109*110* @exception KTX_INVALID_VALUE @p str is @c NULL or @p count is less than zero.111* @exception KTX_INVALID_OPERATION skipping @p count bytes would go beyond EOF.112* @exception KTX_FILE_READ_ERROR an error occurred while reading the file.113* @exception KTX_FILE_UNEXPECTED_EOF not enough data to satisfy the request.114* @p count is set to the number of bytes115* skipped.116*/117static118KTX_error_code ktxFileStream_skip(ktxStream* str, const ktx_size_t count)119{120if (!str)121return KTX_INVALID_VALUE;122123assert(str->type == eStreamTypeFile);124125for (ktx_uint32_t i = 0; i < count; i++) {126int ret = getc(str->data.file);127if (ret == EOF) {128if (feof(str->data.file)) {129return KTX_FILE_UNEXPECTED_EOF;130} else {131return KTX_FILE_READ_ERROR;132}133}134}135str->readpos += count;136137return KTX_SUCCESS;138}139140/**141* @~English142* @brief Write bytes to a ktxFileStream.143*144* @param [in] str pointer to the ktxStream that is the destination of the145* write.146* @param [in] src pointer to the array of elements to be written,147* converted to a const void*.148* @param [in] size size in bytes of each element to be written.149* @param [in] count number of elements, each one with a @p size of size150* bytes.151*152* @return KTX_SUCCESS on success, other KTX_* enum values on error.153*154* @exception KTX_INVALID_VALUE @p str is @c NULL or @p src is @c NULL.155* @exception KTX_FILE_OVERFLOW the requested write would caused the file to156* exceed the maximum supported file size.157* @exception KTX_FILE_WRITE_ERROR a system error occurred while writing the158* file.159*/160static161KTX_error_code ktxFileStream_write(ktxStream* str, const void *src,162const ktx_size_t size,163const ktx_size_t count)164{165if (!str || !src)166return KTX_INVALID_VALUE;167168assert(str->type == eStreamTypeFile);169170if (fwrite(src, size, count, str->data.file) != count) {171if (errno == EFBIG || errno == EOVERFLOW)172return KTX_FILE_OVERFLOW;173else174return KTX_FILE_WRITE_ERROR;175}176177return KTX_SUCCESS;178}179180/**181* @~English182* @brief Get the current read/write position in a ktxFileStream.183*184* @param [in] str pointer to the ktxStream to query.185* @param [in,out] off pointer to variable to receive the offset value.186*187* @return KTX_SUCCESS on success, other KTX_* enum values on error.188*189* @exception KTX_FILE_ISPIPE file descriptor underlying stream is associated190* with a pipe or FIFO so does not have a191* file-position indicator.192* @exception KTX_INVALID_VALUE @p str or @p pos is @c NULL.193*/194static195KTX_error_code ktxFileStream_getpos(ktxStream* str, ktx_off_t* pos)196{197ktx_off_t ftellval;198199if (!str || !pos)200return KTX_INVALID_VALUE;201202assert(str->type == eStreamTypeFile);203204if (str->data.file == stdin) {205*pos = str->readpos;206} else {207/* The cast quiets an Xcode warning when building for "Generic iOS Device".208* I'm not sure why.209*/210ftellval = (ktx_off_t)ftello(str->data.file);211if (ftellval < 0) {212switch (errno) {213case ESPIPE: return KTX_FILE_ISPIPE;214case EOVERFLOW: return KTX_FILE_OVERFLOW;215}216}217218*pos = ftellval;219}220221return KTX_SUCCESS;222}223224/**225* @~English226* @brief Set the current read/write position in a ktxFileStream.227*228* Offset of 0 is the start of the file. This function operates229* like Linux > 3.1's @c lseek() when it is passed a @c whence230* of @c SEEK_DATA as it returns an error if the seek would231* go beyond the end of the file.232*233* @param [in] str pointer to the ktxStream whose r/w position is to be set.234* @param [in] off pointer to the offset value to set.235*236* @return KTX_SUCCESS on success, other KTX_* enum values on error.237*238* Throws the same exceptions as ktxFileStream_getsize() for the reasons given239* there plus the following:240*241* @exception KTX_INVALID_VALUE @p str is @c NULL.242* @exception KTX_INVALID_OPERATION @p pos is > the size of the file or an243* fseek error occurred.244*/245static246KTX_error_code ktxFileStream_setpos(ktxStream* str, ktx_off_t pos)247{248ktx_size_t fileSize;249KTX_error_code result;250251if (!str)252return KTX_INVALID_VALUE;253254assert(str->type == eStreamTypeFile);255256if (str->data.file == stdin) {257if (pos > str->readpos)258return str->skip(str, pos - str->readpos);259else260return KTX_FILE_ISPIPE;261}262263result = str->getsize(str, &fileSize);264265if (result != KTX_SUCCESS) {266// Device is likely not seekable.267return result;268}269270if (pos > (ktx_off_t)fileSize)271return KTX_INVALID_OPERATION;272273if (fseeko(str->data.file, pos, SEEK_SET) < 0)274return KTX_FILE_SEEK_ERROR;275else276return KTX_SUCCESS;277}278279/**280* @~English281* @brief Get the size of a ktxFileStream in bytes.282*283* @param [in] str pointer to the ktxStream whose size is to be queried.284* @param [in,out] size pointer to a variable in which size will be written.285*286* @return KTX_SUCCESS on success, other KTX_* enum values on error.287*288* @exception KTX_FILE_OVERFLOW size is too large to be returned in a289* @c ktx_size_t.290* @exception KTX_FILE_ISPIPE file descriptor underlying stream is associated291* with a pipe or FIFO so does not have a292* file-position indicator.293* @exception KTX_FILE_READ_ERROR a system error occurred while getting the294* size.295* @exception KTX_INVALID_VALUE @p str or @p size is @c NULL.296* @exception KTX_INVALID_OPERATION stream is a tty.297*/298static299KTX_error_code ktxFileStream_getsize(ktxStream* str, ktx_size_t* size)300{301struct stat statbuf;302int statret;303304if (!str || !size)305return KTX_INVALID_VALUE;306307assert(str->type == eStreamTypeFile);308309// Need to flush so that fstat will return the current size.310// Can ignore return value. The only error that can happen is to tell you311// it was a NOP because the file is read only.312#if (defined(_MSC_VER) && _MSC_VER < 1900) || defined(__MINGW64__) && !defined(_UCRT)313// Bug in VS2013 msvcrt. fflush on FILE open for READ changes file offset314// to 4096.315if (str->data.file->_flag & _IOWRT)316(void)fflush(str->data.file);317#else318(void)fflush(str->data.file);319#endif320statret = fstat(fileno(str->data.file), &statbuf);321if (statret < 0) {322switch (errno) {323case EOVERFLOW: return KTX_FILE_OVERFLOW;324case EIO:325default:326return KTX_FILE_READ_ERROR;327}328}329330mode_t ftype = statbuf.st_mode & S_IFMT;331if (ftype == S_IFIFO || ftype == S_IFSOCK)332return KTX_FILE_ISPIPE;333334if (statbuf.st_mode & S_IFCHR)335return KTX_INVALID_OPERATION;336337*size = (ktx_size_t)statbuf.st_size; /* See _getpos for why this cast. */338339return KTX_SUCCESS;340}341342/**343* @~English344* @brief Initialize a ktxFileStream.345*346* @param [in] str pointer to the ktxStream to initialize.347* @param [in] file pointer to the underlying FILE object.348* @param [in] closeFileOnDestruct if not false, stdio file pointer will be closed when ktxStream349* is destructed.350*351* @return KTX_SUCCESS on success, KTX_INVALID_VALUE on error.352*353* @exception KTX_INVALID_VALUE @p stream is @c NULL or @p file is @c NULL.354*/355KTX_error_code ktxFileStream_construct(ktxStream* str, FILE* file,356ktx_bool_t closeFileOnDestruct)357{358if (!str || !file)359return KTX_INVALID_VALUE;360361str->data.file = file;362str->readpos = 0;363str->type = eStreamTypeFile;364str->read = ktxFileStream_read;365str->skip = ktxFileStream_skip;366str->write = ktxFileStream_write;367str->getpos = ktxFileStream_getpos;368str->setpos = ktxFileStream_setpos;369str->getsize = ktxFileStream_getsize;370str->destruct = ktxFileStream_destruct;371str->closeOnDestruct = closeFileOnDestruct;372373return KTX_SUCCESS;374}375376/**377* @~English378* @brief Destruct the stream, potentially closing the underlying FILE.379*380* This only closes the underyling FILE if the @c closeOnDestruct parameter to381* ktxFileStream_construct() was not @c KTX_FALSE.382*383* @param [in] str pointer to the ktxStream whose FILE is to potentially384* be closed.385*/386void387ktxFileStream_destruct(ktxStream* str)388{389assert(str && str->type == eStreamTypeFile);390391if (str->closeOnDestruct)392fclose(str->data.file);393str->data.file = 0;394}395396397