1 /*
2  * dirent.h - dirent API for Microsoft Visual Studio
3  *
4  * Copyright (C) 2006-2012 Toni Ronkko
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining
7  * a copy of this software and associated documentation files (the
8  * ``Software''), to deal in the Software without restriction, including
9  * without limitation the rights to use, copy, modify, merge, publish,
10  * distribute, sublicense, and/or sell copies of the Software, and to
11  * permit persons to whom the Software is furnished to do so, subject to
12  * the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be included
15  * in all copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, EXPRESS
18  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
20  * IN NO EVENT SHALL TONI RONKKO BE LIABLE FOR ANY CLAIM, DAMAGES OR
21  * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
22  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23  * OTHER DEALINGS IN THE SOFTWARE.
24  *
25  * $Id: dirent.h,v 1.20 2014/03/19 17:52:23 tronkko Exp $
26  */
27 module libressl_d.compat.dirent_msvc;
28 
29 
30 private static import core.stdc.config;
31 private static import core.stdc.stddef;
32 private static import core.sys.windows.basetsd;
33 private static import core.sys.windows.winbase;
34 private static import core.sys.windows.windef;
35 private static import core.sys.windows.winnt;
36 private static import libressl_d.compat.limits;
37 public import core.stdc.errno;
38 public import core.stdc.stdarg;
39 public import core.stdc.wchar_;
40 public import core.sys.windows.windows;
41 public import libressl_d.compat.stdio;
42 public import libressl_d.compat.stdlib;
43 public import libressl_d.compat.string;
44 public import libressl_d.compat.sys.stat;
45 public import libressl_d.compat.sys.types;
46 
47 version (Windows):
48 extern (C):
49 nothrow @nogc:
50 
51 alias errno_t = int;
52 
53 .errno_t _set_errno(int);
54 .errno_t mbstowcs_s(size_t*, wchar_t*, size_t, const (char)*, size_t);
55 .errno_t wcstombs_s(size_t*, char*, size_t, const (wchar_t)*, size_t);
56 
57 /**
58  * Indicates that d_type field is available in dirent structure
59  */
60 enum _DIRENT_HAVE_D_TYPE = true;
61 
62 /**
63  * Indicates that d_namlen field is available in dirent structure
64  */
65 enum _DIRENT_HAVE_D_NAMLEN = true;
66 
67 /* Maximum length of file name */
68 enum PATH_MAX = libressl_d.compat.limits.PATH_MAX;
69 
70 static if (!__traits(compiles, libressl_d.compat.stdio.FILENAME_MAX)) {
71 	enum FILENAME_MAX = core.sys.windows.windef.MAX_PATH;
72 } else {
73 	enum FILENAME_MAX = libressl_d.compat.stdio.FILENAME_MAX;
74 }
75 
76 enum NAME_MAX = libressl_d.compat.limits.NAME_MAX;
77 
78 /**
79  * Return the exact length of d_namlen without zero terminator
80  */
81 pragma(inline, true)
82 pure nothrow @trusted @nogc @live
83 void _D_EXACT_NAMLEN(P)(P p)
84 
85 	do
86 	{
87 		return p.d_namlen;
88 	}
89 
90 /**
91  * Return number of bytes needed to store d_namlen
92  */
93 pragma(inline, true)
94 pure nothrow @safe @nogc @live
95 void _D_ALLOC_NAMLEN(P)(P p)
96 
97 	do
98 	{
99 		return libressl_d.compat.limits.PATH_MAX;
100 	}
101 
102 /**
103  * Wide-character version
104  */
105 struct _wdirent
106 {
107 	/**
108 	 * Always zero
109 	 */
110 	core.stdc.config.c_long d_ino;
111 
112 	/**
113 	 * Structure size
114 	 */
115 	ushort d_reclen;
116 
117 	/**
118 	 * Length of name without \0
119 	 */
120 	size_t d_namlen;
121 
122 	/**
123 	 * File type
124 	 */
125 	int d_type;
126 
127 	/**
128 	 * File name
129 	 */
130 	core.stdc.stddef.wchar_t[libressl_d.compat.limits.PATH_MAX] d_name = '\0';
131 }
132 
133 struct _WDIR
134 {
135 	/**
136 	 * Current directory entry
137 	 */
138 	._wdirent ent;
139 
140 	/**
141 	 * Private file data
142 	 */
143 	core.sys.windows.winbase.WIN32_FIND_DATAW data;
144 
145 	/**
146 	 * True if data is valid
147 	 */
148 	int cached;
149 
150 	/**
151 	 * Win32 search handle
152 	 */
153 	core.sys.windows.basetsd.HANDLE handle;
154 
155 	/**
156 	 * Initial directory name
157 	 */
158 	core.stdc.stddef.wchar_t* patt;
159 }
160 
161 /**
162  * Multi-byte character versions
163  */
164 struct dirent
165 {
166 	/**
167 	 * Always zero
168 	 */
169 	core.stdc.config.c_long d_ino;
170 
171 	/**
172 	 * Structure size
173 	 */
174 	ushort d_reclen;
175 
176 	/**
177 	 * Length of name without \0
178 	 */
179 	size_t d_namlen;
180 
181 	/**
182 	 * File type
183 	 */
184 	int d_type;
185 
186 	/**
187 	 * File name
188 	 */
189 	char[libressl_d.compat.limits.PATH_MAX] d_name = '\0';
190 }
191 
192 struct DIR
193 {
194 	.dirent ent;
195 	._WDIR* wdirp;
196 }
197 
198 /*
199  * Open directory stream DIRNAME for read and return a pointer to the
200  * internal working area that is used to retrieve individual directory
201  * entries.
202  */
203 ._WDIR* _wopendir(const (core.stdc.stddef.wchar_t)* dirname)
204 
205 	do
206 	{
207 		/* Must have directory name */
208 		if ((dirname == null) || (dirname[0] == '\0')) {
209 			._set_errno(core.stdc.errno.ENOENT);
210 
211 			return null;
212 		}
213 
214 		/* Allocate new _WDIR structure */
215 		._WDIR* dirp = cast(._WDIR*)(libressl_d.compat.stdlib.malloc(._WDIR.sizeof));
216 
217 		int error = void;
218 
219 		if (dirp != null) {
220 			/* Reset _WDIR structure */
221 			dirp.handle = core.sys.windows.winbase.INVALID_HANDLE_VALUE;
222 			dirp.patt = null;
223 			dirp.cached = 0;
224 
225 			/* Compute the length of full path plus zero terminator */
226 			core.sys.windows.windef.DWORD n = core.sys.windows.winbase.GetFullPathNameW(dirname, 0, null, null);
227 
228 			/* Allocate room for absolute directory name and search pattern */
229 			dirp.patt = cast(core.stdc.stddef.wchar_t*)(libressl_d.compat.stdlib.malloc((core.stdc.stddef.wchar_t.sizeof * n) + 16));
230 
231 			if (dirp.patt != null) {
232 				/*
233 				 * Convert relative directory name to an absolute one.  This
234 				 * allows rewinddir() to function correctly even when current
235 				 * working directory is changed between opendir() and rewinddir().
236 				 */
237 				n = core.sys.windows.winbase.GetFullPathNameW(dirname, n, dirp.patt, null);
238 
239 				if (n > 0) {
240 					/* Append search pattern \* to the directory name */
241 					core.stdc.stddef.wchar_t* p = dirp.patt + n;
242 
243 					if (dirp.patt < p) {
244 						switch (p[-1]) {
245 							case '\\':
246 							case '/':
247 							case ':':
248 								/* Directory ends in path separator, e.g. c:\temp\ */
249 								/*NOP*/
250 								//;
251 
252 								break;
253 
254 							default:
255 								/* Directory name doesn't end in path separator */
256 								*p++ = '\\';
257 						}
258 					}
259 
260 					*p++ = '*';
261 					*p = '\0';
262 
263 					/* Open directory stream and retrieve the first entry */
264 					if (.dirent_first(dirp)) {
265 						/* Directory stream opened successfully */
266 						error = 0;
267 					} else {
268 						/* Cannot retrieve first entry */
269 						error = 1;
270 						._set_errno(core.stdc.errno.ENOENT);
271 					}
272 				} else {
273 					/* Cannot retrieve full path name */
274 					._set_errno(core.stdc.errno.ENOENT);
275 					error = 1;
276 				}
277 			} else {
278 				/* Cannot allocate memory for search pattern */
279 				error = 1;
280 			}
281 		} else {
282 			/* Cannot allocate _WDIR structure */
283 			error = 1;
284 		}
285 
286 		/* Clean up in case of error */
287 		if ((error) && (dirp != null)) {
288 			._wclosedir(dirp);
289 			dirp = null;
290 		}
291 
292 		return dirp;
293 	}
294 
295 /*
296  * Read next directory entry.  The directory entry is returned in dirent
297  * structure in the d_name field.  Individual directory entries returned by
298  * this function include regular files, sub-directories, pseudo-directories
299  * "." and ".." as well as volume labels, hidden files and system files.
300  */
301 ._wdirent* _wreaddir(._WDIR* dirp)
302 
303 	in
304 	{
305 		assert(dirp != null);
306 	}
307 
308 	do
309 	{
310 		/* Read next directory entry */
311 		core.sys.windows.winbase.WIN32_FIND_DATAW* datap = .dirent_next(dirp);
312 
313 		._wdirent* entp = void;
314 
315 		if (datap != null) {
316 			/* Pointer to directory entry to return */
317 			entp = &dirp.ent;
318 
319 			/*
320 			 * Copy file name as wide-character string.  If the file name is too
321 			 * core.stdc.config.c_long to fit in to the destination buffer, then truncate file name
322 			 * to PATH_MAX characters and zero-terminate the buffer.
323 			 */
324 			size_t n = 0;
325 
326 			while (((n + 1) < libressl_d.compat.limits.PATH_MAX) && (datap.cFileName[n] != 0)) {
327 				entp.d_name[n] = datap.cFileName[n];
328 				n++;
329 			}
330 
331 			dirp.ent.d_name[n] = 0;
332 
333 			/* Length of file name excluding zero terminator */
334 			entp.d_namlen = n;
335 
336 			/* File type */
337 			core.sys.windows.windef.DWORD attr = datap.dwFileAttributes;
338 
339 			if ((attr & core.sys.windows.winnt.FILE_ATTRIBUTE_DEVICE) != 0) {
340 				entp.d_type = libressl_d.compat.sys.stat.DT_CHR;
341 			} else if ((attr & core.sys.windows.winnt.FILE_ATTRIBUTE_DIRECTORY) != 0) {
342 				entp.d_type = libressl_d.compat.sys.stat.DT_DIR;
343 			} else {
344 				entp.d_type = libressl_d.compat.sys.stat.DT_REG;
345 			}
346 
347 			/* Reset dummy fields */
348 			entp.d_ino = 0;
349 			entp.d_reclen = ._wdirent.sizeof;
350 		} else {
351 			/* Last directory entry read */
352 			entp = null;
353 		}
354 
355 		return entp;
356 	}
357 
358 /*
359  * Close directory stream opened by opendir() function.  This invalidates the
360  * DIR structure as well as any directory entry read previously by
361  * _wreaddir().
362  */
363 int _wclosedir(._WDIR* dirp)
364 
365 	do
366 	{
367 		int ok = void;
368 
369 		if (dirp != null) {
370 			/* Release search handle */
371 			if (dirp.handle != core.sys.windows.winbase.INVALID_HANDLE_VALUE) {
372 				core.sys.windows.winbase.FindClose(dirp.handle);
373 				dirp.handle = core.sys.windows.winbase.INVALID_HANDLE_VALUE;
374 			}
375 
376 			/* Release search pattern */
377 			if (dirp.patt != null) {
378 				libressl_d.compat.stdlib.free(dirp.patt);
379 				dirp.patt = null;
380 			}
381 
382 			/* Release directory structure */
383 			libressl_d.compat.stdlib.free(dirp);
384 
385 			/*success*/
386 			ok = 0;
387 		} else {
388 			/* Invalid directory stream */
389 			._set_errno(core.stdc.errno.EBADF);
390 
391 			/*failure*/
392 			ok = -1;
393 		}
394 
395 		return ok;
396 	}
397 
398 /*
399  * Rewind directory stream such that _wreaddir() returns the very first
400  * file name again.
401  */
402 void _wrewinddir(._WDIR* dirp)
403 
404 	do
405 	{
406 		if (dirp != null) {
407 			/* Release existing search handle */
408 			if (dirp.handle != core.sys.windows.winbase.INVALID_HANDLE_VALUE) {
409 				core.sys.windows.winbase.FindClose(dirp.handle);
410 				dirp.handle = core.sys.windows.windef.NULL;
411 			}
412 
413 			/* Open new search handle */
414 			.dirent_first(dirp);
415 		}
416 	}
417 
418 /* Get first directory entry(internal) */
419 core.sys.windows.winbase.WIN32_FIND_DATAW* dirent_first(._WDIR* dirp)
420 
421 	in
422 	{
423 		assert(dirp != null);
424 	}
425 
426 	do
427 	{
428 		core.sys.windows.winbase.WIN32_FIND_DATAW* datap = void;
429 
430 		/* Open directory and retrieve the first entry */
431 		dirp.handle = core.sys.windows.winbase.FindFirstFileW(dirp.patt, &dirp.data);
432 
433 		if (dirp.handle != core.sys.windows.winbase.INVALID_HANDLE_VALUE) {
434 			/* a directory entry is now waiting in memory */
435 			datap = &dirp.data;
436 			dirp.cached = 1;
437 		} else {
438 			/* Failed to re-open directory: no directory entry in memory */
439 			dirp.cached = 0;
440 			datap = null;
441 		}
442 
443 		return datap;
444 	}
445 
446 /* Get next directory entry(internal) */
447 core.sys.windows.winbase.WIN32_FIND_DATAW* dirent_next(._WDIR* dirp)
448 
449 	in
450 	{
451 		assert(dirp != null);
452 	}
453 
454 	do
455 	{
456 		core.sys.windows.winbase.WIN32_FIND_DATAW* p = void;
457 
458 		/* Get next directory entry */
459 		if (dirp.cached != 0) {
460 			/* A valid directory entry already in memory */
461 			p = &dirp.data;
462 			dirp.cached = 0;
463 		} else if (dirp.handle != core.sys.windows.winbase.INVALID_HANDLE_VALUE) {
464 			/* Get the next directory entry from stream */
465 			if (core.sys.windows.winbase.FindNextFileW(dirp.handle, &dirp.data) != core.sys.windows.windef.FALSE) {
466 				/* Got a file */
467 				p = &dirp.data;
468 			} else {
469 				/* The very last entry has been processed or an error occured */
470 				core.sys.windows.winbase.FindClose(dirp.handle);
471 				dirp.handle = core.sys.windows.winbase.INVALID_HANDLE_VALUE;
472 				p = null;
473 			}
474 		} else {
475 			/* End of directory stream reached */
476 			p = null;
477 		}
478 
479 		return p;
480 	}
481 
482 /**
483  * Open directory stream using plain old C-string.
484  */
485 .DIR* opendir(const (char)* dirname)
486 
487 	do
488 	{
489 		/* Must have directory name */
490 		if ((dirname == null) || (dirname[0] == '\0')) {
491 			._set_errno(core.stdc.errno.ENOENT);
492 
493 			return null;
494 		}
495 
496 		/* Allocate memory for DIR structure */
497 		.DIR* dirp = cast(.DIR*)(libressl_d.compat.stdlib.malloc(.DIR.sizeof));
498 
499 		int error = void;
500 
501 		if (dirp != null) {
502 			core.stdc.stddef.wchar_t[libressl_d.compat.limits.PATH_MAX] wname = void;
503 			size_t n = void;
504 
505 			/* Convert directory name to wide-character string */
506 			error = .dirent_mbstowcs_s(&n, &(wname[0]), libressl_d.compat.limits.PATH_MAX, dirname, libressl_d.compat.limits.PATH_MAX);
507 
508 			if (!error) {
509 				/* Open directory stream using wide-character name */
510 				dirp.wdirp = ._wopendir(&(wname[0]));
511 
512 				if (dirp.wdirp) {
513 					/* Directory stream opened */
514 					error = 0;
515 				} else {
516 					/* Failed to open directory stream */
517 					error = 1;
518 				}
519 			} else {
520 				/*
521 				 * Cannot convert file name to wide-character string.  This
522 				 * occurs if the string contains invalid multi-byte sequences or
523 				 * the output buffer is too small to contain the resulting
524 				 * string.
525 				 */
526 				error = 1;
527 			}
528 		} else {
529 			/* Cannot allocate DIR structure */
530 			error = 1;
531 		}
532 
533 		/* Clean up in case of error */
534 		if ((error) && (dirp != null)) {
535 			libressl_d.compat.stdlib.free(dirp);
536 			dirp = null;
537 		}
538 
539 		return dirp;
540 	}
541 
542 /**
543  * Read next directory entry.
544  *
545  * When working with text consoles, please note that file names returned by
546  * readdir() are represented in the default ANSI code page while any output to
547  * console is typically formatted on another code page.  Thus, non-ASCII
548  * characters in file names will not usually display correctly on console.  The
549  * problem can be fixed in two ways:(1) change the character set of console
550  * to 1252 using chcp utility and use Lucida Console font, or(2) use
551  * _cprintf function when writing to console.  The _cprinf() will re-encode
552  * ANSI strings to the console code page so many non-ASCII characters will
553  * display correcly.
554  */
555 .dirent* readdir(.DIR* dirp)
556 
557 	in
558 	{
559 		assert(dirp != null);
560 	}
561 
562 	do
563 	{
564 		/* Read next directory entry */
565 		core.sys.windows.winbase.WIN32_FIND_DATAW* datap = .dirent_next(dirp.wdirp);
566 
567 		.dirent* entp = void;
568 
569 		if (datap != null) {
570 			size_t n = void;
571 
572 			/* Attempt to convert file name to multi-byte string */
573 			int error = .dirent_wcstombs_s(&n, &(dirp.ent.d_name[0]), libressl_d.compat.limits.PATH_MAX, &(datap.cFileName[0]), libressl_d.compat.limits.PATH_MAX);
574 
575 			/*
576 			 * If the file name cannot be represented by a multi-byte string,
577 			 * then attempt to use old 8+3 file name.  This allows traditional
578 			 * Unix-code to access some file names despite of unicode
579 			 * characters, although file names may seem unfamiliar to the user.
580 			 *
581 			 * Be ware that the code below cannot come up with a short file
582 			 * name unless the file system provides one.  At least
583 			 * VirtualBox shared folders fail to do this.
584 			 */
585 			if ((error) && (datap.cAlternateFileName[0] != '\0')) {
586 				error = .dirent_wcstombs_s(&n, &(dirp.ent.d_name[0]), libressl_d.compat.limits.PATH_MAX, &(datap.cAlternateFileName[0]), libressl_d.compat.limits.PATH_MAX);
587 			}
588 
589 			if (!error) {
590 				/* Initialize directory entry for return */
591 				entp = &dirp.ent;
592 
593 				/* Length of file name excluding zero terminator */
594 				entp.d_namlen = n - 1;
595 
596 				/* File attributes */
597 				core.sys.windows.windef.DWORD attr = datap.dwFileAttributes;
598 
599 				if ((attr & core.sys.windows.winnt.FILE_ATTRIBUTE_DEVICE) != 0) {
600 					entp.d_type = libressl_d.compat.sys.stat.DT_CHR;
601 				} else if ((attr & core.sys.windows.winnt.FILE_ATTRIBUTE_DIRECTORY) != 0) {
602 					entp.d_type = libressl_d.compat.sys.stat.DT_DIR;
603 				} else {
604 					entp.d_type = libressl_d.compat.sys.stat.DT_REG;
605 				}
606 
607 				/* Reset dummy fields */
608 				entp.d_ino = 0;
609 				entp.d_reclen = .dirent.sizeof;
610 			} else {
611 				/*
612 				 * Cannot convert file name to multi-byte string so construct
613 				 * an errornous directory entry and return that.  Note that
614 				 * we cannot return null as that would stop the processing
615 				 * of directory entries completely.
616 				 */
617 				entp = &dirp.ent;
618 				entp.d_name[0] = '?';
619 				entp.d_name[1] = '\0';
620 				entp.d_namlen = 1;
621 				entp.d_type = libressl_d.compat.sys.stat.DT_UNKNOWN;
622 				entp.d_ino = 0;
623 				entp.d_reclen = 0;
624 			}
625 		} else {
626 			/* No more directory entries */
627 			entp = null;
628 		}
629 
630 		return entp;
631 	}
632 
633 /**
634  * Close directory stream.
635  */
636 int closedir(.DIR* dirp)
637 
638 	do
639 	{
640 		int ok = void;
641 
642 		if (dirp != null) {
643 			/* Close wide-character directory stream */
644 			ok = ._wclosedir(dirp.wdirp);
645 			dirp.wdirp = null;
646 
647 			/* Release multi-byte character version */
648 			libressl_d.compat.stdlib.free(dirp);
649 		} else {
650 			/* Invalid directory stream */
651 			._set_errno(core.stdc.errno.EBADF);
652 
653 			/*failure*/
654 			ok = -1;
655 		}
656 
657 		return ok;
658 	}
659 
660 /**
661  * Rewind directory stream to beginning.
662  */
663 void rewinddir(.DIR* dirp)
664 
665 	in
666 	{
667 		assert(dirp != null);
668 	}
669 
670 	do
671 	{
672 		/* Rewind wide-character string directory stream */
673 		._wrewinddir(dirp.wdirp);
674 	}
675 
676 /* Convert multi-byte string to wide character string */
677 int dirent_mbstowcs_s(size_t* pReturnValue, core.stdc.stddef.wchar_t* wcstr, size_t sizeInWords, const (char)* mbstr, size_t count)
678 
679 	do
680 	{
681 		return .mbstowcs_s(pReturnValue, wcstr, sizeInWords, mbstr, count);
682 	}
683 
684 /* Convert wide-character string to multi-byte string */
685 int dirent_wcstombs_s(size_t* pReturnValue, char* mbstr, size_t sizeInBytes, /* max size of mbstr */ const (core.stdc.stddef.wchar_t)* wcstr, size_t count)
686 
687 	do
688 	{
689 		return .wcstombs_s(pReturnValue, mbstr, sizeInBytes, wcstr, count);
690 	}