sq3: show SQLite error messages on stderr by default
[iv.d.git] / vfs / vfile.d
blob3b38f7aea98af9f4e9bfa409f4f951c87a26909f
1 /* Invisible Vector Library
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 /**
18 * wrap any low-level (or high-level) stream into refcounted struct.
19 * this struct can be used instead of `std.stdio.File` when you need
20 * a concrete type instead of working with generic stream templates.
21 * wrapped stream is thread-safe (i.e. reads, writes, etc), but
22 * wrapper itself isn't.
24 module iv.vfs.vfile /*is aliced*/;
26 //version = vfs_add_std_stdio_wrappers;
27 //version = vfs_debug_name_set;
29 private:
30 static import core.stdc.stdio;
31 static import core.sys.posix.stdio;
32 static import core.sys.posix.unistd;
33 version(vfs_add_std_stdio_wrappers) static import std.stdio;
35 // we need this to simulate `synchronized`
36 extern (C) void _d_monitorenter (Object h) nothrow;
37 extern (C) void _d_monitorexit (Object h) nothrow;
39 import iv.alice;
40 import iv.vfs.types : Seek, VFSHiddenPointerHelper;
41 import iv.vfs.config;
42 import iv.vfs.error;
43 import iv.vfs.pred;
45 version(LDC) {}
46 else {
47 version(vfs_add_std_stdio_wrappers) version = vfs_stdio_wrapper;
50 // uncomment to use zlib instead of internal inflater
51 //version = vfs_use_zlib_unpacker;
53 /// mark struct fields with this for VFile.readStruct
54 public enum IVVFSIgnore;
57 // ////////////////////////////////////////////////////////////////////////// //
58 /// wrapper structure for various streams. kinda like `std.stdio.File`,
59 /// but with less features. not thread-safe for assigns and such, but
60 /// thread-safe for i/o. i.e. you'd better not share this struct between
61 /// threads, but can safely use struct copies in different threads.
62 public struct VFile {
63 private:
64 /*WrappedStreamRC*/usize wstp; // yep, the whole struct size: one pointer
66 package @property WrappedStreamRC wst () const pure nothrow @trusted @nogc { pragma(inline, true); return *cast(WrappedStreamRC*)&wstp; }
68 static bool doDecRef (WrappedStreamRC st) {
69 if (st !is null) {
70 debug(vfs_rc) { import core.stdc.stdio : printf; printf("DO DECREF FOR 0x%08x\n", cast(void*)st); }
71 if (st.decRef) {
72 // free `wst` itself
73 import core.memory : GC;
74 import core.stdc.stdlib : free;
75 debug(vfs_vfile_gc) { import core.stdc.stdio : printf; printf("REMOVING WRAPPER 0x%08x\n", st); }
76 if (st.gcUnregister !is null) {
77 debug(vfs_vfile_gc) { import core.stdc.stdio : printf; printf("CALLING GC CLEANUP DELEGATE FOR WRAPPER 0x%08x\n", st); }
78 st.gcUnregister(cast(void*)st);
80 free(cast(void*)st);
81 return true;
84 return false;
87 this (void* wptr) { wstp = cast(usize)wptr; }
88 //this (usize wptr) { wstp = wptr; }
90 public:
91 this (const VFile fl) {
92 wstp = fl.wstp;
93 if (wstp) wst.incRef();
96 version(vfs_stdio_wrapper)
97 this (std.stdio.File fl, const(char)[] fname=null) {
98 try {
99 wstp = WrapStdioFile(fl, fname);
100 } catch (Exception e) {
101 // chain exception
102 throw new VFSException("can't open file", __FILE__, __LINE__, e);
106 /// this will throw if `fl` is `null`; `fl` is (not) owned by VFile now
107 this (core.stdc.stdio.FILE* fl, bool own=true) {
108 if (fl is null) throw new VFSException("can't open file");
109 if (own) wstp = WrapLibcFile!true(fl, null); else wstp = WrapLibcFile!false(fl, null);
112 /// this will throw if `fl` is `null`; `fl` is (not) owned by VFile now
113 this (core.stdc.stdio.FILE* fl, const(char)[] fname, bool own=true) {
114 if (fl is null) throw new VFSException("can't open file");
115 if (own) wstp = WrapLibcFile!true(fl, fname); else wstp = WrapLibcFile!false(fl, fname);
118 /// this will throw if `fl` is `null`; `fl` is (not) owned by VFile now
119 static import etc.c.zlib;
120 package(iv.vfs) static VFile OpenGZ (etc.c.zlib.gzFile fl, bool own=true) {
121 if (fl is null) throw new VFSException("can't open file");
122 VFile fres;
123 if (own) fres.wstp = WrapGZFile!true(fl, null); else fres.wstp = WrapGZFile!false(fl, null);
124 return fres;
127 /// this will throw if `fl` is `null`; `fl` is (not) owned by VFile now
128 package(iv.vfs) static VFile OpenGZ (etc.c.zlib.gzFile fl, const(char)[] afname, bool own=true) {
129 if (fl is null) throw new VFSException("can't open file");
130 VFile fres;
131 if (own) fres.wstp = WrapGZFile!true(fl, afname); else fres.wstp = WrapGZFile!false(fl, afname);
132 return fres;
135 /// wrap file descriptor; `fd` is owned by VFile now; can throw
136 static if (VFS_NORMAL_OS) this (int fd, bool own=true) {
137 if (fd < 0) throw new VFSException("can't open file");
138 if (own) wstp = WrapFD!true(fd, null); else wstp = WrapFD!true(fd, null);
141 /// wrap file descriptor; `fd` is owned by VFile now; can throw
142 static if (VFS_NORMAL_OS) this (int fd, const(char)[] fname, bool own=true) {
143 if (fd < 0) throw new VFSException("can't open file");
144 if (own) wstp = WrapFD!true(fd, fname); else wstp = WrapFD!true(fd, fname);
147 /// open named file with VFS engine; start with "/" or "./" to use only disk files
148 this(T:const(char)[]) (T fname, const(char)[] mode=null) {
149 import iv.vfs.main : vfsOpenFile;
150 debug(vfs_rc) { import core.stdc.stdio : printf; printf("CTOR:STR(%.*s)\n", cast(uint)fname.length, fname.ptr); }
151 auto fl = vfsOpenFile(fname, mode);
152 debug(vfs_rc) { import core.stdc.stdio : printf; printf("CTOR(0x%08x)\n", cast(void*)fl.wstp); }
153 wstp = fl.wstp;
154 fl.wstp = 0;
155 //this = fl;
158 this (this) nothrow @trusted @nogc {
159 debug(vfs_rc) { import core.stdc.stdio : printf; printf("POSTBLIT(0x%08x)\n", cast(void*)wstp); }
160 debug(vfs_rc_trace) {
161 try { throw new Exception("stack trace"); } catch (Exception e) { import core.stdc.stdio; string es = e.toString; printf("*** %.*s", cast(uint)es.length, es.ptr); }
163 if (wst !is null) wst.incRef();
166 // WARNING: dtor hides exceptions!
167 ~this () nothrow {
168 debug(vfs_rc) { import core.stdc.stdio : printf; printf("DTOR(0x%08x)\n", cast(void*)wstp); }
169 debug(vfs_rc_trace) {
170 try { throw new Exception("stack trace"); } catch (Exception e) { import core.stdc.stdio; string es = e.toString; printf("*** %.*s", cast(uint)es.length, es.ptr); }
172 try {
173 doDecRef(wst);
174 } catch (Exception e) {
178 @property bool opCast(T) () const nothrow @safe @nogc if (is(T == bool)) { return this.isOpen; }
180 @property const(char)[] name () const nothrow @safe @nogc {
181 if (!wstp) return null;
182 version(aliced) {
183 synchronized(wst) return wst.name;
184 } else {
185 // vanilla sux!
186 return wst.name;
190 @property bool isOpen () const nothrow @safe @nogc {
191 if (!wstp) return false;
192 version(aliced) {
193 synchronized(wst) return wst.isOpen;
194 } else {
195 // vanilla sux!
196 return wst.isOpen;
200 void close () {
201 try {
202 auto oldo = wst;
203 wstp = 0;
204 doDecRef(oldo);
205 } catch (Exception e) {
206 // chain exception
207 throw new VFSException("read error", __FILE__, __LINE__, e);
211 @property bool eof () { return (!wstp || wst.eof); }
213 T[] rawRead(T) (T[] buf) if (!is(T == const) && !is(T == immutable)) {
214 if (!isOpen) throw new VFSException("can't read from closed stream");
215 if (buf.length > 0) {
216 ssize res;
217 try {
218 synchronized(wst) res = wst.read(buf.ptr, buf.length*T.sizeof);
219 } catch (Exception e) {
220 // chain exception
221 throw new VFSException("read error", __FILE__, __LINE__, e);
223 if (res == -1 || res%T.sizeof != 0) throw new VFSException("read error");
224 return buf[0..res/T.sizeof];
225 } else {
226 return buf[0..0];
230 private T[] rawReadNoLock(T) (T[] buf) if (!is(T == const) && !is(T == immutable)) {
231 if (!isOpen) throw new VFSException("can't read from closed stream");
232 if (buf.length > 0) {
233 ssize res;
234 try {
235 res = wst.read(buf.ptr, buf.length*T.sizeof);
236 } catch (Exception e) {
237 // chain exception
238 throw new VFSException("read error", __FILE__, __LINE__, e);
240 if (res == -1 || res%T.sizeof != 0) throw new VFSException("read error");
241 return buf[0..res/T.sizeof];
242 } else {
243 return buf[0..0];
247 /// read exact size or throw error
248 T[] rawReadExact(T) (T[] buf) if (!is(T == const) && !is(T == immutable)) {
249 if (buf.length == 0) return buf;
250 auto left = buf.length*T.sizeof;
251 auto dp = cast(ubyte*)buf.ptr;
252 synchronized(wst) {
253 try {
254 while (left > 0) {
255 ssize res = wst.read(dp, left);
256 if (res <= 0) throw new VFSException("read error");
257 dp += res;
258 left -= res;
260 } catch (Exception e) {
261 // chain exception
262 throw new VFSException("read error", __FILE__, __LINE__, e);
265 return buf;
268 private T[] rawReadExactNoLock(T) (T[] buf) if (!is(T == const) && !is(T == immutable)) {
269 if (buf.length == 0) return buf;
270 auto left = buf.length*T.sizeof;
271 auto dp = cast(ubyte*)buf.ptr;
272 try {
273 while (left > 0) {
274 ssize res = wst.read(dp, left);
275 if (res <= 0) throw new VFSException("read error");
276 dp += res;
277 left -= res;
279 } catch (Exception e) {
280 // chain exception
281 throw new VFSException("read error", __FILE__, __LINE__, e);
283 return buf;
286 void rawWrite(T) (in T[] buf) {
287 if (!isOpen) throw new VFSException("can't write to closed stream");
288 if (buf.length > 0) {
289 ssize res;
290 try {
291 synchronized(wst) res = wst.write(buf.ptr, buf.length*T.sizeof);
292 } catch (Exception e) {
293 // chain exception
294 throw new VFSException("read error", __FILE__, __LINE__, e);
296 if (res == -1 || res%T.sizeof != 0) throw new VFSException("write error");
300 private void rawWriteNoLock(T) (in T[] buf) {
301 if (!isOpen) throw new VFSException("can't write to closed stream");
302 if (buf.length > 0) {
303 ssize res;
304 try {
305 res = wst.write(buf.ptr, buf.length*T.sizeof);
306 } catch (Exception e) {
307 // chain exception
308 throw new VFSException("read error", __FILE__, __LINE__, e);
310 if (res == -1 || res%T.sizeof != 0) throw new VFSException("write error");
314 alias rawWriteExact = rawWrite; // for convenience
316 long seek (long offset, int origin=Seek.Set) {
317 if (!isOpen) throw new VFSException("can't seek in closed stream");
318 long p;
319 try {
320 synchronized(wst) p = wst.lseek(offset, origin);
321 } catch (Exception e) {
322 // chain exception
323 throw new VFSException("seek error", __FILE__, __LINE__, e);
325 if (p == -1) throw new VFSException("seek error");
326 return p;
329 @property long tell () {
330 if (!isOpen) throw new VFSException("can't get position in closed stream");
331 long p;
332 try {
333 synchronized(wst) p = wst.lseek(0, Seek.Cur);
334 } catch (Exception e) {
335 // chain exception
336 throw new VFSException("tell error", __FILE__, __LINE__, e);
338 if (p == -1) throw new VFSException("tell error");
339 return p;
342 @property bool hasSize () {
343 if (!isOpen) throw new VFSException("can't query closed stream");
344 synchronized(wst) return wst.hasSize;
347 @property long size () {
348 if (!isOpen) throw new VFSException("can't get size of closed stream");
349 bool noChain = false;
350 long p;
351 try {
352 synchronized(wst) {
353 if (wst.hasSize) {
354 p = wst.getsize;
355 if (p == -1) { noChain = true; throw new VFSException("size error"); }
356 } else {
357 auto opos = wst.lseek(0, Seek.Cur);
358 if (opos == -1) { noChain = true; throw new VFSException("size error"); }
359 p = wst.lseek(0, Seek.End);
360 if (p == -1) { noChain = true; throw new VFSException("size error"); }
361 if (wst.lseek(opos, Seek.Set) == -1) { noChain = true; throw new VFSException("size error"); }
364 } catch (Exception e) {
365 // chain exception
366 if (noChain) throw e;
367 throw new VFSException("size error", __FILE__, __LINE__, e);
369 return p;
372 void flush () {
373 if (!isOpen) throw new VFSException("can't get size of closed stream");
374 bool noChain = false;
375 try {
376 if (!wst.flush) {
377 noChain = true;
378 throw new VFSException("flush error");
380 } catch (Exception e) {
381 // chain exception
382 if (noChain) throw e;
383 throw new VFSException("flush error", __FILE__, __LINE__, e);
387 void opAssign (VFile src) nothrow {
388 if (!wstp && !src.wstp) return;
389 try {
390 debug(vfs_rc) { import core.stdc.stdio : printf; printf("***OPASSIGN(0x%08x -> 0x%08x)\n", cast(void*)src.wstp, cast(void*)wstp); }
391 if (wstp) {
392 // assigning to opened stream
393 if (src.wstp) {
394 // both streams are active
395 if (wstp == src.wstp) return; // nothing to do
396 auto oldo = wst;
397 auto newo = src.wst;
398 newo.incRef();
399 // replace stream object
400 wstp = src.wstp;
401 // release old stream
402 doDecRef(oldo);
403 } else {
404 // just close this one
405 auto oldo = wst;
406 wstp = 0;
407 doDecRef(oldo);
409 } else if (src.wstp) {
410 // this stream is closed, but other is active; easy deal
411 wstp = src.wstp;
412 wst.incRef();
414 } catch (Exception e) {
415 // chain exception
416 //throw new VFSException("read error", __FILE__, __LINE__, e);
420 usize toHash () const pure nothrow @safe @nogc { return wstp; } // yeah, so simple
421 bool opEquals() (auto ref VFile s) const { return (wstp == s.wstp); }
423 // make this output stream
424 void put (const(char)[] s...) { rawWrite(s); }
425 //void put (const(wchar)[] s...) { rawWrite(s); }
426 //void put (const(dchar)[] s...) { rawWrite(s); }
428 static struct LockedWriterImpl {
429 private VFile fl;
431 private this (VFile afl) nothrow {
432 if (afl.wstp) {
433 import core.atomic;
434 fl = afl;
435 if (atomicOp!"+="(fl.wst.wrrc, 1) == 1) {
436 //{ import core.stdc.stdio; printf("LockedWriterImpl(0x%08x): lock!\n", cast(uint)fl.wstp); }
437 _d_monitorenter(fl.wst); // emulate `synchronized(fl.wst)` enter
442 this (this) {
443 if (fl.wstp) {
444 import core.atomic;
445 atomicOp!"+="(fl.wst.wrrc, 1);
449 ~this () {
450 if (fl.wstp) {
451 import core.atomic;
452 if (atomicOp!"-="(fl.wst.wrrc, 1) == 0) {
453 //{ import core.stdc.stdio; printf("LockedWriterImpl(0x%08x): unlock!\n", cast(uint)fl.wstp); }
454 _d_monitorexit(fl.wst); // emulate `synchronized(fl.wst)` exit
456 fl = VFile.init; // just in case
460 void put (const(char)[] s...) { fl.rawWriteNoLock(s); }
463 @property LockedWriterImpl lockedWriter () { return LockedWriterImpl(this); }
465 static struct UnlockedWriterImpl {
466 private VFile fl;
468 private this (VFile afl) nothrow {
469 if (afl.wstp) fl = afl;
472 ~this () { fl = VFile.init; /* just in case */ }
474 void put (const(char)[] s...) { fl.rawWriteNoLock(s); }
477 @property UnlockedWriterImpl unlockedWriter () { return UnlockedWriterImpl(this); }
479 // stream i/o functions
480 version(LittleEndian) {
481 private enum MyEHi = "LE";
482 private enum MyELo = "le";
483 private enum ItEHi = "BE";
484 private enum ItELo = "be";
485 } else {
486 private enum MyEHi = "BE";
487 private enum MyELo = "be";
488 private enum ItEHi = "LE";
489 private enum ItELo = "le";
492 public enum MyEndianness = MyEHi;
494 // ////////////////////////////////////////////////////////////////////// //
495 /// write integer value of the given type, with the given endianness (default: little-endian)
496 /// usage: st.writeNum!ubyte(10)
497 void writeNum(T, string es="LE") (T n) if (__traits(isIntegral, T)) {
498 static assert(T.sizeof <= 8); // just in case
499 static if (es == MyEHi || es == MyELo || T.sizeof == 1) {
500 rawWrite((&n)[0..1]);
501 } else static if (es == ItEHi || es == ItELo) {
502 ubyte[T.sizeof] b = void;
503 version(LittleEndian) {
504 // convert to big-endian
505 foreach_reverse (ref x; b) { x = n&0xff; n >>= 8; }
506 } else {
507 // convert to little-endian
508 foreach (ref x; b) { x = n&0xff; n >>= 8; }
510 rawWrite(b[]);
511 } else {
512 static assert(0, "invalid endianness: '"~es~"'");
516 /// read integer value of the given type, with the given endianness (default: little-endian)
517 /// usage: auto v = st.readNum!ubyte
518 T readNum(T, string es="LE") () if (__traits(isIntegral, T)) {
519 static assert(T.sizeof <= 8); // just in case
520 static if (es == MyEHi || es == MyELo || T.sizeof == 1) {
521 T v = void;
522 rawReadExact((&v)[0..1]);
523 return v;
524 } else static if (es == ItEHi || es == ItELo) {
525 ubyte[T.sizeof] b = void;
526 rawReadExact(b[]);
527 T v = 0;
528 version(LittleEndian) {
529 // convert from big-endian
530 foreach (ubyte x; b) { v <<= 8; v |= x; }
531 } else {
532 // conver from little-endian
533 foreach_reverse (ubyte x; b) { v <<= 8; v |= x; }
535 return v;
536 } else {
537 static assert(0, "invalid endianness: '"~es~"'");
541 private enum reverseBytesMixin = "
542 foreach (idx; 0..b.length/2) {
543 ubyte t = b[idx];
544 b[idx] = b[$-idx-1];
545 b[$-idx-1] = t;
549 /// write floating value of the given type, with the given endianness (default: little-endian)
550 /// usage: st.writeNum!float(10)
551 void writeNum(T, string es="LE") (T n) if (__traits(isFloating, T)) {
552 static assert(T.sizeof <= 8); // just in case
553 static if (es == MyEHi || es == MyELo) {
554 rawWrite((&n)[0..1]);
555 } else static if (es == ItEHi || es == ItELo) {
556 import core.stdc.string : memcpy;
557 ubyte[T.sizeof] b = void;
558 memcpy(b.ptr, &v, T.sizeof);
559 mixin(reverseBytesMixin);
560 rawWrite(b[]);
561 } else {
562 static assert(0, "invalid endianness: '"~es~"'");
566 /// read floating value of the given type, with the given endianness (default: little-endian)
567 /// usage: auto v = st.readNum!float
568 T readNum(T, string es="LE") () if (__traits(isFloating, T)) {
569 static assert(T.sizeof <= 8); // just in case
570 T v = void;
571 static if (es == MyEHi || es == MyELo) {
572 rawReadExact((&v)[0..1]);
573 } else static if (es == ItEHi || es == ItELo) {
574 import core.stdc.string : memcpy;
575 ubyte[T.sizeof] b = void;
576 rawReadExact(b[]);
577 mixin(reverseBytesMixin);
578 memcpy(&v, b.ptr, T.sizeof);
579 } else {
580 static assert(0, "invalid endianness: '"~es~"'");
582 return v;
585 /// write btc-style integer
586 void writeVarUNum(T, string es="LE") (T n) if (__traits(isIntegral, T) && __traits(isUnsigned, T)) {
587 static assert(T.sizeof <= 8); // just in case
588 if (n < 253) {
589 writeNum!(ubyte, es)(cast(ubyte)n);
590 } else if (n <= 255) {
591 writeNum!(ubyte, es)(253);
592 writeNum!(ushort, es)(cast(ushort)n);
593 } else {
594 // ushort?
595 static if (T.sizeof == 2) {
596 writeNum!(ubyte, es)(253);
597 writeNum!(ushort, es)(cast(ushort)n);
598 } else {
599 // fits into ushort?
600 if (n <= ushort.max) {
601 writeNum!(ubyte, es)(253);
602 writeNum!(ushort, es)(cast(ushort)n);
603 } else {
604 // uint?
605 static if (T.sizeof == 4) {
606 writeNum!(ubyte, es)(254);
607 writeNum!(uint, es)(cast(uint)n);
608 } else {
609 // fits into uint?
610 if (n <= uint.max) {
611 writeNum!(ubyte, es)(254);
612 writeNum!(uint, es)(cast(uint)n);
613 } else {
614 // ulong
615 writeNum!(ubyte, es)(255);
616 writeNum!(ulong, es)(cast(ulong)n);
624 /// read btc-style integer
625 T readVarUNum(T, string es="LE") () if (__traits(isIntegral, T) && __traits(isUnsigned, T)) {
626 ubyte b = readNum!(ubyte, es);
627 if (b < 253) return cast(T)b;
628 switch (b) {
629 case 253: // ushort
630 ushort v = readNum!(ushort, es);
631 if (v < 253) throw new VFSException("invalid varnum");
632 // check overflow
633 static if (T.sizeof == 1) {
634 import std.conv : ConvOverflowException;
635 if (v > ubyte.max) throw new ConvOverflowException("varnum overflow");
637 return cast(T)v;
638 case 254: // uint
639 uint v = readNum!(uint, es);
640 if (v < 253) throw new VFSException("invalid varnum");
641 // check overflow
642 static if (T.sizeof < uint.sizeof) {
643 import std.conv : ConvOverflowException;
644 if (v > T.max) throw new ConvOverflowException("varnum overflow");
646 return cast(T)v;
647 case 255: // ulong
648 ulong v = readNum!(ulong, es);
649 if (v < 253) throw new VFSException("invalid varnum");
650 // check overflow
651 static if (T.sizeof < ulong.sizeof) {
652 import std.conv : ConvOverflowException;
653 if (v > T.max) throw new ConvOverflowException("varnum overflow");
655 return cast(T)v;
656 default: break;
658 return cast(T)b;
662 // ////////////////////////////////////////////////////////////////////////// //
663 // first byte: bit 7 is sign; bit 6 is "has more bytes" mark; bits 0..5: first number bits
664 // next bytes: bit 7 is "has more bytes" mark; bits 0..6: next number bits
665 void writeXInt(T:ulong) (T vv) {
666 ubyte[16] buf = void; // actually, 10 is enough ;-)
667 static if (T.sizeof == ulong.sizeof) ulong v = cast(ulong)vv;
668 else static if (!__traits(isUnsigned, T)) ulong v = cast(ulong)cast(long)vv; // extend sign bits
669 else ulong v = cast(ulong)vv;
670 uint len = 1; // at least
671 // now write as signed
672 if (v == 0x8000_0000_0000_0000UL) {
673 // special (negative zero)
674 buf.ptr[0] = 0x80;
675 } else {
676 if (v&0x8000_0000_0000_0000UL) {
677 v = (v^~0uL)+1; // negate v
678 buf.ptr[0] = 0x80; // sign bit
679 } else {
680 buf.ptr[0] = 0;
682 buf.ptr[0] |= v&0x3f;
683 v >>= 6;
684 if (v != 0) buf.ptr[0] |= 0x40; // has more
685 while (v != 0) {
686 buf.ptr[len] = v&0x7f;
687 v >>= 7;
688 if (v > 0) buf.ptr[len] |= 0x80; // has more
689 ++len;
692 rawWrite(buf.ptr[0..len]);
695 T readXInt(T:ulong) () {
696 import std.conv : ConvOverflowException;
697 ulong v = 0;
698 ubyte c = void;
699 // first byte contains sign flag
700 rawReadExact((&c)[0..1]);
701 if (c == 0x80) {
702 // special (negative zero)
703 v = 0x8000_0000_0000_0000UL;
704 } else {
705 bool neg = ((c&0x80) != 0);
706 v = c&0x3f;
707 c <<= 1;
708 // 63/7 == 9, so we can shift at most 56==(7*8) bits
709 ubyte shift = 6;
710 while (c&0x80) {
711 if (shift > 62) throw new ConvOverflowException("readXInt overflow");
712 rawReadExact((&c)[0..1]);
713 ulong n = c&0x7f;
714 if (shift == 62 && n > 1) throw new ConvOverflowException("readXInt overflow");
715 n <<= shift;
716 v |= n;
717 shift += 7;
719 if (neg) v = (v^~0uL)+1; // negate v
721 // now convert to output
722 static if (T.sizeof == v.sizeof) {
723 return v;
724 } else static if (!__traits(isUnsigned, T)) {
725 auto l = cast(long)v;
726 if (v < T.min) throw new ConvOverflowException("readXInt underflow");
727 if (v > T.max) throw new ConvOverflowException("readXInt overflow");
728 return cast(T)l;
729 } else {
730 if (v > T.max) throw new ConvOverflowException("readXInt overflow");
731 return cast(T)v;
735 // ////////////////////////////////////////////////////////////////////// //
736 void readStruct(string es="LE", SS) (ref SS st) if (is(SS == struct)) {
737 void unserData(T) (ref T v) {
738 import std.traits : Unqual;
739 alias UT = Unqual!T;
740 static if (is(T : V[], V)) {
741 // array
742 static if (__traits(isStaticArray, T)) {
743 foreach (ref it; v) unserData(it);
744 } else static if (is(UT == char)) {
745 // special case: dynamic `char[]` array will be loaded as asciiz string
746 char c;
747 for (;;) {
748 if (rawRead((&c)[0..1]).length == 0) break; // don't require trailing zero on eof
749 if (c == 0) break;
750 v ~= c;
752 } else {
753 assert(0, "cannot load dynamic arrays yet");
755 } else static if (is(T : V[K], K, V)) {
756 assert(0, "cannot load associative arrays yet");
757 } else static if (__traits(isIntegral, UT) || __traits(isFloating, UT)) {
758 // this takes care of `*char` and `bool` too
759 v = cast(UT)readNum!(UT, es);
760 } else static if (is(T == struct)) {
761 // struct
762 import std.traits : FieldNameTuple, hasUDA;
763 foreach (string fldname; FieldNameTuple!T) {
764 static if (!hasUDA!(__traits(getMember, T, fldname), IVVFSIgnore)) {
765 unserData(__traits(getMember, v, fldname));
771 unserData(st);
776 // ////////////////////////////////////////////////////////////////////////// //
777 // base refcounted class for wrapped stream
778 package class WrappedStreamRC {
779 protected:
780 shared uint rc = 1;
781 shared uint wrrc = 0; // locked writer rc
782 bool eofhit;
783 //string fname;
784 char[512] fnamebuf=0;
785 usize fnameptr;
786 usize fnamelen;
788 this (const(char)[] aname) nothrow @trusted @nogc { setFileName(aname); }
790 final void setFileName (const(char)[] aname) nothrow @trusted @nogc {
791 if (aname.length) {
792 if (aname.length <= fnamebuf.length) {
793 if (fnameptr) { import core.stdc.stdlib : free; free(cast(void*)fnameptr); fnameptr = 0; }
794 fnamebuf[0..aname.length] = aname;
795 fnamelen = aname.length;
796 version(Windows) foreach (ref char ch; fnamebuf[0..fnamelen]) if (ch == '\\') ch = '/';
797 } else {
798 import core.stdc.stdlib : realloc;
799 auto nb = cast(char*)realloc(cast(void*)fnameptr, aname.length);
800 if (nb !is null) {
801 nb[0..aname.length] = aname[];
802 fnameptr = cast(usize)nb;
803 fnamelen = aname.length;
804 version(Windows) foreach (ref char ch; nb[0..fnamelen]) if (ch == '\\') ch = '/';
805 } else {
806 fnamelen = 0;
809 } else {
810 if (fnameptr) { import core.stdc.stdlib : free; free(cast(void*)fnameptr); fnameptr = 0; }
811 fnamelen = 0;
813 version(vfs_debug_name_set) {
814 import core.stdc.stdio;
815 stderr.fprintf("WrappedStreamRC: setFileName: aname");
816 if (aname is null) stderr.fprintf(" IS NULL"); else stderr.fprintf("=<%.*s>", cast(uint)aname.length, aname.ptr);
817 stderr.fprintf("; set name=<%.*s>\n", cast(uint)name.length, name.ptr);
821 // this shouldn't be called, ever
822 ~this () nothrow @trusted {
823 assert(0); // why we are here?!
824 //if (gcUnregister !is null) gcUnregister(cast(void*)this);
827 final void incRef () nothrow @trusted @nogc {
828 import core.atomic;
829 if (atomicOp!"+="(rc, 1) == 0) assert(0); // hey, this is definitely a bug!
830 debug(vfs_rc) { import core.stdc.stdio : printf; printf("INCREF(0x%08x): %u (was %u)...\n", cast(void*)this, rc, rc-1); }
833 // return true if this class is dead
834 final bool decRef () {
835 // no need to protect this code with `synchronized`, as only one thread can reach zero rc anyway
836 import core.atomic;
837 debug(vfs_rc) { import core.stdc.stdio : printf; printf("DECREF(0x%08x): %u (will be %u)...\n", cast(void*)this, rc, rc-1); }
838 auto xrc = atomicOp!"-="(rc, 1);
839 debug(vfs_rc) { import core.stdc.stdio : printf; printf(" DECREF(0x%08x): %u %u...\n", cast(void*)this, rc, xrc); }
840 if (xrc == rc.max) assert(0); // hey, this is definitely a bug!
841 if (xrc == 0) {
842 import core.memory : GC;
843 import core.stdc.stdlib : free;
844 synchronized(this) { setFileName(null); close(); } // finalize stream; should be synchronized right here
845 return true;
846 } else {
847 return false;
851 protected:
852 void function (void* self) nothrow gcUnregister;
854 protected:
855 final bool hasName () const pure nothrow @safe @nogc { return (fnamelen != 0); }
856 @property const(char)[] name () const nothrow @trusted @nogc { return (fnamelen ? (fnameptr ? (cast(const(char)*)fnameptr)[0..fnamelen] : fnamebuf.ptr[0..fnamelen]) : ""); }
857 @property bool eof () { return eofhit; }
858 abstract @property bool isOpen () const nothrow @safe @nogc;
859 abstract void close ();
860 ssize read (void* buf, usize count) { return -1; }
861 ssize write (in void* buf, usize count) { return -1; }
862 long lseek (long offset, int origin) { return -1; }
863 // override this if your stream has `flush()`
864 bool flush () { return true; }
865 // override this if your stream has dedicated `size`
866 @property bool hasSize () { return false; }
867 long getsize () { return -1; } // so it won't conflict with `iv.pred.size`
871 // ////////////////////////////////////////////////////////////////////////// //
872 usize newWS (CT, A...) (A args) if (is(CT : WrappedStreamRC)) {
873 import core.exception : onOutOfMemoryErrorNoGC;
874 import core.memory : GC;
875 import core.stdc.stdlib : malloc;
876 import core.stdc.string : memset;
877 import std.conv : emplace;
878 enum instSize = __traits(classInstanceSize, CT);
879 // let's hope that malloc() aligns returned memory right
880 auto mem = malloc(instSize);
881 if (mem is null) onOutOfMemoryErrorNoGC(); // oops
882 memset(mem, 0, instSize);
883 emplace!CT(mem[0..instSize], args);
884 bool createUnregister = false;
886 debug(vfs_vfile_gc) import core.stdc.stdio : printf;
887 auto pbm = __traits(getPointerBitmap, CT);
888 debug(vfs_vfile_gc) printf("[%.*s]: size=%u (%u) (%u)\n", cast(uint)CT.stringof.length, CT.stringof.ptr, cast(uint)pbm[0], cast(uint)instSize, cast(uint)(pbm[0]/usize.sizeof));
889 immutable(ubyte)* p = cast(immutable(ubyte)*)(pbm.ptr+1);
890 usize bitnum = 0;
891 immutable end = pbm[0]/usize.sizeof;
892 while (bitnum < end) {
893 if (p[bitnum/8]&(1U<<(bitnum%8))) {
894 usize len = 1;
895 while (bitnum+len < end && (p[(bitnum+len)/8]&(1U<<((bitnum+len)%8))) != 0) ++len;
896 debug(vfs_vfile_gc) printf(" #%u (%u)\n", cast(uint)(bitnum*usize.sizeof), cast(uint)len);
897 GC.addRange((cast(usize*)mem)+bitnum, usize.sizeof*len);
898 createUnregister = true;
899 bitnum += len;
900 } else {
901 ++bitnum;
905 if (createUnregister) {
906 debug(vfs_vfile_gc) { import core.stdc.stdio : printf; printf("REGISTERING CG CLEANUP DELEGATE FOR WRAPPER 0x%08x\n", cast(uint)mem); }
907 (*cast(CT*)&mem).gcUnregister = function (void* self) {
908 debug(vfs_vfile_gc) import core.stdc.stdio : printf;
909 debug(vfs_vfile_gc) { import core.stdc.stdio : printf; printf("DESTROYING WRAPPER 0x%08x\n", cast(uint)self); }
910 auto pbm = __traits(getPointerBitmap, CT);
911 debug(vfs_vfile_gc) printf("[%.*s]: size=%u (%u) (%u)\n", cast(uint)CT.stringof.length, CT.stringof.ptr, cast(uint)pbm[0], cast(uint)instSize, cast(uint)(pbm[0]/usize.sizeof));
912 immutable(ubyte)* p = cast(immutable(ubyte)*)(pbm.ptr+1);
913 usize bitnum = 0;
914 immutable end = pbm[0]/usize.sizeof;
915 while (bitnum < end) {
916 if (p[bitnum/8]&(1U<<(bitnum%8))) {
917 usize len = 1;
918 while (bitnum+len < end && (p[(bitnum+len)/8]&(1U<<((bitnum+len)%8))) != 0) ++len;
919 debug(vfs_vfile_gc) printf(" #%u (%u)\n", cast(uint)(bitnum*usize.sizeof), cast(uint)len);
920 GC.removeRange((cast(usize*)self)+bitnum);
921 bitnum += len;
922 } else {
923 ++bitnum;
928 debug(vfs_vfile_gc) { import core.stdc.stdio : printf; printf("CREATED WRAPPER 0x%08x\n", mem); }
929 return cast(usize)mem;
933 // ////////////////////////////////////////////////////////////////////////// //
934 version(VFS_NORMAL_OS) enum VFSSigRepeatCount = 2;
936 // ////////////////////////////////////////////////////////////////////////// //
937 version(vfs_stdio_wrapper)
938 final class WrappedStreamStdioFile : WrappedStreamRC {
939 private:
940 std.stdio.File fl;
942 public this (std.stdio.File afl, const(char)[] afname) { fl = afl; super(afname); } // fuck! emplace needs it
944 protected:
945 override @property const(char)[] name () { return (hasName ? super.name : fl.name); }
946 override @property bool isOpen () const nothrow @safe @nogc { return fl.isOpen; }
947 override @property bool eof () { return fl.eof; }
949 override void close () { if (fl.isOpen) fl.close(); }
951 override ssize read (void* buf, usize count) {
952 if (count == 0) return 0;
953 return fl.rawRead(buf[0..count]).length;
956 override ssize write (in void* buf, usize count) {
957 if (count == 0) return 0;
958 fl.rawWrite(buf[0..count]);
959 return count;
962 override long lseek (long offset, int origin) { fl.seek(offset, origin); return fl.tell; }
964 override bool flush () { fl.flush(); return true; }
966 override @property bool hasSize () { return true; }
967 long getsize () { return fl.size; }
971 version(vfs_stdio_wrapper)
972 usize WrapStdioFile (std.stdio.File fl, const(char)[] fname=null) {
973 return newWS!WrappedStreamStdioFile(fl, fname);
977 // ////////////////////////////////////////////////////////////////////////// //
978 private import core.stdc.errno;
980 final class WrappedStreamLibcFile(bool ownfl=true) : WrappedStreamRC {
981 private:
982 //core.stdc.stdio.FILE* fl;
983 usize flp; // hide from GC
984 final @property core.stdc.stdio.FILE* fl () const pure nothrow @trusted @nogc { return cast(core.stdc.stdio.FILE*)flp; }
985 final @property void fl (core.stdc.stdio.FILE* afl) pure nothrow @trusted @nogc { flp = cast(usize)afl; }
987 public this (core.stdc.stdio.FILE* afl, const(char)[] afname) { fl = afl; super(afname); } // fuck! emplace needs it
989 protected:
990 override @property bool isOpen () const nothrow @safe @nogc { return (flp != 0); }
991 override @property bool eof () { return (flp == 0 || core.stdc.stdio.feof(fl) != 0); }
993 override void close () {
994 if (fl !is null) {
995 static if (ownfl) {
996 import std.exception : ErrnoException;
997 auto res = core.stdc.stdio.fclose(fl);
998 fl = null;
999 if (res != 0) throw new ErrnoException("can't close file", __FILE__, __LINE__);
1000 } else {
1001 fl = null;
1006 override ssize read (void* buf, usize count) {
1007 if (fl is null || core.stdc.stdio.ferror(fl)) return -1;
1008 if (count == 0) return 0;
1009 version(VFS_NORMAL_OS) int sigsleft = VFSSigRepeatCount;
1010 for (;;) {
1011 auto res = core.stdc.stdio.fread(buf, 1, count, fl);
1012 if (res == 0) return (core.stdc.stdio.ferror(fl) ? -1 : 0);
1013 version(VFS_NORMAL_OS) {
1014 if (res == -1) {
1015 import core.stdc.errno;
1016 if (errno == EINTR) { if (sigsleft-- > 0) { core.stdc.stdio.clearerr(fl); continue; } }
1019 return res;
1023 override ssize write (in void* buf, usize count) {
1024 if (fl is null || core.stdc.stdio.ferror(fl)) return -1;
1025 if (count == 0) return 0;
1026 version(VFS_NORMAL_OS) int sigsleft = VFSSigRepeatCount;
1027 for (;;) {
1028 auto res = core.stdc.stdio.fwrite(buf, 1, count, fl);
1029 if (res == 0) return (core.stdc.stdio.ferror(fl) ? -1 : 0);
1030 version(VFS_NORMAL_OS) {
1031 if (res == -1) {
1032 import core.stdc.errno;
1033 if (errno == EINTR) { if (sigsleft-- > 0) { core.stdc.stdio.clearerr(fl); continue; } }
1036 return res;
1040 override long lseek (long offset, int origin) {
1041 if (fl is null) return -1;
1042 version(VFS_NORMAL_OS) int sigsleft = VFSSigRepeatCount;
1043 for (;;) {
1044 static if (VFS_NORMAL_OS) {
1045 auto res = core.sys.posix.stdio.fseeko(fl, offset, origin);
1046 } else {
1047 // windoze sux
1048 if (offset < int.min || offset > int.max) return -1;
1049 auto res = core.stdc.stdio.fseek(fl, cast(int)offset, origin);
1051 if (res != -1) {
1052 core.stdc.stdio.clearerr(fl);
1053 break;
1054 } else {
1055 version(VFS_NORMAL_OS) {
1056 import core.stdc.errno;
1057 if (errno == EINTR) { if (sigsleft-- > 0) { core.stdc.stdio.clearerr(fl); continue; } }
1059 return res;
1062 static if (VFS_NORMAL_OS) {
1063 return core.sys.posix.stdio.ftello(fl);
1064 } else {
1065 return core.stdc.stdio.ftell(fl);
1069 override bool flush () {
1070 if (fl is null) return false;
1071 if (core.stdc.stdio.fflush(fl) == 0) return true;
1072 // check for special file
1073 import core.stdc.errno;
1074 if (errno == EROFS || errno == EINVAL) return true; // this is pipe, fifo, socket, etc., assume success
1075 return false;
1080 usize WrapLibcFile(bool ownfl=true) (core.stdc.stdio.FILE* fl, const(char)[] fname=null) {
1081 return newWS!(WrappedStreamLibcFile!ownfl)(fl, fname);
1085 // ////////////////////////////////////////////////////////////////////////// //
1086 final class WrappedStreamGZFile(bool ownfl=true) : WrappedStreamRC {
1087 private import etc.c.zlib;
1088 private:
1089 usize flp; // hide from GC
1090 final @property gzFile fl () const pure nothrow @trusted @nogc { return cast(gzFile)flp; }
1091 final @property void fl (gzFile afl) pure nothrow @trusted @nogc { flp = cast(usize)afl; }
1093 ulong cachedSize = ulong.max;
1094 ulong newpos = 0;
1096 int err () nothrow @trusted {
1097 int res = 0;
1098 if (flp != 0) gzerror(fl, &res);
1099 return res;
1102 public this (gzFile afl, const(char)[] afname) { fl = afl; super(afname); } // fuck! emplace needs it
1104 // this shit tries to workaround "convenient features" of gzio
1105 // i really should rewrite the whole gz stuff and got rid of zlib
1106 bool fixPosition () {
1107 if (newpos >= int.max) return false; // alas
1108 auto cpos = gztell(fl);
1109 if (cpos == -1) return false; // something is VERY wrong
1110 if (cpos == newpos) return true;
1111 // position changed
1112 version(VFS_NORMAL_OS) int sigsleft = VFSSigRepeatCount;
1113 if (newpos < cpos) {
1114 // have to rewind
1115 for (;;) {
1116 if (gzrewind(fl) < 0) {
1117 version(VFS_NORMAL_OS) {
1118 import core.stdc.errno;
1119 if (errno == EINTR) { if (sigsleft-- > 0) { gzclearerr(fl); continue; } }
1121 return false;
1122 } else {
1123 break;
1126 cpos = gztell(fl);
1127 if (cpos == -1) return false; // something is VERY wrong
1129 if (newpos < cpos) return false; // why?!
1130 version(VFS_NORMAL_OS) sigsleft = VFSSigRepeatCount;
1131 for (;;) {
1132 auto res = gzseek(fl, cast(int)newpos, 0); // fuck you, phobos!
1133 if (res != -1) {
1134 gzclearerr(fl);
1135 } else {
1136 version(VFS_NORMAL_OS) {
1137 import core.stdc.errno;
1138 if (errno == EINTR) { if (sigsleft-- > 0) { gzclearerr(fl); continue; } }
1140 return false;
1142 break;
1144 if (gztell(fl) != newpos) return false; // something is VERY wrong
1145 return true;
1148 protected:
1149 override @property bool isOpen () const nothrow @safe @nogc { return (flp != 0); }
1150 override @property bool eof () { return (flp == 0 || gzeof(fl) != 0); }
1152 override void close () {
1153 if (fl !is null) {
1154 static if (ownfl) {
1155 auto res = gzclose(fl);
1156 fl = null;
1157 if (res != Z_BUF_ERROR && res != Z_OK) throw new VFSException("can't close file", __FILE__, __LINE__);
1158 } else {
1159 fl = null;
1164 override ssize read (void* buf, usize count) {
1165 if (fl is null || err()) return -1;
1166 if (count == 0) return 0;
1167 if (!fixPosition) return -1;
1168 version(VFS_NORMAL_OS) int sigsleft = VFSSigRepeatCount;
1169 for (;;) {
1170 static if (is(typeof(&gzfread))) {
1171 auto res = gzfread(buf, 1, count, fl);
1172 } else {
1173 static if (count.sizeof > uint.sizeof) { if (count >= int.max) return -1; }
1174 auto res = gzread(fl, buf, cast(uint)count);
1176 version(VFS_NORMAL_OS) {
1177 if (res == -1) {
1178 import core.stdc.errno;
1179 if (errno == EINTR) { if (sigsleft-- > 0) { gzclearerr(fl); continue; } }
1182 if (res == 0) {
1183 //{ import core.stdc.stdio; printf("res=0; pos=%u; err=%d\n", cast(uint)newpos, err()); }
1184 return (err() ? -1 : 0);
1186 newpos += res;
1187 return res;
1191 override ssize write (in void* buf, usize count) {
1192 if (fl is null || err()) return -1;
1193 if (count == 0) return 0;
1194 if (!fixPosition) return -1;
1195 version(VFS_NORMAL_OS) int sigsleft = VFSSigRepeatCount;
1196 for (;;) {
1197 static if (is(typeof(&gzfwrite))) {
1198 auto res = gzfwrite(cast(void*)buf, 1, count, fl); // fuck you, phobos!
1199 } else {
1200 static if (count.sizeof > uint.sizeof) { if (count >= int.max) return -1; }
1201 auto res = gzwrite(fl, cast(void*)buf, cast(uint)count);
1203 version(VFS_NORMAL_OS) {
1204 if (res == -1) {
1205 import core.stdc.errno;
1206 if (errno == EINTR) { if (sigsleft-- > 0) { gzclearerr(fl); continue; } }
1209 if (res == 0) return (err() ? -1 : 0);
1210 newpos += res;
1211 if (cachedSize == ulong.max || newpos > cachedSize) cachedSize = newpos; // fix cached file size
1212 return res;
1216 override long lseek (long offset, int origin) {
1217 if (fl is null) return -1;
1218 //{ import core.stdc.stdio; printf("ofs=%d; orig=%d\n", cast(int)offset, origin); }
1219 // size query, and we have cached size?
1220 if (origin == 2 && offset == 0 && cachedSize != ulong.max) {
1221 // don't do anything
1222 newpos = cachedSize;
1223 return cachedSize;
1225 // ok, "normal" seek
1226 static if (offset.sizeof > int.sizeof) {
1227 if (offset < int.min || offset > int.max) return -1;
1229 version(VFS_NORMAL_OS) int sigsleft = VFSSigRepeatCount;
1230 for (;;) {
1231 auto res = gzseek(fl, cast(int)offset, origin); // fuck you, phobos!
1232 if (res != -1) {
1233 gzclearerr(fl);
1234 } else {
1235 version(VFS_NORMAL_OS) {
1236 import core.stdc.errno;
1237 if (errno == EINTR) { if (sigsleft-- > 0) { gzclearerr(fl); continue; } }
1239 // ok, gzio sux and cannot seek, fallback to file reading if this is size query
1240 //{ import core.stdc.stdio; printf("ERR: ofs=%d; orig=%d\n", cast(int)offset, origin); }
1241 if (origin == 2 && offset == 0 && cachedSize == ulong.max) {
1242 char[512] buf = void;
1243 newpos = gztell(fl); // just in case
1244 for (;;) {
1245 auto rd = read(buf.ptr, buf.length);
1246 if (rd < 0) return -1; // alas
1247 if (rd == 0) break; // done
1249 // no more bytes; assume that it is file size
1250 //{ import core.stdc.stdio; printf("np0: %u\n", cast(uint)newpos); }
1251 newpos = gztell(fl); // just in case
1252 //{ import core.stdc.stdio; printf("np1: %u\n", cast(uint)newpos); }
1253 cachedSize = newpos;
1254 return newpos;
1256 return res;
1258 //{ import core.stdc.stdio; printf("%d\n", cast(int)gztell(fl)); }
1259 newpos = gztell(fl);
1260 return newpos;
1264 override bool flush () {
1265 if (fl is null) return false;
1266 return (gzflush(fl, Z_FINISH) == 0);
1270 static import etc.c.zlib;
1272 usize WrapGZFile(bool ownfl=true) (etc.c.zlib.gzFile fl, const(char)[] fname=null) {
1273 return newWS!(WrappedStreamGZFile!ownfl)(fl, fname);
1277 // ////////////////////////////////////////////////////////////////////////// //
1278 static if (VFS_NORMAL_OS) final class WrappedStreamFD(bool own) : WrappedStreamRC {
1279 private:
1280 int fd;
1282 public this (int afd, const(char)[] afname) { fd = afd; eofhit = (afd < 0); super(afname); } // fuck! emplace needs it
1284 protected:
1285 override @property bool isOpen () const nothrow @safe @nogc { return (fd >= 0); }
1287 override void close () {
1288 if (fd >= 0) {
1289 import std.exception : ErrnoException;
1290 static if (own) {
1291 debug(vfs_rc) { import core.stdc.stdio : printf; printf("******** CLOSING FD %u\n", cast(uint)fd); }
1292 } else {
1293 debug(vfs_rc) { import core.stdc.stdio : printf; printf("******** RELEASING FD %u\n", cast(uint)fd); }
1295 static if (own) auto res = core.sys.posix.unistd.close(fd);
1296 fd = -1;
1297 eofhit = true;
1298 static if (own) if (res < 0) throw new ErrnoException("can't close file", __FILE__, __LINE__);
1299 } else {
1300 fd = -1;
1301 eofhit = true;
1305 override ssize read (void* buf, usize count) {
1306 if (fd < 0) return -1;
1307 if (count == 0) return 0;
1308 version(VFS_NORMAL_OS) int sigsleft = VFSSigRepeatCount;
1309 for (;;) {
1310 auto res = core.sys.posix.unistd.read(fd, buf, count);
1311 version(VFS_NORMAL_OS) {
1312 if (res == -1) {
1313 import core.stdc.errno;
1314 if (errno == EINTR) { if (sigsleft-- > 0) continue; }
1317 if (res != count) eofhit = true;
1318 return res;
1322 override ssize write (in void* buf, usize count) {
1323 if (fd < 0) return -1;
1324 if (count == 0) return 0;
1325 version(VFS_NORMAL_OS) int sigsleft = VFSSigRepeatCount;
1326 for (;;) {
1327 auto res = core.sys.posix.unistd.write(fd, buf, count);
1328 version(VFS_NORMAL_OS) {
1329 if (res == -1) {
1330 import core.stdc.errno;
1331 if (errno == EINTR) { if (sigsleft-- > 0) continue; }
1334 if (res != count) eofhit = true;
1335 return res;
1339 override long lseek (long offset, int origin) {
1340 if (fd < 0) return -1;
1341 version(VFS_NORMAL_OS) int sigsleft = VFSSigRepeatCount;
1342 for (;;) {
1343 auto res = core.sys.posix.unistd.lseek(fd, offset, origin);
1344 if (res != -1) {
1345 eofhit = false;
1346 } else {
1347 version(VFS_NORMAL_OS) {
1348 import core.stdc.errno;
1349 if (errno == EINTR) { if (sigsleft-- > 0) continue; }
1352 return res;
1356 override bool flush () {
1357 import core.sys.posix.unistd : fdatasync;
1358 if (fd < 0) return false;
1359 if (fdatasync(fd) == 0) return true;
1360 // check for special file
1361 import core.stdc.errno;
1362 if (errno == EROFS || errno == EINVAL) return true; // this is pipe, fifo, socket, etc., assume success
1363 return false;
1368 static if (VFS_NORMAL_OS) usize WrapFD(bool own) (int fd, const(char)[] fname=null) {
1369 return newWS!(WrappedStreamFD!own)(fd, fname);
1373 // ////////////////////////////////////////////////////////////////////////// //
1374 final class WrappedStreamAny(ST) : WrappedStreamRC {
1375 private:
1376 ST st;
1377 bool closed;
1379 // fuck! emplace needs it
1380 public this() (auto ref ST ast, const(char)[] afname) {
1381 version(vfs_debug_name_set) {
1382 import core.stdc.stdio;
1383 stderr.fprintf("WrappedStreamAny(%s).ctor: afname", ST.stringof.ptr);
1384 if (afname is null) stderr.fprintf(" IS NULL\n"); else stderr.fprintf("=<%.*s>\n", cast(uint)afname.length, afname.ptr);
1386 st = ast;
1387 super(afname);
1388 static if (streamHasIsOpen!ST) {
1389 closed = !st.isOpen;
1390 } else {
1391 closed = false;
1395 protected:
1396 // prefer passed name, if it is not null
1397 override @property const(char)[] name () const nothrow @trusted @nogc {
1398 if (fnamelen && !closed) {
1399 return (fnameptr ? (cast(const(char)*)fnameptr)[0..fnamelen] : fnamebuf.ptr[0..fnamelen]);
1400 } else {
1401 static if (streamHasName!ST) {
1402 return (closed ? null : (hasName ? super.name : st.name));
1403 } else {
1404 return (fnameptr ? "" : null);
1409 override @property bool isOpen () const nothrow @safe @nogc {
1410 static if (streamHasIsOpen!ST) {
1411 if (closed) return true;
1412 return st.isOpen;
1413 } else {
1414 return !closed;
1418 override @property bool eof () {
1419 if (closed) return true;
1420 static if (streamHasEof!ST) {
1421 return st.eof;
1422 } else {
1423 return eofhit;
1427 override void close () {
1428 if (!closed) {
1429 closed = true;
1430 eofhit = true;
1431 static if (streamHasClose!ST) st.close();
1432 st = ST.init;
1436 override ssize read (void* buf, usize count) {
1437 if (closed) return -1;
1438 if (count == 0) return 0;
1439 static if (isLowLevelStreamR!ST) {
1440 auto res = st.read(buf, count);
1441 if (res != count) eofhit = true;
1442 return res;
1443 } else static if (isReadableStream!ST) {
1444 return st.rawRead(buf[0..count]).length;
1445 } else {
1446 return -1;
1450 override ssize write (in void* buf, usize count) {
1451 if (closed) return -1;
1452 if (count == 0) return 0;
1453 static if (isLowLevelStreamW!ST) {
1454 auto res = st.write(buf, count);
1455 if (res != count) eofhit = true;
1456 return res;
1457 } else static if (isWriteableStream!ST) {
1458 st.rawWrite(buf[0..count]);
1459 return count;
1460 } else {
1461 return -1;
1465 override long lseek (long offset, int origin) {
1466 if (origin != Seek.Set && origin != Seek.Cur && origin != Seek.End) return -1;
1467 static if (isLowLevelStreamS!ST) {
1468 // has low-level seek
1469 if (closed) return -1;
1470 auto res = st.lseek(offset, origin);
1471 if (res != -1) eofhit = false;
1472 return res;
1473 } else static if (streamHasSeek!ST) {
1474 // has high-level seek
1475 if (closed) return -1;
1476 st.seek(offset, origin);
1477 eofhit = false;
1478 return st.tell;
1479 } else {
1480 // no seek at all
1481 return -1;
1485 override bool flush () {
1486 static if (streamHasFlush!ST) {
1487 static if (is(typeof(st.flush()) == bool)) return st.flush();
1488 else static if (is(typeof(st.flush()) : long)) return (st.flush() == 0);
1489 else { st.flush(); return true; }
1490 } else {
1491 return true;
1495 override @property bool hasSize () { static if (streamHasSizeLowLevel!ST) return true; else return false; }
1496 override long getsize () { static if (streamHasSizeLowLevel!ST) return st.getsize; else return -1; }
1500 /// wrap `std.stdio.File` into `VFile`
1501 version(vfs_stdio_wrapper)
1502 public VFile wrapStream (std.stdio.File st, const(char)[] fname=null) { return VFile(st, fname); }
1504 /// wrap another `VFile` into `VFile` (a perfectly idiotic action)
1505 public VFile wrapStream (VFile st) { return st; }
1507 /// wrap libc `FILE*` into `VFile`
1508 public VFile wrapStream (core.stdc.stdio.FILE* st, const(char)[] fname=null) { return VFile(st, fname); }
1510 static if (VFS_NORMAL_OS) {
1511 /// wrap file descriptor into `VFile`
1512 public VFile wrapStream (int fd, const(char)[] fname=null) { return VFile(fd, fname); }
1515 /** wrap any valid i/o stream into `VFile`.
1516 * "valid" stream should emplement one of two interfaces described below.
1517 * only one thread can call stream operations at a time, it's guaranteed by `VFile`.
1518 * note that any function is free to throw, `VFile` will take care of that.
1520 * low-level interface:
1522 * [mandatory] `ssize read (void* buf, usize count);`
1524 * read bytes; should read up to `count` bytes and return number of bytes read.
1525 * should return -1 on error. can't be called with `count == 0`.
1527 * [mandatory] `ssize write (in void* buf, usize count);`
1529 * write bytes; should write exactly `count` bytes and return number of bytes written.
1530 * should return -1 on error. can't be called with `count == 0`. note that if you
1531 * will return something that is not equal to `count` (i.e. will write less bytes than
1532 * requested), `VFile` will throw.
1534 * [mandatory] `long lseek (long offset, int origin);`
1536 * seek into stream. `origin` is one of `Seek.Set`, `Seek.Cur`, or `Seek.End` (can't be
1537 * called with another values). should return resulting offset from stream start or -1
1538 * on error. note that this method will be used to implement `tell()` and `size()`
1539 * VFile APIs, so make it as fast as you can.
1541 * should return stream name, or throw on error. can return empty name.
1543 * or high-level interface:
1545 * [mandatory] `void[] rawRead (void[] buf);`
1547 * read bytes; should read up to `buf.length` bytes and return slice with read bytes.
1548 * should throw on error. can't be called with empty buf.
1550 * [mandatory] `void rawWrite (in void[] buf);`
1552 * write bytes; should write exactly `buf.length` bytes.
1553 * should throw on error (note that if it wrote less bytes than requested, it is an
1554 * error too).
1556 * [mandatory] `void seek (long offset, int origin);`
1558 * seek into stream. `origin` is one of `Seek.Set`, `Seek.Cur`, or `Seek.End`.
1559 * should throw on error (including invalid `origin`).
1561 * [mandatory] `@property long tell ();`
1563 * should return current position in stream. should throw on error.
1565 * [mandatory] `@property long getsize ();`
1567 * should return stream size. should throw on error.
1569 * common interface, optional:
1571 * [optional] `@property const(char)[] name ();`
1573 * should return stream name, or throw on error. can return empty name.
1575 * [optional] `@property bool isOpen ();`
1577 * should return `true` if the stream is opened, or throw on error.
1579 * [optional] `@property bool eof ();`
1581 * should return `true` if end of stream is reached, or throw on error.
1582 * note that EOF flag may be set in i/o methods, so you can be at EOF,
1583 * but this method can still return `false`. i.e. it is unreliable.
1585 * [optional] `void close ();`
1587 * should close stream, or throw on error. VFile won't call that on
1588 * streams that returns `false` from `isOpen()`, but you'd better
1589 * handle this situation yourself.
1591 * [optional] `bool flush ();`
1593 * flush unwritten data (if your stream supports writing).
1594 * return `true` on success.
1597 public VFile wrapStream(ST) (auto ref ST st, const(char)[] fname=null)
1598 if (isReadableStream!ST || isWriteableStream!ST || isLowLevelStreamR!ST || isLowLevelStreamW!ST)
1600 return VFile(cast(void*)newWS!(WrappedStreamAny!ST)(st, fname));
1604 // ////////////////////////////////////////////////////////////////////////// //
1605 private struct PartialLowLevelRO {
1606 VFile zfl; // original file
1607 long stpos; // starting position
1608 long size; // unpacked size
1609 long pos; // current file position
1610 bool eofhit;
1612 this (VFile fl, long astpos, long asize) {
1613 stpos = astpos;
1614 size = asize;
1615 zfl = fl;
1618 @property bool isOpen () const nothrow @safe @nogc { return zfl.isOpen; }
1619 @property bool eof () { return eofhit; }
1621 void close () {
1622 eofhit = true;
1623 if (zfl.isOpen) zfl.close();
1626 ssize read (void* buf, usize count) {
1627 if (buf is null) return -1;
1628 if (count == 0 || size == 0) return 0;
1629 if (!isOpen) return -1; // read error
1630 if (pos >= size) { eofhit = true; return 0; } // EOF
1631 if (size-pos < count) { eofhit = true; count = cast(usize)(size-pos); }
1632 zfl.seek(stpos+pos);
1633 auto rd = zfl.rawRead(buf[0..count]);
1634 pos += rd.length;
1635 return rd.length;
1638 ssize write (in void* buf, usize count) { return -1; }
1640 long lseek (long ofs, int origin) {
1641 if (!isOpen) return -1;
1642 //TODO: overflow checks
1643 switch (origin) {
1644 case Seek.Set: break;
1645 case Seek.Cur: ofs += pos; break;
1646 case Seek.End:
1647 if (ofs > 0) ofs = 0;
1648 ofs += size;
1649 break;
1650 default:
1651 return -1;
1653 if (ofs < 0) return -1;
1654 eofhit = false;
1655 if (ofs > size) ofs = size;
1656 pos = ofs;
1657 return pos;
1662 /// wrap VFile into read-only stream, with given offset and length.
1663 /// if `len` == -1, wrap from starting position to file end.
1664 public VFile wrapStreamRO (VFile st, long stpos=0, long len=-1, const(char)[] fname=null) {
1665 if (stpos < 0) throw new VFSException("invalid starting position");
1666 if (len == -1) len = st.size-stpos;
1667 if (len < 0) throw new VFSException("invalid length");
1668 //return wrapStream(PartialLowLevelRO(st, stpos, len), fname);
1669 return VFile(cast(void*)newWS!(WrappedStreamAny!PartialLowLevelRO)(PartialLowLevelRO(st, stpos, len), (fname !is null ? fname : st.name)));
1673 // ////////////////////////////////////////////////////////////////////////// //
1674 public enum VFSZLibMode {
1675 Raw,
1676 ZLib,
1677 Zip, // special mode for zip archives
1681 // ////////////////////////////////////////////////////////////////////////// //
1682 version(vfs_use_zlib_unpacker) {
1683 struct ZLibLowLevelRO {
1684 private import etc.c.zlib;
1686 enum ibsize = 32768;
1688 VFile zfl; // archive file
1689 VFSZLibMode mode;
1690 long stpos; // starting position
1691 long size; // unpacked size
1692 long pksize; // packed size
1693 long pos; // current file position
1694 long prpos; // previous file position
1695 long pkpos; // current position in DAT
1696 ubyte[] pkb; // packed data
1697 z_stream zs;
1698 bool eoz;
1699 bool eofhit;
1700 // reading one byte from zlib fuckin' fails. shit.
1701 ubyte[65536] updata;
1702 uint uppos, upused;
1703 bool upeoz;
1705 this (VFile fl, VFSZLibMode amode, long aupsize, long astpos, long asize) {
1706 if (amode == VFSZLibMode.Raw && aupsize < 0) aupsize = asize;
1707 zfl = fl;
1708 stpos = astpos;
1709 size = aupsize;
1710 pksize = asize;
1711 mode = amode;
1712 uppos = upused = 0;
1713 upeoz = false;
1716 @property bool isOpen () const nothrow @safe @nogc { return zfl.isOpen; }
1717 @property bool eof () { return eofhit; }
1719 void close () {
1720 import core.stdc.stdlib : free;
1721 eofhit = true;
1722 if (pkb.length) {
1723 inflateEnd(&zs);
1724 free(pkb.ptr);
1725 pkb = null;
1727 uppos = upused = 0;
1728 upeoz = true;
1729 eoz = true;
1730 if (zfl.isOpen) zfl.close();
1733 private bool initZStream (bool reinit=false) {
1734 import core.stdc.stdlib : malloc, free;
1735 if (mode == VFSZLibMode.Raw || (!reinit && pkb.ptr !is null)) return true;
1736 // allocate buffer for packed data
1737 if (pkb.ptr is null) {
1738 auto pb = cast(ubyte*)malloc(ibsize);
1739 if (pb is null) return false;
1740 pkb = pb[0..ibsize];
1742 zs.avail_in = 0;
1743 zs.avail_out = 0;
1744 // initialize unpacker
1745 // -15 is a magic value used to decompress zip files:
1746 // it has the effect of not requiring the 2 byte header and 4 byte trailer
1747 if (inflateInit2(&zs, (mode == VFSZLibMode.Zip ? -15 : 15)) != Z_OK) {
1748 free(pkb.ptr);
1749 pkb = null;
1750 return false;
1752 eoz = false;
1753 // we are ready
1754 return true;
1757 private bool readPackedChunk () {
1758 if (zs.avail_in > 0) return true;
1759 if (pkpos >= pksize) return false;
1760 zs.next_in = cast(typeof(zs.next_in))pkb.ptr;
1761 zs.avail_in = cast(uint)(pksize-pkpos > ibsize ? ibsize : pksize-pkpos);
1762 zfl.seek(stpos+pkpos);
1763 auto rd = zfl.rawRead(pkb[0..zs.avail_in]);
1764 if (rd.length == 0) return false;
1765 zs.avail_in = cast(int)rd.length;
1766 pkpos += zs.avail_in;
1767 return true;
1770 private bool unpackNextChunk () {
1771 while (zs.avail_out > 0) {
1772 if (eoz) return (size < 0); // `false` for known size, `true` for unknown size
1773 if (uppos >= upused) {
1774 if (upeoz) { eoz = true; continue; }
1775 if (!readPackedChunk()) return false;
1776 auto sv0 = zs.avail_out;
1777 auto sv1 = zs.next_out;
1778 uppos = 0;
1779 zs.avail_out = cast(uint)updata.length;
1780 zs.next_out = cast(ubyte*)updata.ptr;
1781 auto err = inflate(&zs, Z_SYNC_FLUSH);
1782 upused = cast(uint)(updata.length-zs.avail_out);
1783 zs.avail_out = sv0;
1784 zs.next_out = sv1;
1785 //if (err == Z_BUF_ERROR) { import iv.writer; writeln("*** OUT OF BUFFER!"); }
1786 if (err != Z_STREAM_END && err != Z_OK) return false;
1787 if (err == Z_STREAM_END) upeoz = true;
1788 } else {
1789 auto ptr = cast(ubyte*)zs.next_out;
1790 *ptr = updata.ptr[uppos++];
1791 --zs.avail_out;
1792 ++zs.next_out;
1795 return true;
1798 bool findUnpackedSize () {
1799 ubyte[1024] tbuf = void;
1800 //size = pos; // current size
1801 //{ import core.stdc.stdio; printf("findUnpackedSize: starting...\n"); }
1802 scope(exit) { import core.stdc.stdio; printf("findUnpackedSize: done...\n"); }
1803 for (;;) {
1804 uint rd = cast(uint)tbuf.length;
1805 zs.next_out = cast(typeof(zs.next_out))tbuf.ptr;
1806 zs.avail_out = rd;
1807 //{ import core.stdc.stdio; printf("findUnpackedSize: reading %u bytes...\n", rd); }
1808 if (!unpackNextChunk()) return false;
1809 rd -= zs.avail_out;
1810 //{ import core.stdc.stdio; printf("findUnpackedSize: read %u bytes...\n", rd); }
1811 if (pos+rd < 0) return false; // file too big
1812 prpos = (pos += rd);
1813 //{ import core.stdc.stdio; printf("findUnpackedSize: prpos=%u\n", cast(uint)prpos); }
1814 if (zs.avail_out != 0) break;
1816 size = pos;
1817 return true;
1820 ssize read (void* buf, usize count) {
1821 if (buf is null) return -1;
1822 if (count == 0 || size == 0) return 0;
1823 if (!isOpen) return -1; // read error
1824 if (size >= 0 && pos >= size) { eofhit = true; return 0; } // EOF
1825 if (mode == VFSZLibMode.Raw) {
1826 if (size-pos < count) { eofhit = true; count = cast(usize)(size-pos); }
1827 zfl.seek(stpos+pos);
1828 auto rd = zfl.rawRead(buf[0..count]);
1829 pos += rd.length;
1830 return rd.length;
1831 } else {
1832 if (pkb.ptr is null && !initZStream()) return -1;
1833 // do we want to seek backward?
1834 if (prpos > pos) {
1835 // yes, rewind
1836 inflateEnd(&zs);
1837 uppos = upused = 0;
1838 upeoz = false;
1839 zs = zs.init;
1840 pkpos = 0;
1841 if (!initZStream(true)) return -1;
1842 prpos = 0;
1844 // do we need to seek forward?
1845 if (prpos < pos) {
1846 // yes, skip data
1847 ubyte[1024] tbuf = void;
1848 auto skp = pos-prpos;
1849 while (skp > 0) {
1850 uint rd = cast(uint)(skp > tbuf.length ? tbuf.length : skp);
1851 zs.next_out = cast(typeof(zs.next_out))tbuf.ptr;
1852 zs.avail_out = rd;
1853 if (!unpackNextChunk()) return -1;
1854 skp -= rd;
1856 prpos = pos;
1858 // unpack data
1859 if (size >= 0 && size-pos < count) { eofhit = true; count = cast(usize)(size-pos); }
1860 zs.next_out = cast(typeof(zs.next_out))buf;
1861 zs.avail_out = cast(uint)count;
1862 if (!unpackNextChunk()) return -1;
1863 if (size < 0 && zs.avail_out > 0) {
1864 eofhit = true;
1865 count -= zs.avail_out;
1866 size = pos+count;
1868 prpos = (pos += count);
1869 return count;
1873 ssize write (in void* buf, usize count) { return -1; }
1875 long lseek (long ofs, int origin) {
1876 if (!isOpen) return -1;
1877 //TODO: overflow checks
1878 switch (origin) {
1879 case Seek.Set: break;
1880 case Seek.Cur: ofs += pos; break;
1881 case Seek.End:
1882 if (ofs > 0) ofs = 0;
1883 if (size < 0) {
1884 if (pkb.ptr is null && !initZStream()) return -1;
1885 if (!findUnpackedSize) return -1;
1887 ofs += size;
1888 break;
1889 default:
1890 return -1;
1892 if (ofs < 0) return -1;
1893 if (size >= 0 && ofs > size) ofs = size;
1894 pos = ofs;
1895 eofhit = false;
1896 return pos;
1899 } else {
1900 // inflate
1901 import iv.vfs.inflate;
1903 struct ZLibLowLevelRO {
1904 VFile zfl; // archive file
1905 VFSZLibMode mode;
1906 InfStream* ifs;
1907 long stpos; // starting position
1908 long size; // unpacked size
1909 long pksize; // packed size
1910 long pkpos; // current position in packed data
1911 long pos; // current file position (number of unpacked bytes read)
1912 long prpos; // previous file position (seek is done when reading)
1913 bool eofhit; // did we hit EOF on last read?
1915 int readBuf (ubyte[] buf) {
1916 assert(buf.length > 0);
1917 assert(buf.length < int.max/2);
1918 //{ import core.stdc.stdio; printf("inf: reading %u bytes (pkpos=%d; pksize=%d)\n", cast(uint)buf.length, cast(int)pkpos, cast(int)pksize); }
1919 if (pkpos >= pksize) return 0; // eof
1920 int toread = cast(int)buf.length;
1921 if (toread > pksize-pkpos) toread = cast(int)(pksize-pkpos);
1922 assert(toread > 0);
1923 zfl.seek(stpos+pkpos);
1924 auto rd = zfl.rawRead(buf[0..toread]);
1925 if (rd.length == 0) { pkpos = pksize; return 0; } // eof
1926 pkpos += cast(int)rd.length;
1927 return cast(int)rd.length;
1930 this (VFile fl, VFSZLibMode amode, long aupsize, long astpos, long asize) {
1931 //{ import core.stdc.stdio; printf("inf: aupsize=%d; astpos=%d; asize=%d\n", cast(int)aupsize, cast(int)astpos, cast(int)asize); }
1932 if (amode == VFSZLibMode.Raw && aupsize < 0) aupsize = asize;
1933 zfl = fl;
1934 stpos = astpos;
1935 size = aupsize;
1936 pksize = asize;
1937 pkpos = 0;
1938 mode = amode;
1941 @property bool isOpen () const nothrow @safe @nogc { return zfl.isOpen; }
1942 @property bool eof () { return eofhit; }
1944 void inflateInit () {
1945 if (mode == VFSZLibMode.Raw) return;
1946 if (ifs is null) {
1947 import core.stdc.stdlib : malloc;
1948 import core.stdc.string : memset;
1949 ifs = cast(InfStream*)malloc(InfStream.sizeof);
1950 if (ifs is null) throw new Exception("out of memory");
1951 memset(ifs, 0, InfStream.sizeof);
1953 ifs.reinit(mode == VFSZLibMode.ZLib ? InfStream.Mode.ZLib : InfStream.Mode.Deflate);
1956 void close () {
1957 if (ifs !is null) {
1958 import core.stdc.stdlib : free;
1959 free(ifs);
1960 ifs = null;
1962 eofhit = true;
1963 if (zfl.isOpen) zfl.close();
1966 bool findUnpackedSize () {
1967 ubyte[1024] tbuf = void;
1968 if (ifs is null) inflateInit(); // here, 'cause struct can be copied
1969 //{ import core.stdc.stdio; printf("findUnpackedSize: starting...\n"); }
1970 //scope(exit) { import core.stdc.stdio; printf("findUnpackedSize: done...\n"); }
1971 for (;;) {
1972 //{ import core.stdc.stdio; printf("findUnpackedSize: reading %u bytes...\n", cast(uint)tbuf.length); }
1973 auto rd = ifs.rawRead(&readBuf, tbuf[]);
1974 //{ import core.stdc.stdio; printf("findUnpackedSize: read %u bytes...\n", cast(uint)rd.length); }
1975 if (rd.length == 0) break;
1976 prpos += rd.length;
1977 //{ import core.stdc.stdio; printf("findUnpackedSize: prpos=%u\n", cast(uint)prpos); }
1979 size = pos = prpos;
1980 return true;
1983 ssize read (void* buf, usize count) {
1984 if (buf is null) return -1;
1985 if (count == 0 || size == 0) return 0;
1986 if (!isOpen) return -1; // read error
1987 if (size >= 0 && pos >= size) { eofhit = true; return 0; } // EOF
1988 if (mode == VFSZLibMode.Raw) {
1989 // raw mode
1990 if (size-pos < count) { eofhit = true; count = cast(usize)(size-pos); }
1991 zfl.seek(stpos+pos);
1992 auto rd = zfl.rawRead(buf[0..count]);
1993 if (rd.length == 0) eofhit = true; // just in case
1994 pos += rd.length;
1995 return rd.length;
1996 } else {
1997 // unpack file part
1998 // do we want to seek backward?
1999 if (prpos > pos) {
2000 // yes, rewind
2001 pkpos = 0;
2002 inflateInit();
2003 prpos = 0;
2004 } else {
2005 if (ifs is null) inflateInit(); // here, 'cause struct can be copied
2007 // do we need to seek forward?
2008 if (prpos < pos) {
2009 // yes, skip data
2010 ubyte[1024] tbuf = void;
2011 auto skp = pos-prpos;
2012 //{ import core.stdc.stdio; printf("00: skp=%d; prpos=%d; pos=%d\n", cast(int)skp, cast(int)prpos, cast(int)pos); }
2013 while (skp > 0) {
2014 uint rd = cast(uint)(skp <= tbuf.length ? skp : tbuf.length);
2015 auto b = ifs.rawRead(&readBuf, tbuf[0..rd]);
2016 if (b.length == 0) { eofhit = true; return -1; }
2017 prpos += b.length;
2018 skp -= b.length;
2020 //{ import core.stdc.stdio; printf("01: prpos=%d; pos=%d\n", cast(int)prpos, cast(int)pos); }
2022 assert(pos == prpos);
2023 // unpack data
2024 if (size >= 0 && size-pos < count) { eofhit = true; count = cast(usize)(size-pos); }
2025 auto rdb = ifs.rawRead(&readBuf, buf[0..count]);
2026 if (rdb.length == 0) { eofhit = true; return 0; }
2027 count = rdb.length;
2028 prpos = (pos += count);
2029 return count;
2033 ssize write (in void* buf, usize count) { return -1; }
2035 long lseek (long ofs, int origin) {
2036 if (!isOpen) return -1;
2037 //TODO: overflow checks
2038 switch (origin) {
2039 case Seek.Set: break;
2040 case Seek.Cur: ofs += pos; break;
2041 case Seek.End:
2042 if (ofs > 0) ofs = 0;
2043 if (size < 0) {
2044 if (mode == VFSZLibMode.Raw) return -1;
2045 if (!findUnpackedSize()) return -1;
2047 ofs += size;
2048 break;
2049 default:
2050 return -1;
2052 if (ofs < 0) return -1;
2053 if (size >= 0 && ofs > size) ofs = size;
2054 pos = ofs;
2055 eofhit = (pos >= size);
2056 return pos;
2059 } // version
2063 void foo () {
2064 auto zro = ZLibLowLevelRO(VFile("a"), VFSZLibMode.ZLib, 10, 0, 10, "foo");
2069 /// wrap VFile into read-only zlib-packed stream, with given offset and length.
2070 /// if `len` == -1, wrap from starting position to file end.
2071 /// `upsize`: size of unpacked file (-1: size unknown)
2072 public VFile wrapZLibStreamRO (VFile st, VFSZLibMode mode, long upsize, long stpos=0, long len=-1, const(char)[] fname=null) {
2073 if (stpos < 0) throw new VFSException("invalid starting position");
2074 if (upsize < 0 && upsize != -1) throw new VFSException("invalid unpacked size");
2075 if (len == -1) len = st.size-stpos;
2076 if (len < 0) throw new VFSException("invalid length");
2077 //return wrapStream(ZLibLowLevelRO(st, mode, upsize, stpos, len), fname);
2078 //{ import core.stdc.stdio; if (fname is null) stderr.fprintf("FNAME IS NULL\n"); else stderr.fprintf("FNAME: <%.*s>\n", cast(uint)fname.length, fname.ptr); }
2079 return VFile(cast(void*)newWS!(WrappedStreamAny!ZLibLowLevelRO)(ZLibLowLevelRO(st, mode, upsize, stpos, len), (fname !is null ? fname : st.name)));
2082 /// the same as previous function, but using VFSZLibMode.ZLib, as most people is using it
2083 public VFile wrapZLibStreamRO (VFile st, long upsize, long stpos=0, long len=-1, const(char)[] fname=null) {
2084 return wrapZLibStreamRO(st, VFSZLibMode.ZLib, upsize, stpos, len, fname);
2088 // ////////////////////////////////////////////////////////////////////////// //
2089 struct ZLibLowLevelWO {
2090 private import etc.c.zlib;
2092 enum obsize = 32768;
2094 VFile zfl; // destination file
2095 VFSZLibMode mode;
2096 long stpos; // starting position
2097 long pos; // current file position (from stpos)
2098 long prpos; // previous file position (from stpos)
2099 ubyte[] pkb; // packed data
2100 z_stream zs;
2101 bool eofhit;
2102 int complevel;
2104 this (VFile fl, VFSZLibMode amode, int acomplevel=-1) {
2105 zfl = fl;
2106 stpos = fl.tell;
2107 mode = amode;
2108 if (acomplevel < 0) acomplevel = 6;
2109 if (acomplevel > 9) acomplevel = 9;
2110 complevel = 9;
2113 @property bool isOpen () const nothrow @safe @nogc { return (!eofhit && zfl.isOpen); }
2114 @property bool eof () { return isOpen; }
2116 void close () {
2117 scope(exit) {
2118 import core.stdc.stdlib : free;
2119 eofhit = true;
2120 if (pkb !is null) free(pkb.ptr);
2121 pkb = null;
2123 //{ import core.stdc.stdio : printf; printf("CLOSING...\n"); }
2124 if (!eofhit) {
2125 scope(exit) zfl.close();
2126 if (zfl.isOpen && pkb !is null) {
2127 int err;
2128 // do leftovers
2129 //{ import core.stdc.stdio : printf; printf("writing %u bytes; avail_out: %u bytes\n", cast(uint)zs.avail_in, cast(uint)zs.avail_out); }
2130 for (;;) {
2131 zs.avail_in = 0;
2132 err = deflate(&zs, Z_FINISH);
2133 if (err != Z_OK && err != Z_STREAM_END && err != Z_BUF_ERROR) {
2134 //{ import core.stdc.stdio; printf("cerr: %d\n", err); }
2135 throw new VFSException("zlib compression error");
2137 if (zs.avail_out < obsize) {
2138 //{ import core.stdc.stdio : printf; printf("flushing %u bytes (avail_out: %u bytes)\n", cast(uint)(obsize-zs.avail_out), cast(uint)zs.avail_out); }
2139 if (prpos != pos) throw new VFSException("zlib compression seek error");
2140 zfl.seek(stpos+pos);
2141 zfl.rawWriteExact(pkb[0..obsize-zs.avail_out]);
2142 pos += obsize-zs.avail_out;
2143 prpos = pos;
2144 zs.next_out = pkb.ptr;
2145 zs.avail_out = obsize;
2147 if (err != Z_OK && err != Z_BUF_ERROR) break;
2149 // succesfully flushed?
2150 if (err != Z_STREAM_END) throw new VFSException("zlib compression error");
2152 deflateEnd(&zs);
2156 private bool initZStream () {
2157 import core.stdc.stdlib : malloc, free;
2158 if (mode == VFSZLibMode.Raw || pkb.ptr !is null) return true;
2159 // allocate buffer for packed data
2160 if (pkb.ptr is null) {
2161 auto pb = cast(ubyte*)malloc(obsize);
2162 if (pb is null) return false;
2163 pkb = pb[0..obsize];
2165 zs.next_out = pkb.ptr;
2166 zs.avail_out = obsize;
2167 zs.next_in = null;
2168 zs.avail_in = 0;
2169 // initialize packer
2170 // -15 is a magic value used to decompress zip files:
2171 // it has the effect of not requiring the 2 byte header and 4 byte trailer
2172 if (deflateInit2(&zs, Z_BEST_COMPRESSION, Z_DEFLATED, (mode == VFSZLibMode.Zip ? -15 : 15), complevel, 0) != Z_OK) {
2173 free(pkb.ptr);
2174 pkb = null;
2175 eofhit = true;
2176 return false;
2178 zs.next_out = pkb.ptr;
2179 zs.avail_out = obsize;
2180 zs.next_in = null;
2181 zs.avail_in = 0;
2182 // we are ready
2183 return true;
2186 ssize read (void* buf, usize count) { return -1; }
2188 ssize write (in void* buf, usize count) {
2189 if (buf is null) return -1;
2190 if (count == 0) return 0;
2191 if (mode == VFSZLibMode.Raw) {
2192 if (prpos != pos) return -1;
2193 zfl.seek(stpos+prpos);
2194 zfl.rawWriteExact(buf[0..count]);
2195 prpos += count;
2196 pos = prpos;
2197 } else {
2198 if (!initZStream()) return -1;
2199 auto css = count;
2200 auto bp = cast(const(ubyte)*)buf;
2201 while (css > 0) {
2202 zs.next_in = cast(typeof(zs.next_in))bp;
2203 zs.avail_in = (css > 0x3fff_ffff ? 0x3fff_ffff : cast(uint)css);
2204 bp += zs.avail_in;
2205 css -= zs.avail_in;
2206 // now process the whole input
2207 while (zs.avail_in > 0) {
2208 // write buffer
2209 //{ import core.stdc.stdio : printf; printf("writing %u bytes; avail_out: %u bytes\n", cast(uint)zs.avail_in, cast(uint)zs.avail_out); }
2210 if (zs.avail_out == 0) {
2211 if (prpos != pos) return -1;
2212 zfl.seek(stpos+prpos);
2213 zfl.rawWriteExact(pkb[0..obsize]);
2214 prpos += obsize;
2215 pos = prpos;
2216 zs.next_out = pkb.ptr;
2217 zs.avail_out = obsize;
2219 auto err = deflate(&zs, Z_NO_FLUSH);
2220 if (err != Z_OK) return -1;
2224 return count;
2227 long lseek (long ofs, int origin) {
2228 if (!isOpen) return -1;
2229 //TODO: overflow checks
2230 switch (origin) {
2231 case Seek.Set: break;
2232 case Seek.Cur: ofs += prpos; break;
2233 case Seek.End: if (ofs > 0) ofs = 0; ofs += pos; break;
2234 default: return -1;
2236 if (ofs < 0) return -1;
2237 if (ofs > pos) ofs = pos;
2238 prpos = ofs;
2239 return pos;
2244 /// wrap VFile into write-only zlib-packing stream.
2245 /// default compression mode is 9.
2246 public VFile wrapZLibStreamWO (VFile st, VFSZLibMode mode, int complevel=9, const(char)[] fname=null) {
2247 //return wrapStream(ZLibLowLevelWO(st, mode, complevel), fname);
2248 return VFile(cast(void*)newWS!(WrappedStreamAny!ZLibLowLevelWO)(ZLibLowLevelWO(st, mode, complevel), (fname !is null ? fname : st.name)));
2251 /// the same as previous function, but using VFSZLibMode.ZLib, as most people is using it
2252 public VFile wrapZLibStreamWO (VFile st, int complevel=9, const(char)[] fname=null) {
2253 return wrapZLibStreamWO(st, VFSZLibMode.ZLib, complevel, (fname !is null ? fname : st.name));
2257 // ////////////////////////////////////////////////////////////////////////// //
2258 // WARNING! RW streams will set NO_INTERIOR!
2259 public alias MemoryStreamRW = MemoryStreamImpl!(true, false);
2260 public alias MemoryStreamRWRef = MemoryStreamImpl!(true, true);
2261 public alias MemoryStreamRO = MemoryStreamImpl!(false, false);
2264 // ////////////////////////////////////////////////////////////////////////// //
2265 // not thread-safe
2266 struct MemoryStreamImpl(bool rw, bool asref) {
2267 private:
2268 static if (rw) {
2269 static if (asref)
2270 ubyte[]* data;
2271 else
2272 ubyte[] data;
2273 } else {
2274 static assert(!asref, "wtf?!");
2275 const(ubyte)[] data;
2277 usize curpos;
2278 bool eofhit;
2279 bool closed = false;
2281 public:
2282 static if (usize.sizeof == 4) {
2283 enum MaxSize = 0x7fff_ffffU;
2284 } else {
2285 enum MaxSize = 0x7fff_ffff_ffff_ffffUL;
2288 public:
2289 static if (rw) {
2290 static if (asref) {
2291 this (ref ubyte[] adata) @trusted {
2292 if (adata.length > MaxSize) throw new VFSException("buffer too big");
2293 data = &adata;
2294 eofhit = (adata.length == 0);
2296 @property ubyte[]* bytes () pure nothrow @safe @nogc { return data; }
2297 } else {
2298 this (const(ubyte)[] adata) @trusted {
2299 if (adata.length > MaxSize) throw new VFSException("buffer too big");
2300 data = cast(typeof(data))(adata.dup);
2301 eofhit = (adata.length == 0);
2303 @property const(ubyte)[] bytes () pure nothrow @safe @nogc { return data; }
2305 } else {
2306 this (const(void)[] adata) @trusted {
2307 if (adata.length > MaxSize) throw new VFSException("buffer too big");
2308 data = cast(typeof(data))(adata);
2309 eofhit = (adata.length == 0);
2311 @property const(ubyte)[] bytes () pure nothrow @safe @nogc { return data; }
2314 @property const pure nothrow @safe @nogc {
2315 long getsize () { return data.length; }
2316 long tell () { return curpos; }
2317 bool eof () { return eofhit; }
2318 bool isOpen () { return !closed; }
2321 void seek (long offset, int origin=Seek.Set) @trusted {
2322 if (closed) throw new VFSException("can't seek in closed stream");
2323 switch (origin) {
2324 case Seek.Set:
2325 if (offset < 0 || offset > MaxSize) throw new VFSException("invalid offset");
2326 curpos = cast(usize)offset;
2327 break;
2328 case Seek.Cur:
2329 if (offset < -cast(long)curpos || offset > MaxSize-curpos) throw new VFSException("invalid offset");
2330 curpos += offset;
2331 break;
2332 case Seek.End:
2333 if (offset < -cast(long)data.length || offset > MaxSize-data.length) throw new VFSException("invalid offset");
2334 curpos = cast(usize)(cast(long)data.length+offset);
2335 break;
2336 default: throw new VFSException("invalid offset origin");
2338 eofhit = false;
2341 ssize read (void* buf, usize count) {
2342 if (closed) return -1;
2343 if (curpos >= data.length) { eofhit = true; return 0; }
2344 if (count > 0) {
2345 import core.stdc.string : memcpy;
2346 usize rlen = data.length-curpos;
2347 if (rlen >= count) rlen = count; else eofhit = true;
2348 assert(rlen != 0);
2349 memcpy(buf, data.ptr+curpos, rlen);
2350 curpos += rlen;
2351 return cast(ssize)rlen;
2352 } else {
2353 return 0;
2357 ssize write (in void* buf, usize count) {
2358 static if (rw) {
2359 import core.stdc.string : memcpy;
2360 if (closed) return -1;
2361 if (count == 0) return 0;
2362 if (count > MaxSize-curpos) return -1;
2363 if (data.length < curpos+count) {
2364 auto optr = data.ptr;
2365 data.length = curpos+count;
2366 if (data.ptr !is optr) {
2367 import core.memory : GC;
2368 optr = data.ptr;
2369 if (optr is GC.addrOf(optr)) GC.setAttr(optr, GC.BlkAttr.NO_INTERIOR);
2372 memcpy(data.ptr+curpos, buf, count);
2373 curpos += count;
2374 return count;
2375 } else {
2376 return -1;
2380 void close () pure nothrow @safe @nogc { curpos = 0; data = null; eofhit = true; closed = true; }
2384 // ////////////////////////////////////////////////////////////////////////// //
2385 version(vfs_test_stream) {
2386 import std.stdio : File, stdout;
2388 private void dump (const(ubyte)[] data, File fl=stdout) @trusted {
2389 for (usize ofs = 0; ofs < data.length; ofs += 16) {
2390 fl.writef("%04X:", ofs);
2391 foreach (immutable i; 0..16) {
2392 if (i == 8) fl.write(' ');
2393 if (ofs+i < data.length) fl.writef(" %02X", data[ofs+i]); else fl.write(" ");
2395 fl.write(" ");
2396 foreach (immutable i; 0..16) {
2397 if (ofs+i >= data.length) break;
2398 if (i == 8) fl.write(' ');
2399 ubyte b = data[ofs+i];
2400 if (b <= 32 || b >= 127) fl.write('.'); else fl.writef("%c", cast(char)b);
2402 fl.writeln();
2406 static assert(isReadableStream!MemoryStreamRO);
2407 static assert(!isWriteableStream!MemoryStreamRO);
2408 static assert(!isRWStream!MemoryStreamRO);
2409 static assert(isSeekableStream!MemoryStreamRO);
2410 static assert(streamHasClose!MemoryStreamRO);
2411 static assert(streamHasEof!MemoryStreamRO);
2412 static assert(streamHasSeek!MemoryStreamRO);
2413 static assert(streamHasTell!MemoryStreamRO);
2414 static assert(streamHasSize!MemoryStreamRO);
2416 unittest {
2418 auto ms = MemoryStreamRW();
2419 ms.rawWrite("hello");
2420 assert(!ms.eof);
2421 assert(ms.data == cast(ubyte[])"hello");
2422 //dump(ms.data);
2423 ushort[3] d;
2424 ms.seek(0);
2425 assert(!ms.eof);
2426 assert(ms.rawRead(d[0..2]).length == 2);
2427 assert(!ms.eof);
2428 assert(d == [0x6568, 0x6c6c, 0]);
2429 ms.seek(1);
2430 assert(ms.rawRead(d[0..2]).length == 2);
2431 assert(d == [0x6c65, 0x6f6c, 0]);
2432 assert(!ms.eof);
2433 //dump(cast(ubyte[])d);
2436 auto ms = new MemoryStreamRW();
2437 wchar[] a = ['\u0401', '\u0280', '\u089e'];
2438 ms.rawWrite(a);
2439 assert(ms.bytes == cast(const(ubyte)[])x"01 04 80 02 9E 08");
2440 //dump(ms.data);
2443 auto ms = MemoryStreamRO("hello");
2444 assert(ms.data == cast(const(ubyte)[])"hello");
2449 /// wrap read-only memory buffer into VFile
2450 public VFile wrapMemoryRO (const(void)[] buf, const(char)[] fname=null) {
2451 //return wrapStream(MemoryStreamRO(buf), fname);
2452 return VFile(cast(void*)newWS!(WrappedStreamAny!MemoryStreamRO)(MemoryStreamRO(buf), fname));
2455 /// wrap read-write memory buffer into VFile; duplicates data
2456 public VFile wrapMemoryRW (const(ubyte)[] buf, const(char)[] fname=null) {
2457 //return wrapStream(MemoryStreamRW(buf), fname);
2458 return VFile(cast(void*)newWS!(WrappedStreamAny!MemoryStreamRW)(MemoryStreamRW(buf), fname));
2462 // ////////////////////////////////////////////////////////////////////////// //
2463 /// wrap libc stdout
2464 public VFile wrapStdout () {
2465 static if (VFS_NORMAL_OS) {
2466 return VFile(1, false); // don't own
2467 } else {
2468 import core.stdc.stdio : stdout;
2469 if (stdout !is null) return VFile(stdout, "<stdout>", false); // don't own
2470 return VFile.init;
2474 /// wrap libc stderr
2475 public VFile wrapStderr () {
2476 static if (VFS_NORMAL_OS) {
2477 return VFile(2, false); // don't own
2478 } else {
2479 import core.stdc.stdio : stderr;
2480 if (stderr !is null) return VFile(stderr, "<stderr>", false); // don't own
2481 return VFile.init;
2485 /// wrap libc stdin
2486 public VFile wrapStdin () {
2487 static if (VFS_NORMAL_OS) {
2488 return VFile(0, false); // don't own
2489 } else {
2490 import core.stdc.stdio : stdin;
2491 if (stdin !is null) return VFile(stdin, "<stdin>", false); // don't own
2492 return VFile.init;
2497 // ////////////////////////////////////////////////////////////////////////// //
2498 /* handy building block for various unpackers/decoders
2499 * XPS API (XPS should be a struct):
2501 * XPS.InitUpkBufSize = initial size of intermediate "unpack buffer", which will be used
2502 * to store unpacked data chunks
2504 * -1 means "allocate as much as necessary" (with -upkbufsize as initial size)
2505 * 0 means "allocate as much as necessary" (with no initial allocation)
2506 * >0 means "this is exact size, we will never need more than that"
2508 * void setup (VFile fl, ulong agflags, long apos, long apksize, long aupksize);
2509 * initialize decoder
2510 * fl = input file; store it somewhere
2511 * agflags = flags, decoder should know what they are for
2512 * apos = encoded data offset in fl
2513 * apksize = encoded data size
2514 * aupksize = decoded data size
2516 * void reset ();
2517 * reset decoder; will be called when engine wants to start decoding again from apos
2518 * should reset all necessary vars, etc.
2520 * void close ();
2521 * close fl, shutdown decoder, free memory, etc. nothing else will be called after this
2523 * bool unpackChunk (scope VStreamDecoderLowLevelROPutBytesDg pdg);
2524 * decode chunk of arbitrary size, use `putUnpackedBytes()` to put unpacked bytes into
2525 * buffer. return `false` if EOF was hit, otherwise try to emit at least one byte.
2526 * note that `unpackChunk()` can emit no bytes and return `true` to indicate that it
2527 * can be called again to get more data.
2528 * pdg = delegate that should be called to emit decoded bytes
2530 public alias VStreamDecoderLowLevelROPutBytesDg = void delegate (const(ubyte)[] bts...) @trusted;
2532 public struct VStreamDecoderLowLevelRO(XPS) {
2533 private:
2534 static assert(XPS.InitUpkBufSize != int.min, "are you insane?");
2535 XPS epl;
2536 long size; // unpacked size
2537 long pos; // current file position
2538 long prpos; // previous file position
2539 bool eofhit;
2540 bool epleof;
2541 bool closed;
2542 static if (XPS.InitUpkBufSize <= 0) {
2543 mixin VFSHiddenPointerHelper!(ubyte, "upkbuf");
2544 uint upkbufsize;
2545 } else {
2546 ubyte[XPS.InitUpkBufSize] upkbuf;
2547 enum upkbufsize = cast(uint)XPS.InitUpkBufSize;
2549 uint upkbufused, upkbufpos;
2551 public:
2552 this (VFile fl, ulong agflags, long aupsize, long astpos, long asize) {
2553 //debug(vfs_vfile_gc) { import core.stdc.stdio : printf; printf("upkbuf ofs=%u\n", (cast(uint)&upkbuf)-(cast(uint)&this)); }
2554 if (aupsize > uint.max) aupsize = uint.max;
2555 if (asize > uint.max) asize = uint.max;
2556 epl.setup(fl, agflags, astpos, cast(uint)asize, cast(uint)aupsize);
2557 epleof = eofhit = (aupsize == 0);
2558 size = aupsize;
2559 closed = !fl.isOpen;
2560 static if (XPS.InitUpkBufSize < 0) {
2561 if (!closed) {
2562 import core.stdc.stdlib : malloc;
2563 upkbuf = cast(ubyte*)malloc(-XPS.InitUpkBufSize);
2564 if (upkbuf is null) throw new VFSException("out of memory");
2565 upkbufsize = -XPS.InitUpkBufSize;
2570 @property bool isOpen () const pure nothrow @safe @nogc { return !closed; }
2571 @property bool eof () const pure nothrow @safe @nogc { return eofhit; }
2573 void close () {
2574 if (!closed) {
2575 //{ import core.stdc.stdio : printf; printf("CLOSED!\n"); }
2576 epleof = eofhit = true;
2577 closed = true;
2578 static if (XPS.InitUpkBufSize <= 0) {
2579 import core.stdc.stdlib : free;
2580 if (upkbuf !is null) free(upkbuf);
2581 upkbuf = null;
2582 upkbufsize = 0;
2584 upkbufused = upkbufpos = 0;
2585 epl.close();
2589 private void reset () {
2590 upkbufused = 0;
2591 epl.reset();
2594 private ssize doRealRead (void* buf, usize count) {
2595 if (count == 0) return 0; // the thing that should not be
2596 if (closed) return -1;
2597 auto dest = cast(ubyte*)buf;
2598 auto left = count;
2599 while (left > 0 && !closed) {
2600 // use the data that left from the unpacked chunk
2601 if (upkbufpos < upkbufused) {
2602 auto pkx = upkbufused-upkbufpos;
2603 if (pkx > left) pkx = cast(uint)left;
2604 static if (XPS.InitUpkBufSize <= 0) {
2605 dest[0..pkx] = upkbuf[upkbufpos..upkbufpos+pkx];
2606 } else {
2607 dest[0..pkx] = upkbuf.ptr[upkbufpos..upkbufpos+pkx];
2609 dest += pkx;
2610 upkbufpos += pkx;
2611 left -= pkx;
2612 } else {
2613 if (epleof) break;
2614 // no data in unpacked chunk, request new
2615 upkbufused = upkbufpos = 0;
2616 while (upkbufused == 0 && !closed && !epleof) {
2617 if (!epl.unpackChunk(&putUnpackedByte)) epleof = true;
2621 return count-left;
2624 ssize read (void* buf, usize count) {
2625 if (buf is null) return -1;
2626 if (count == 0 || size == 0) return 0;
2627 if (!isOpen) return -1; // read error
2628 if (count > ssize.max) count = ssize.max;
2629 if (size >= 0 && pos >= size) { eofhit = true; return 0; } // EOF
2630 // do we want to seek backward?
2631 if (prpos > pos) {
2632 // yes, rewind
2633 reset();
2634 epleof = eofhit = (size == 0);
2635 prpos = 0;
2637 // do we need to seek forward?
2638 if (prpos < pos) {
2639 // yes, skip data
2640 ubyte[512] tmp = 0;
2641 while (prpos < pos) {
2642 auto xrd = pos-prpos;
2643 auto rd = doRealRead(tmp.ptr, cast(uint)(xrd > tmp.length ? tmp.length : xrd));
2644 if (rd <= 0) return -1;
2645 prpos += rd;
2647 if (prpos != pos) return -1;
2649 assert(prpos == pos);
2650 // unpack data
2651 if (size >= 0 && size-pos < count) {
2652 count = cast(usize)(size-pos);
2653 if (count == 0) return 0;
2654 if (count > ssize.max) count = ssize.max;
2656 auto rd = doRealRead(buf, count);
2657 pos = (prpos += rd);
2658 return rd;
2661 ssize write (in void* buf, usize count) { return -1; }
2663 long lseek (long ofs, int origin) {
2664 if (!isOpen) return -1;
2665 //TODO: overflow checks
2666 switch (origin) {
2667 case Seek.Set: break;
2668 case Seek.Cur: ofs += pos; break;
2669 case Seek.End:
2670 if (ofs > 0) ofs = 0;
2671 ofs += size;
2672 break;
2673 default:
2674 return -1;
2676 if (ofs < 0) return -1;
2677 eofhit = (size >= 0 && ofs >= size);
2678 pos = ofs;
2679 return pos;
2682 private:
2683 void putUnpackedByte (const(ubyte)[] bts...) @trusted {
2684 //{ import core.stdc.stdio : printf; printf("putUnpackedByte: %u; upkbufused=%u; upkbufsize=%u\n", cast(uint)bts.length, upkbufused, upkbufsize); }
2685 if (closed) return; // just in case
2686 foreach (ubyte b; bts[]) {
2687 static if (XPS.InitUpkBufSize <= 0) {
2688 if (upkbufused >= upkbufsize) {
2689 // allocate more memory for unpacked data buffer
2690 import core.stdc.stdlib : realloc;
2691 auto newsz = (upkbufsize ? upkbufsize*2 : 256*1024);
2692 if (newsz <= upkbufsize) throw new Exception("out of memory");
2693 auto nbuf = cast(ubyte*)realloc(upkbuf, newsz);
2694 if (!nbuf) throw new Exception("out of memory");
2695 upkbuf = nbuf;
2696 upkbufsize = newsz;
2697 //{ import core.stdc.stdio : printf; printf(" grow: upkbufsize=%u\n", upkbufsize); }
2699 //assert(upkbufused < upkbufsize);
2700 //assert(upkbuf);
2701 upkbuf[upkbufused++] = b;
2702 } else {
2703 if (upkbufused >= upkbuf.length) throw new Exception("out of unpack buffer");
2704 upkbuf.ptr[upkbufused++] = b;