filestream.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. /* -*- tab-width: 4; -*- */
  2. /* vi: set sw=2 ts=4 expandtab: */
  3. /*
  4. * Copyright 2010-2020 The Khronos Group Inc.
  5. * SPDX-License-Identifier: Apache-2.0
  6. */
  7. /**
  8. * @file
  9. * @~English
  10. *
  11. * @brief Implementation of ktxStream for FILE.
  12. *
  13. * @author Maksim Kolesin, Under Development
  14. * @author Georg Kolling, Imagination Technology
  15. * @author Mark Callow, HI Corporation
  16. */
  17. #include <assert.h>
  18. #include <errno.h>
  19. #include <inttypes.h>
  20. #include <string.h>
  21. /* I need these on Linux. Why? */
  22. #define __USE_LARGEFILE 1 // For declaration of ftello, etc.
  23. #define __USE_POSIX 1 // For declaration of fileno.
  24. #define _POSIX_SOURCE 1 // For both the above in Emscripten.
  25. #include <stdio.h>
  26. #include <stdlib.h>
  27. #include <sys/types.h> // For stat.h on Windows
  28. #define __USE_MISC 1 // For declaration of S_IF...
  29. #include <sys/stat.h>
  30. #include "ktx.h"
  31. #include "ktxint.h"
  32. #include "filestream.h"
  33. // Gotta love Windows :-(
  34. #if defined(_MSC_VER)
  35. #if defined(_WIN64)
  36. #define ftello _ftelli64
  37. #define fseeko _fseeki64
  38. #else
  39. #define ftello ftell
  40. #define fseeko fseek
  41. #endif
  42. #define fileno _fileno
  43. #define fstat _fstat
  44. #define stat _stat
  45. #define S_IFIFO _S_IFIFO
  46. #define S_IFSOCK 0xC000
  47. typedef unsigned short mode_t;
  48. #endif
  49. #if defined(__MINGW32__)
  50. #define S_IFSOCK 0xC000
  51. #endif
  52. #define KTX_FILE_STREAM_MAX (1 << (sizeof(ktx_off_t) - 1) - 1)
  53. /**
  54. * @~English
  55. * @brief Read bytes from a ktxFileStream.
  56. *
  57. * @param [in] str pointer to the ktxStream from which to read.
  58. * @param [out] dst pointer to a block of memory with a size
  59. * of at least @p size bytes, converted to a void*.
  60. * @param [in,out] count pointer to total count of bytes to be read.
  61. * On completion set to number of bytes read.
  62. *
  63. * @return KTX_SUCCESS on success, other KTX_* enum values on error.
  64. *
  65. * @exception KTX_INVALID_VALUE @p dst is @c NULL or @p src is @c NULL.
  66. * @exception KTX_FILE_READ_ERROR an error occurred while reading the file.
  67. * @exception KTX_FILE_UNEXPECTED_EOF not enough data to satisfy the request.
  68. */
  69. static
  70. KTX_error_code ktxFileStream_read(ktxStream* str, void* dst, const ktx_size_t count)
  71. {
  72. ktx_size_t nread;
  73. if (!str || !dst)
  74. return KTX_INVALID_VALUE;
  75. assert(str->type == eStreamTypeFile);
  76. if ((nread = fread(dst, 1, count, str->data.file)) != count) {
  77. if (feof(str->data.file)) {
  78. return KTX_FILE_UNEXPECTED_EOF;
  79. } else {
  80. return KTX_FILE_READ_ERROR;
  81. }
  82. }
  83. str->readpos += count;
  84. return KTX_SUCCESS;
  85. }
  86. /**
  87. * @~English
  88. * @brief Skip bytes in a ktxFileStream.
  89. *
  90. * @param [in] str pointer to a ktxStream object.
  91. * @param [in] count number of bytes to be skipped.
  92. *
  93. * In order to support applications reading from stdin, read characters
  94. * rather than using seek functions.
  95. *
  96. * @return KTX_SUCCESS on success, other KTX_* enum values on error.
  97. *
  98. * @exception KTX_INVALID_VALUE @p str is @c NULL or @p count is less than zero.
  99. * @exception KTX_INVALID_OPERATION skipping @p count bytes would go beyond EOF.
  100. * @exception KTX_FILE_READ_ERROR an error occurred while reading the file.
  101. * @exception KTX_FILE_UNEXPECTED_EOF not enough data to satisfy the request.
  102. * @p count is set to the number of bytes
  103. * skipped.
  104. */
  105. static
  106. KTX_error_code ktxFileStream_skip(ktxStream* str, const ktx_size_t count)
  107. {
  108. if (!str)
  109. return KTX_INVALID_VALUE;
  110. assert(str->type == eStreamTypeFile);
  111. for (ktx_uint32_t i = 0; i < count; i++) {
  112. int ret = getc(str->data.file);
  113. if (ret == EOF) {
  114. if (feof(str->data.file)) {
  115. return KTX_FILE_UNEXPECTED_EOF;
  116. } else {
  117. return KTX_FILE_READ_ERROR;
  118. }
  119. }
  120. }
  121. str->readpos += count;
  122. return KTX_SUCCESS;
  123. }
  124. /**
  125. * @~English
  126. * @brief Write bytes to a ktxFileStream.
  127. *
  128. * @param [in] str pointer to the ktxStream that is the destination of the
  129. * write.
  130. * @param [in] src pointer to the array of elements to be written,
  131. * converted to a const void*.
  132. * @param [in] size size in bytes of each element to be written.
  133. * @param [in] count number of elements, each one with a @p size of size
  134. * bytes.
  135. *
  136. * @return KTX_SUCCESS on success, other KTX_* enum values on error.
  137. *
  138. * @exception KTX_INVALID_VALUE @p str is @c NULL or @p src is @c NULL.
  139. * @exception KTX_FILE_OVERFLOW the requested write would caused the file to
  140. * exceed the maximum supported file size.
  141. * @exception KTX_FILE_WRITE_ERROR a system error occurred while writing the
  142. * file.
  143. */
  144. static
  145. KTX_error_code ktxFileStream_write(ktxStream* str, const void *src,
  146. const ktx_size_t size,
  147. const ktx_size_t count)
  148. {
  149. if (!str || !src)
  150. return KTX_INVALID_VALUE;
  151. assert(str->type == eStreamTypeFile);
  152. if (fwrite(src, size, count, str->data.file) != count) {
  153. if (errno == EFBIG || errno == EOVERFLOW)
  154. return KTX_FILE_OVERFLOW;
  155. else
  156. return KTX_FILE_WRITE_ERROR;
  157. }
  158. return KTX_SUCCESS;
  159. }
  160. /**
  161. * @~English
  162. * @brief Get the current read/write position in a ktxFileStream.
  163. *
  164. * @param [in] str pointer to the ktxStream to query.
  165. * @param [in,out] off pointer to variable to receive the offset value.
  166. *
  167. * @return KTX_SUCCESS on success, other KTX_* enum values on error.
  168. *
  169. * @exception KTX_FILE_ISPIPE file descriptor underlying stream is associated
  170. * with a pipe or FIFO so does not have a
  171. * file-position indicator.
  172. * @exception KTX_INVALID_VALUE @p str or @p pos is @c NULL.
  173. */
  174. static
  175. KTX_error_code ktxFileStream_getpos(ktxStream* str, ktx_off_t* pos)
  176. {
  177. ktx_off_t ftellval;
  178. if (!str || !pos)
  179. return KTX_INVALID_VALUE;
  180. assert(str->type == eStreamTypeFile);
  181. if (str->data.file == stdin) {
  182. *pos = str->readpos;
  183. } else {
  184. /* The cast quiets an Xcode warning when building for "Generic iOS Device".
  185. * I'm not sure why.
  186. */
  187. ftellval = (ktx_off_t)ftello(str->data.file);
  188. if (ftellval < 0) {
  189. switch (errno) {
  190. case ESPIPE: return KTX_FILE_ISPIPE;
  191. case EOVERFLOW: return KTX_FILE_OVERFLOW;
  192. }
  193. }
  194. *pos = ftellval;
  195. }
  196. return KTX_SUCCESS;
  197. }
  198. /**
  199. * @~English
  200. * @brief Set the current read/write position in a ktxFileStream.
  201. *
  202. * Offset of 0 is the start of the file. This function operates
  203. * like Linux > 3.1's @c lseek() when it is passed a @c whence
  204. * of @c SEEK_DATA as it returns an error if the seek would
  205. * go beyond the end of the file.
  206. *
  207. * @param [in] str pointer to the ktxStream whose r/w position is to be set.
  208. * @param [in] off pointer to the offset value to set.
  209. *
  210. * @return KTX_SUCCESS on success, other KTX_* enum values on error.
  211. *
  212. * Throws the same exceptions as ktxFileStream_getsize() for the reasons given
  213. * there plus the following:
  214. *
  215. * @exception KTX_INVALID_VALUE @p str is @c NULL.
  216. * @exception KTX_INVALID_OPERATION @p pos is > the size of the file or an
  217. * fseek error occurred.
  218. */
  219. static
  220. KTX_error_code ktxFileStream_setpos(ktxStream* str, ktx_off_t pos)
  221. {
  222. ktx_size_t fileSize;
  223. KTX_error_code result;
  224. if (!str)
  225. return KTX_INVALID_VALUE;
  226. assert(str->type == eStreamTypeFile);
  227. if (str->data.file == stdin) {
  228. if (pos > str->readpos)
  229. return str->skip(str, pos - str->readpos);
  230. else
  231. return KTX_FILE_ISPIPE;
  232. }
  233. result = str->getsize(str, &fileSize);
  234. if (result != KTX_SUCCESS) {
  235. // Device is likely not seekable.
  236. return result;
  237. }
  238. if (pos > (ktx_off_t)fileSize)
  239. return KTX_INVALID_OPERATION;
  240. if (fseeko(str->data.file, pos, SEEK_SET) < 0)
  241. return KTX_FILE_SEEK_ERROR;
  242. else
  243. return KTX_SUCCESS;
  244. }
  245. /**
  246. * @~English
  247. * @brief Get the size of a ktxFileStream in bytes.
  248. *
  249. * @param [in] str pointer to the ktxStream whose size is to be queried.
  250. * @param [in,out] size pointer to a variable in which size will be written.
  251. *
  252. * @return KTX_SUCCESS on success, other KTX_* enum values on error.
  253. *
  254. * @exception KTX_FILE_OVERFLOW size is too large to be returned in a
  255. * @c ktx_size_t.
  256. * @exception KTX_FILE_ISPIPE file descriptor underlying stream is associated
  257. * with a pipe or FIFO so does not have a
  258. * file-position indicator.
  259. * @exception KTX_FILE_READ_ERROR a system error occurred while getting the
  260. * size.
  261. * @exception KTX_INVALID_VALUE @p str or @p size is @c NULL.
  262. * @exception KTX_INVALID_OPERATION stream is a tty.
  263. */
  264. static
  265. KTX_error_code ktxFileStream_getsize(ktxStream* str, ktx_size_t* size)
  266. {
  267. struct stat statbuf;
  268. int statret;
  269. if (!str || !size)
  270. return KTX_INVALID_VALUE;
  271. assert(str->type == eStreamTypeFile);
  272. // Need to flush so that fstat will return the current size.
  273. // Can ignore return value. The only error that can happen is to tell you
  274. // it was a NOP because the file is read only.
  275. #if (defined(_MSC_VER) && _MSC_VER < 1900) || defined(__MINGW64__) && !defined(_UCRT)
  276. // Bug in VS2013 msvcrt. fflush on FILE open for READ changes file offset
  277. // to 4096.
  278. if (str->data.file->_flag & _IOWRT)
  279. #endif
  280. (void)fflush(str->data.file);
  281. statret = fstat(fileno(str->data.file), &statbuf);
  282. if (statret < 0) {
  283. switch (errno) {
  284. case EOVERFLOW: return KTX_FILE_OVERFLOW;
  285. case EIO:
  286. default:
  287. return KTX_FILE_READ_ERROR;
  288. }
  289. }
  290. mode_t ftype = statbuf.st_mode & S_IFMT;
  291. if (ftype == S_IFIFO || ftype == S_IFSOCK)
  292. return KTX_FILE_ISPIPE;
  293. if (statbuf.st_mode & S_IFCHR)
  294. return KTX_INVALID_OPERATION;
  295. *size = (ktx_size_t)statbuf.st_size; /* See _getpos for why this cast. */
  296. return KTX_SUCCESS;
  297. }
  298. /**
  299. * @~English
  300. * @brief Initialize a ktxFileStream.
  301. *
  302. * @param [in] str pointer to the ktxStream to initialize.
  303. * @param [in] file pointer to the underlying FILE object.
  304. * @param [in] closeFileOnDestruct if not false, stdio file pointer will be closed when ktxStream
  305. * is destructed.
  306. *
  307. * @return KTX_SUCCESS on success, KTX_INVALID_VALUE on error.
  308. *
  309. * @exception KTX_INVALID_VALUE @p stream is @c NULL or @p file is @c NULL.
  310. */
  311. KTX_error_code ktxFileStream_construct(ktxStream* str, FILE* file,
  312. ktx_bool_t closeFileOnDestruct)
  313. {
  314. if (!str || !file)
  315. return KTX_INVALID_VALUE;
  316. str->data.file = file;
  317. str->readpos = 0;
  318. str->type = eStreamTypeFile;
  319. str->read = ktxFileStream_read;
  320. str->skip = ktxFileStream_skip;
  321. str->write = ktxFileStream_write;
  322. str->getpos = ktxFileStream_getpos;
  323. str->setpos = ktxFileStream_setpos;
  324. str->getsize = ktxFileStream_getsize;
  325. str->destruct = ktxFileStream_destruct;
  326. str->closeOnDestruct = closeFileOnDestruct;
  327. return KTX_SUCCESS;
  328. }
  329. /**
  330. * @~English
  331. * @brief Destruct the stream, potentially closing the underlying FILE.
  332. *
  333. * This only closes the underyling FILE if the @c closeOnDestruct parameter to
  334. * ktxFileStream_construct() was not @c KTX_FALSE.
  335. *
  336. * @param [in] str pointer to the ktxStream whose FILE is to potentially
  337. * be closed.
  338. */
  339. void
  340. ktxFileStream_destruct(ktxStream* str)
  341. {
  342. assert(str && str->type == eStreamTypeFile);
  343. if (str->closeOnDestruct)
  344. fclose(str->data.file);
  345. str->data.file = 0;
  346. }