1 /** 2 Thread based asynchronous file I/O fallback implementation 3 4 Copyright: © 2012 RejectedSoftware e.K. 5 Authors: Sönke Ludwig 6 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 7 */ 8 module vibe.core.drivers.threadedfile; 9 10 import vibe.core.core : yield; 11 import vibe.core.log; 12 import vibe.core.driver; 13 import vibe.utils.string; 14 15 import std.algorithm; 16 import std.conv; 17 import std.exception; 18 import std.string; 19 import core.stdc.errno; 20 21 version(Posix){ 22 import core.sys.posix.fcntl; 23 import core.sys.posix.sys.stat; 24 import core.sys.posix.unistd; 25 } 26 version(Windows){ 27 import core.sys.windows.stat; 28 29 private { 30 // TODO: use CreateFile/HANDLE instead of the Posix API on Windows 31 32 extern(C) { 33 alias off_t = sizediff_t; 34 int open(in char* name, int mode, ...); 35 int chmod(in char* name, int mode); 36 int close(int fd); 37 int read(int fd, void *buffer, uint count); 38 int write(int fd, in void *buffer, uint count); 39 off_t lseek(int fd, off_t offset, int whence); 40 } 41 42 enum O_RDONLY = 0; 43 enum O_WRONLY = 1; 44 enum O_RDWR = 2; 45 enum O_APPEND = 8; 46 enum O_CREAT = 0x0100; 47 enum O_TRUNC = 0x0200; 48 enum O_BINARY = 0x8000; 49 50 enum _S_IREAD = 0x0100; /* read permission, owner */ 51 enum _S_IWRITE = 0x0080; /* write permission, owner */ 52 alias stat_t = struct_stat; 53 } 54 } 55 else 56 { 57 enum O_BINARY = 0; 58 } 59 60 private { 61 enum SEEK_SET = 0; 62 enum SEEK_CUR = 1; 63 enum SEEK_END = 2; 64 } 65 66 final class ThreadedFileStream : FileStream { 67 @safe: 68 69 private { 70 int m_fileDescriptor; 71 Path m_path; 72 ulong m_size; 73 ulong m_ptr = 0; 74 FileMode m_mode; 75 bool m_ownFD = true; 76 } 77 78 this(Path path, FileMode mode) 79 { 80 auto pathstr = path.toNativeString(); 81 () @trusted { 82 final switch(mode) { 83 case FileMode.read: 84 m_fileDescriptor = open(pathstr.toStringz(), O_RDONLY|O_BINARY); 85 break; 86 case FileMode.readWrite: 87 m_fileDescriptor = open(pathstr.toStringz(), O_RDWR|O_BINARY); 88 break; 89 case FileMode.createTrunc: 90 m_fileDescriptor = open(pathstr.toStringz(), O_RDWR|O_CREAT|O_TRUNC|O_BINARY, octal!644); 91 break; 92 case FileMode.append: 93 m_fileDescriptor = open(pathstr.toStringz(), O_WRONLY|O_CREAT|O_APPEND|O_BINARY, octal!644); 94 break; 95 } 96 } (); 97 98 if( m_fileDescriptor < 0 ) 99 //throw new Exception(format("Failed to open '%s' with %s: %d", pathstr, cast(int)mode, errno)); 100 throw new Exception("Failed to open file '"~pathstr~"'."); 101 102 this(m_fileDescriptor, path, mode); 103 } 104 105 this(int fd, Path path, FileMode mode) 106 { 107 assert(fd >= 0); 108 m_fileDescriptor = fd; 109 m_path = path; 110 m_mode = mode; 111 112 version(linux){ 113 // stat_t seems to be defined wrong on linux/64 114 m_size = .lseek(m_fileDescriptor, 0, SEEK_END); 115 } else { 116 stat_t st; 117 () @trusted { fstat(m_fileDescriptor, &st); } (); 118 m_size = st.st_size; 119 120 // (at least) on windows, the created file is write protected 121 version(Windows){ 122 if( mode == FileMode.createTrunc ) 123 () @trusted { chmod(path.toNativeString().toStringz(), S_IREAD|S_IWRITE); } (); 124 } 125 } 126 () @trusted { lseek(m_fileDescriptor, 0, SEEK_SET); } (); 127 128 logDebug("opened file %s with %d bytes as %d", path.toNativeString(), m_size, m_fileDescriptor); 129 } 130 131 ~this() 132 { 133 close(); 134 } 135 136 @property int fd() { return m_fileDescriptor; } 137 @property Path path() const { return m_path; } 138 @property bool isOpen() const { return m_fileDescriptor >= 0; } 139 @property ulong size() const { return m_size; } 140 @property bool readable() const { return m_mode != FileMode.append; } 141 @property bool writable() const { return m_mode != FileMode.read; } 142 143 void takeOwnershipOfFD() 144 { 145 enforce(m_ownFD); 146 m_ownFD = false; 147 } 148 149 void seek(ulong offset) 150 { 151 version (Win32) { 152 enforce(offset <= off_t.max, "Cannot seek above 4GB on Windows x32."); 153 auto pos = () @trusted { return .lseek(m_fileDescriptor, cast(off_t)offset, SEEK_SET); } (); 154 } else auto pos = () @trusted { return .lseek(m_fileDescriptor, offset, SEEK_SET); } (); 155 enforce(pos == offset, "Failed to seek in file."); 156 m_ptr = offset; 157 } 158 159 ulong tell() { return m_ptr; } 160 161 void close() 162 { 163 if( m_fileDescriptor != -1 && m_ownFD ){ 164 () @trusted { .close(m_fileDescriptor); } (); 165 m_fileDescriptor = -1; 166 } 167 } 168 169 @property bool empty() const { assert(this.readable); return m_ptr >= m_size; } 170 @property ulong leastSize() const { assert(this.readable); return m_size - m_ptr; } 171 @property bool dataAvailableForRead() { return true; } 172 173 const(ubyte)[] peek() 174 { 175 return null; 176 } 177 178 alias read = Stream.read; 179 size_t read(scope ubyte[] dst, IOMode) 180 { 181 assert(this.readable); 182 size_t len = dst.length; 183 while (dst.length > 0) { 184 enforce(dst.length <= leastSize); 185 auto sz = min(dst.length, 4096); 186 enforce(() @trusted { return .read(m_fileDescriptor, dst.ptr, cast(int)sz); } () == sz, "Failed to read data from disk."); 187 dst = dst[sz .. $]; 188 m_ptr += sz; 189 yield(); 190 } 191 return len; 192 } 193 194 alias write = Stream.write; 195 size_t write(in ubyte[] bytes_, IOMode) 196 { 197 const(ubyte)[] bytes = bytes_; 198 assert(this.writable); 199 size_t len = bytes_.length; 200 while (bytes.length > 0) { 201 auto sz = min(bytes.length, 4096); 202 auto ret = () @trusted { return .write(m_fileDescriptor, bytes.ptr, cast(int)sz); } (); 203 enforce(ret == sz, "Failed to write data to disk."~to!string(sz)~" "~to!string(errno)~" "~to!string(ret)~" "~to!string(m_fileDescriptor)); 204 bytes = bytes[sz .. $]; 205 m_ptr += sz; 206 yield(); 207 } 208 return len; 209 } 210 211 void flush() 212 { 213 assert(this.writable); 214 } 215 216 void finalize() 217 { 218 flush(); 219 } 220 } 221 222 unittest { // issue #1189 223 auto fil = new ThreadedFileStream(Path(".unittest.tmp"), FileMode.createTrunc); 224 scope (exit) { 225 fil.close(); 226 removeFile(".unittest.tmp"); 227 } 228 immutable(ubyte)[] msg = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; 229 fil.write(msg); 230 fil.seek(5); 231 ubyte[3] buf; 232 fil.read(buf); 233 assert(buf == [5, 6, 7]); 234 }