1 module eventcore.drivers.winapi.files;
2 
3 version (Windows):
4 
5 import eventcore.driver;
6 import eventcore.drivers.winapi.core;
7 import eventcore.internal.ioworker;
8 import eventcore.internal.win32;
9 
10 private extern(Windows) @trusted nothrow @nogc {
11 	BOOL SetEndOfFile(HANDLE hFile);
12 }
13 
14 final class WinAPIEventDriverFiles : EventDriverFiles {
15 @safe /*@nogc*/ nothrow:
16 	private {
17 		WinAPIEventDriverCore m_core;
18 		IOWorkerPool m_workerPool;
19 	}
20 
21 	this(WinAPIEventDriverCore core)
22 	@nogc {
23 		m_core = core;
24 	}
25 
26 	override FileFD open(string path, FileOpenMode mode)
27 	{
28 		import std.utf : toUTF16z;
29 
30 		auto access = mode == FileOpenMode.readWrite || mode == FileOpenMode.createTrunc ? (GENERIC_WRITE | GENERIC_READ) :
31 						mode == FileOpenMode.append ? FILE_APPEND_DATA : GENERIC_READ;
32 		auto shareMode = FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE;
33 		auto creation = mode == FileOpenMode.createTrunc ? CREATE_ALWAYS : mode == FileOpenMode.append? OPEN_ALWAYS : OPEN_EXISTING;
34 
35 		auto handle = () @trusted {
36 			scope (failure) assert(false);
37 			return CreateFileW(path.toUTF16z, access, shareMode, null, creation,
38 				FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, null);
39 		} ();
40 		auto errorcode = GetLastError();
41 		if (handle == INVALID_HANDLE_VALUE)
42 			return FileFD.invalid;
43 
44 		if (mode == FileOpenMode.createTrunc && errorcode == ERROR_ALREADY_EXISTS) {
45 			BOOL ret = SetEndOfFile(handle);
46 			if (!ret) {
47 				CloseHandle(handle);
48 				return FileFD.invalid;
49 			}
50 		}
51 
52 		return adoptInternal(handle);
53 	}
54 
55 	override FileFD adopt(int system_handle)
56 	{
57 		return adoptInternal(() @trusted { return cast(HANDLE)system_handle; } ());
58 	}
59 
60 	private FileFD adoptInternal(HANDLE handle)
61 	{
62 		DWORD f;
63 		if (!() @trusted { return GetHandleInformation(handle, &f); } ())
64 			return FileFD.invalid;
65 
66 		auto s = m_core.setupSlot!FileSlot(handle);
67 
68 		s.read.overlapped.driver = m_core;
69 		s.read.overlapped.hEvent = handle;
70 		s.write.overlapped.driver = m_core;
71 		s.write.overlapped.hEvent = handle;
72 
73 		return FileFD(cast(size_t)handle, m_core.m_handles[handle].validationCounter);
74 	}
75 
76 	override void close(FileFD file, FileCloseCallback on_closed)
77 	{
78 		static void doCloseCleanup(CloseParams p)
79 		{
80 			p.core.removeWaiter();
81 			if (p.callback) p.callback(p.file, p.status);
82 		}
83 
84 		static void doClose(FileFD file, FileCloseCallback on_closed,
85 			shared(WinAPIEventDriverCore) core)
86 		{
87 			CloseParams p;
88 			CloseStatus st;
89 			p.file = file;
90 			p.callback = on_closed;
91 			p.core = () @trusted { return cast(WinAPIEventDriverCore)core; } ();
92 			if (CloseHandle(idToHandle(file))) p.status = CloseStatus.ok;
93 			else p.status = CloseStatus.ioError;
94 
95 			() @trusted { core.runInOwnerThread(&doCloseCleanup, p); } ();
96 		}
97 
98 		if (!isValid(file)) {
99 			on_closed(file, CloseStatus.invalidHandle);
100 			return;
101 		}
102 
103 		auto h = idToHandle(file);
104 		auto slot = () @trusted { return &m_core.m_handles[h]; } ();
105 		if (slot.file.read.overlapped.hEvent != INVALID_HANDLE_VALUE)
106 			slot.file.read.overlapped.hEvent = slot.file.write.overlapped.hEvent = INVALID_HANDLE_VALUE;
107 		m_core.addWaiter();
108 		m_core.discardEvents(&slot.file.read.overlapped, &slot.file.write.overlapped);
109 		m_core.freeSlot(h);
110 		workerPool.run!doClose(file, on_closed, () @trusted { return cast(shared)m_core; } ());
111 	}
112 
113 	override ulong getSize(FileFD file)
114 	{
115 		if (!isValid(file)) return ulong.max;
116 
117 		LARGE_INTEGER size;
118 		auto succeeded = () @trusted { return GetFileSizeEx(idToHandle(file), &size); } ();
119 		if (!succeeded || size.QuadPart < 0)
120 			return ulong.max;
121 		return size.QuadPart;
122 	}
123 
124 	override void truncate(FileFD file, ulong size, FileIOCallback on_finish)
125 	@trusted {
126 		if (!isValid(file)) {
127 			on_finish(file, IOStatus.invalidHandle, 0);
128 			return;
129 		}
130 
131 		auto h = idToHandle(file);
132 
133 		// FIXME: do this in a separate thread
134 
135 		LARGE_INTEGER li = {QuadPart: size};
136 		if (!SetFilePointerEx(h, li, null, FILE_BEGIN)) {
137 			on_finish(file, IOStatus.error, 0);
138 			return;
139 		}
140 
141 		if (!SetEndOfFile(h)) {
142 			on_finish(file, IOStatus.error, 0);
143 			return;
144 		}
145 
146 		on_finish(file, IOStatus.ok, 0);
147 	}
148 
149 	override void write(FileFD file, ulong offset, const(ubyte)[] buffer, IOMode mode, FileIOCallback on_write_finish)
150 	{
151 		if (!isValid(file)) {
152 			on_write_finish(file, IOStatus.invalidHandle, 0);
153 			return;
154 		}
155 
156 		auto h = idToHandle(file);
157 		auto slot = &m_core.m_handles[h].file.write;
158 
159 		if (slot.overlapped.hEvent == INVALID_HANDLE_VALUE) {
160 			on_write_finish(file, IOStatus.disconnected, 0);
161 			return;
162 		}
163 
164 		if (!buffer.length) {
165 			on_write_finish(file, IOStatus.ok, 0);
166 			return;
167 		}
168 
169 		slot.bytesTransferred = 0;
170 		slot.offset = offset;
171 		slot.buffer = buffer;
172 		slot.mode = mode;
173 		slot.callback = on_write_finish;
174 		m_core.addWaiter();
175 		startIO!(WriteFileEx, true)(h, slot);
176 	}
177 
178 	override void read(FileFD file, ulong offset, ubyte[] buffer, IOMode mode, FileIOCallback on_read_finish)
179 	{
180 		if (!isValid(file)) {
181 			on_read_finish(file, IOStatus.invalidHandle, 0);
182 			return;
183 		}
184 
185 		auto h = idToHandle(file);
186 		auto slot = &m_core.m_handles[h].file.read;
187 
188 		if (slot.overlapped.hEvent == INVALID_HANDLE_VALUE) {
189 			on_read_finish(file, IOStatus.disconnected, 0);
190 			return;
191 		}
192 
193 		if (!buffer.length) {
194 			on_read_finish(file, IOStatus.ok, 0);
195 			return;
196 		}
197 
198 		slot.bytesTransferred = 0;
199 		slot.offset = offset;
200 		slot.buffer = buffer;
201 		slot.mode = mode;
202 		slot.callback = on_read_finish;
203 		m_core.addWaiter();
204 		startIO!(ReadFileEx, false)(h, slot);
205 	}
206 
207 	override void cancelWrite(FileFD file)
208 	{
209 		if (!isValid(file)) return;
210 
211 		auto h = idToHandle(file);
212 		cancelIO!true(h, m_core.m_handles[h].file.write);
213 	}
214 
215 	override void cancelRead(FileFD file)
216 	{
217 		if (!isValid(file)) return;
218 
219 		auto h = idToHandle(file);
220 		cancelIO!false(h, m_core.m_handles[h].file.read);
221 	}
222 
223 	override bool isValid(FileFD handle)
224 	const {
225 		auto h = idToHandle(handle);
226 		if (auto ps = h in m_core.m_handles)
227 			return ps.validationCounter == handle.validationCounter;
228 		return false;
229 	}
230 
231 	override void addRef(FileFD descriptor)
232 	{
233 		if (!isValid(descriptor)) return;
234 
235 		m_core.m_handles[idToHandle(descriptor)].addRef();
236 	}
237 
238 	override bool releaseRef(FileFD descriptor)
239 	{
240 		if (!isValid(descriptor)) return true;
241 
242 		auto h = idToHandle(descriptor);
243 		auto slot = &m_core.m_handles[h];
244 		return slot.releaseRef({ close(descriptor, null); });
245 	}
246 
247 	protected override void* rawUserData(FileFD descriptor, size_t size, DataInitializer initialize, DataInitializer destroy)
248 	@system {
249 		return m_core.rawUserDataImpl(idToHandle(descriptor), size, initialize, destroy);
250 	}
251 
252 	private static void startIO(alias fun, bool RO)(HANDLE h, FileSlot.Direction!RO* slot)
253 	{
254 		import std.algorithm.comparison : min;
255 
256 		with (slot.overlapped.overlapped) {
257 			Internal = 0;
258 			InternalHigh = 0;
259 			Offset = cast(uint)(slot.offset & 0xFFFFFFFF);
260 			OffsetHigh = cast(uint)(slot.offset >> 32);
261 			hEvent = h;
262 		}
263 
264 		auto nbytes = min(slot.buffer.length, DWORD.max);
265 		auto handler = &overlappedIOHandler!(onIOFinished!(fun, RO));
266 		if (!() @trusted { return fun(h, &slot.buffer[0], nbytes, &slot.overlapped.overlapped, handler); } ()) {
267 			slot.overlapped.driver.removeWaiter();
268 			slot.invokeCallback(IOStatus.error, slot.bytesTransferred);
269 		}
270 	}
271 
272 	private void cancelIO(bool RO)(HANDLE h, ref FileSlot.Direction!RO slot)
273 	{
274 		if (slot.callback) {
275 			m_core.removeWaiter();
276 			() @trusted { CancelIoEx(h, &slot.overlapped.overlapped); } ();
277 			slot.callback = null;
278 			slot.buffer = null;
279 		}
280 	}
281 
282 	private static nothrow
283 	void onIOFinished(alias fun, bool RO)(DWORD error, DWORD bytes_transferred, OVERLAPPED_CORE* overlapped)
284 	{
285 		HANDLE handle = overlapped.hEvent;
286 		if (handle == INVALID_HANDLE_VALUE) return;
287 		auto cslot = () @trusted { return &overlapped.driver.m_handles[handle]; } ();
288 		FileFD id = FileFD(cast(size_t)overlapped.hEvent, cslot.validationCounter);
289 
290 		static if (RO)
291 			auto slot = () @trusted { return &cslot.file.write; } ();
292 		else
293 			auto slot = () @trusted { return &cslot.file.read; } ();
294 		assert(slot !is null);
295 
296 		if (!slot.callback) {
297 			// request was already cancelled
298 			return;
299 		}
300 
301 		if (error != 0) {
302 			overlapped.driver.removeWaiter();
303 			slot.invokeCallback(IOStatus.error, slot.bytesTransferred + bytes_transferred);
304 			return;
305 		}
306 
307 		slot.bytesTransferred += bytes_transferred;
308 		slot.offset += bytes_transferred;
309 
310 		if (slot.bytesTransferred >= slot.buffer.length || slot.mode != IOMode.all) {
311 			overlapped.driver.removeWaiter();
312 			slot.invokeCallback(IOStatus.ok, slot.bytesTransferred);
313 		} else {
314 			startIO!(fun, RO)(handle, slot);
315 		}
316 	}
317 
318 	private @property ref IOWorkerPool workerPool()
319 	{
320 		if (!m_workerPool)
321 			m_workerPool = acquireIOWorkerPool();
322 		return m_workerPool;
323 	}
324 
325 	private static HANDLE idToHandle(FileFD id)
326 	@trusted @nogc {
327 		return cast(HANDLE)cast(size_t)id;
328 	}
329 }
330 
331 private static struct CloseParams {
332 	FileFD file;
333 	FileCloseCallback callback;
334 	CloseStatus status;
335 	WinAPIEventDriverCore core;
336 }