1 /** 2 I/O streams. 3 4 Copyright: Copyright Guillaume Piolat 2022 5 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 */ 7 module gamut.io; 8 9 import core.stdc.stdio; 10 import core.stdc.string: memcpy; 11 import core.stdc.stdlib: malloc, free, realloc; 12 public import core.stdc.config: c_long; 13 public import core.stdc.stdio: SEEK_SET, SEEK_CUR, SEEK_END; 14 15 nothrow @nogc: 16 17 18 // Limits of I/O in gamut. 19 // Callbacks are modelled upon C stdlib functions, some of those use c_long or int. So, 32-bit is a possibility. 20 enum size_t GAMUT_MAX_POSSIBLE_MEMORY_OFFSET = 0x7fff_fffe; /// Can't open file larger than this much bytes 21 enum size_t GAMUT_MAX_POSSIBLE_SIMULTANEOUS_READ = 0x7fff_ffff; /// Can't read more bytes than this at once 22 enum size_t GAMUT_MAX_POSSIBLE_SIMULTANEOUS_WRITE = 0x7fff_ffff; /// Can't write more bytes than this at once 23 24 static assert(GAMUT_MAX_POSSIBLE_MEMORY_OFFSET + 1 <= cast(long) int.max); 25 26 // Note: those function pointers made to be binary compatible with ftell/fseek/fwrite/fread/feof. 27 extern(C) @system 28 { 29 /// A function with same signature and semantics than `fread`. 30 /// 31 /// Some details from the Linux man pages: 32 /// 33 /// "On success, fread() and fwrite() return the number of items read 34 /// or written. This number equals the number of bytes transferred 35 /// only when size is 1. If an error occurs, or the end of the file 36 /// is reached, the return value is a short item count (or zero). 37 /// 38 /// The file position indicator for the stream is advanced by the 39 /// number of bytes successfully read or written. 40 /// 41 /// fread() does not distinguish between end-of-file and error, and 42 /// callers must use feof() and ferror() to determine which 43 /// occurred. (well, ferror not available in gamut). 44 /// 45 /// Params: 46 /// buffer Where to read. Must be able to hold `size` * `count` bytes. 47 /// size Size of elements to read in stream. 48 /// count Number of elements to read in stream. 49 /// 50 /// Returns: 51 /// Number of item successfully read. If return value != `count`, there was an error. 52 /// 53 /// Limitations: it is forbidden to ask more than 0x7fffffff bytes at once. 54 alias ReadProc = size_t function(void* buffer, size_t size, size_t count, IOHandle handle); 55 56 /// A function with same signature and semantics than `fwrite`. 57 alias WriteProc = size_t function(const(void)* buffer, size_t size, size_t count, IOHandle handle); 58 59 /// A function with same signature and semantics than `fseek`. 60 /// Origin: position from which offset is added 61 /// SEEK_SET = beginning of file. 62 /// SEEK_CUR = Current position of file pointer. 63 /// SEEK_END = end of file. 64 /// This function returns zero if successful, or else it returns a non-zero value. 65 /// Note: c_long offsets in gamut can always be cast to int without loss. 66 alias SeekProc = int function(IOHandle handle, c_long offset, int origin); 67 68 /// A function with same signature and semantics than `ftell`. 69 /// Tells where we are in the file. -1 if error. 70 /// Note: c_long offsets in gamut can always be cast to int without loss. 71 alias TellProc = c_long function(IOHandle handle); 72 73 /// A function with same signature and semantics than `feof`. 74 /// From Linux man: 75 /// "The function `feof()` tests the end-of-file indicator for the stream pointed to by stream, 76 /// returning nonzero if it is set." 77 alias EofProc = int function(IOHandle handle); 78 } 79 80 81 /// Can be a `FILE*` handle, a `FIMEMORY`, a `WrappedIO`... 82 /// identifies the I/O stream. 83 alias IOHandle = void*; 84 85 /// I/O abstraction, to support load/write from a file, from memory, or from user-provided callbacks. 86 struct IOStream 87 { 88 nothrow @nogc @safe: 89 90 /// A function with semantics and signature similar to `fread`. 91 ReadProc read; 92 93 /// A function with semantics and signature similar to `fwrite`. 94 WriteProc write; 95 96 /// A function with semantics and signature similar to `fseek`. 97 SeekProc seek; 98 99 /// A function with semantics and signature similar to `ftell`. 100 TellProc tell; 101 102 /// A function with semantics and signature similar to `feof`. 103 EofProc eof; 104 105 /// Skip bytes. 106 /// Returns: true if it was possible to skip those bytes. false if there was an I/O error, or end of file. 107 /// Limitations: `nbytes` must be from 0 to 0x7fffffff 108 bool skipBytes(IOHandle handle, int nbytes) nothrow @nogc @trusted 109 { 110 assert(nbytes >= 0 && nbytes <= GAMUT_MAX_POSSIBLE_SIMULTANEOUS_READ); 111 return seek(handle, nbytes, SEEK_CUR) == 0; 112 } 113 114 /// Seek to beginning of the I/O stream. 115 /// Returns: true if successful. 116 bool rewind(IOHandle handle) nothrow @nogc @trusted 117 { 118 return seek(handle, 0, SEEK_SET) == 0; 119 } 120 121 /// Seek to asolute position in the I/O stream. 122 /// Useful because some function need to preserve it. 123 bool seekAbsolute(IOHandle handle, c_long offset) nothrow @nogc @trusted 124 { 125 return seek(handle, offset, SEEK_SET) == 0; 126 } 127 128 /// Helper to read one ubyte in stream. 129 /// On error, sets `err` to `true` and return 0. 130 ubyte read_ubyte(IOHandle handle, bool* err) nothrow @nogc @trusted 131 { 132 ubyte v; 133 if (1 == read(&v, 1, 1, handle)) 134 { 135 *err = false; 136 return v; 137 } 138 else 139 { 140 *err = true; 141 return 0; 142 } 143 } 144 145 /// Helper to read one little-endian ushort in stream. 146 /// On error, sets `err` to `true` and return 0. 147 ushort read_ushort_LE(IOHandle handle, bool* err) nothrow @nogc @trusted 148 { 149 ushort v; // Note: no support for BigEndian here 150 if (2 == read(&v, 1, 2, handle)) 151 { 152 *err = false; 153 return v; 154 } 155 else 156 { 157 *err = true; 158 return 0; 159 } 160 } 161 162 /// Helper to read one little-endian uint in stream. 163 /// On error, sets `err` to `true` and return 0. 164 uint read_uint_LE(IOHandle handle, bool* err) nothrow @nogc @trusted 165 { 166 uint v; // Note: no support for BigEndian here 167 if (4 == read(&v, 1, 4, handle)) 168 { 169 *err = false; 170 return v; 171 } 172 else 173 { 174 *err = true; 175 return 0; 176 } 177 } 178 179 package: 180 181 /// Setup the IOStream for reading a file. The passed `IOHandle` will need to be a `FILE*`. 182 /// For internal Gamut usage. 183 void setupForFileIO() pure @trusted 184 { 185 read = cast(ReadProc) &fread; 186 write = cast(WriteProc) &fwrite; 187 seek = cast(SeekProc) &fseek; 188 tell = cast(TellProc) &ftell; 189 eof = cast(EofProc) &feof; 190 } 191 192 /// Setup the IOStream for using a a file. The passed `IOHandle` will need to be a `MemoryFile*`. 193 /// For internal Gamut usage. 194 void setupForMemoryIO() pure @trusted 195 { 196 read = cast(ReadProc) &mread; 197 write = cast(WriteProc) &mwrite; 198 seek = cast(SeekProc) &mseek; 199 tell = cast(TellProc) &mtell; 200 eof = cast(EofProc) &meof; 201 } 202 203 /// Setup the IOStream for wrapping another IOStream and logging what happens. 204 /// The passed `IOHandle` will need to be a `WrappedIO`. 205 /// For internal Gamut usage. 206 debug void setupSetupForLogging(ref IOStream io) pure @trusted 207 { 208 io.read = &debug_fread; 209 io.write = &debug_fwrite; 210 io.seek = &debug_fseek; 211 io.tell = &debug_ftell; 212 io.eof = &debug_feof; 213 } 214 } 215 216 debug package struct WrappedIO 217 { 218 IOStream* wrapped; /// I/O object being wrapped. 219 IOHandle handle; /// Original handle. 220 } 221 222 package bool fileIsStartingWithSignature(IOStream *io, IOHandle handle, immutable ubyte[] signature) 223 { 224 assert(signature.length <= 16); 225 226 // save I/O cursor 227 c_long offset = io.tell(handle); 228 229 ubyte[16] header; 230 bool enoughBytes = (signature.length == io.read(header.ptr, 1, signature.length, handle)); 231 bool match = enoughBytes && (signature == header[0..signature.length]); 232 233 // restore I/O cursor 234 if (!io.seekAbsolute(handle, offset)) 235 return false; // TODO: that rare error should propagate somehow 236 237 return match; 238 } 239 240 debug extern(C) @system private 241 { 242 // Note: these functions expect a `WrappedIO` to be passed as handle. 243 import core.stdc.stdio; 244 245 size_t debug_fwrite (const(void)* buffer, size_t size, size_t count, IOHandle handle) 246 { 247 WrappedIO* wio = cast(WrappedIO*) handle; 248 printf("Write %lld elements of %lld bytes\n", cast(long)count, cast(long)size); 249 size_t r = wio.wrapped.write(buffer, size, count, wio.handle); 250 printf(" => written %lld elements\n", cast(long)r); 251 return r; 252 } 253 254 size_t debug_fread (void* buffer, size_t size, size_t count, IOHandle handle) 255 { 256 WrappedIO* wio = cast(WrappedIO*) handle; 257 printf("Read %lld elements of %lld bytes\n", cast(long)count, cast(long)size); 258 size_t r = wio.wrapped.read(buffer, size, count, wio.handle); 259 printf(" => read %lld elements\n", cast(long)r); 260 return r; 261 } 262 263 int debug_fseek(IOHandle handle, c_long offset, int origin) 264 { 265 WrappedIO* wio = cast(WrappedIO*) handle; 266 printf("Seek to offset %lld, mode %d\n", cast(long) offset, origin); 267 int r = wio.wrapped.seek(wio.handle, offset, origin); 268 if (r == 0) 269 printf(" => success\n", r); 270 else 271 printf(" => failure\n"); 272 return r; 273 } 274 275 c_long debug_ftell(IOHandle handle) 276 { 277 WrappedIO* wio = cast(WrappedIO*) handle; 278 printf("Tell offset\n"); 279 c_long r = wio.wrapped.tell(wio.handle); 280 printf(" => offset is %lld\n", cast(long)r); 281 return r; 282 } 283 284 int debug_feof(IOHandle handle) 285 { 286 WrappedIO* wio = cast(WrappedIO*) handle; 287 printf("Is feof?\n"); 288 int r = wio.wrapped.eof(wio.handle); 289 printf(" => returned %d\n", r); 290 return r; 291 } 292 } 293 294 295 package: 296 297 /// This is basically an owned buffer, with capacity, optionally borrowed. 298 /// The original things being that it can both be used for reading and writing. 299 struct MemoryFile 300 { 301 public nothrow @nogc @safe: 302 303 /// If the memory is owned by MemoryFile, or borrowed. 304 bool owned = false; 305 306 /// If can only read from buffer. 307 bool readOnly = false; 308 309 /// Pointer to data (owned or borrowed). 310 /// if `owned`, the buffer is guaranteed to be allocated with malloc/free/realloc. 311 ubyte* data = null; 312 313 /// Length of buffer. 314 size_t bytes = 0; 315 316 /// Current pointer in the buffer. 317 size_t offset = 0; 318 319 /// Size of the underlying allocation, meaningful if `owned`. 320 size_t capacity = 0; 321 322 /// Return internal data pointer (allocated with malloc/free) 323 /// stream doesn't own it anymore, the caller does instead. 324 /// Can only be called if that buffer is owned is the first place. 325 ubyte[] releaseData() @trusted 326 { 327 assert (owned); 328 owned = false; 329 if (data is null) 330 return null; 331 ubyte* v = data; 332 data = null; 333 return v[0..offset]; 334 } 335 336 @disable this(this); 337 338 ~this() @trusted 339 { 340 if (owned) 341 { 342 free(data); 343 data = null; 344 } 345 } 346 347 /// Initialize empty buffer for writing. 348 /// Must be a T.init object. 349 void initEmpty() 350 { 351 owned = true; 352 } 353 354 /// Initialize buffer as reading a slice. 355 /// Must be a T.init object. 356 void initFromExistingSlice(const(ubyte)[] arr) @system 357 { 358 owned = false; 359 readOnly = true; 360 data = cast(ubyte*) arr.ptr; // const_cast here 361 bytes = arr.length; 362 } 363 364 // Resize internal buffer so that it exceeds numBytes. 365 // Such buffers are grow-only. 366 void ensureCapacity(size_t numBytes) @trusted 367 { 368 assert(owned); 369 370 if (capacity >= numBytes) 371 return; 372 373 // Take greater of numBytes and 2 x current capacity, as the new capacity. 374 size_t newCapacity = numBytes; 375 size_t doubleCap = 1 + 2 * capacity; 376 if (doubleCap > newCapacity) 377 newCapacity = doubleCap; 378 379 capacity = newCapacity; 380 data = cast(ubyte*) realloc(data, newCapacity); 381 } 382 } 383 384 extern(C) @system 385 { 386 c_long mtell(MemoryFile *stream) 387 { 388 assert (stream !is null); 389 390 // Files larger than 0x7fffffff bytes not supported, return errors. 391 if (stream.offset > GAMUT_MAX_POSSIBLE_MEMORY_OFFSET) 392 return -1; 393 394 return cast(c_long) stream.offset; 395 } 396 397 int mseek(MemoryFile *stream, c_long offset, int origin) 398 { 399 assert (stream !is null); 400 401 long baseOffset; 402 if (origin == SEEK_CUR) 403 { 404 baseOffset = stream.offset; 405 } 406 else if (origin == SEEK_END) 407 { 408 baseOffset = stream.bytes; 409 } 410 else if (origin == SEEK_SET) 411 { 412 baseOffset = 0; 413 } 414 long newOffset = baseOffset + offset; 415 assert(newOffset < cast(long)GAMUT_MAX_POSSIBLE_MEMORY_OFFSET); 416 417 // It is valid to seek from 0 to bytes. 418 // 0________________N-1 N N+1 419 // ^ ok ^ ok ^ not ok 420 bool success = newOffset >= 0 && newOffset <= stream.bytes; 421 422 if (!success) 423 return -1; 424 425 stream.offset = cast(size_t) newOffset; 426 return 0; 427 } 428 429 size_t mread(void *buffer, size_t size, size_t count, MemoryFile *stream) 430 { 431 assert (stream !is null); 432 433 size_t available = stream.bytes - stream.offset; 434 assert (available >= 0); // cursor not allowed to be after eof 435 436 assert(size <= GAMUT_MAX_POSSIBLE_SIMULTANEOUS_READ); 437 assert(count <= GAMUT_MAX_POSSIBLE_SIMULTANEOUS_READ); 438 long needed = cast(long)size * cast(long)count; // won't overflow 439 440 assert(needed <= GAMUT_MAX_POSSIBLE_SIMULTANEOUS_READ); 441 442 size_t toRead; 443 if (available >= needed) 444 toRead = count; 445 else 446 toRead = available / size; 447 448 size_t bytes = toRead * cast(size_t)size; 449 memcpy(buffer, &stream.data[stream.offset], bytes); 450 stream.offset += bytes; 451 return toRead; 452 } 453 454 size_t mwrite(void *buffer, size_t size, size_t count, MemoryFile *stream) 455 { 456 assert (stream !is null); 457 assert(size <= GAMUT_MAX_POSSIBLE_SIMULTANEOUS_WRITE); 458 assert(count <= GAMUT_MAX_POSSIBLE_SIMULTANEOUS_WRITE); 459 size_t bytes = cast(size_t)size * cast(size_t)count; // won't overflow 460 assert(bytes <= GAMUT_MAX_POSSIBLE_SIMULTANEOUS_WRITE); 461 stream.ensureCapacity(stream.offset + bytes); 462 memcpy(&stream.data[stream.offset], buffer, bytes); 463 stream.offset += bytes; 464 return count; 465 } 466 467 int meof(MemoryFile *stream) 468 { 469 assert(stream); 470 return (stream.offset >= stream.bytes) ? 1 : 0; 471 } 472 }