1 /**
2  * File stuff
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <ae@cy.md>
12  */
13 
14 module ae.sys.file;
15 
16 import core.stdc.wchar_;
17 import core.thread;
18 
19 import std.array;
20 import std.conv;
21 import std.file;
22 import std.path;
23 import std.range.primitives;
24 import std.stdio : File;
25 import std.string;
26 import std.typecons;
27 import std.utf;
28 
29 import ae.sys.cmd : getCurrentThreadID;
30 import ae.utils.path;
31 
32 public import std.typecons : No, Yes;
33 
34 deprecated alias wcscmp = core.stdc.wchar_.wcscmp;
35 deprecated alias wcslen = core.stdc.wchar_.wcslen;
36 
37 version(Windows) import ae.sys.windows.imports;
38 
39 // ************************************************************************
40 
41 version (Windows)
42 {
43 	// Work around std.file overload
44 	mixin(importWin32!(q{winnt}, null, q{FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_REPARSE_POINT}));
45 }
46 version (Posix)
47 {
48 	private import core.stdc.errno;
49 	private import core.sys.posix.dirent;
50 	private import core.stdc.string;
51 }
52 
53 // ************************************************************************
54 
55 deprecated string[] fastListDir(bool recursive = false, bool symlinks=false)(string pathname, string pattern = null)
56 {
57 	string[] result;
58 
59 	listDir!((e) {
60 		static if (!symlinks)
61 		{
62 			// Note: shouldn't this just skip recursion?
63 			if (e.isSymlink)
64 				return;
65 		}
66 
67 		if (pattern && !globMatch(e.baseName, pattern))
68 			return;
69 
70 		static if (recursive)
71 		{
72 			if (e.entryIsDir)
73 			{
74 				// Note: why exclude directories from results?
75 				e.recurse();
76 				return;
77 			}
78 		}
79 
80 		result ~= e.fullName;
81 	})(pathname);
82 	return result;
83 }
84 
85 // ************************************************************************
86 
87 version (Windows)
88 {
89 	mixin(importWin32!(q{winnt}, null, q{WCHAR}));
90 	mixin(importWin32!(q{winbase}, null, q{WIN32_FIND_DATAW}));
91 }
92 
93 /// The OS's "native" filesystem character type (private in Phobos).
94 version (Windows)
95 	alias FSChar = WCHAR;
96 else version (Posix)
97 	alias FSChar = char;
98 else
99 	static assert(0);
100 
101 /// Reads a time field from a stat_t with full precision (private in Phobos).
102 SysTime statTimeToStdTime(string which)(ref const stat_t statbuf)
103 {
104 	auto unixTime = mixin(`statbuf.st_` ~ which ~ `time`);
105 	auto stdTime = unixTimeToStdTime(unixTime);
106 
107 	static if (is(typeof(mixin(`statbuf.st_` ~ which ~ `tim`))))
108 		stdTime += mixin(`statbuf.st_` ~ which ~ `tim.tv_nsec`) / 100;
109 	else
110 	static if (is(typeof(mixin(`statbuf.st_` ~ which ~ `timensec`))))
111 		stdTime += mixin(`statbuf.st_` ~ which ~ `timensec`) / 100;
112 	else
113 	static if (is(typeof(mixin(`statbuf.st_` ~ which ~ `time_nsec`))))
114 		stdTime += mixin(`statbuf.st_` ~ which ~ `time_nsec`) / 100;
115 	else
116 	static if (is(typeof(mixin(`statbuf.__st_` ~ which ~ `timensec`))))
117 		stdTime += mixin(`statbuf.__st_` ~ which ~ `timensec`) / 100;
118 
119 	return SysTime(stdTime);
120 }
121 
122 version (OSX)
123     version = Darwin;
124 else version (iOS)
125     version = Darwin;
126 else version (TVOS)
127     version = Darwin;
128 else version (WatchOS)
129     version = Darwin;
130 
131 private
132 version (Posix)
133 {
134 	// TODO: upstream into Druntime
135 	extern (C)
136 	{
137 		int dirfd(DIR *dirp) pure nothrow @nogc;
138 		int openat(int fd, const char *path, int oflag, ...) nothrow @nogc;
139 
140 		version (Darwin)
141 		{
142 			pragma(mangle, "fstatat$INODE64")
143 			int fstatat(int fd, const char *path, stat_t *buf, int flag) nothrow @nogc;
144 
145 			pragma(mangle, "fdopendir$INODE64")
146 			DIR *fdopendir(int fd) nothrow @nogc;
147 		}
148 		else
149 		{
150 			int fstatat(int fd, const(char)* path, stat_t* buf, int flag) nothrow @nogc;
151 			DIR *fdopendir(int fd) nothrow @nogc;
152 		}
153 	}
154 	version (linux)
155 	{
156 		enum AT_SYMLINK_NOFOLLOW = 0x100;
157 		enum O_DIRECTORY = 0x10000;
158 	}
159 	version (Darwin)
160 	{
161 		enum AT_SYMLINK_NOFOLLOW = 0x20;
162 		enum O_DIRECTORY = 0x100000;
163 	}
164 	version (FreeBSD)
165 	{
166 		enum AT_SYMLINK_NOFOLLOW = 0x200;
167 		enum O_DIRECTORY = 0x20000;
168 	}
169 }
170 
171 import ae.utils.range : nullTerminated;
172 
173 // https://issues.dlang.org/show_bug.cgi?id=7016
174 version (Windows) static import ae.sys.windows.misc;
175 
176 /**
177    Fast templated directory iterator
178 
179    Example:
180    ---
181    string[] entries;
182    listDir!((e) {
183 	   entries ~= e.fullName.relPath(tmpDir);
184 	   if (e.entryIsDir)
185 		   e.recurse();
186    })(tmpDir);
187    ---
188 
189    If called with `Yes.includeRoot`, then the callback is
190    first called with the argument, even if it's not a directory.
191 */
192 template listDir(alias handler, Flag!q{includeRoot} includeRoot = No.includeRoot)
193 {
194 private: // (This is an eponymous template, so this is to aid documentation generators.)
195 	/*non-static*/ struct Context
196 	{
197 		// Tether to handler alias context
198 		void callHandler(Entry* e) { handler(e); }
199 
200 		bool timeToStop = false;
201 
202 		FSChar[] pathBuf;
203 	}
204 
205 	/// A pointer to this type will be passed to the `listDir` predicate.
206 	public static struct Entry
207 	{
208 		version (Posix)
209 		{
210 			private const(char)* name; /// Name relative to `dirFD`.
211 			dirent* ent; /// POSIX `dirent`. May be null with `Yes.includeRoot`.
212 
213 			private stat_t[enumLength!StatTarget] statBuf;
214 
215 			/// Result of `stat` call.
216 			/// Other values are the same as `errno`.
217 			enum StatResult : int
218 			{
219 				noInfo       =       0, /// Not called yet.
220 				statOK       = int.max, /// All OK
221 				unknownError = int.min, /// `errno` returned 0 or `int.max`
222 			}
223 
224 			int dirFD; /// POSIX directory file descriptor.
225 		}
226 		version (Windows)
227 		{
228 			WIN32_FIND_DATAW findData; /// Windows `WIN32_FIND_DATAW`.
229 		}
230 
231 		// Cached values.
232 		// Cleared (memset to 0) for every directory entry.
233 		struct Data
234 		{
235 			const(FSChar)[] baseNameFS;
236 			string baseName;
237 			string fullName;
238 			size_t pathTailPos;
239 
240 			version (Posix)
241 			{
242 				StatResult[enumLength!StatTarget] statResult;
243 			}
244 		}
245 		Data data;
246 
247 		// Recursion
248 
249 		Entry* parent; ///
250 		private Context* context;
251 
252 		/// Request recursion on the current `entry`.
253 		version (Posix)
254 		{
255 			void recurse()
256 			{
257 				import core.sys.posix.fcntl;
258 				int flags = O_RDONLY;
259 				static if (is(typeof(O_DIRECTORY)))
260 					flags |= O_DIRECTORY;
261 				auto fd = openat(dirFD, this.name, flags);
262 				errnoEnforce(fd >= 0,
263 					"Failed to open %s as subdirectory of directory %s"
264 					.format(this.baseNameFS, this.parent.fullName));
265 				auto subdir = fdopendir(fd);
266 				errnoEnforce(subdir,
267 					"Failed to open subdirectory %s of directory %s as directory"
268 					.format(this.baseNameFS, this.parent.fullName));
269 				scan(subdir, fd, &this);
270 			}
271 		}
272 		else
273 		version (Windows)
274 		{
275 			void recurse()
276 			{
277 				needFullPath();
278 				appendString(context.pathBuf,
279 					data.pathTailPos, "\\*.*\0"w);
280 				scan(&this);
281 			}
282 		}
283 
284 		/// Stop iteration.
285 		void stop() { context.timeToStop = true; }
286 
287 		// Name
288 
289 		/// Returns a pointer to the base file name, as a
290 		/// null-terminated string, in the operating system's
291 		/// character type.  Fastest.
292 		const(FSChar)* baseNameFSPtr() pure nothrow @nogc
293 		{
294 			version (Posix) return name;
295 			version (Windows) return findData.cFileName.ptr;
296 		}
297 
298 		// Bounded variant of std.string.fromStringz for static arrays.
299 		private static T[] fromStringz(T, size_t n)(ref T[n] buf)
300 		{
301 			foreach (i, c; buf)
302 				if (!c)
303 					return buf[0 .. i];
304 			// This should only happen in case of an OS / libc bug.
305 			assert(false, "File name buffer is not null-terminated");
306 		}
307 
308 		/// Returns the base file name, as a D character array, in the
309 		/// operating system's character type.  Fast.
310 		const(FSChar)[] baseNameFS() pure nothrow @nogc
311 		{
312 			if (!data.baseNameFS)
313 			{
314 				version (Posix) data.baseNameFS = .fromStringz(name);
315 				version (Windows) data.baseNameFS = fromStringz(findData.cFileName);
316 			}
317 			return data.baseNameFS;
318 		}
319 
320 		/// Returns the base file name as a D string.  Allocates.
321 		string baseName() // allocates
322 		{
323 			if (!data.baseName)
324 				data.baseName = baseNameFS.to!string;
325 			return data.baseName;
326 		}
327 
328 		private void needFullPath() nothrow @nogc
329 		{
330 			if (!data.pathTailPos)
331 			{
332 				version (Posix)
333 					parent.needFullPath();
334 				version (Windows)
335 				{
336 					// directory separator was added during recursion
337 					auto startPos = parent.data.pathTailPos + 1;
338 				}
339 				version (Posix)
340 				{
341 					immutable FSChar[] separator = "/";
342 					auto startPos = appendString(context.pathBuf,
343 						parent.data.pathTailPos, separator);
344 				}
345 				data.pathTailPos = appendString(context.pathBuf,
346 					startPos,
347 					baseNameFSPtr.nullTerminated
348 				);
349 			}
350 		}
351 
352 		/// Returns the full file name, as a D character array, in the
353 		/// operating system's character type.  Fast.
354 		const(FSChar)[] fullNameFS() nothrow @nogc // fast
355 		{
356 			needFullPath();
357 			return context.pathBuf[0 .. data.pathTailPos];
358 		}
359 
360 		/// Returns the full file name as a D string.  Allocates.
361 		string fullName() // allocates
362 		{
363 			if (!data.fullName)
364 				data.fullName = fullNameFS.to!string;
365 			return data.fullName;
366 		}
367 
368 		// Attributes
369 
370 		version (Posix)
371 		{
372 			/// We can stat two different things on POSIX: the directory entry itself,
373 			/// or the link target (if the directory entry is a symbolic link).
374 			enum StatTarget
375 			{
376 				dirEntry,   /// do not dereference (lstat)
377 				linkTarget, /// dereference
378 			}
379 
380 			private bool tryStat(StatTarget target)() nothrow @nogc
381 			{
382 				if (data.statResult[target] == StatResult.noInfo)
383 				{
384 					// If we already did the other kind of stat, can we reuse its result?
385 					if (data.statResult[1 - target] != StatResult.noInfo)
386 					{
387 						// Yes, if we know this isn't a link from the directory entry.
388 						static if (__traits(compiles, ent.d_type))
389 							if (ent && ent.d_type != DT_UNKNOWN && ent.d_type != DT_LNK)
390 								goto reuse;
391 						// Yes, if we already found out this isn't a link from an lstat call.
392 						static if (target == StatTarget.linkTarget)
393 							if (data.statResult[StatTarget.dirEntry] == StatResult.statOK
394 								&& (statBuf[StatTarget.dirEntry].st_mode & S_IFMT) != S_IFLNK)
395 								goto reuse;
396 					}
397 
398 					if (false)
399 					{
400 					reuse:
401 						statBuf[target] = statBuf[1 - target];
402 						data.statResult[target] = data.statResult[1 - target];
403 					}
404 					else
405 					{
406 						int flags = target == StatTarget.dirEntry ? AT_SYMLINK_NOFOLLOW : 0;
407 						auto res = fstatat(dirFD, name, &statBuf[target], flags);
408 						if (res)
409 						{
410 							auto error = errno;
411 							data.statResult[target] = cast(StatResult)error;
412 							if (error == StatResult.noInfo || error == StatResult.statOK)
413 								data.statResult[target] = StatResult.unknownError; // unknown error?
414 						}
415 						else
416 							data.statResult[target] = StatResult.statOK; // no error
417 					}
418 				}
419 				return data.statResult[target] == StatResult.statOK;
420 			}
421 
422 			private ErrnoException statError(StatTarget target)()
423 			{
424 				errno = data.statResult[target];
425 				return new ErrnoException("Failed to stat " ~
426 					(target == StatTarget.linkTarget ? "link target" : "directory entry") ~
427 					": " ~ fullName);
428 			}
429 
430 			/// Return the result of `stat` / `lstat` (depending on `target`)
431 			/// for this `Entry`, performing it first if necessary.
432 			stat_t* needStat(StatTarget target)()
433 			{
434 				if (!tryStat!target)
435 					throw statError!target();
436 				return &statBuf[target];
437 			}
438 
439 			// Check if this is an object of the given type.
440 			private bool deIsType(typeof(DT_REG) dType, typeof(S_IFREG) statType)
441 			{
442 				static if (__traits(compiles, ent.d_type))
443 					if (ent && ent.d_type != DT_UNKNOWN)
444 						return ent.d_type == dType;
445 
446 				return (needStat!(StatTarget.dirEntry)().st_mode & S_IFMT) == statType;
447 			}
448 
449 			/// Returns true if this is a symlink.
450 			@property bool isSymlink()
451 			{
452 				return deIsType(DT_LNK, S_IFLNK);
453 			}
454 
455 			/// Returns true if this is a directory.
456 			/// You probably want to use this one to decide whether to recurse.
457 			@property bool entryIsDir()
458 			{
459 				return deIsType(DT_DIR, S_IFDIR);
460 			}
461 
462 			// Check if this is an object of the given type, or a link pointing to one.
463 			private bool ltIsType(typeof(DT_REG) dType, typeof(S_IFREG) statType)
464 			{
465 				static if (__traits(compiles, ent.d_type))
466 					if (ent && ent.d_type != DT_UNKNOWN && ent.d_type != DT_LNK)
467 						return ent.d_type == dType;
468 
469 				if (tryStat!(StatTarget.linkTarget)())
470 					return (statBuf[StatTarget.linkTarget].st_mode & S_IFMT) == statType;
471 
472 				if (isSymlink()) // broken symlink?
473 					return false; // a broken symlink does not point at anything.
474 
475 				throw statError!(StatTarget.linkTarget)();
476 			}
477 
478 			/// Returns true if this is a file, or a link pointing to one.
479 			@property bool isFile()
480 			{
481 				return ltIsType(DT_REG, S_IFREG);
482 			}
483 
484 			/// Returns true if this is a directory, or a link pointing to one.
485 			@property bool isDir()
486 			{
487 				return ltIsType(DT_DIR, S_IFDIR);
488 			}
489 
490 			/// Returns the raw POSIX attributes of this directory entry,
491 			/// or the link target if this directory entry is a symlink.
492 			@property uint attributes()
493 			{
494 				return needStat!(StatTarget.linkTarget)().st_mode;
495 			}
496 
497 			/// Returns the raw POSIX attributes of this directory entry.
498 			@property uint linkAttributes()
499 			{
500 				return needStat!(StatTarget.dirEntry)().st_mode;
501 			}
502 
503 			// Other attributes
504 
505 			/// Returns the "c" time of this directory entry,
506 			/// or the link target if this directory entry is a symlink.
507 			@property SysTime timeStatusChanged()
508 			{
509 				return statTimeToStdTime!"c"(*needStat!(StatTarget.linkTarget)());
510 			}
511 
512 			/// Returns the "a" time of this directory entry,
513 			/// or the link target if this directory entry is a symlink.
514 			@property SysTime timeLastAccessed()
515 			{
516 				return statTimeToStdTime!"a"(*needStat!(StatTarget.linkTarget)());
517 			}
518 
519 			/// Returns the "m" time of this directory entry,
520 			/// or the link target if this directory entry is a symlink.
521 			@property SysTime timeLastModified()
522 			{
523 				return statTimeToStdTime!"m"(*needStat!(StatTarget.linkTarget)());
524 			}
525 
526 			/// Returns the "birth" time of this directory entry,
527 			/// or the link target if this directory entry is a symlink.
528 			static if (is(typeof(&statTimeToStdTime!"birth")))
529 			@property SysTime timeCreated()
530 			{
531 				return statTimeToStdTime!"birth"(*needStat!(StatTarget.linkTarget)());
532 			}
533 
534 			/// Returns the size in bytes of this directory entry,
535 			/// or the link target if this directory entry is a symlink.
536 			@property ulong size()
537 			{
538 				return needStat!(StatTarget.linkTarget)().st_size;
539 			}
540 
541 			/// Returns the inode number of this directory entry,
542 			/// or the link target if this directory entry is a symlink.
543 			@property ulong fileID()
544 			{
545 				static if (__traits(compiles, ent.d_ino))
546 					if (ent)
547 						return ent.d_ino;
548 				return needStat!(StatTarget.linkTarget)().st_ino;
549 			}
550 		}
551 
552 		version (Windows)
553 		{
554 			/// Returns true if this is a directory, or a reparse point pointing to one.
555 			@property bool isDir() const pure nothrow
556 			{
557 				return (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
558 			}
559 
560 			/// Returns true if this is a file, or a reparse point pointing to one.
561 			@property bool isFile() const pure nothrow
562 			{
563 				return !isDir;
564 			}
565 
566 			/// Returns true if this is a reparse point.
567 			@property bool isSymlink() const pure nothrow
568 			{
569 				return (findData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
570 			}
571 
572 			/// Returns true if this is a directory.
573 			/// You probably want to use this one to decide whether to recurse.
574 			@property bool entryIsDir() const pure nothrow
575 			{
576 				return isDir && !isSymlink;
577 			}
578 
579 			/// Returns the raw Windows attributes of this directory entry.
580 			@property uint attributes() const pure nothrow
581 			{
582 				return findData.dwFileAttributes;
583 			}
584 
585 			/// Returns the size in bytes of this directory entry.
586 			@property ulong size() const pure nothrow
587 			{
588 				return makeUlong(findData.nFileSizeLow, findData.nFileSizeHigh);
589 			}
590 
591 			/// Returns the creation time of this directory entry.
592 			@property SysTime timeCreated() const
593 			{
594 				return FILETIMEToSysTime(&findData.ftCreationTime);
595 			}
596 
597 			/// Returns the last access time of this directory entry.
598 			@property SysTime timeLastAccessed() const
599 			{
600 				return FILETIMEToSysTime(&findData.ftLastAccessTime);
601 			}
602 
603 			/// Returns the last modification time of this directory entry.
604 			@property SysTime timeLastModified() const
605 			{
606 				return FILETIMEToSysTime(&findData.ftLastWriteTime);
607 			}
608 
609 			/// Returns the 64-bit unique file index of this file.
610 			@property ulong fileID()
611 			{
612 				return getFileID(fullName);
613 			}
614 		}
615 	}
616 
617 	version (Posix)
618 	{
619 		// The length of the buffer on the stack.
620 		enum initialPathBufLength = 256;
621 
622 		private static void scan(DIR* dir, int dirFD, Entry* parentEntry)
623 		{
624 			Entry entry = void;
625 			entry.parent = parentEntry;
626 			entry.context = entry.parent.context;
627 			entry.dirFD = dirFD;
628 
629 			scope(exit) closedir(dir);
630 
631 			dirent* ent;
632 			while ((ent = readdir(dir)) != null)
633 			{
634 				// Apparently happens on some OS X versions.
635 				enforce(ent.d_name[0],
636 					"Empty dir entry name (OS bug?)");
637 
638 				// Skip "." and ".."
639 				if (ent.d_name[0] == '.' && (
640 						ent.d_name[1] == 0 ||
641 						(ent.d_name[1] == '.' && ent.d_name[2] == 0)))
642 					continue;
643 
644 				entry.ent = ent;
645 				entry.name = ent.d_name.ptr;
646 				entry.data = Entry.Data.init;
647 				entry.context.callHandler(&entry);
648 				if (entry.context.timeToStop)
649 					break;
650 			}
651 		}
652 	}
653 
654 	enum isPath(Path) = (isForwardRange!Path || isSomeString!Path) &&
655 		isSomeChar!(ElementEncodingType!Path);
656 
657 	import core.stdc.stdlib : malloc, realloc, free;
658 
659 	static FSChar[] reallocPathBuf(FSChar[] buf, size_t newLength) nothrow @nogc
660 	{
661 		if (buf.length == initialPathBufLength) // current buffer is on stack
662 		{
663 			auto ptr = cast(FSChar*) malloc(newLength * FSChar.sizeof);
664 			ptr[0 .. buf.length] = buf[];
665 			return ptr[0 .. newLength];
666 		}
667 		else // current buffer on C heap (malloc'd above)
668 		{
669 			auto ptr = cast(FSChar*) realloc(buf.ptr, newLength * FSChar.sizeof);
670 			return ptr[0 .. newLength];
671 		}
672 	}
673 
674 	// Append a string to the buffer, reallocating as necessary.
675 	// Returns the new length of the string in the buffer.
676 	static size_t appendString(Str)(ref FSChar[] buf, size_t pos, Str str) nothrow @nogc
677 	if (isPath!Str)
678 	{
679 		static if (ElementEncodingType!Str.sizeof == FSChar.sizeof
680 			&& is(typeof(str.length)))
681 		{
682 			// No transcoding needed and length known
683 			auto remainingSpace = buf.length - pos;
684 			if (str.length > remainingSpace)
685 				buf = reallocPathBuf(buf, (pos + str.length) * 3 / 2);
686 			buf[pos .. pos + str.length] = str[];
687 			pos += str.length;
688 		}
689 		else
690 		{
691 			// Need to transcode
692 			auto p = buf.ptr + pos;
693 			auto bufEnd = buf.ptr + buf.length;
694 			foreach (c; byUTF!FSChar(str))
695 			{
696 				if (p == bufEnd) // out of room
697 				{
698 					auto newBuf = reallocPathBuf(buf, buf.length * 3 / 2);
699 
700 					// Update pointers to point into the new buffer.
701 					p = newBuf.ptr + (p - buf.ptr);
702 					buf = newBuf;
703 					bufEnd = buf.ptr + buf.length;
704 				}
705 				*p++ = c;
706 			}
707 			pos = p - buf.ptr;
708 		}
709 		return pos;
710 	}
711 
712 	version (Windows)
713 	{
714 		mixin(importWin32!(q{winbase}));
715 		import ae.sys.windows.misc : makeUlong;
716 
717 		// The length of the buffer on the stack.
718 		enum initialPathBufLength = MAX_PATH;
719 
720 		enum FIND_FIRST_EX_LARGE_FETCH = 2;
721 		enum FindExInfoBasic = cast(FINDEX_INFO_LEVELS)1;
722 
723 		static void scan(Entry* parentEntry)
724 		{
725 			Entry entry = void;
726 			entry.parent = parentEntry;
727 			entry.context = parentEntry.context;
728 
729 			HANDLE hFind = FindFirstFileExW(
730 				entry.context.pathBuf.ptr,
731 				FindExInfoBasic,
732 				&entry.findData,
733 				FINDEX_SEARCH_OPS.FindExSearchNameMatch,
734 				null,
735 				FIND_FIRST_EX_LARGE_FETCH, // https://blogs.msdn.microsoft.com/oldnewthing/20131024-00/?p=2843
736 			);
737 			if (hFind == INVALID_HANDLE_VALUE)
738 				throw new WindowsException(GetLastError(),
739 					text("FindFirstFileW: ", parentEntry.fullNameFS));
740 			scope(exit) FindClose(hFind);
741 			do
742 			{
743 				// Skip "." and ".."
744 				auto fn = entry.findData.cFileName.ptr;
745 				if (fn[0] == '.' && (
746 						fn[1] == 0 ||
747 						(fn[1] == '.' && fn[2] == 0)))
748 					continue;
749 
750 				entry.data = Entry.Data.init;
751 				entry.context.callHandler(&entry);
752 				if (entry.context.timeToStop)
753 					break;
754 			}
755 			while (FindNextFileW(hFind, &entry.findData));
756 			if (GetLastError() != ERROR_NO_MORE_FILES)
757 				throw new WindowsException(GetLastError(),
758 					text("FindNextFileW: ", parentEntry.fullNameFS));
759 		}
760 	}
761 
762 	public void listDir(Path)(Path dirPath)
763 	if (isPath!Path)
764 	{
765 		import std.internal.cstring;
766 
767 		if (dirPath.empty)
768 			return listDir(".");
769 
770 		Context context;
771 
772 		FSChar[initialPathBufLength] pathBufStore = void;
773 		context.pathBuf = pathBufStore[];
774 
775 		scope (exit)
776 		{
777 			if (context.pathBuf.length != initialPathBufLength)
778 				free(context.pathBuf.ptr);
779 		}
780 
781 		Entry rootEntry;
782 		rootEntry.context = &context;
783 
784 		auto endPos = appendString(context.pathBuf, 0, dirPath);
785 		appendString(context.pathBuf, endPos, "\0");
786 		rootEntry.data.pathTailPos = endPos - (endPos > 0 && context.pathBuf[endPos - 1].isDirSeparator() ? 1 : 0);
787 		assert(rootEntry.data.pathTailPos > 0);
788 
789 		static if (includeRoot)
790 		{
791 			version (Posix)
792 			{
793 				version (OSX)
794 					enum AT_FDCWD = -2;
795 				else
796 					import core.sys.posix.fcntl : AT_FDCWD;
797 
798 				rootEntry.dirFD = AT_FDCWD;
799 				rootEntry.ent = null;
800 				auto name = tempCString(dirPath);
801 				rootEntry.name = name;
802 
803 				context.callHandler(&rootEntry);
804 			}
805 			else
806 			version (Windows)
807 			{
808 				while (rootEntry.data.pathTailPos && !context.pathBuf[rootEntry.data.pathTailPos].isDirSeparator())
809 					rootEntry.data.pathTailPos--;
810 				scan(&rootEntry);
811 			}
812 		}
813 		else
814 		{
815 			version (Posix)
816 			{
817 				auto dir = opendir(tempCString(dirPath));
818 				checkDir(dir, dirPath);
819 
820 				scan(dir, dirfd(dir), &rootEntry);
821 			}
822 			else
823 			version (Windows)
824 			{
825 				const WCHAR[] tailString = endPos == 0 || context.pathBuf[endPos - 1].isDirSeparator() ? "*.*\0"w : "\\*.*\0"w;
826 				appendString(context.pathBuf, endPos, tailString);
827 
828 				scan(&rootEntry);
829 			}
830 		}
831 	}
832 
833 	// Workaround for https://github.com/ldc-developers/ldc/issues/2960
834 	version (Posix)
835 	private void checkDir(Path)(DIR* dir, auto ref Path dirPath)
836 	{
837 		errnoEnforce(dir, "Failed to open directory " ~ dirPath);
838 	}
839 }
840 
841 unittest
842 {
843 	auto tmpDir = deleteme ~ "-dir";
844 	if (tmpDir.exists) tmpDir.removeRecurse();
845 	mkdirRecurse(tmpDir);
846 	scope(exit) rmdirRecurse(tmpDir);
847 
848 	touch(tmpDir ~ "/a");
849 	touch(tmpDir ~ "/b");
850 	mkdir(tmpDir ~ "/c");
851 	touch(tmpDir ~ "/c/1");
852 	touch(tmpDir ~ "/c/2");
853 
854 	string[] entries;
855 	listDir!((e) {
856 		assert(equal(e.fullNameFS, e.fullName));
857 		entries ~= e.fullName.relPath(tmpDir);
858 		if (e.entryIsDir)
859 			e.recurse();
860 	})(tmpDir);
861 
862 	assert(equal(
863 		entries.sort,
864 		["a", "b", "c", "c/1", "c/2"].map!(name => name.replace("/", dirSeparator)),
865 	), text(entries));
866 
867 	entries = null;
868 	import std.ascii : isDigit;
869 	listDir!((e) {
870 		entries ~= e.fullName.relPath(tmpDir);
871 		if (e.baseNameFS[0].isDigit)
872 			e.stop();
873 		else
874 		if (e.entryIsDir)
875 			e.recurse();
876 	})(tmpDir);
877 
878 	assert(entries.length < 5 && entries[$-1][$-1].isDigit, text(entries));
879 
880 	// Test Yes.includeRoot
881 	tmpDir.listDir!((e) {
882 		assert(e.fullName == tmpDir, e.fullName ~ " != " ~ tmpDir);
883 		assert(e.entryIsDir);
884 	}, Yes.includeRoot);
885 
886 	entries = null;
887 	tmpDir.listDir!((e) {
888 		assert(equal(e.fullNameFS, e.fullName));
889 		entries ~= e.fullName.relPath(tmpDir);
890 		if (e.entryIsDir)
891 			e.recurse();
892 	}, Yes.includeRoot);
893 
894 	assert(equal(
895 		entries.sort,
896 		[".", "a", "b", "c", "c/1", "c/2"].map!(name => name.replace("/", dirSeparator)),
897 	), text(entries));
898 
899 	// Symlink test
900 	(){
901 		// Wine's implementation of symlinks/junctions is incomplete
902 		version (Windows)
903 			if (getWineVersion())
904 				return;
905 
906 		dirLink("c", tmpDir ~ "/d");
907 		dirLink("x", tmpDir ~ "/e");
908 
909 		string[] entries;
910 		listDir!((e) {
911 			entries ~= e.fullName.relPath(tmpDir);
912 			if (e.entryIsDir)
913 				e.recurse();
914 		})(tmpDir);
915 
916 		assert(equal(
917 			entries.sort,
918 			["a", "b", "c", "c/1", "c/2", "d", "e"].map!(name => name.replace("/", dirSeparator)),
919 		));
920 
921 		// Recurse into symlinks
922 
923 		entries = null;
924 		listDir!((e) {
925 			entries ~= e.fullName.relPath(tmpDir);
926 			if (e.isDir)
927 				try
928 					e.recurse();
929 				catch (Exception e) // broken junctions on Windows throw
930 					{}
931 		})(tmpDir);
932 
933 		assert(equal(
934 			entries.sort,
935 			["a", "b", "c", "c/1", "c/2", "d", "d/1", "d/2", "e"].map!(name => name.replace("/", dirSeparator)),
936 		));
937 	}();
938 }
939 
940 // ************************************************************************
941 
942 private string buildPath2(string[] segments...) { return segments.length ? buildPath(segments) : null; }
943 
944 /// Shell-like expansion of ?, * and ** in path components
945 DirEntry[] fileList(string pattern)
946 {
947 	auto components = cast(string[])array(pathSplitter(pattern));
948 	foreach (i, component; components[0..$-1])
949 		if (component.contains("?") || component.contains("*")) // TODO: escape?
950 		{
951 			DirEntry[] expansions; // TODO: filter range instead?
952 			auto dir = buildPath2(components[0..i]);
953 			if (component == "**")
954 				expansions = array(dirEntries(dir, SpanMode.depth));
955 			else
956 				expansions = array(dirEntries(dir, component, SpanMode.shallow));
957 
958 			DirEntry[] result;
959 			foreach (expansion; expansions)
960 				if (expansion.isDir())
961 					result ~= fileList(buildPath(expansion.name ~ components[i+1..$]));
962 			return result;
963 		}
964 
965 	auto dir = buildPath2(components[0..$-1]);
966 	if (!dir || exists(dir))
967 		return array(dirEntries(dir, components[$-1], SpanMode.shallow));
968 	else
969 		return null;
970 }
971 
972 /// ditto
973 DirEntry[] fileList(string pattern0, string[] patterns...)
974 {
975 	DirEntry[] result;
976 	foreach (pattern; [pattern0] ~ patterns)
977 		result ~= fileList(pattern);
978 	return result;
979 }
980 
981 /// ditto
982 deprecated string[] fastFileList(string pattern)
983 {
984 	auto components = cast(string[])array(pathSplitter(pattern));
985 	foreach (i, component; components[0..$-1])
986 		if (component.contains("?") || component.contains("*")) // TODO: escape?
987 		{
988 			string[] expansions; // TODO: filter range instead?
989 			auto dir = buildPath2(components[0..i]);
990 			if (component == "**")
991 				expansions = fastListDir!true(dir);
992 			else
993 				expansions = fastListDir(dir, component);
994 
995 			string[] result;
996 			foreach (expansion; expansions)
997 				if (expansion.isDir())
998 					result ~= fastFileList(buildPath(expansion ~ components[i+1..$]));
999 			return result;
1000 		}
1001 
1002 	auto dir = buildPath2(components[0..$-1]);
1003 	if (!dir || exists(dir))
1004 		return fastListDir(dir, components[$-1]);
1005 	else
1006 		return null;
1007 }
1008 
1009 /// ditto
1010 deprecated string[] fastFileList(string pattern0, string[] patterns...)
1011 {
1012 	string[] result;
1013 	foreach (pattern; [pattern0] ~ patterns)
1014 		result ~= fastFileList(pattern);
1015 	return result;
1016 }
1017 
1018 // ************************************************************************
1019 
1020 import std.datetime;
1021 import std.exception;
1022 
1023 deprecated SysTime getMTime(string name)
1024 {
1025 	return timeLastModified(name);
1026 }
1027 
1028 /// If target exists, update its modification time;
1029 /// otherwise create it as an empty file.
1030 void touch(in char[] target)
1031 {
1032 	if (exists(target))
1033 	{
1034 		auto now = Clock.currTime();
1035 		setTimes(target, now, now);
1036 	}
1037 	else
1038 		std.file.write(target, "");
1039 }
1040 
1041 /// Returns true if the target file doesn't exist,
1042 /// or source is newer than the target.
1043 bool newerThan(string source, string target)
1044 {
1045 	if (!target.exists)
1046 		return true;
1047 	return source.timeLastModified() > target.timeLastModified();
1048 }
1049 
1050 /// Returns true if the target file doesn't exist,
1051 /// or any of the sources are newer than the target.
1052 bool anyNewerThan(string[] sources, string target)
1053 {
1054 	if (!target.exists)
1055 		return true;
1056 	auto targetTime = target.timeLastModified();
1057 	return sources.any!(source => source.timeLastModified() > targetTime)();
1058 }
1059 
1060 version (Posix)
1061 {
1062 	import core.sys.posix.sys.stat;
1063 	import core.sys.posix.unistd;
1064 
1065 	/// Get the ID of the user owning this file.
1066 	int getOwner(string fn)
1067 	{
1068 		stat_t s;
1069 		errnoEnforce(stat(toStringz(fn), &s) == 0, "stat: " ~ fn);
1070 		return s.st_uid;
1071 	}
1072 
1073 	/// Get the ID of the group owning this file.
1074 	int getGroup(string fn)
1075 	{
1076 		stat_t s;
1077 		errnoEnforce(stat(toStringz(fn), &s) == 0, "stat: " ~ fn);
1078 		return s.st_gid;
1079 	}
1080 
1081 	/// Set the owner user and group of this file.
1082 	void setOwner(string fn, int uid, int gid)
1083 	{
1084 		errnoEnforce(chown(toStringz(fn), uid, gid) == 0, "chown: " ~ fn);
1085 	}
1086 }
1087 
1088 /// Try to rename; copy/delete if rename fails
1089 void move(string src, string dst)
1090 {
1091 	try
1092 		src.rename(dst);
1093 	catch (Exception e)
1094 	{
1095 		atomicCopy(src, dst);
1096 		src.remove();
1097 	}
1098 }
1099 
1100 /// Make sure that the given directory exists
1101 /// (and create parent directories as necessary).
1102 void ensureDirExists(string path)
1103 {
1104 	if (!path.exists)
1105 		path.mkdirRecurse();
1106 }
1107 
1108 /// Make sure that the path to the given file name
1109 /// exists (and create directories as necessary).
1110 void ensurePathExists(string fn)
1111 {
1112 	fn.dirName.ensureDirExists();
1113 }
1114 
1115 static import core.stdc.errno;
1116 version (Windows)
1117 {
1118 	static import core.sys.windows.winerror;
1119 	static import std.windows.syserror;
1120 	static import ae.sys.windows.exception;
1121 }
1122 
1123 /// Catch common Phobos exception types corresponding to file operations.
1124 bool collectOSError(alias checkCError, alias checkWinError)(scope void delegate() operation)
1125 {
1126 	mixin(() {
1127 		string code = q{
1128 			try
1129 			{
1130 				operation();
1131 				return true;
1132 			}
1133 			catch (FileException e)
1134 			{
1135 				version (Windows)
1136 					bool collect = checkWinError(e.errno);
1137 				else
1138 					bool collect = checkCError(e.errno);
1139 				if (collect)
1140 					return false;
1141 				else
1142 					throw e;
1143 			}
1144 			catch (ErrnoException e)
1145 			{
1146 				if (checkCError(e.errno))
1147 					return false;
1148 				else
1149 					throw e;
1150 			}
1151 		};
1152 		version(Windows) code ~= q{
1153 			catch (std.windows.syserror.WindowsException e)
1154 			{
1155 				if (checkWinError(e.code))
1156 					return false;
1157 				else
1158 					throw e;
1159 			}
1160 			catch (ae.sys.windows.exception.WindowsException e)
1161 			{
1162 				if (checkWinError(e.code))
1163 					return false;
1164 				else
1165 					throw e;
1166 			}
1167 		};
1168 		return code;
1169 	}());
1170 }
1171 
1172 /// Collect a "file not found" error.
1173 alias collectNotFoundError = collectOSError!(
1174 	errno => errno == core.stdc.errno.ENOENT,
1175 	(code) { version(Windows) return
1176 			 code == core.sys.windows.winerror.ERROR_FILE_NOT_FOUND ||
1177 			 code == core.sys.windows.winerror.ERROR_PATH_NOT_FOUND; },
1178 );
1179 
1180 ///
1181 unittest
1182 {
1183 	auto fn = deleteme;
1184 	if (fn.exists) fn.removeRecurse();
1185 	foreach (dg; [
1186 		{ openFile(fn, "rb"); },
1187 		{ mkdir(fn.buildPath("b")); },
1188 		{ hardLink(fn, fn ~ "2"); },
1189 	])
1190 		assert(!dg.collectNotFoundError);
1191 }
1192 
1193 /// Collect a "file already exists" error.
1194 alias collectFileExistsError = collectOSError!(
1195 	errno => errno == core.stdc.errno.EEXIST,
1196 	(code) { version(Windows) return
1197 			 code == core.sys.windows.winerror.ERROR_FILE_EXISTS ||
1198 			 code == core.sys.windows.winerror.ERROR_ALREADY_EXISTS; },
1199 );
1200 
1201 ///
1202 unittest
1203 {
1204 	auto fn = deleteme;
1205 	foreach (dg; [
1206 		{ mkdir(fn); },
1207 		{ openFile(fn, "wxb"); },
1208 		{ touch(fn ~ "2"); hardLink(fn ~ "2", fn); },
1209 	])
1210 	{
1211 		if (fn.exists) fn.removeRecurse();
1212 		assert( dg.collectFileExistsError);
1213 		assert(!dg.collectFileExistsError);
1214 	}
1215 }
1216 
1217 import ae.utils.text;
1218 
1219 /// Forcibly remove a file or directory.
1220 /// If atomic is true, the entire directory is deleted "atomically"
1221 /// (it is first moved/renamed to another location).
1222 /// On Windows, this will move the file/directory out of the way,
1223 /// if it is in use and cannot be deleted (but can be renamed).
1224 void forceDelete(Flag!"atomic" atomic=Yes.atomic)(string fn, Flag!"recursive" recursive = No.recursive)
1225 {
1226 	import std.process : environment;
1227 	version(Windows)
1228 	{
1229 		mixin(importWin32!q{winnt});
1230 		mixin(importWin32!q{winbase});
1231 	}
1232 
1233 	auto name = fn.baseName();
1234 	fn = fn.absolutePath().longPath();
1235 
1236 	version(Windows)
1237 	{
1238 		auto fnW = toUTF16z(fn);
1239 		auto attr = GetFileAttributesW(fnW);
1240 		wenforce(attr != INVALID_FILE_ATTRIBUTES, "GetFileAttributes");
1241 		if (attr & FILE_ATTRIBUTE_READONLY)
1242 			SetFileAttributesW(fnW, attr & ~FILE_ATTRIBUTE_READONLY).wenforce("SetFileAttributes");
1243 	}
1244 
1245 	static if (atomic)
1246 	{
1247 		// To avoid zombifying locked directories, try renaming it first.
1248 		// Attempting to delete a locked directory will make it inaccessible.
1249 
1250 		bool tryMoveTo(string target)
1251 		{
1252 			target = target.longPath();
1253 			if (target.endsWith(dirSeparator))
1254 				target = target[0..$-1];
1255 			if (target.length && !target.exists)
1256 				return false;
1257 
1258 			string newfn;
1259 			do
1260 				newfn = format("%s%sdeleted-%s.%s.%s", target, dirSeparator, name, thisProcessID, randomString());
1261 			while (newfn.exists);
1262 
1263 			version(Windows)
1264 			{
1265 				auto newfnW = toUTF16z(newfn);
1266 				if (!MoveFileW(fnW, newfnW))
1267 					return false;
1268 			}
1269 			else
1270 			{
1271 				try
1272 					rename(fn, newfn);
1273 				catch (FileException e)
1274 					return false;
1275 			}
1276 
1277 			fn = newfn;
1278 			version(Windows) fnW = newfnW;
1279 			return true;
1280 		}
1281 
1282 		void tryMove()
1283 		{
1284 			auto tmp = environment.get("TEMP");
1285 			if (tmp)
1286 				if (tryMoveTo(tmp))
1287 					return;
1288 
1289 			version(Windows)
1290 				string tempDir = fn[0..7]~"Temp";
1291 			else
1292 				enum tempDir = "/tmp";
1293 
1294 			if (tryMoveTo(tempDir))
1295 				return;
1296 
1297 			if (tryMoveTo(fn.dirName()))
1298 				return;
1299 
1300 			throw new Exception("Unable to delete " ~ fn ~ " atomically (all rename attempts failed)");
1301 		}
1302 
1303 		tryMove();
1304 	}
1305 
1306 	version(Windows)
1307 	{
1308 		if (attr & FILE_ATTRIBUTE_DIRECTORY)
1309 		{
1310 			if (recursive && (attr & FILE_ATTRIBUTE_REPARSE_POINT) == 0)
1311 			{
1312 				foreach (de; fn.dirEntries(SpanMode.shallow))
1313 					forceDelete!(No.atomic)(de.name, Yes.recursive);
1314 			}
1315 			// Will fail if !recursive and directory is not empty
1316 			RemoveDirectoryW(fnW).wenforce("RemoveDirectory");
1317 		}
1318 		else
1319 			DeleteFileW(fnW).wenforce("DeleteFile");
1320 	}
1321 	else
1322 	{
1323 		if (recursive)
1324 			fn.removeRecurse();
1325 		else
1326 			if (fn.isDir)
1327 				fn.rmdir();
1328 			else
1329 				fn.remove();
1330 	}
1331 }
1332 
1333 
1334 deprecated void forceDelete(bool atomic)(string fn, bool recursive = false) { forceDelete!(cast(Flag!"atomic")atomic)(fn, cast(Flag!"recursive")recursive); }
1335 //deprecated void forceDelete()(string fn, bool recursive) { forceDelete!(Yes.atomic)(fn, cast(Flag!"recursive")recursive); }
1336 
1337 deprecated unittest
1338 {
1339 	mkdir("testdir"); touch("testdir/b"); forceDelete!(false     )("testdir", true);
1340 	mkdir("testdir"); touch("testdir/b"); forceDelete!(true      )("testdir", true);
1341 }
1342 
1343 unittest
1344 {
1345 	mkdir("testdir"); touch("testdir/b"); forceDelete             ("testdir", Yes.recursive);
1346 	mkdir("testdir"); touch("testdir/b"); forceDelete!(No .atomic)("testdir", Yes.recursive);
1347 	mkdir("testdir"); touch("testdir/b"); forceDelete!(Yes.atomic)("testdir", Yes.recursive);
1348 }
1349 
1350 /// If fn is a directory, delete it recursively.
1351 /// Otherwise, delete the file or symlink fn.
1352 void removeRecurse(string fn)
1353 {
1354 	auto attr = fn.getAttributes();
1355 	if (attr.attrIsSymlink)
1356 	{
1357 		version (Windows)
1358 			if (attr.attrIsDir)
1359 				fn.rmdir();
1360 			else
1361 				fn.remove();
1362 		else
1363 			fn.remove();
1364 	}
1365 	else
1366 	if (attr.attrIsDir)
1367 		version (Windows)
1368 			fn.forceDelete!(No.atomic)(Yes.recursive); // For read-only files
1369 		else
1370 			fn.rmdirRecurse();
1371 	else
1372 		fn.remove();
1373 }
1374 
1375 /// Create an empty directory, deleting
1376 /// all its contents if it already exists.
1377 void recreateEmptyDirectory()(string dir)
1378 {
1379 	if (dir.exists)
1380 		dir.forceDelete(Yes.recursive);
1381 	mkdir(dir);
1382 }
1383 
1384 /// Copy a directory recursively.
1385 void copyRecurse(DirEntry src, string dst)
1386 {
1387 	version (Posix)
1388 		if (src.isSymlink)
1389 			return symlink(dst, readLink(src));
1390 	if (src.isFile)
1391 		return copy(src, dst, PreserveAttributes.yes);
1392 	dst.mkdir();
1393 	foreach (de; src.dirEntries(SpanMode.shallow))
1394 		copyRecurse(de, dst.buildPath(de.baseName));
1395 }
1396 void copyRecurse(string src, string dst) { copyRecurse(DirEntry(src), dst); } /// ditto
1397 
1398 /// Return true if the given file would be hidden from directory listings.
1399 /// Returns true for files starting with `'.'`, and, on Windows, hidden files.
1400 bool isHidden()(string fn)
1401 {
1402 	if (baseName(fn).startsWith("."))
1403 		return true;
1404 	version (Windows)
1405 	{
1406 		mixin(importWin32!q{winnt});
1407 		if (getAttributes(fn) & FILE_ATTRIBUTE_HIDDEN)
1408 			return true;
1409 	}
1410 	return false;
1411 }
1412 
1413 /// Return a file's unique ID.
1414 ulong getFileID()(string fn)
1415 {
1416 	version (Windows)
1417 	{
1418 		mixin(importWin32!q{winnt});
1419 		mixin(importWin32!q{winbase});
1420 
1421 		auto fnW = toUTF16z(fn);
1422 		auto h = CreateFileW(fnW, FILE_READ_ATTRIBUTES, 0, null, OPEN_EXISTING, 0, HANDLE.init);
1423 		wenforce(h!=INVALID_HANDLE_VALUE, fn);
1424 		scope(exit) CloseHandle(h);
1425 		BY_HANDLE_FILE_INFORMATION fi;
1426 		GetFileInformationByHandle(h, &fi).wenforce("GetFileInformationByHandle");
1427 
1428 		ULARGE_INTEGER li;
1429 		li.LowPart  = fi.nFileIndexLow;
1430 		li.HighPart = fi.nFileIndexHigh;
1431 		auto result = li.QuadPart;
1432 		enforce(result, "Null file ID");
1433 		return result;
1434 	}
1435 	else
1436 	{
1437 		return DirEntry(fn).statBuf.st_ino;
1438 	}
1439 }
1440 
1441 unittest
1442 {
1443 	auto base = deleteme;
1444 	touch(base ~ "a");
1445 	scope(exit) remove(base ~ "a");
1446 	hardLink(base ~ "a", base ~ "b");
1447 	scope(exit) remove(base ~ "b");
1448 	touch(base ~ "c");
1449 	scope(exit) remove(base ~ "c");
1450 	assert(getFileID(base ~ "a") == getFileID(base ~ "b"));
1451 	assert(getFileID(base ~ "a") != getFileID(base ~ "c"));
1452 }
1453 
1454 deprecated alias std.file.getSize getSize2;
1455 
1456 /// Using UNC paths bypasses path length limitation when using Windows wide APIs.
1457 string longPath(string s)
1458 {
1459 	version (Windows)
1460 	{
1461 		if (!s.startsWith(`\\`))
1462 			return `\\?\` ~ s.absolutePath().buildNormalizedPath().replace(`/`, `\`);
1463 	}
1464 	return s;
1465 }
1466 
1467 version (Windows)
1468 {
1469 	static if (__traits(compiles, { mixin importWin32!q{winnt}; }))
1470 		static mixin(importWin32!q{winnt});
1471 
1472 	/// Common code for creating Windows reparse points.
1473 	private void createReparsePoint(string reparseBufferName, string extraInitialization, string reparseTagName)(in char[] target, in char[] print, in char[] link)
1474 	{
1475 		mixin(importWin32!q{winbase});
1476 		mixin(importWin32!q{windef});
1477 		mixin(importWin32!q{winioctl});
1478 
1479 		enum SYMLINK_FLAG_RELATIVE = 1;
1480 
1481 		HANDLE hLink = CreateFileW(link.toUTF16z(), GENERIC_READ | GENERIC_WRITE, 0, null, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, null);
1482 		wenforce(hLink && hLink != INVALID_HANDLE_VALUE, "CreateFileW");
1483 		scope(exit) CloseHandle(hLink);
1484 
1485 		enum pathOffset =
1486 			mixin(q{REPARSE_DATA_BUFFER..} ~ reparseBufferName)            .offsetof +
1487 			mixin(q{REPARSE_DATA_BUFFER..} ~ reparseBufferName)._PathBuffer.offsetof;
1488 
1489 		auto targetW = target.toUTF16();
1490 		auto printW  = print .toUTF16();
1491 
1492 		// Despite MSDN, two NUL-terminating characters are needed, one for each string.
1493 
1494 		auto pathBufferSize = targetW.length + 1 + printW.length + 1; // in chars
1495 		auto buf = new ubyte[pathOffset + pathBufferSize * WCHAR.sizeof];
1496 		auto r = cast(REPARSE_DATA_BUFFER*)buf.ptr;
1497 
1498 		r.ReparseTag = mixin(reparseTagName);
1499 		r.ReparseDataLength = to!WORD(buf.length - mixin(q{r..} ~ reparseBufferName).offsetof);
1500 
1501 		auto pathBuffer = mixin(q{r..} ~ reparseBufferName).PathBuffer;
1502 		auto p = pathBuffer;
1503 
1504 		mixin(q{r..} ~ reparseBufferName).SubstituteNameOffset = to!WORD((p-pathBuffer) * WCHAR.sizeof);
1505 		mixin(q{r..} ~ reparseBufferName).SubstituteNameLength = to!WORD(targetW.length * WCHAR.sizeof);
1506 		p[0..targetW.length] = targetW;
1507 		p += targetW.length;
1508 		*p++ = 0;
1509 
1510 		mixin(q{r..} ~ reparseBufferName).PrintNameOffset      = to!WORD((p-pathBuffer) * WCHAR.sizeof);
1511 		mixin(q{r..} ~ reparseBufferName).PrintNameLength      = to!WORD(printW .length * WCHAR.sizeof);
1512 		p[0..printW.length] = printW;
1513 		p += printW.length;
1514 		*p++ = 0;
1515 
1516 		assert(p-pathBuffer == pathBufferSize);
1517 
1518 		mixin(extraInitialization);
1519 
1520 		DWORD dwRet; // Needed despite MSDN
1521 		DeviceIoControl(hLink, FSCTL_SET_REPARSE_POINT, buf.ptr, buf.length.to!DWORD(), null, 0, &dwRet, null).wenforce("DeviceIoControl");
1522 	}
1523 
1524 	/// Attempt to acquire the specified privilege.
1525 	void acquirePrivilege(S)(S name)
1526 	{
1527 		mixin(importWin32!q{winbase});
1528 		mixin(importWin32!q{windef});
1529 
1530 		import ae.sys.windows;
1531 
1532 		HANDLE hToken = null;
1533 		wenforce(OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken));
1534 		scope(exit) CloseHandle(hToken);
1535 
1536 		TOKEN_PRIVILEGES tp;
1537 		wenforce(LookupPrivilegeValue(null, name.toUTF16z(), &tp.Privileges[0].Luid), "LookupPrivilegeValue");
1538 
1539 		tp.PrivilegeCount = 1;
1540 		tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
1541 		wenforce(AdjustTokenPrivileges(hToken, FALSE, &tp, cast(DWORD)TOKEN_PRIVILEGES.sizeof, null, null), "AdjustTokenPrivileges");
1542 	}
1543 
1544 	/// Link a directory.
1545 	/// Uses symlinks on POSIX, and directory junctions on Windows.
1546 	void dirLink()(in char[] original, in char[] link)
1547 	{
1548 		mkdir(link);
1549 		scope(failure) rmdir(link);
1550 
1551 		auto target = `\??\` ~ (cast(string)original).absolutePath((cast(string)link.dirName).absolutePath).buildNormalizedPath;
1552 		if (target[$-1] != '\\')
1553 			target ~= '\\';
1554 
1555 		createReparsePoint!(q{MountPointReparseBuffer}, q{}, q{IO_REPARSE_TAG_MOUNT_POINT})(target, null, link);
1556 	}
1557 
1558 	/// Windows implementation of `std.file.symlink`.
1559 	void symlink()(in char[] original, in char[] link)
1560 	{
1561 		mixin(importWin32!q{winnt});
1562 
1563 		acquirePrivilege(SE_CREATE_SYMBOLIC_LINK_NAME);
1564 
1565 		touch(link);
1566 		scope(failure) remove(link);
1567 
1568 		createReparsePoint!(q{SymbolicLinkReparseBuffer}, q{r.SymbolicLinkReparseBuffer.Flags = link.isAbsolute() ? 0 : SYMLINK_FLAG_RELATIVE;}, q{IO_REPARSE_TAG_SYMLINK})(original, original, link);
1569 	}
1570 }
1571 else
1572 	alias std.file.symlink dirLink; /// `std.file.symlink` is used to implement `dirLink` on POSIX.
1573 
1574 version(Windows) version(unittest) static mixin(importWin32!q{winnt});
1575 
1576 unittest
1577 {
1578 	// Wine's implementation of symlinks/junctions is incomplete
1579 	version (Windows)
1580 		if (getWineVersion())
1581 			return;
1582 
1583 	mkdir("a"); scope(exit) rmdir("a"[]);
1584 	touch("a/f"); scope(exit) remove("a/f");
1585 	dirLink("a", "b"); scope(exit) version(Windows) rmdir("b"); else remove("b");
1586 	//symlink("a/f", "c"); scope(exit) remove("c");
1587 	assert("b".isSymlink());
1588 	//assert("c".isSymlink());
1589 	assert("b/f".exists());
1590 }
1591 
1592 version (Windows)
1593 {
1594 	/// Create a hard link.
1595 	void hardLink()(string src, string dst)
1596 	{
1597 		mixin(importWin32!q{w32api});
1598 
1599 		static assert(_WIN32_WINNT >= 0x501, "CreateHardLinkW not available for target Windows platform. Specify -version=WindowsXP");
1600 
1601 		mixin(importWin32!q{winnt});
1602 		mixin(importWin32!q{winbase});
1603 
1604 		wenforce(CreateHardLinkW(toUTF16z(dst), toUTF16z(src), null), "CreateHardLink failed: " ~ src ~ " -> " ~ dst);
1605 	}
1606 
1607 	/// Deletes a file, which might be a read-only hard link
1608 	/// (thus, deletes the read-only file/link without affecting other links to it).
1609 	void deleteHardLink()(string fn)
1610 	{
1611 		mixin(importWin32!q{winbase});
1612 
1613 		auto fnW = toUTF16z(fn);
1614 
1615 		DWORD attrs = GetFileAttributesW(fnW);
1616 		wenforce(attrs != INVALID_FILE_ATTRIBUTES, "GetFileAttributesW failed: " ~ fn);
1617 
1618 		if (attrs & FILE_ATTRIBUTE_READONLY)
1619 			SetFileAttributesW(fnW, attrs & ~FILE_ATTRIBUTE_READONLY)
1620 			.wenforce("SetFileAttributesW failed: " ~ fn);
1621 		HANDLE h = CreateFileW(fnW, GENERIC_READ|GENERIC_WRITE, 7, null, OPEN_EXISTING,
1622 					FILE_FLAG_DELETE_ON_CLOSE, null);
1623 		wenforce(h != INVALID_HANDLE_VALUE, "CreateFileW failed: " ~ fn);
1624 		if (attrs & FILE_ATTRIBUTE_READONLY)
1625 			SetFileAttributesW(fnW, attrs)
1626 			.wenforce("SetFileAttributesW failed: " ~ fn);
1627 		CloseHandle(h).wenforce("CloseHandle failed: " ~ fn);
1628 	}
1629 }
1630 version (Posix)
1631 {
1632 	/// Create a hard link.
1633 	void hardLink()(string src, string dst)
1634 	{
1635 		import core.sys.posix.unistd;
1636 		errnoEnforce(link(toUTFz!(const char*)(src), toUTFz!(const char*)(dst)) == 0, "link() failed: " ~ dst);
1637 	}
1638 
1639 	alias deleteHardLink = remove; /// `std.file.remove` is used to implement `deleteHardLink` on POSIX.
1640 }
1641 
1642 unittest
1643 {
1644 	write("a", "foo"); scope(exit) remove("a");
1645 	hardLink("a", "b");
1646 	assert("b".readText == "foo");
1647 	deleteHardLink("b");
1648 	assert(!"b".exists);
1649 }
1650 
1651 version (Posix)
1652 {
1653 	/// Wrapper around the C `realpath` function.
1654 	string realPath(string path)
1655 	{
1656 		// TODO: Windows version
1657 		import core.sys.posix.stdlib;
1658 		auto p = realpath(toUTFz!(const char*)(path), null);
1659 		errnoEnforce(p, "realpath");
1660 		string result = fromStringz(p).idup;
1661 		free(p);
1662 		return result;
1663 	}
1664 }
1665 
1666 // /proc/self/mounts parsing
1667 version (linux)
1668 {
1669 	/// A parsed line from /proc/self/mounts.
1670 	struct MountInfo
1671 	{
1672 		string spec; /// device path
1673 		string file; /// mount path
1674 		string vfstype; /// file system
1675 		string mntops; /// options
1676 		int freq; /// dump flag
1677 		int passno; /// fsck order
1678 	}
1679 
1680 	private string unescapeMountString(in char[] s)
1681 	{
1682 		string result;
1683 
1684 		size_t p = 0;
1685 		for (size_t i=0; i+3<s.length;)
1686 		{
1687 			auto c = s[i];
1688 			if (c == '\\')
1689 			{
1690 				result ~= s[p..i];
1691 				result ~= to!int(s[i+1..i+4], 8);
1692 				i += 4;
1693 				p = i;
1694 			}
1695 			else
1696 				i++;
1697 		}
1698 		result ~= s[p..$];
1699 		return result;
1700 	}
1701 
1702 	unittest
1703 	{
1704 		assert(unescapeMountString(`a\040b\040c`) == "a b c");
1705 		assert(unescapeMountString(`\040`) == " ");
1706 	}
1707 
1708 	/// Parse a line from /proc/self/mounts.
1709 	MountInfo parseMountInfo(in char[] line)
1710 	{
1711 		const(char)[][6] parts;
1712 		copy(line.splitter(" "), parts[]);
1713 		return MountInfo(
1714 			unescapeMountString(parts[0]),
1715 			unescapeMountString(parts[1]),
1716 			unescapeMountString(parts[2]),
1717 			unescapeMountString(parts[3]),
1718 			parts[4].to!int,
1719 			parts[5].to!int,
1720 		);
1721 	}
1722 
1723 	/// Returns an iterator of MountInfo structs.
1724 	auto getMounts()
1725 	{
1726 		return File("/proc/self/mounts", "rb").byLine().map!parseMountInfo();
1727 	}
1728 
1729 	/// Get MountInfo with longest mount point matching path.
1730 	/// Returns MountInfo.init if none match.
1731 	MountInfo getPathMountInfo(R)(R mounts, string path)
1732 	if (isInputRange!R && is(ElementType!R : MountInfo))
1733 	{
1734 		path = realPath(path);
1735 		size_t bestLength; MountInfo bestInfo;
1736 		foreach (ref info; mounts)
1737 		{
1738 			if (path.pathStartsWith(info.file))
1739 			{
1740 				if (bestLength < info.file.length)
1741 				{
1742 					bestLength = info.file.length;
1743 					bestInfo = info;
1744 				}
1745 			}
1746 		}
1747 		return bestInfo;
1748 	}
1749 
1750 	MountInfo getPathMountInfo(string path)
1751 	{
1752 		return getMounts().getPathMountInfo(path);
1753 	}
1754 
1755 	/// Get the name of the filesystem that the given path is mounted under.
1756 	/// Returns null if none match.
1757 	string getPathFilesystem(string path)
1758 	{
1759 		return getPathMountInfo(path).vfstype;
1760 	}
1761 }
1762 
1763 // ****************************************************************************
1764 
1765 version (linux)
1766 {
1767 	import core.sys.linux.sys.xattr;
1768 	import core.stdc.errno;
1769 	private alias ENOATTR = ENODATA;
1770 
1771 	/// AA-like object for accessing a file's extended attributes.
1772 	struct XAttrs(Obj, string funPrefix)
1773 	{
1774 		private Obj obj;
1775 
1776 		mixin("alias getFun = " ~ funPrefix ~ "getxattr;");
1777 		mixin("alias setFun = " ~ funPrefix ~ "setxattr;");
1778 		mixin("alias removeFun = " ~ funPrefix ~ "removexattr;");
1779 		mixin("alias listFun = " ~ funPrefix ~ "listxattr;");
1780 
1781 		/// True if extended attributes are supported on this filesystem.
1782 		bool supported()
1783 		{
1784 			auto size = getFun(obj, "user.\x01", null, 0);
1785 			return size >= 0 || errno != EOPNOTSUPP;
1786 		}
1787 
1788 		/// Read an extended attribute.
1789 		void[] opIndex(string key)
1790 		{
1791 			auto cKey = key.toStringz();
1792 			sizediff_t size = 0;
1793 			void[] buf;
1794 			do
1795 			{
1796 				buf.length = size;
1797 				size = getFun(obj, cKey, buf.ptr, buf.length);
1798 				errnoEnforce(size >= 0, __traits(identifier, getFun));
1799 			} while (size != buf.length);
1800 			return buf;
1801 		}
1802 
1803 		/// Check if an extended attribute is present.
1804 		bool opBinaryRight(string op)(string key)
1805 		if (op == "in")
1806 		{
1807 			auto cKey = key.toStringz();
1808 			auto size = getFun(obj, cKey, null, 0);
1809 			if (size >= 0)
1810 				return true;
1811 			else
1812 			if (errno == ENOATTR)
1813 				return false;
1814 			else
1815 				errnoEnforce(false, __traits(identifier, getFun));
1816 			assert(false);
1817 		}
1818 
1819 		/// Write an extended attribute.
1820 		void opIndexAssign(in void[] value, string key)
1821 		{
1822 			auto ret = setFun(obj, key.toStringz(), value.ptr, value.length, 0);
1823 			errnoEnforce(ret == 0, __traits(identifier, setFun));
1824 		}
1825 
1826 		/// Delete an extended attribute.
1827 		void remove(string key)
1828 		{
1829 			auto ret = removeFun(obj, key.toStringz());
1830 			errnoEnforce(ret == 0, __traits(identifier, removeFun));
1831 		}
1832 
1833 		/// Return a list of all extended attribute names.
1834 		string[] keys()
1835 		{
1836 			sizediff_t size = 0;
1837 			char[] buf;
1838 			do
1839 			{
1840 				buf.length = size;
1841 				size = listFun(obj, buf.ptr, buf.length);
1842 				errnoEnforce(size >= 0, __traits(identifier, listFun));
1843 			} while (size != buf.length);
1844 
1845 			char[][] result;
1846 			size_t start;
1847 			foreach (p, c; buf)
1848 				if (!c)
1849 				{
1850 					result ~= buf[start..p];
1851 					start = p+1;
1852 				}
1853 
1854 			return cast(string[])result;
1855 		}
1856 	}
1857 
1858 	/// Return `XAttrs` for the given path,
1859 	/// or the link destination if the path leads to as symbolic link.
1860 	auto xAttrs(string path)
1861 	{
1862 		return XAttrs!(const(char)*, "")(path.toStringz());
1863 	}
1864 
1865 	/// Return `XAttrs` for the given path.
1866 	auto linkXAttrs(string path)
1867 	{
1868 		return XAttrs!(const(char)*, "l")(path.toStringz());
1869 	}
1870 
1871 	/// Return `XAttrs` for the given open file.
1872 	auto xAttrs(ref const File f)
1873 	{
1874 		return XAttrs!(int, "f")(f.fileno);
1875 	}
1876 
1877 	///
1878 	unittest
1879 	{
1880 		if (!xAttrs(".").supported)
1881 		{
1882 			import std.stdio : stderr;
1883 			stderr.writeln("ae.sys.file: xattrs not supported on current filesystem, skipping test.");
1884 			return;
1885 		}
1886 
1887 		enum fn = "test.txt";
1888 		std.file.write(fn, "test");
1889 		scope(exit) remove(fn);
1890 
1891 		auto attrs = xAttrs(fn);
1892 		enum key = "user.foo";
1893 		assert(key !in attrs);
1894 		assertThrown!ErrnoException(attrs[key]);
1895 		assert(attrs.keys == []);
1896 
1897 		attrs[key] = "bar";
1898 		assert(key in attrs);
1899 		assert(attrs[key] == "bar");
1900 		assert(attrs.keys == [key]);
1901 
1902 		attrs.remove(key);
1903 		assert(key !in attrs);
1904 		assert(attrs.keys == []);
1905 	}
1906 }
1907 
1908 // ****************************************************************************
1909 
1910 version (Windows)
1911 {
1912 	/// Enumerate all hard links to the specified file.
1913 	// TODO: Return a range
1914 	string[] enumerateHardLinks()(string fn)
1915 	{
1916 		mixin(importWin32!q{winnt});
1917 		mixin(importWin32!q{winbase});
1918 
1919 		alias extern(System) HANDLE function(LPCWSTR lpFileName, DWORD dwFlags, LPDWORD StringLength, PWCHAR LinkName) TFindFirstFileNameW;
1920 		alias extern(System) BOOL function(HANDLE hFindStream, LPDWORD StringLength, PWCHAR LinkName) TFindNextFileNameW;
1921 
1922 		auto kernel32 = GetModuleHandle("kernel32.dll");
1923 		auto FindFirstFileNameW = cast(TFindFirstFileNameW)GetProcAddress(kernel32, "FindFirstFileNameW").wenforce("GetProcAddress(FindFirstFileNameW)");
1924 		auto FindNextFileNameW = cast(TFindNextFileNameW)GetProcAddress(kernel32, "FindNextFileNameW").wenforce("GetProcAddress(FindNextFileNameW)");
1925 
1926 		static WCHAR[0x8000] buf;
1927 		DWORD len = buf.length;
1928 		auto h = FindFirstFileNameW(toUTF16z(fn), 0, &len, buf.ptr);
1929 		wenforce(h != INVALID_HANDLE_VALUE, "FindFirstFileNameW");
1930 		scope(exit) FindClose(h);
1931 
1932 		string[] result;
1933 		do
1934 		{
1935 			enforce(len > 0 && len < buf.length && buf[len-1] == 0, "Bad FindFirst/NextFileNameW result");
1936 			result ~= buf[0..len-1].toUTF8();
1937 			len = buf.length;
1938 			auto ok = FindNextFileNameW(h, &len, buf.ptr);
1939 			if (!ok && GetLastError() == ERROR_HANDLE_EOF)
1940 				break;
1941 			wenforce(ok, "FindNextFileNameW");
1942 		} while(true);
1943 		return result;
1944 	}
1945 }
1946 
1947 /// Obtain the hard link count for the given file.
1948 uint hardLinkCount(string fn)
1949 {
1950 	version (Windows)
1951 	{
1952 		// TODO: Optimize (don't transform strings)
1953 		return cast(uint)fn.enumerateHardLinks.length;
1954 	}
1955 	else
1956 	{
1957 		import core.sys.posix.sys.stat;
1958 
1959 		stat_t s;
1960 		errnoEnforce(stat(fn.toStringz(), &s) == 0, "stat");
1961 		return s.st_nlink.to!uint;
1962 	}
1963 }
1964 
1965 // https://issues.dlang.org/show_bug.cgi?id=7016
1966 version (unittest)
1967 	version (Windows)
1968 		import ae.sys.windows.misc : getWineVersion;
1969 
1970 unittest
1971 {
1972 	// FindFirstFileNameW not implemented in Wine
1973 	version (Windows)
1974 		if (getWineVersion())
1975 			return;
1976 
1977 	touch("a.test");
1978 	scope(exit) remove("a.test");
1979 	assert("a.test".hardLinkCount() == 1);
1980 
1981 	hardLink("a.test", "b.test");
1982 	scope(exit) remove("b.test");
1983 	assert("a.test".hardLinkCount() == 2);
1984 	assert("b.test".hardLinkCount() == 2);
1985 
1986 	version(Windows)
1987 	{
1988 		auto paths = enumerateHardLinks("a.test");
1989 		assert(paths.length == 2);
1990 		paths.sort();
1991 		assert(paths[0].endsWith(`\a.test`), paths[0]);
1992 		assert(paths[1].endsWith(`\b.test`));
1993 	}
1994 }
1995 
1996 /// Argument-reversed version of `std.file.write`,
1997 /// usable at the end of an UFCS chain.
1998 static if (is(typeof({ import std.stdio : toFile; })))
1999 {
2000 	static import std.stdio;
2001 	alias toFile = std.stdio.toFile;
2002 }
2003 else
2004 {
2005 	void toFile(in void[] data, in char[] name)
2006 	{
2007 		std.file.write(name, data);
2008 	}
2009 }
2010 
2011 /// Same as toFile, but accepts void[] and does not conflict with the
2012 /// std.stdio function.
2013 void writeTo(in void[] data, in char[] target)
2014 {
2015 	std.file.write(target, data);
2016 }
2017 
2018 /// Polyfill for Windows fopen implementations with support for UNC
2019 /// paths and the 'x' subspecifier.
2020 File openFile()(string fn, string mode = "rb")
2021 {
2022 	File f;
2023 	static if (is(typeof(&f.windowsHandleOpen)))
2024 	{
2025 		import core.sys.windows.windows;
2026 		import ae.sys.windows.exception;
2027 
2028 		string winMode, cMode;
2029 		foreach (c; mode)
2030 		{
2031 			switch (c)
2032 			{
2033 				case 'r':
2034 				case 'w':
2035 				case 'a':
2036 				case '+':
2037 				case 'x':
2038 					winMode ~= c;
2039 					break;
2040 				case 'b':
2041 				case 't':
2042 					break;
2043 				default:
2044 					assert(false, "Unknown character in mode");
2045 			}
2046 			if (c != 'x')
2047 				cMode ~= c;
2048 		}
2049 		DWORD access, creation;
2050 		bool append;
2051 		switch (winMode)
2052 		{
2053 			case "r"  : access = GENERIC_READ                ; creation = OPEN_EXISTING; break;
2054 			case "r+" : access = GENERIC_READ | GENERIC_WRITE; creation = OPEN_EXISTING; break;
2055 			case "w"  : access =                GENERIC_WRITE; creation = CREATE_ALWAYS; break;
2056 			case "w+" : access = GENERIC_READ | GENERIC_WRITE; creation = CREATE_ALWAYS; break;
2057 			case "a"  : access =                GENERIC_WRITE; creation = OPEN_ALWAYS  ; version (CRuntime_Microsoft) append = true; break;
2058 			case "a+" : access = GENERIC_READ | GENERIC_WRITE; creation = OPEN_ALWAYS  ; version (CRuntime_Microsoft) assert(false, "MSVCRT can't fdopen with a+"); else break;
2059 			case "wx" : access =                GENERIC_WRITE; creation = CREATE_NEW   ; break;
2060 			case "w+x": access = GENERIC_READ | GENERIC_WRITE; creation = CREATE_NEW   ; break;
2061 			case "ax" : access =                GENERIC_WRITE; creation = CREATE_NEW   ; version (CRuntime_Microsoft) append = true; break;
2062 			case "a+x": access = GENERIC_READ | GENERIC_WRITE; creation = CREATE_NEW   ; version (CRuntime_Microsoft) assert(false, "MSVCRT can't fdopen with a+"); else break;
2063 			default: assert(false, "Bad file mode: " ~ mode);
2064 		}
2065 
2066 		auto pathW = toUTF16z(longPath(fn));
2067 		auto h = CreateFileW(pathW, access, FILE_SHARE_READ, null, creation, 0, HANDLE.init);
2068 		wenforce(h != INVALID_HANDLE_VALUE);
2069 
2070 		if (append)
2071 			h.SetFilePointer(0, null, FILE_END);
2072 
2073 		f.windowsHandleOpen(h, cMode);
2074 	}
2075 	else
2076 		f.open(fn, mode);
2077 	return f;
2078 }
2079 
2080 unittest
2081 {
2082 	enum Existence { any, mustExist, mustNotExist }
2083 	enum Pos { none /* not readable/writable */, start, end, empty }
2084 	static struct Behavior
2085 	{
2086 		Existence existence;
2087 		bool truncating;
2088 		Pos read, write;
2089 	}
2090 
2091 	void test(string mode, in Behavior expected)
2092 	{
2093 		static if (isVersion!q{CRuntime_Microsoft} || isVersion!q{OSX})
2094 			if (mode == "a+" || mode == "a+x")
2095 				return;
2096 
2097 		Behavior behavior;
2098 
2099 		static int counter;
2100 		auto fn = text(deleteme, counter++);
2101 
2102 		collectException(fn.remove());
2103 		bool mustExist    = !!collectException(openFile(fn, mode));
2104 		touch(fn);
2105 		bool mustNotExist = !!collectException(openFile(fn, mode));
2106 
2107 		if (!mustExist)
2108 			if (!mustNotExist)
2109 				behavior.existence = Existence.any;
2110 			else
2111 				behavior.existence = Existence.mustNotExist;
2112 		else
2113 			if (!mustNotExist)
2114 				behavior.existence = Existence.mustExist;
2115 			else
2116 				assert(false, "Can't open file whether it exists or not");
2117 
2118 		void create()
2119 		{
2120 			if (mustNotExist)
2121 				collectException(fn.remove());
2122 			else
2123 				write(fn, "foo");
2124 		}
2125 
2126 		create();
2127 		openFile(fn, mode);
2128 		behavior.truncating = getSize(fn) == 0;
2129 
2130 		create();
2131 		{
2132 			auto f = openFile(fn, mode);
2133 			ubyte[] buf;
2134 			if (collectException(f.rawRead(new ubyte[1]), buf))
2135 			{
2136 				behavior.read = Pos.none;
2137 				// Work around https://issues.dlang.org/show_bug.cgi?id=19751
2138 				f.reopen(fn, "w");
2139 			}
2140 			else
2141 			if (buf.length)
2142 				behavior.read = Pos.start;
2143 			else
2144 			if (f.size)
2145 				behavior.read = Pos.end;
2146 			else
2147 				behavior.read = Pos.empty;
2148 		}
2149 
2150 		create();
2151 		{
2152 			string s;
2153 			{
2154 				auto f = openFile(fn, mode);
2155 				if (collectException(f.rawWrite("b")))
2156 				{
2157 					s = null;
2158 					// Work around https://issues.dlang.org/show_bug.cgi?id=19751
2159 					f.reopen(fn, "w");
2160 				}
2161 				else
2162 				{
2163 					f.close();
2164 					s = fn.readText;
2165 				}
2166 			}
2167 
2168 			if (s is null)
2169 				behavior.write = Pos.none;
2170 			else
2171 			if (s == "b")
2172 				behavior.write = Pos.empty;
2173 			else
2174 			if (s.endsWith("b"))
2175 				behavior.write = Pos.end;
2176 			else
2177 			if (s.startsWith("b"))
2178 				behavior.write = Pos.start;
2179 			else
2180 				assert(false, "Can't detect write position");
2181 		}
2182 
2183 
2184 		if (behavior != expected)
2185 		{
2186 			import ae.utils.array : isOneOf;
2187 			version (Windows)
2188 				if (getWineVersion() && mode.isOneOf("w", "a", "wx", "ax"))
2189 				{
2190 					// Ignore bug in Wine msvcrt implementation
2191 					return;
2192 				}
2193 
2194 			assert(false, text(mode, ": expected ", expected, ", got ", behavior));
2195 		}
2196 	}
2197 
2198 	test("r"  , Behavior(Existence.mustExist   , false, Pos.start, Pos.none ));
2199 	test("r+" , Behavior(Existence.mustExist   , false, Pos.start, Pos.start));
2200 	test("w"  , Behavior(Existence.any         , true , Pos.none , Pos.empty));
2201 	test("w+" , Behavior(Existence.any         , true , Pos.empty, Pos.empty));
2202 	test("a"  , Behavior(Existence.any         , false, Pos.none , Pos.end  ));
2203 	test("a+" , Behavior(Existence.any         , false, Pos.start, Pos.end  ));
2204 	test("wx" , Behavior(Existence.mustNotExist, true , Pos.none , Pos.empty));
2205 	test("w+x", Behavior(Existence.mustNotExist, true , Pos.empty, Pos.empty));
2206 	test("ax" , Behavior(Existence.mustNotExist, true , Pos.none , Pos.empty));
2207 	test("a+x", Behavior(Existence.mustNotExist, true , Pos.empty, Pos.empty));
2208 }
2209 
2210 private version(Windows)
2211 {
2212 	version (CRuntime_Microsoft)
2213 	{
2214 		alias chsize_size_t = long;
2215 		extern(C) int _chsize_s(int fd, chsize_size_t size);
2216 		alias chsize = _chsize_s;
2217 	}
2218 	else
2219 	{
2220 		import core.stdc.config : c_long;
2221 		alias chsize_size_t = c_long;
2222 		extern(C) int chsize(int fd, c_long size);
2223 	}
2224 }
2225 
2226 /// Truncate the given file to the given size.
2227 void truncate(File f, ulong length)
2228 {
2229 	f.flush();
2230 	version (Windows)
2231 		chsize(f.fileno, length.to!chsize_size_t);
2232 	else
2233 		ftruncate(f.fileno, length.to!off_t);
2234 }
2235 
2236 unittest
2237 {
2238 	write("test.txt", "abcde");
2239 	auto f = File("test.txt", "r+b");
2240 	f.write("xyz");
2241 	f.truncate(f.tell);
2242 	f.close();
2243 	assert("test.txt".readText == "xyz");
2244 }
2245 
2246 /// Calculate the digest of a file.
2247 auto fileDigest(Digest)(string fn)
2248 {
2249 	import std.range.primitives;
2250 	Digest context;
2251 	context.start();
2252 	put(context, openFile(fn, "rb").byChunk(64 * 1024));
2253 	auto digest = context.finish();
2254 	return digest;
2255 }
2256 
2257 /// Calculate the MD5 hash of a file.
2258 template mdFile()
2259 {
2260 	import std.digest.md;
2261 	alias mdFile = fileDigest!MD5;
2262 }
2263 
2264 version (HAVE_WIN32)
2265 unittest
2266 {
2267 	import std.digest : toHexString;
2268 	write("test.txt", "Hello, world!");
2269 	scope(exit) remove("test.txt");
2270 	assert(mdFile("test.txt").toHexString() == "6CD3556DEB0DA54BCA060B4C39479839");
2271 }
2272 
2273 /// Calculate the digest of a file, and memoize it.
2274 auto fileDigestCached(Digest)(string fn)
2275 {
2276 	static typeof(Digest.init.finish())[ulong] cache;
2277 	auto id = getFileID(fn);
2278 	auto phash = id in cache;
2279 	if (phash)
2280 		return *phash;
2281 	return cache[id] = fileDigest!Digest(fn);
2282 }
2283 
2284 /// Calculate the MD5 hash of a file, and memoize it.
2285 template mdFileCached()
2286 {
2287 	import std.digest.md;
2288 	alias mdFileCached = fileDigestCached!MD5;
2289 }
2290 
2291 version (HAVE_WIN32)
2292 unittest
2293 {
2294 	import std.digest : toHexString;
2295 	write("test.txt", "Hello, world!");
2296 	scope(exit) remove("test.txt");
2297 	assert(mdFileCached("test.txt").toHexString() == "6CD3556DEB0DA54BCA060B4C39479839");
2298 	write("test.txt", "Something else");
2299 	assert(mdFileCached("test.txt").toHexString() == "6CD3556DEB0DA54BCA060B4C39479839");
2300 }
2301 
2302 /// Read a File (which might be a stream) into an array
2303 ubyte[] readFile(File f)
2304 {
2305 	import std.range.primitives;
2306 	auto result = appender!(ubyte[]);
2307 	put(result, f.byChunk(64*1024));
2308 	return result.data;
2309 }
2310 
2311 unittest
2312 {
2313 	auto s = "0123456789".replicate(10_000);
2314 	write("test.txt", s);
2315 	scope(exit) remove("test.txt");
2316 	assert(readFile(File("test.txt")) == s);
2317 }
2318 
2319 /// Read exactly `buf.length` bytes and return true.
2320 /// On EOF, return false.
2321 bool readExactly(File f, ubyte[] buf)
2322 {
2323 	if (!buf.length)
2324 		return true;
2325 	auto read = f.rawRead(buf);
2326 	if (read.length==0) return false;
2327 	enforce(read.length == buf.length, "Unexpected end of file");
2328 	return true;
2329 }
2330 
2331 private
2332 version (Windows)
2333 {
2334 	version (CRuntime_DigitalMars)
2335 		extern(C) sizediff_t read(int, void*, size_t);
2336 	else
2337 	{
2338 		extern(C) sizediff_t _read(int, void*, size_t);
2339 		alias read = _read;
2340 	}
2341 }
2342 else
2343 	import core.sys.posix.unistd : read;
2344 
2345 /// Like `File.rawRead`, but returns as soon as any data is available.
2346 void[] readPartial(File f, void[] buf)
2347 {
2348 	assert(buf.length);
2349 	auto numRead = read(f.fileno, buf.ptr, buf.length);
2350 	errnoEnforce(numRead >= 0);
2351 	return buf[0 .. numRead];
2352 }
2353 
2354 /// Like std.file.readText for non-UTF8
2355 ascii readAscii()(string fileName)
2356 {
2357 	return cast(ascii)readFile(openFile(fileName, "rb"));
2358 }
2359 
2360 // https://issues.dlang.org/show_bug.cgi?id=7016
2361 version(Posix) static import ae.sys.signals;
2362 
2363 /// Start a thread which writes data to f asynchronously.
2364 Thread writeFileAsync(File f, in void[] data)
2365 {
2366 	static class Writer : Thread
2367 	{
2368 		File target;
2369 		const void[] data;
2370 
2371 		this(ref File f, in void[] data)
2372 		{
2373 			this.target = f;
2374 			this.data = data;
2375 			super(&run);
2376 		}
2377 
2378 		void run()
2379 		{
2380 			version (Posix)
2381 			{
2382 				import ae.sys.signals;
2383 				collectSignal(SIGPIPE, &write);
2384 			}
2385 			else
2386 				write();
2387 		}
2388 
2389 		void write()
2390 		{
2391 			target.rawWrite(data);
2392 			target.close();
2393 		}
2394 	}
2395 
2396 	auto t = new Writer(f, data);
2397 	t.start();
2398 	return t;
2399 }
2400 
2401 /// Start a thread which reads data from f asynchronously.
2402 /// Call the returning delegate to obtain the read data,
2403 /// blocking until the read completes.
2404 ubyte[] delegate() readFileAsync(File f)
2405 {
2406 	static class Reader : Thread
2407 	{
2408 		File target;
2409 		ubyte[] data;
2410 
2411 		this(ref File f)
2412 		{
2413 			this.target = f;
2414 			this.data = data;
2415 			super(&run);
2416 		}
2417 
2418 		void run()
2419 		{
2420 			data = readFile(target);
2421 		}
2422 	}
2423 
2424 	auto t = new Reader(f);
2425 	t.start();
2426 	return {
2427 		t.join();
2428 		return t.data;
2429 	};
2430 }
2431 
2432 /// Write data to a file, and ensure it gets written to disk
2433 /// before this function returns.
2434 /// Consider using as atomic!syncWrite.
2435 /// See also: syncUpdate
2436 void syncWrite()(string target, in void[] data)
2437 {
2438 	auto f = File(target, "wb");
2439 	f.rawWrite(data);
2440 	version (Windows)
2441 	{
2442 		mixin(importWin32!q{windows});
2443 		FlushFileBuffers(f.windowsHandle);
2444 	}
2445 	else
2446 	{
2447 		import core.sys.posix.unistd;
2448 		fsync(f.fileno);
2449 	}
2450 	f.close();
2451 }
2452 
2453 /// Atomically save data to a file (if the file doesn't exist,
2454 /// or its contents differs). The update operation as a whole
2455 /// is not atomic, only the write is.
2456 void syncUpdate()(string fn, in void[] data)
2457 {
2458 	if (!fn.exists || fn.read() != data)
2459 		atomic!(syncWrite!())(fn, data);
2460 }
2461 
2462 version(Windows) import ae.sys.windows.exception;
2463 
2464 /// Create a named pipe, and allow interacting with it using a `std.stdio.File`.
2465 struct NamedPipeImpl
2466 {
2467 	immutable string fileName; ///
2468 
2469 	/// Create a named pipe, and reserve a filename.
2470 	this()(string name)
2471 	{
2472 		version(Windows)
2473 		{
2474 			mixin(importWin32!q{winbase});
2475 
2476 			fileName = `\\.\pipe\` ~ name;
2477 			auto h = CreateNamedPipeW(fileName.toUTF16z, PIPE_ACCESS_OUTBOUND, PIPE_TYPE_BYTE, 10, 4096, 4096, 0, null).wenforce("CreateNamedPipeW");
2478 			f.windowsHandleOpen(h, "wb");
2479 		}
2480 		else
2481 		{
2482 			import core.sys.posix.sys.stat;
2483 
2484 			fileName = `/tmp/` ~ name ~ `.fifo`;
2485 			mkfifo(fileName.toStringz, S_IWUSR | S_IRUSR);
2486 		}
2487 	}
2488 
2489 	/// Wait for a peer to open the other end of the pipe.
2490 	File connect()()
2491 	{
2492 		version(Windows)
2493 		{
2494 			mixin(importWin32!q{winbase});
2495 			mixin(importWin32!q{windef});
2496 
2497 			BOOL bSuccess = ConnectNamedPipe(f.windowsHandle, null);
2498 
2499 			// "If a client connects before the function is called, the function returns zero
2500 			// and GetLastError returns ERROR_PIPE_CONNECTED. This can happen if a client
2501 			// connects in the interval between the call to CreateNamedPipe and the call to
2502 			// ConnectNamedPipe. In this situation, there is a good connection between client
2503 			// and server, even though the function returns zero."
2504 			if (!bSuccess)
2505 				wenforce(GetLastError() == ERROR_PIPE_CONNECTED, "ConnectNamedPipe");
2506 
2507 			return f;
2508 		}
2509 		else
2510 		{
2511 			return File(fileName, "w");
2512 		}
2513 	}
2514 
2515 	~this()
2516 	{
2517 		version(Windows)
2518 		{
2519 			// File.~this will take care of cleanup
2520 		}
2521 		else
2522 			fileName.remove();
2523 	}
2524 
2525 private:
2526 	File f;
2527 }
2528 alias NamedPipe = RefCounted!NamedPipeImpl; /// ditto
2529 
2530 import ae.utils.textout : StringBuilder;
2531 
2532 /// Avoid std.stdio.File.readln's memory corruption bug
2533 /// https://issues.dlang.org/show_bug.cgi?id=13856
2534 string safeReadln(File f)
2535 {
2536 	StringBuilder buf;
2537 	char[1] arr;
2538 	while (true)
2539 	{
2540 		auto result = f.rawRead(arr[]);
2541 		if (!result.length)
2542 			break;
2543 		buf.put(result);
2544 		if (result[0] == '\x0A')
2545 			break;
2546 	}
2547 	return buf.get();
2548 }
2549 
2550 // ****************************************************************************
2551 
2552 /// Change the current directory to the given directory. Does nothing if dir is null.
2553 /// Return a scope guard which, upon destruction, restores the previous directory.
2554 /// Asserts that only one thread has changed the process's current directory at any time.
2555 auto pushd(string dir)
2556 {
2557 	import core.atomic;
2558 
2559 	static int threadCount = 0;
2560 	static shared int processCount = 0;
2561 
2562 	static struct Popd
2563 	{
2564 		string oldPath;
2565 		this(string cwd) { oldPath = cwd; }
2566 		~this() { if (oldPath) pop(); }
2567 		@disable this();
2568 		@disable this(this);
2569 
2570 		void pop()
2571 		{
2572 			assert(oldPath);
2573 			scope(exit) oldPath = null;
2574 			chdir(oldPath);
2575 
2576 			auto newThreadCount = --threadCount;
2577 			auto newProcessCount = atomicOp!"-="(processCount, 1);
2578 			assert(newThreadCount == newProcessCount); // Shouldn't happen
2579 		}
2580 	}
2581 
2582 	string cwd;
2583 	if (dir)
2584 	{
2585 		auto newThreadCount = ++threadCount;
2586 		auto newProcessCount = atomicOp!"+="(processCount, 1);
2587 		assert(newThreadCount == newProcessCount, "Another thread already has an active pushd");
2588 
2589 		cwd = getcwd();
2590 		chdir(dir);
2591 	}
2592 	return Popd(cwd);
2593 }
2594 
2595 // ****************************************************************************
2596 
2597 import std.algorithm;
2598 import std.process : thisProcessID;
2599 import std.traits;
2600 import std.typetuple;
2601 import ae.utils.meta;
2602 
2603 /// Parameter names that `atomic` assumes
2604 /// indicate a destination file by default.
2605 enum targetParameterNames = "target/to/name/dst";
2606 
2607 /// Wrap an operation which creates a file or directory,
2608 /// so that it is created safely and, for files, atomically
2609 /// (by performing the underlying operation to a temporary
2610 /// location, then renaming the completed file/directory to
2611 /// the actual target location). targetName specifies the name
2612 /// of the parameter containing the target file/directory.
2613 auto atomic(alias impl, string targetName = targetParameterNames)(staticMap!(Unqual, ParameterTypeTuple!impl) args)
2614 {
2615 	enum targetIndex = findParameter([ParameterIdentifierTuple!impl], targetName, __traits(identifier, impl));
2616 	return atomic!(impl, targetIndex)(args);
2617 }
2618 
2619 /// ditto
2620 auto atomic(alias impl, size_t targetIndex)(staticMap!(Unqual, ParameterTypeTuple!impl) args)
2621 {
2622 	// idup for https://issues.dlang.org/show_bug.cgi?id=12503
2623 	auto target = args[targetIndex].idup;
2624 	auto temp = "%s.%s.%s.temp".format(target, thisProcessID, getCurrentThreadID);
2625 	if (temp.exists) temp.removeRecurse();
2626 	scope(success) rename(temp, target);
2627 	scope(failure) if (temp.exists) temp.removeRecurse();
2628 	args[targetIndex] = temp;
2629 	return impl(args);
2630 }
2631 
2632 /// ditto
2633 // Workaround for https://issues.dlang.org/show_bug.cgi?id=12230
2634 // Can't be an overload because of https://issues.dlang.org/show_bug.cgi?id=13374
2635 //R atomicDg(string targetName = "target", R, Args...)(R delegate(Args) impl, staticMap!(Unqual, Args) args)
2636 auto atomicDg(size_t targetIndexA = size_t.max, Impl, Args...)(Impl impl, Args args)
2637 {
2638 	enum targetIndex = targetIndexA == size_t.max ? ParameterTypeTuple!impl.length-1 : targetIndexA;
2639 	return atomic!(impl, targetIndex)(args);
2640 }
2641 
2642 deprecated alias safeUpdate = atomic;
2643 
2644 unittest
2645 {
2646 	enum fn = "atomic.tmp";
2647 	scope(exit) if (fn.exists) fn.remove();
2648 
2649 	atomic!touch(fn);
2650 	assert(fn.exists);
2651 	fn.remove();
2652 
2653 	atomicDg(&touch, fn);
2654 	assert(fn.exists);
2655 }
2656 
2657 /// Wrap an operation so that it is skipped entirely
2658 /// if the target already exists. Implies atomic.
2659 auto cached(alias impl, string targetName = targetParameterNames)(ParameterTypeTuple!impl args)
2660 {
2661 	enum targetIndex = findParameter([ParameterIdentifierTuple!impl], targetName, __traits(identifier, impl));
2662 	auto target = args[targetIndex];
2663 	if (!target.exists)
2664 		atomic!(impl, targetIndex)(args);
2665 	return target;
2666 }
2667 
2668 /// ditto
2669 // Exists due to the same reasons as atomicDg
2670 auto cachedDg(size_t targetIndexA = size_t.max, Impl, Args...)(Impl impl, Args args)
2671 {
2672 	enum targetIndex = targetIndexA == size_t.max ? ParameterTypeTuple!impl.length-1 : targetIndexA;
2673 	auto target = args[targetIndex];
2674 	if (!target.exists)
2675 		atomic!(impl, targetIndex)(args);
2676 	return target;
2677 }
2678 
2679 deprecated alias obtainUsing = cached;
2680 
2681 /// Create a file, or replace an existing file's contents
2682 /// atomically.
2683 /// Note: Consider using atomic!syncWrite or
2684 /// atomic!syncUpdate instead.
2685 alias atomicWrite = atomic!_writeProxy;
2686 deprecated alias safeWrite = atomicWrite;
2687 /*private*/ void _writeProxy(string target, in void[] data)
2688 {
2689 	std.file.write(target, data);
2690 }
2691 
2692 // Work around for https://github.com/D-Programming-Language/phobos/pull/2784#issuecomment-68117241
2693 private void copy2(string source, string target) { std.file.copy(source, target); }
2694 
2695 /// Copy a file, or replace an existing file's contents
2696 /// with another file's, atomically.
2697 alias atomic!copy2 atomicCopy;
2698 
2699 unittest
2700 {
2701 	enum fn = "cached.tmp";
2702 	scope(exit) if (fn.exists) fn.remove();
2703 
2704 	cached!touch(fn);
2705 	assert(fn.exists);
2706 
2707 	std.file.write(fn, "test");
2708 
2709 	cachedDg!0(&_writeProxy, fn, "test2");
2710 	assert(fn.readText() == "test");
2711 }
2712 
2713 // ****************************************************************************
2714 
2715 /// Composes a function which generates a file name
2716 /// with a function which creates the file.
2717 /// Returns the file name.
2718 template withTarget(alias targetGen, alias fun)
2719 {
2720 	auto withTarget(Args...)(auto ref Args args)
2721 	{
2722 		auto target = targetGen(args);
2723 		fun(args, target);
2724 		return target;
2725 	}
2726 }
2727 
2728 /// Two-argument buildPath with reversed arguments.
2729 /// Useful for UFCS chaining.
2730 string prependPath(string target, string path)
2731 {
2732 	return buildPath(path, target);
2733 }