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/>.
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;
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;
40 import iv
.vfs
.types
: Seek
, VFSHiddenPointerHelper
;
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.
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
) {
70 debug(vfs_rc
) { import core
.stdc
.stdio
: printf
; printf("DO DECREF FOR 0x%08x\n", cast(void*)st
); }
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
);
87 this (void* wptr
) { wstp
= cast(usize
)wptr
; }
88 //this (usize wptr) { wstp = wptr; }
91 this (const VFile fl
) {
93 if (wstp
) wst
.incRef();
96 version(vfs_stdio_wrapper
)
97 this (std
.stdio
.File fl
, const(char)[] fname
=null) {
99 wstp
= WrapStdioFile(fl
, fname
);
100 } catch (Exception e
) {
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");
123 if (own
) fres
.wstp
= WrapGZFile
!true(fl
, null); else fres
.wstp
= WrapGZFile
!false(fl
, null);
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");
131 if (own
) fres
.wstp
= WrapGZFile
!true(fl
, afname
); else fres
.wstp
= WrapGZFile
!false(fl
, afname
);
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
); }
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!
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
); }
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;
183 synchronized(wst
) return wst
.name
;
190 @property bool isOpen () const nothrow @safe @nogc {
191 if (!wstp
) return false;
193 synchronized(wst
) return wst
.isOpen
;
205 } catch (Exception e
) {
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) {
218 synchronized(wst
) res
= wst
.read(buf
.ptr
, buf
.length
*T
.sizeof
);
219 } catch (Exception e
) {
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
];
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) {
235 res
= wst
.read(buf
.ptr
, buf
.length
*T
.sizeof
);
236 } catch (Exception e
) {
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
];
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
;
255 ssize res
= wst
.read(dp
, left
);
256 if (res
<= 0) throw new VFSException("read error");
260 } catch (Exception e
) {
262 throw new VFSException("read error", __FILE__
, __LINE__
, e
);
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
;
274 ssize res
= wst
.read(dp
, left
);
275 if (res
<= 0) throw new VFSException("read error");
279 } catch (Exception e
) {
281 throw new VFSException("read error", __FILE__
, __LINE__
, e
);
286 void rawWrite(T
) (in T
[] buf
) {
287 if (!isOpen
) throw new VFSException("can't write to closed stream");
288 if (buf
.length
> 0) {
291 synchronized(wst
) res
= wst
.write(buf
.ptr
, buf
.length
*T
.sizeof
);
292 } catch (Exception e
) {
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) {
305 res
= wst
.write(buf
.ptr
, buf
.length
*T
.sizeof
);
306 } catch (Exception e
) {
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");
320 synchronized(wst
) p
= wst
.lseek(offset
, origin
);
321 } catch (Exception e
) {
323 throw new VFSException("seek error", __FILE__
, __LINE__
, e
);
325 if (p
== -1) throw new VFSException("seek error");
329 @property long tell () {
330 if (!isOpen
) throw new VFSException("can't get position in closed stream");
333 synchronized(wst
) p
= wst
.lseek(0, Seek
.Cur
);
334 } catch (Exception e
) {
336 throw new VFSException("tell error", __FILE__
, __LINE__
, e
);
338 if (p
== -1) throw new VFSException("tell error");
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;
355 if (p
== -1) { noChain
= true; throw new VFSException("size error"); }
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
) {
366 if (noChain
) throw e
;
367 throw new VFSException("size error", __FILE__
, __LINE__
, e
);
373 if (!isOpen
) throw new VFSException("can't get size of closed stream");
374 bool noChain
= false;
378 throw new VFSException("flush error");
380 } catch (Exception e
) {
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;
390 debug(vfs_rc
) { import core
.stdc
.stdio
: printf
; printf("***OPASSIGN(0x%08x -> 0x%08x)\n", cast(void*)src
.wstp
, cast(void*)wstp
); }
392 // assigning to opened stream
394 // both streams are active
395 if (wstp
== src
.wstp
) return; // nothing to do
399 // replace stream object
401 // release old stream
404 // just close this one
409 } else if (src
.wstp
) {
410 // this stream is closed, but other is active; easy deal
414 } catch (Exception e
) {
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
{
431 private this (VFile afl
) nothrow {
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
445 atomicOp
!"+="(fl
.wst
.wrrc
, 1);
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
{
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";
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; }
507 // convert to little-endian
508 foreach (ref x
; b
) { x
= n
&0xff; n
>>= 8; }
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) {
522 rawReadExact((&v
)[0..1]);
524 } else static if (es
== ItEHi || es
== ItELo
) {
525 ubyte[T
.sizeof
] b
= void;
528 version(LittleEndian
) {
529 // convert from big-endian
530 foreach (ubyte x
; b
) { v
<<= 8; v |
= x
; }
532 // conver from little-endian
533 foreach_reverse (ubyte x
; b
) { v
<<= 8; v |
= x
; }
537 static assert(0, "invalid endianness: '"~es
~"'");
541 private enum reverseBytesMixin
= "
542 foreach (idx; 0..b.length/2) {
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
);
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
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;
577 mixin(reverseBytesMixin
);
578 memcpy(&v
, b
.ptr
, T
.sizeof
);
580 static assert(0, "invalid endianness: '"~es
~"'");
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
589 writeNum
!(ubyte, es
)(cast(ubyte)n
);
590 } else if (n
<= 255) {
591 writeNum
!(ubyte, es
)(253);
592 writeNum
!(ushort, es
)(cast(ushort)n
);
595 static if (T
.sizeof
== 2) {
596 writeNum
!(ubyte, es
)(253);
597 writeNum
!(ushort, es
)(cast(ushort)n
);
600 if (n
<= ushort.max
) {
601 writeNum
!(ubyte, es
)(253);
602 writeNum
!(ushort, es
)(cast(ushort)n
);
605 static if (T
.sizeof
== 4) {
606 writeNum
!(ubyte, es
)(254);
607 writeNum
!(uint, es
)(cast(uint)n
);
611 writeNum
!(ubyte, es
)(254);
612 writeNum
!(uint, es
)(cast(uint)n
);
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
;
630 ushort v
= readNum
!(ushort, es
);
631 if (v
< 253) throw new VFSException("invalid varnum");
633 static if (T
.sizeof
== 1) {
634 import std
.conv
: ConvOverflowException
;
635 if (v
> ubyte.max
) throw new ConvOverflowException("varnum overflow");
639 uint v
= readNum
!(uint, es
);
640 if (v
< 253) throw new VFSException("invalid varnum");
642 static if (T
.sizeof
< uint.sizeof
) {
643 import std
.conv
: ConvOverflowException
;
644 if (v
> T
.max
) throw new ConvOverflowException("varnum overflow");
648 ulong v
= readNum
!(ulong, es
);
649 if (v
< 253) throw new VFSException("invalid varnum");
651 static if (T
.sizeof
< ulong.sizeof
) {
652 import std
.conv
: ConvOverflowException
;
653 if (v
> T
.max
) throw new ConvOverflowException("varnum overflow");
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)
676 if (v
&0x8000_0000_0000_0000UL) {
677 v
= (v^
~0uL)+1; // negate v
678 buf
.ptr
[0] = 0x80; // sign bit
682 buf
.ptr
[0] |
= v
&0x3f;
684 if (v
!= 0) buf
.ptr
[0] |
= 0x40; // has more
686 buf
.ptr
[len
] = v
&0x7f;
688 if (v
> 0) buf
.ptr
[len
] |
= 0x80; // has more
692 rawWrite(buf
.ptr
[0..len
]);
695 T
readXInt(T
:ulong) () {
696 import std
.conv
: ConvOverflowException
;
699 // first byte contains sign flag
700 rawReadExact((&c
)[0..1]);
702 // special (negative zero)
703 v
= 0x8000_0000_0000_0000UL;
705 bool neg = ((c
&0x80) != 0);
708 // 63/7 == 9, so we can shift at most 56==(7*8) bits
711 if (shift
> 62) throw new ConvOverflowException("readXInt overflow");
712 rawReadExact((&c
)[0..1]);
714 if (shift
== 62 && n
> 1) throw new ConvOverflowException("readXInt overflow");
719 if (neg) v
= (v^
~0uL)+1; // negate v
721 // now convert to output
722 static if (T
.sizeof
== v
.sizeof
) {
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");
730 if (v
> T
.max
) throw new ConvOverflowException("readXInt overflow");
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
;
740 static if (is(T
: V
[], V
)) {
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
748 if (rawRead((&c
)[0..1]).length
== 0) break; // don't require trailing zero on eof
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)) {
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
));
776 // ////////////////////////////////////////////////////////////////////////// //
777 // base refcounted class for wrapped stream
778 package class WrappedStreamRC
{
781 shared uint wrrc
= 0; // locked writer rc
784 char[512] fnamebuf
=0;
788 this (const(char)[] aname
) nothrow @trusted @nogc { setFileName(aname
); }
790 final void setFileName (const(char)[] aname
) nothrow @trusted @nogc {
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
= '/';
798 import core
.stdc
.stdlib
: realloc
;
799 auto nb
= cast(char*)realloc(cast(void*)fnameptr
, aname
.length
);
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
= '/';
810 if (fnameptr
) { import core
.stdc
.stdlib
: free
; free(cast(void*)fnameptr
); fnameptr
= 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 {
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
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!
842 import core
.memory
: GC
;
843 import core
.stdc
.stdlib
: free
;
844 synchronized(this) { setFileName(null); close(); } // finalize stream; should be synchronized right here
852 void function (void* self
) nothrow gcUnregister
;
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);
891 immutable end
= pbm
[0]/usize
.sizeof
;
892 while (bitnum
< end
) {
893 if (p
[bitnum
/8]&(1U<<(bitnum
%8))) {
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;
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);
914 immutable end
= pbm
[0]/usize
.sizeof
;
915 while (bitnum
< end
) {
916 if (p
[bitnum
/8]&(1U<<(bitnum
%8))) {
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
);
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
{
942 public this (std
.stdio
.File afl
, const(char)[] afname
) { fl
= afl
; super(afname
); } // fuck! emplace needs it
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
]);
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
{
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
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 () {
996 import std
.exception
: ErrnoException
;
997 auto res
= core
.stdc
.stdio
.fclose(fl
);
999 if (res
!= 0) throw new ErrnoException("can't close file", __FILE__
, __LINE__
);
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
;
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
) {
1015 import core
.stdc
.errno
;
1016 if (errno
== EINTR
) { if (sigsleft
-- > 0) { core
.stdc
.stdio
.clearerr(fl
); continue; } }
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
;
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
) {
1032 import core
.stdc
.errno
;
1033 if (errno
== EINTR
) { if (sigsleft
-- > 0) { core
.stdc
.stdio
.clearerr(fl
); continue; } }
1040 override long lseek (long offset
, int origin
) {
1041 if (fl
is null) return -1;
1042 version(VFS_NORMAL_OS
) int sigsleft
= VFSSigRepeatCount
;
1044 static if (VFS_NORMAL_OS
) {
1045 auto res
= core
.sys
.posix
.stdio
.fseeko(fl
, offset
, origin
);
1048 if (offset
< int.min || offset
> int.max
) return -1;
1049 auto res
= core
.stdc
.stdio
.fseek(fl
, cast(int)offset
, origin
);
1052 core
.stdc
.stdio
.clearerr(fl
);
1055 version(VFS_NORMAL_OS
) {
1056 import core
.stdc
.errno
;
1057 if (errno
== EINTR
) { if (sigsleft
-- > 0) { core
.stdc
.stdio
.clearerr(fl
); continue; } }
1062 static if (VFS_NORMAL_OS
) {
1063 return core
.sys
.posix
.stdio
.ftello(fl
);
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
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
;
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
;
1096 int err () nothrow @trusted {
1098 if (flp
!= 0) gzerror(fl
, &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;
1112 version(VFS_NORMAL_OS
) int sigsleft
= VFSSigRepeatCount
;
1113 if (newpos
< cpos
) {
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; } }
1127 if (cpos
== -1) return false; // something is VERY wrong
1129 if (newpos
< cpos
) return false; // why?!
1130 version(VFS_NORMAL_OS
) sigsleft
= VFSSigRepeatCount
;
1132 auto res
= gzseek(fl
, cast(int)newpos
, 0); // fuck you, phobos!
1136 version(VFS_NORMAL_OS
) {
1137 import core
.stdc
.errno
;
1138 if (errno
== EINTR
) { if (sigsleft
-- > 0) { gzclearerr(fl
); continue; } }
1144 if (gztell(fl
) != newpos
) return false; // something is VERY wrong
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 () {
1155 auto res
= gzclose(fl
);
1157 if (res
!= Z_BUF_ERROR
&& res
!= Z_OK
) throw new VFSException("can't close file", __FILE__
, __LINE__
);
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
;
1170 static if (is(typeof(&gzfread
))) {
1171 auto res
= gzfread(buf
, 1, count
, fl
);
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
) {
1178 import core
.stdc
.errno
;
1179 if (errno
== EINTR
) { if (sigsleft
-- > 0) { gzclearerr(fl
); continue; } }
1183 //{ import core.stdc.stdio; printf("res=0; pos=%u; err=%d\n", cast(uint)newpos, err()); }
1184 return (err() ?
-1 : 0);
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
;
1197 static if (is(typeof(&gzfwrite
))) {
1198 auto res
= gzfwrite(cast(void*)buf
, 1, count
, fl
); // fuck you, phobos!
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
) {
1205 import core
.stdc
.errno
;
1206 if (errno
== EINTR
) { if (sigsleft
-- > 0) { gzclearerr(fl
); continue; } }
1209 if (res
== 0) return (err() ?
-1 : 0);
1211 if (cachedSize
== ulong.max || newpos
> cachedSize
) cachedSize
= newpos
; // fix cached file size
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
;
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
;
1231 auto res
= gzseek(fl
, cast(int)offset
, origin
); // fuck you, phobos!
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
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
;
1258 //{ import core.stdc.stdio; printf("%d\n", cast(int)gztell(fl)); }
1259 newpos
= gztell(fl
);
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
{
1282 public this (int afd
, const(char)[] afname
) { fd
= afd
; eofhit
= (afd
< 0); super(afname
); } // fuck! emplace needs it
1285 override @property bool isOpen () const nothrow @safe @nogc { return (fd
>= 0); }
1287 override void close () {
1289 import std
.exception
: ErrnoException
;
1291 debug(vfs_rc
) { import core
.stdc
.stdio
: printf
; printf("******** CLOSING FD %u\n", cast(uint)fd
); }
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
);
1298 static if (own
) if (res
< 0) throw new ErrnoException("can't close file", __FILE__
, __LINE__
);
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
;
1310 auto res
= core
.sys
.posix
.unistd
.read(fd
, buf
, count
);
1311 version(VFS_NORMAL_OS
) {
1313 import core
.stdc
.errno
;
1314 if (errno
== EINTR
) { if (sigsleft
-- > 0) continue; }
1317 if (res
!= count
) eofhit
= true;
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
;
1327 auto res
= core
.sys
.posix
.unistd
.write(fd
, buf
, count
);
1328 version(VFS_NORMAL_OS
) {
1330 import core
.stdc
.errno
;
1331 if (errno
== EINTR
) { if (sigsleft
-- > 0) continue; }
1334 if (res
!= count
) eofhit
= true;
1339 override long lseek (long offset
, int origin
) {
1340 if (fd
< 0) return -1;
1341 version(VFS_NORMAL_OS
) int sigsleft
= VFSSigRepeatCount
;
1343 auto res
= core
.sys
.posix
.unistd
.lseek(fd
, offset
, origin
);
1347 version(VFS_NORMAL_OS
) {
1348 import core
.stdc
.errno
;
1349 if (errno
== EINTR
) { if (sigsleft
-- > 0) continue; }
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
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
{
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
);
1388 static if (streamHasIsOpen
!ST
) {
1389 closed
= !st
.isOpen
;
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
]);
1401 static if (streamHasName
!ST
) {
1402 return (closed ?
null : (hasName ?
super.name
: st
.name
));
1404 return (fnameptr ?
"" : null);
1409 override @property bool isOpen () const nothrow @safe @nogc {
1410 static if (streamHasIsOpen
!ST
) {
1411 if (closed
) return true;
1418 override @property bool eof () {
1419 if (closed
) return true;
1420 static if (streamHasEof
!ST
) {
1427 override void close () {
1431 static if (streamHasClose
!ST
) st
.close();
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;
1443 } else static if (isReadableStream
!ST
) {
1444 return st
.rawRead(buf
[0..count
]).length
;
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;
1457 } else static if (isWriteableStream
!ST
) {
1458 st
.rawWrite(buf
[0..count
]);
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;
1473 } else static if (streamHasSeek
!ST
) {
1474 // has high-level seek
1475 if (closed
) return -1;
1476 st
.seek(offset
, origin
);
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; }
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
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
1612 this (VFile fl
, long astpos
, long asize
) {
1618 @property bool isOpen () const nothrow @safe @nogc { return zfl
.isOpen
; }
1619 @property bool eof () { return eofhit
; }
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
]);
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
1644 case Seek
.Set
: break;
1645 case Seek
.Cur
: ofs
+= pos
; break;
1647 if (ofs
> 0) ofs
= 0;
1653 if (ofs
< 0) return -1;
1655 if (ofs
> size
) ofs
= size
;
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
{
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
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
1700 // reading one byte from zlib fuckin' fails. shit.
1701 ubyte[65536] updata
;
1705 this (VFile fl
, VFSZLibMode amode
, long aupsize
, long astpos
, long asize
) {
1706 if (amode
== VFSZLibMode
.Raw
&& aupsize
< 0) aupsize
= asize
;
1716 @property bool isOpen () const nothrow @safe @nogc { return zfl
.isOpen
; }
1717 @property bool eof () { return eofhit
; }
1720 import core
.stdc
.stdlib
: free
;
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
];
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
) {
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
;
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
;
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
);
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;
1789 auto ptr
= cast(ubyte*)zs
.next_out
;
1790 *ptr
= updata
.ptr
[uppos
++];
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"); }
1804 uint rd
= cast(uint)tbuf
.length
;
1805 zs
.next_out
= cast(typeof(zs
.next_out
))tbuf
.ptr
;
1807 //{ import core.stdc.stdio; printf("findUnpackedSize: reading %u bytes...\n", rd); }
1808 if (!unpackNextChunk()) return false;
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;
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
]);
1832 if (pkb
.ptr
is null && !initZStream()) return -1;
1833 // do we want to seek backward?
1841 if (!initZStream(true)) return -1;
1844 // do we need to seek forward?
1847 ubyte[1024] tbuf
= void;
1848 auto skp
= pos
-prpos
;
1850 uint rd
= cast(uint)(skp
> tbuf
.length ? tbuf
.length
: skp
);
1851 zs
.next_out
= cast(typeof(zs
.next_out
))tbuf
.ptr
;
1853 if (!unpackNextChunk()) return -1;
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) {
1865 count
-= zs
.avail_out
;
1868 prpos
= (pos
+= 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
1879 case Seek
.Set
: break;
1880 case Seek
.Cur
: ofs
+= pos
; break;
1882 if (ofs
> 0) ofs
= 0;
1884 if (pkb
.ptr
is null && !initZStream()) return -1;
1885 if (!findUnpackedSize
) return -1;
1892 if (ofs
< 0) return -1;
1893 if (size
>= 0 && ofs
> size
) ofs
= size
;
1901 import iv
.vfs
.inflate
;
1903 struct ZLibLowLevelRO
{
1904 VFile zfl
; // archive file
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
);
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
;
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;
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
);
1958 import core
.stdc
.stdlib
: free
;
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"); }
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;
1977 //{ import core.stdc.stdio; printf("findUnpackedSize: prpos=%u\n", cast(uint)prpos); }
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
) {
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
1998 // do we want to seek backward?
2005 if (ifs
is null) inflateInit(); // here, 'cause struct can be copied
2007 // do we need to seek forward?
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); }
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; }
2020 //{ import core.stdc.stdio; printf("01: prpos=%d; pos=%d\n", cast(int)prpos, cast(int)pos); }
2022 assert(pos
== prpos
);
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; }
2028 prpos
= (pos
+= 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
2039 case Seek
.Set
: break;
2040 case Seek
.Cur
: ofs
+= pos
; break;
2042 if (ofs
> 0) ofs
= 0;
2044 if (mode
== VFSZLibMode
.Raw
) return -1;
2045 if (!findUnpackedSize()) return -1;
2052 if (ofs
< 0) return -1;
2053 if (size
>= 0 && ofs
> size
) ofs
= size
;
2055 eofhit
= (pos
>= size
);
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
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
2104 this (VFile fl
, VFSZLibMode amode
, int acomplevel
=-1) {
2108 if (acomplevel
< 0) acomplevel
= 6;
2109 if (acomplevel
> 9) acomplevel
= 9;
2113 @property bool isOpen () const nothrow @safe @nogc { return (!eofhit
&& zfl
.isOpen
); }
2114 @property bool eof () { return isOpen
; }
2118 import core
.stdc
.stdlib
: free
;
2120 if (pkb
!is null) free(pkb
.ptr
);
2123 //{ import core.stdc.stdio : printf; printf("CLOSING...\n"); }
2125 scope(exit
) zfl
.close();
2126 if (zfl
.isOpen
&& pkb
!is null) {
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); }
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
;
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");
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
;
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
) {
2178 zs
.next_out
= pkb
.ptr
;
2179 zs
.avail_out
= obsize
;
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
]);
2198 if (!initZStream()) return -1;
2200 auto bp
= cast(const(ubyte)*)buf
;
2202 zs
.next_in
= cast(typeof(zs
.next_in
))bp
;
2203 zs
.avail_in
= (css
> 0x3fff_ffff ?
0x3fff_ffff
: cast(uint)css
);
2206 // now process the whole input
2207 while (zs
.avail_in
> 0) {
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
]);
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;
2227 long lseek (long ofs
, int origin
) {
2228 if (!isOpen
) return -1;
2229 //TODO: overflow checks
2231 case Seek
.Set
: break;
2232 case Seek
.Cur
: ofs
+= prpos
; break;
2233 case Seek
.End
: if (ofs
> 0) ofs
= 0; ofs
+= pos
; break;
2236 if (ofs
< 0) return -1;
2237 if (ofs
> pos
) ofs
= 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 // ////////////////////////////////////////////////////////////////////////// //
2266 struct MemoryStreamImpl(bool rw
, bool asref
) {
2274 static assert(!asref
, "wtf?!");
2275 const(ubyte)[] data
;
2279 bool closed
= false;
2282 static if (usize
.sizeof
== 4) {
2283 enum MaxSize
= 0x7fff_ffffU
;
2285 enum MaxSize
= 0x7fff_ffff_ffff_ffffUL
;
2291 this (ref ubyte[] adata
) @trusted {
2292 if (adata
.length
> MaxSize
) throw new VFSException("buffer too big");
2294 eofhit
= (adata
.length
== 0);
2296 @property ubyte[]* bytes () pure nothrow @safe @nogc { return data
; }
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
; }
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");
2325 if (offset
< 0 || offset
> MaxSize
) throw new VFSException("invalid offset");
2326 curpos
= cast(usize
)offset
;
2329 if (offset
< -cast(long)curpos || offset
> MaxSize
-curpos
) throw new VFSException("invalid offset");
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
);
2336 default: throw new VFSException("invalid offset origin");
2341 ssize
read (void* buf
, usize count
) {
2342 if (closed
) return -1;
2343 if (curpos
>= data
.length
) { eofhit
= true; return 0; }
2345 import core
.stdc
.string
: memcpy
;
2346 usize rlen
= data
.length
-curpos
;
2347 if (rlen
>= count
) rlen
= count
; else eofhit
= true;
2349 memcpy(buf
, data
.ptr
+curpos
, rlen
);
2351 return cast(ssize
)rlen
;
2357 ssize
write (in void* buf
, usize count
) {
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
;
2369 if (optr
is GC
.addrOf(optr
)) GC
.setAttr(optr
, GC
.BlkAttr
.NO_INTERIOR
);
2372 memcpy(data
.ptr
+curpos
, buf
, count
);
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(" ");
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
);
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
);
2418 auto ms
= MemoryStreamRW();
2419 ms
.rawWrite("hello");
2421 assert(ms
.data
== cast(ubyte[])"hello");
2426 assert(ms
.rawRead(d
[0..2]).length
== 2);
2428 assert(d
== [0x6568, 0x6c6c, 0]);
2430 assert(ms
.rawRead(d
[0..2]).length
== 2);
2431 assert(d
== [0x6c65, 0x6f6c, 0]);
2433 //dump(cast(ubyte[])d);
2436 auto ms
= new MemoryStreamRW();
2437 wchar[] a
= ['\u0401', '\u0280', '\u089e'];
2439 assert(ms
.bytes
== cast(const(ubyte)[])x
"01 04 80 02 9E 08");
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
2468 import core
.stdc
.stdio
: stdout
;
2469 if (stdout
!is null) return VFile(stdout
, "<stdout>", false); // don't own
2474 /// wrap libc stderr
2475 public VFile
wrapStderr () {
2476 static if (VFS_NORMAL_OS
) {
2477 return VFile(2, false); // don't own
2479 import core
.stdc
.stdio
: stderr
;
2480 if (stderr
!is null) return VFile(stderr
, "<stderr>", false); // don't own
2486 public VFile
wrapStdin () {
2487 static if (VFS_NORMAL_OS
) {
2488 return VFile(0, false); // don't own
2490 import core
.stdc
.stdio
: stdin
;
2491 if (stdin
!is null) return VFile(stdin
, "<stdin>", false); // don't own
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
2517 * reset decoder; will be called when engine wants to start decoding again from apos
2518 * should reset all necessary vars, etc.
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
) {
2534 static assert(XPS
.InitUpkBufSize
!= int.min
, "are you insane?");
2536 long size
; // unpacked size
2537 long pos
; // current file position
2538 long prpos
; // previous file position
2542 static if (XPS
.InitUpkBufSize
<= 0) {
2543 mixin VFSHiddenPointerHelper
!(ubyte, "upkbuf");
2546 ubyte[XPS
.InitUpkBufSize
] upkbuf
;
2547 enum upkbufsize
= cast(uint)XPS
.InitUpkBufSize
;
2549 uint upkbufused
, upkbufpos
;
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);
2559 closed
= !fl
.isOpen
;
2560 static if (XPS
.InitUpkBufSize
< 0) {
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
; }
2575 //{ import core.stdc.stdio : printf; printf("CLOSED!\n"); }
2576 epleof
= eofhit
= true;
2578 static if (XPS
.InitUpkBufSize
<= 0) {
2579 import core
.stdc
.stdlib
: free
;
2580 if (upkbuf
!is null) free(upkbuf
);
2584 upkbufused
= upkbufpos
= 0;
2589 private void 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
;
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
];
2607 dest
[0..pkx
] = upkbuf
.ptr
[upkbufpos
..upkbufpos
+pkx
];
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;
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?
2634 epleof
= eofhit
= (size
== 0);
2637 // do we need to seek forward?
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;
2647 if (prpos
!= pos
) return -1;
2649 assert(prpos
== pos
);
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
);
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
2667 case Seek
.Set
: break;
2668 case Seek
.Cur
: ofs
+= pos
; break;
2670 if (ofs
> 0) ofs
= 0;
2676 if (ofs
< 0) return -1;
2677 eofhit
= (size
>= 0 && ofs
>= size
);
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");
2697 //{ import core.stdc.stdio : printf; printf(" grow: upkbufsize=%u\n", upkbufsize); }
2699 //assert(upkbufused < upkbufsize);
2701 upkbuf
[upkbufused
++] = b
;
2703 if (upkbufused
>= upkbuf
.length
) throw new Exception("out of unpack buffer");
2704 upkbuf
.ptr
[upkbufused
++] = b
;