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 modulegamut.io;
8 9 importcore.stdc.stdio;
10 importcore.stdc.string: memcpy;
11 importcore.stdc.stdlib: malloc, free, realloc;
12 publicimportcore.stdc.config: c_long;
13 publicimportcore.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 enumsize_tGAMUT_MAX_POSSIBLE_MEMORY_OFFSET = 0x7fff_fffe; /// Can't open file larger than this much bytes21 enumsize_tGAMUT_MAX_POSSIBLE_SIMULTANEOUS_READ = 0x7fff_ffff; /// Can't read more bytes than this at once22 enumsize_tGAMUT_MAX_POSSIBLE_SIMULTANEOUS_WRITE = 0x7fff_ffff; /// Can't write more bytes than this at once23 24 staticassert(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) @system28 {
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 read34 /// or written. This number equals the number of bytes transferred35 /// only when size is 1. If an error occurs, or the end of the file36 /// is reached, the return value is a short item count (or zero).37 ///38 /// The file position indicator for the stream is advanced by the39 /// number of bytes successfully read or written.40 ///41 /// fread() does not distinguish between end-of-file and error, and42 /// callers must use feof() and ferror() to determine which43 /// 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 aliasReadProc = size_tfunction(void* buffer, size_tsize, size_tcount, IOHandlehandle);
55 56 /// A function with same signature and semantics than `fwrite`.57 aliasWriteProc = size_tfunction(const(void)* buffer, size_tsize, size_tcount, IOHandlehandle);
58 59 /// A function with same signature and semantics than `fseek`.60 /// Origin: position from which offset is added61 /// 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 aliasSeekProc = intfunction(IOHandlehandle, c_longoffset, intorigin);
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 aliasTellProc = c_longfunction(IOHandlehandle);
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 aliasEofProc = intfunction(IOHandlehandle);
78 }
79 80 81 /// Can be a `FILE*` handle, a `FIMEMORY`, a `WrappedIO`...82 /// identifies the I/O stream.83 aliasIOHandle = void*;
84 85 /// I/O abstraction, to support load/write from a file, from memory, or from user-provided callbacks.86 structIOStream87 {
88 nothrow @nogc @safe:
89 90 /// A function with semantics and signature similar to `fread`.91 ReadProcread;
92 93 /// A function with semantics and signature similar to `fwrite`.94 WriteProcwrite;
95 96 /// A function with semantics and signature similar to `fseek`.97 SeekProcseek;
98 99 /// A function with semantics and signature similar to `ftell`.100 TellProctell;
101 102 /// A function with semantics and signature similar to `feof`.103 EofProceof;
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 0x7fffffff108 boolskipBytes(IOHandlehandle, intnbytes) nothrow @nogc @trusted109 {
110 assert(nbytes >= 0 && nbytes <= GAMUT_MAX_POSSIBLE_SIMULTANEOUS_READ);
111 returnseek(handle, nbytes, SEEK_CUR) == 0;
112 }
113 114 /// Seek to beginning of the I/O stream.115 /// Returns: true if successful.116 boolrewind(IOHandlehandle) nothrow @nogc @trusted117 {
118 returnseek(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 boolseekAbsolute(IOHandlehandle, c_longoffset) nothrow @nogc @trusted124 {
125 returnseek(handle, offset, SEEK_SET) == 0;
126 }
127 128 package:
129 130 /// Setup the IOStream for reading a file. The passed `IOHandle` will need to be a `FILE*`.131 /// For internal Gamut usage.132 voidsetupForFileIO() pure @trusted133 {
134 read = cast(ReadProc) &fread;
135 write = cast(WriteProc) &fwrite;
136 seek = cast(SeekProc) &fseek;
137 tell = cast(TellProc) &ftell;
138 eof = cast(EofProc) &feof;
139 }
140 141 /// Setup the IOStream for using a a file. The passed `IOHandle` will need to be a `MemoryFile*`.142 /// For internal Gamut usage.143 voidsetupForMemoryIO() pure @trusted144 {
145 read = cast(ReadProc) &mread;
146 write = cast(WriteProc) &mwrite;
147 seek = cast(SeekProc) &mseek;
148 tell = cast(TellProc) &mtell;
149 eof = cast(EofProc) &meof;
150 }
151 152 /// Setup the IOStream for wrapping another IOStream and logging what happens.153 /// The passed `IOHandle` will need to be a `WrappedIO`.154 /// For internal Gamut usage.155 debugvoidsetupSetupForLogging(refIOStreamio) pure @trusted156 {
157 io.read = &debug_fread;
158 io.write = &debug_fwrite;
159 io.seek = &debug_fseek;
160 io.tell = &debug_ftell;
161 io.eof = &debug_feof;
162 }
163 }
164 165 debugpackagestructWrappedIO166 {
167 IOStream* wrapped; /// I/O object being wrapped.168 IOHandlehandle; /// Original handle.169 }
170 171 packageboolfileIsStartingWithSignature(IOStream *io, IOHandlehandle, immutableubyte[] signature)
172 {
173 assert(signature.length <= 16);
174 175 // save I/O cursor176 c_longoffset = io.tell(handle);
177 178 ubyte[16] header;
179 boolenoughBytes = (signature.length == io.read(header.ptr, 1, signature.length, handle));
180 boolmatch = enoughBytes && (signature == header[0..signature.length]);
181 182 // restore I/O cursor183 if (!io.seekAbsolute(handle, offset))
184 returnfalse; // TODO: that rare error should propagate somehow185 186 returnmatch;
187 }
188 189 debugextern(C) @systemprivate190 {
191 // Note: these functions expect a `WrappedIO` to be passed as handle.192 importcore.stdc.stdio;
193 194 size_tdebug_fwrite (const(void)* buffer, size_tsize, size_tcount, IOHandlehandle)
195 {
196 WrappedIO* wio = cast(WrappedIO*) handle;
197 printf("Write %lld elements of %lld bytes\n", cast(long)count, cast(long)size);
198 size_tr = wio.wrapped.write(buffer, size, count, wio.handle);
199 printf(" => written %lld elements\n", cast(long)r);
200 returnr;
201 }
202 203 size_tdebug_fread (void* buffer, size_tsize, size_tcount, IOHandlehandle)
204 {
205 WrappedIO* wio = cast(WrappedIO*) handle;
206 printf("Read %lld elements of %lld bytes\n", cast(long)count, cast(long)size);
207 size_tr = wio.wrapped.read(buffer, size, count, wio.handle);
208 printf(" => read %lld elements\n", cast(long)r);
209 returnr;
210 }
211 212 intdebug_fseek(IOHandlehandle, c_longoffset, intorigin)
213 {
214 WrappedIO* wio = cast(WrappedIO*) handle;
215 printf("Seek to offset %lld, mode %d\n", cast(long) offset, origin);
216 intr = wio.wrapped.seek(wio.handle, offset, origin);
217 if (r == 0)
218 printf(" => success\n", r);
219 else220 printf(" => failure\n");
221 returnr;
222 }
223 224 c_longdebug_ftell(IOHandlehandle)
225 {
226 WrappedIO* wio = cast(WrappedIO*) handle;
227 printf("Tell offset\n");
228 c_longr = wio.wrapped.tell(wio.handle);
229 printf(" => offset is %lld\n", cast(long)r);
230 returnr;
231 }
232 233 intdebug_feof(IOHandlehandle)
234 {
235 WrappedIO* wio = cast(WrappedIO*) handle;
236 printf("Is feof?\n");
237 intr = wio.wrapped.eof(wio.handle);
238 printf(" => returned %d\n", r);
239 returnr;
240 }
241 }
242 243 244 package:
245 246 /// This is basically an owned buffer, with capacity, optionally borrowed.247 /// The original things being that it can both be used for reading and writing.248 structMemoryFile249 {
250 publicnothrow @nogc @safe:
251 252 /// If the memory is owned by MemoryFile, or borrowed.253 boolowned = false;
254 255 /// If can only read from buffer.256 boolreadOnly = false;
257 258 /// Pointer to data (owned or borrowed).259 /// if `owned`, the buffer is guaranteed to be allocated with malloc/free/realloc.260 ubyte* data = null;
261 262 /// Length of buffer.263 size_tbytes = 0;
264 265 /// Current pointer in the buffer.266 size_toffset = 0;
267 268 /// Size of the underlying allocation, meaningful if `owned`.269 size_tcapacity = 0;
270 271 /// Return internal data pointer (allocated with malloc/free)272 /// stream doesn't own it anymore, the caller does instead.273 /// Can only be called if that buffer is owned is the first place.274 ubyte[] releaseData() @trusted275 {
276 assert (owned);
277 owned = false;
278 if (dataisnull)
279 returnnull;
280 ubyte* v = data;
281 data = null;
282 returnv[0..offset];
283 }
284 285 @disablethis(this);
286 287 ~this() @trusted288 {
289 if (owned)
290 {
291 free(data);
292 data = null;
293 }
294 }
295 296 /// Initialize empty buffer for writing.297 /// Must be a T.init object.298 voidinitEmpty()
299 {
300 owned = true;
301 }
302 303 /// Initialize buffer as reading a slice.304 /// Must be a T.init object.305 voidinitFromExistingSlice(const(ubyte)[] arr) @system306 {
307 owned = false;
308 readOnly = true;
309 data = cast(ubyte*) arr.ptr; // const_cast here310 bytes = arr.length;
311 }
312 313 // Resize internal buffer so that it exceeds numBytes.314 // Such buffers are grow-only.315 voidensureCapacity(size_tnumBytes) @trusted316 {
317 assert(owned);
318 319 if (capacity >= numBytes)
320 return;
321 322 // Take greater of numBytes and 2 x current capacity, as the new capacity.323 size_tnewCapacity = numBytes;
324 size_tdoubleCap = 1 + 2 * capacity;
325 if (doubleCap > newCapacity)
326 newCapacity = doubleCap;
327 328 capacity = newCapacity;
329 data = cast(ubyte*) realloc(data, newCapacity);
330 }
331 }
332 333 extern(C) @system334 {
335 c_longmtell(MemoryFile *stream)
336 {
337 assert (stream !isnull);
338 339 // Files larger than 0x7fffffff bytes not supported, return errors.340 if (stream.offset > GAMUT_MAX_POSSIBLE_MEMORY_OFFSET)
341 return -1;
342 343 returncast(c_long) stream.offset;
344 }
345 346 intmseek(MemoryFile *stream, c_longoffset, intorigin)
347 {
348 assert (stream !isnull);
349 350 longbaseOffset;
351 if (origin == SEEK_CUR)
352 {
353 baseOffset = stream.offset;
354 }
355 elseif (origin == SEEK_END)
356 {
357 baseOffset = stream.bytes;
358 }
359 elseif (origin == SEEK_SET)
360 {
361 baseOffset = 0;
362 }
363 longnewOffset = baseOffset + offset;
364 assert(newOffset < cast(long)GAMUT_MAX_POSSIBLE_MEMORY_OFFSET);
365 366 // It is valid to seek from 0 to bytes.367 // 0________________N-1 N N+1368 // ^ ok ^ ok ^ not ok369 boolsuccess = newOffset >= 0 && newOffset <= stream.bytes;
370 371 if (!success)
372 return -1;
373 374 stream.offset = cast(size_t) newOffset;
375 return0;
376 }
377 378 size_tmread(void *buffer, size_tsize, size_tcount, MemoryFile *stream)
379 {
380 assert (stream !isnull);
381 382 size_tavailable = stream.bytes - stream.offset;
383 assert (available >= 0); // cursor not allowed to be after eof384 385 assert(size <= GAMUT_MAX_POSSIBLE_SIMULTANEOUS_READ);
386 assert(count <= GAMUT_MAX_POSSIBLE_SIMULTANEOUS_READ);
387 longneeded = cast(long)size * cast(long)count; // won't overflow388 389 assert(needed <= GAMUT_MAX_POSSIBLE_SIMULTANEOUS_READ);
390 391 size_ttoRead;
392 if (available >= needed)
393 toRead = count;
394 else395 toRead = available / size;
396 397 size_tbytes = toRead * cast(size_t)size;
398 memcpy(buffer, &stream.data[stream.offset], bytes);
399 stream.offset += bytes;
400 returntoRead;
401 }
402 403 size_tmwrite(void *buffer, size_tsize, size_tcount, MemoryFile *stream)
404 {
405 assert (stream !isnull);
406 assert(size <= GAMUT_MAX_POSSIBLE_SIMULTANEOUS_WRITE);
407 assert(count <= GAMUT_MAX_POSSIBLE_SIMULTANEOUS_WRITE);
408 size_tbytes = cast(size_t)size * cast(size_t)count; // won't overflow409 assert(bytes <= GAMUT_MAX_POSSIBLE_SIMULTANEOUS_WRITE);
410 stream.ensureCapacity(stream.offset + bytes);
411 memcpy(&stream.data[stream.offset], buffer, bytes);
412 stream.offset += bytes;
413 returncount;
414 }
415 416 intmeof(MemoryFile *stream)
417 {
418 assert(stream);
419 return (stream.offset >= stream.bytes) ? 1 : 0;
420 }
421 }