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 }