1%%	options
2
3copyright owner	=	Dirk Krause
4copyright year	=	2015-xxxx
5SPDX-License-Identifier:	BSD-3-Clause
6
7
8
9%%	header
10
11/**	@file
12	Directory traversal using wchar_t file names.
13
14	CRT on Windows: Optional.
15*/
16
17#ifndef DK4CONF_H_INCLUDED
18#if DK4_BUILDING_DKTOOLS4
19#include "dk4conf.h"
20#else
21#include <dktools-4/dk4conf.h>
22#endif
23#endif
24
25#ifndef DK4TYPES_H_INCLUDED
26#if DK4_BUILDING_DKTOOLS4
27#include <libdk4base/dk4types.h>
28#else
29#include <dktools-4/dk4types.h>
30#endif
31#endif
32
33#ifndef DK4ERROR_H_INCLUDED
34#if DK4_BUILDING_DKTOOLS4
35#include <libdk4base/dk4error.h>
36#else
37#include <dktools-4/dk4error.h>
38#endif
39#endif
40
41#ifndef DK4STO_H_INCLUDED
42#if DK4_BUILDING_DKTOOLS4
43#include <libdk4c/dk4sto.h>
44#else
45#include <dktools-4/dk4sto.h>
46#endif
47#endif
48
49
50/**	Directory structure.
51*/
52typedef struct {
53  wchar_t	*path;		/**< Directory path. */
54  dk4_sto_t	*s_dir;		/**< Storage for directories. */
55  dk4_sto_it_t	*i_dir;		/**< Iterator for directories. */
56  dk4_sto_t	*s_file;	/**< Storage for non-directories. */
57  dk4_sto_it_t	*i_file;	/**< Iterator for non-directories. */
58  size_t	 maxlen;	/**< Maximum entry name length. */
59} dk4_dir_wc_t;
60
61#ifdef __cplusplus
62extern "C" {
63#endif
64
65/**	Open directory, create directory structure.
66	@param	path	Directory path name.
67	@param	om	Opening mode.
68	@param	erp	Error report, may be NULL.
69	@return	Pointer to new directory structure on success, NULL on error.
70	On success use dk4dir_wc_close() to destroy the directory
71	structure.
72
73	Error codes:
74	- DK4_E_INVALID_ARGUMENTS<br>
75	  if path is NULL,
76	- DK4_E_NOT_SUPPORTED<br>
77	  if no function to traverse directories was found during build process,
78	- DK4_E_MEMORY_ALLOCATION_FAILED<br>
79	  if a memory allocation failed,
80	- DK4_E_MATH_OVERFLOW<br>
81	  if a mathematical overflow occured in a size calculation,
82	- DK4_E_BUFFER_TOO_SMALL<br>
83	  if one of the involved directory or file names is too long for an
84	  internal buffer,
85	- DK4_E_OPENDIR_FAILED<br>
86	  with errno value in iDetails1 if the attempt to open the directory
87	  failed,
88	- DK4_E_FINDFIRSTFILE_FAILED<br>
89	  with GetLastError() value in lDetails1 if the FindFirstFile()
90	  function failed on Windows,
91	- DK4_E_SYSTEM<br>
92	  if the stat() or lstat() function failed for one of the directory
93	  items.
94*/
95dk4_dir_wc_t *
96dk4dir_wc_open(const wchar_t *path, int om, dk4_er_t *erp);
97
98/**	Destroy directory structure.
99	@param	dptr	Structure to destroy.
100*/
101void
102dk4dir_wc_close(dk4_dir_wc_t *dptr);
103
104/**	Reset directory traversal.
105	@param	dptr	Directory structure.
106*/
107void
108dk4dir_wc_reset(dk4_dir_wc_t *dptr);
109
110/**	Retrieve directory path.
111	@param	dptr	Directory structure.
112	@return	Directory path on success,
113		NULL on error or if no directory was specified for a
114		file name expansion.
115*/
116const wchar_t *
117dk4dir_wc_get_path(dk4_dir_wc_t const *dptr);
118
119/**	Retrieve next directory name.
120	@param	dptr	Directory structure.
121	@return	Pointer to short name on success, NULL if there are no
122	more directories.
123*/
124const wchar_t *
125dk4dir_wc_next_dir(dk4_dir_wc_t *dptr);
126
127/**	Retrieve next file (non-directory) name.
128	@param	dptr	Directory structure.
129	@return	Pointer to short name on success, NULL if there are no more
130	files.
131*/
132const wchar_t *
133dk4dir_wc_next_file(dk4_dir_wc_t *dptr);
134
135/**	Skip files (remove information about files, release memory).
136	@param	dptr	Directory structure.
137*/
138void
139dk4dir_wc_skip_files(dk4_dir_wc_t *dptr);
140
141/**	Compare two file or directory names.
142	@param	l	Left name.
143	@param	r	Right name.
144	@param	cr	Comparison criteria (ignored).
145	@return	1 if l>r, 0 if l=r, -1 if l<r.
146*/
147int
148dk4dir_wc_compare(const void *l, const void *r, int cr);
149
150/**	Get maximum entry name length.
151	@param	dptr	Directory.
152	@return	Maximum name length.
153*/
154size_t
155dk4dir_wc_get_max_entry_length(dk4_dir_wc_t const *dptr);
156
157/**	Create full path name for short file name in existing buffer.
158	@param	buffer	Result buffer.
159	@param	szbuf	Size of result buffer (number of wchar_t).
160	@param	pdir	Directory fn was obtained from.
161	@param	fn	Short file or directory name.
162	@param	erp	Error report, may be NULL.
163	@return	1 on success, 0 on error.
164
165	Error codes:
166	- DK4_E_INVALID_ARGUMENTS<br>
167	  if buffer, pdir or fn is NULL or szbuf is 0,
168	- DK4_E_BUFFER_TOO_SMALL<br>
169	  if the buffer is too small.
170*/
171int
172dk4dir_wc_full_name_buffer(
173  wchar_t		*buffer,
174  size_t		 szbuf,
175  dk4_dir_wc_t	const	*pdir,
176  wchar_t	const	*fn,
177  dk4_er_t		*erp
178);
179
180/**	Create full path name for short file name in newly allocated buffer.
181	@param	pdir	Directory fn was obtained from.
182	@param	fn	Short file or directory name.
183	@param	erp	Error report, may be NULL.
184	@return	Valid pointer to newly allocated buffer on success,
185	NULL on error.
186	On success use dk4mem_free() to release the memory when done with
187	the name.
188
189	Error codes:
190	- DK4_E_INVALID_ARGUMENTS<br>
191	  if pdir or fn is NULL,
192	- DK4_E_MATH_OVERFLOW<br>
193	  if the size calculation results in a numeric overflow.
194	- DK4_E_MEMORY_ALLOCATION_FAILED<br>
195	  if the memory allocation failed,
196	- DK4_E_BUFFER_TOO_SMALL<br>
197	  if the buffer is too small.
198*/
199wchar_t *
200dk4dir_wc_full_name_new(
201  dk4_dir_wc_t	const	*pdir,
202  wchar_t	const	*fn,
203  dk4_er_t		*erp
204);
205
206#ifdef __cplusplus
207}
208#endif
209
210%%	module
211
212#include "dk4conf.h"
213
214#if DK4_ON_WINDOWS
215#ifndef WINDOWS_H_INCLUDED
216#include <windows.h>
217#define	WINDOWS_H_INCLUDED 1
218#endif
219#endif
220
221#include <libdk4c/dk4dirwc.h>
222
223#ifndef DK4DIR_H_INCLUDED
224#include <libdk4c/dk4dir.h>
225#endif
226
227#ifndef	DK4PATHW_H_INCLUDED
228#include <libdk4c/dk4pathw.h>
229#endif
230
231#ifndef DK4STR8_H_INCLUDED
232#include <libdk4base/dk4strw.h>
233#endif
234
235#ifndef DK4STAT_H_INCLUDED
236#include <libdk4c/dk4stat.h>
237#endif
238
239#ifndef DK4STAT8_H_INCLUDED
240#include <libdk4c/dk4statw.h>
241#endif
242
243#ifndef DK4MEM_H_INCLUDED
244#include <libdk4base/dk4mem.h>
245#endif
246
247#ifndef DK4MPL_H_INCLUDED
248#include <libdk4base/dk4mpl.h>
249#endif
250
251#ifndef DK4MAASZ_H_INCLUDED
252#include <libdk4ma/dk4maasz.h>
253#endif
254
255#if DK4_HAVE_SYS_TYPES_H
256#ifndef SYS_TYPES_H_INCLUDED
257#include <sys/types.h>
258#define	SYS_TYPES_H_INCLUDED 1
259#endif
260#endif
261
262#if DK4_HAVE_WCHAR_H
263#ifndef WCHAR_H_INCLUDED
264#include <wchar.h>
265#define WCHAR_H_INCLUDED 1
266#endif
267#endif
268
269#if DK4_HAVE_DIRENT_H
270#ifndef DIRENT_H_INCLUDED
271#include <dirent.h>
272#define	DIRENT_H_INCLUDED 1
273#endif
274#endif
275
276#if DK4_HAVE_ERRNO_H
277#ifndef ERRNO_H_INCLUDED
278#include <errno.h>
279#define	ERRNO_H_INCLUDED 1
280#endif
281#endif
282
283#if	DK4_HAVE_ASSERT_H
284#ifndef	ASSERT_H_INCLUDED
285#include <assert.h>
286#define	ASSERT_H_INCLUDED 1
287#endif
288#endif
289
290#include <libdk4base/dk4unused.h>
291
292
293
294
295$!trace-include
296
297
298
299#if DK4_ON_WINDOWS
300
301int
302dk4dir_wc_compare(const void *l, const void *r, int DK4_ARG_UNUSED(cr))
303{
304  int		back = 0;
305  DK4_UNUSED_ARG(cr)
306  if (NULL != l) {
307    if (NULL != r) {
308      back = dk4strw_pathcmp((const wchar_t *)l, (const wchar_t *)r);
309    } else {
310      back = 1;
311    }
312  } else {
313    if (NULL != r) { back = -1; }
314  }
315  return back;
316}
317
318#endif
319
320
321
322static
323void
324dk4dir_wc_clean_storage(dk4_sto_t *s, dk4_sto_it_t *i)
325{
326  wchar_t	*n;
327  if (NULL != s) {
328    if (NULL != i) {
329      dk4sto_it_reset(i);
330      while (NULL != (n = (wchar_t *)dk4sto_it_next(i))) {
331        dk4mem_free(n);
332      }
333      dk4sto_it_close(i);
334    }
335    dk4sto_close(s);
336  }
337}
338
339
340
341void
342dk4dir_wc_close(dk4_dir_wc_t *dptr)
343{
344#if	DK4_USE_ASSERT
345  assert(NULL != dptr);
346#endif
347  if (NULL != dptr) {
348    dk4mem_release(dptr->path);
349    dk4dir_wc_clean_storage(dptr->s_dir, dptr->i_dir);
350    dptr->s_dir = NULL; dptr->i_dir = NULL;
351    dk4dir_wc_clean_storage(dptr->s_file, dptr->i_file);
352    dptr->s_file = NULL; dptr->i_file = NULL;
353    dk4mem_free(dptr);
354  }
355}
356
357
358
359#if DK4_ON_WINDOWS
360/**	Fixed texts used by the module.
361*/
362static const wchar_t * const	dk4dir_wc_kw[] = {
363$!string-table	prefix=L
364#
365#	0	Current directory
366#
367.
368#
369#	1	Parent directory
370#
371..
372#
373#	2	Separator for non-Windows systems
374#
375/
376#
377#	3	Separator for Windows systems
378#
379\\
380#
381#	4	Pattern for all files
382#
383*
384$!end
385};
386
387
388
389static
390int
391dk4dir_wc_fill_with_pab(
392  dk4_dir_wc_t	*dptr,
393  int		 om,
394  wchar_t	*pab,
395  size_t	 szpab,
396  dk4_er_t	*erp
397)
398{
399  WIN32_FIND_DATAW	 ffdata;		/* Find result */
400  HANDLE		 ffres;			/* Result from findfirst */
401  dkChar		*nn;			/* Copy of name, dyn */
402  DWORD			 attr;			/* File attributes */
403  size_t		 slen;			/* Entry name length. */
404  int			 isdir;			/* Flag: Entry is directory */
405  int			 back	= 0;
406  $? "+ dk4dir_wc_fill_directory"
407#if	DK4_USE_ASSERT
408  assert(NULL != dptr);
409  assert(NULL != pab);
410  assert(0 < szpab);
411#endif
412  if (dk4strw_cpy_s(pab, szpab, dptr->path, erp)) {
413    if (dk4strw_cat_s(pab, szpab, dk4dir_wc_kw[3], erp)) {
414      if (dk4strw_cat_s(pab, szpab, dk4dir_wc_kw[4], erp)) {
415	ffres = FindFirstFileW(pab, &ffdata);
416	if (INVALID_HANDLE_VALUE != ffres) {
417	  back = 1;
418	  do {
419	    if (0 != dk4strw_cmp(dk4dir_wc_kw[0], ffdata.cFileName)) {
420	      if (0 != dk4strw_cmp(dk4dir_wc_kw[1], ffdata.cFileName)) {
421	        slen = dk4strw_len(ffdata.cFileName);
422		if (slen > dptr->maxlen) { dptr->maxlen = slen; }
423	        nn = dk4strw_dup(ffdata.cFileName, erp);
424		if (NULL != nn) {
425		  isdir = 0;
426		  attr = ffdata.dwFileAttributes;
427		  if (0 != (FILE_ATTRIBUTE_DIRECTORY & attr)) {
428		    isdir = 1;
429		    if (0 != (FILE_ATTRIBUTE_REPARSE_POINT & attr)) {
430		      if (0 != (DK4_DIR_SYMLINK_DIR_AS_FILE & om)) {
431		        isdir = 0;
432		      }
433		    }
434		  }
435		  if (0 != isdir) {
436		    if (0 == dk4sto_add(dptr->s_dir, (void *)nn, erp)) {
437		      back = 0;
438		      dk4mem_free(nn);
439		    }
440		  } else {
441		    if (0 == dk4sto_add(dptr->s_file, (void *)nn, erp)) {
442		      back = 0;
443		      dk4mem_free(nn);
444		    }
445		  }
446		} else {
447		  back = 0;
448		}
449	      }
450	    }
451	  } while(FindNextFileW(ffres, &ffdata));
452	  FindClose(ffres);
453	} else {
454	  /* ERROR: FindFirstFile failed */
455	  dk4error_set_ldetails(
456	    erp, DK4_E_FINDFIRSTFILE_FAILED,
457	    (long)((unsigned long)GetLastError())
458	  );
459	}
460      }
461    }
462  }
463  $? "- dk4dir_wc_fill_directory %d", back
464  return back;
465}
466
467
468
469static
470int
471dk4dir_wc_fill_with_local(dk4_dir_wc_t *dptr, int om, dk4_er_t *erp)
472{
473  wchar_t	pab[DK4_MAX_PATH];
474#if	DK4_USE_ASSERT
475  assert(NULL != dptr);
476#endif
477  return (dk4dir_wc_fill_with_pab(dptr, om, pab, DK4_SIZEOF(pab,wchar_t), erp));
478}
479
480
481
482/**	Fill directory structure with data about subdirectories and files.
483	@param	dptr	Directory structure to fill.
484	@param	om	Opening mode.
485	@param	erp	Error report, may be NULL.
486	@return	1 on success, 0 on errors.
487*/
488static
489int
490dk4dir_wc_fill_directory(dk4_dir_wc_t *dptr, int om, dk4_er_t *erp)
491{
492  dk4_er_t	 er;
493  wchar_t	*pab;
494  size_t	 dptrlgt;
495  int		 back	= 0;
496
497#if	DK4_USE_ASSERT
498  assert(NULL != dptr);
499#endif
500  dptrlgt = dk4strw_len(dptr->path);
501  if ((DK4_MAX_PATH - 2) > dptrlgt) {
502    back = dk4dir_wc_fill_with_local(dptr, om, erp);
503  } else {
504    dk4error_init(&er);
505    dptrlgt = dk4ma_size_t_add(dptrlgt, 3, &er);
506    if (DK4_E_NONE == er.ec) {
507      pab = dk4mem_new(wchar_t, dptrlgt, erp);
508      if (NULL != pab) {
509        back = dk4dir_wc_fill_with_pab(dptr, om, pab, dptrlgt, erp);
510        dk4mem_free(pab);
511      }
512    } else {
513      dk4error_copy(erp, &er);
514    }
515  }
516  return back;
517}
518
519#endif
520
521
522
523dk4_dir_wc_t *
524dk4dir_wc_open(
525  const wchar_t	*path,
526#if DK4_ON_WINDOWS
527  int		 om,
528#else
529  int		 DK4_ARG_UNUSED(om),
530#endif
531  dk4_er_t	*erp
532)
533{
534  dk4_dir_wc_t	*back	= NULL;
535#if DK4_ON_WINDOWS
536  int		 ok	= 0;
537#endif
538#if	DK4_USE_ASSERT
539  assert(NULL != path);
540#endif
541  if (NULL != path) {
542#if DK4_ON_WINDOWS
543    back = dk4mem_new(dk4_dir_wc_t, 1, erp);
544    if (NULL != back) {
545      back->path = NULL;
546      back->s_dir = NULL;
547      back->i_dir = NULL;
548      back->s_file = NULL;
549      back->i_file = NULL;
550      back->path = dk4strw_dup(path, erp);
551      back->maxlen = 0;
552      if (NULL != back->path) {
553        back->s_dir = dk4sto_open(erp);
554	if (NULL != back->s_dir) {
555	  if (0 != (DK4_DIR_OPEN_SORTED & om)) {
556	    dk4sto_set_comp(back->s_dir, dk4dir_wc_compare, 0);
557	  }
558	  back->i_dir = dk4sto_it_open(back->s_dir, erp);
559	  if (NULL != back->i_dir) {
560	    back->s_file = dk4sto_open(erp);
561	    if (NULL != back->s_file) {
562	      if (0 != (DK4_DIR_OPEN_SORTED & om)) {
563	        dk4sto_set_comp(back->s_file, dk4dir_wc_compare, 0);
564	      }
565	      back->i_file = dk4sto_it_open(back->s_file, erp);
566	      if (NULL != back->i_file) {
567	        ok = dk4dir_wc_fill_directory(back, om, erp);
568	      }
569	    }
570	  }
571	}
572      }
573      if (0 == ok) {
574        dk4dir_wc_close(back);
575	back = NULL;
576      }
577    }
578#else
579    DK4_UNUSED_ARG(om)
580    dk4error_set_simple_error_code(erp, DK4_E_NOT_SUPPORTED);
581#endif
582  } else {
583    dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS);
584  }
585  return back;
586}
587
588
589
590void
591dk4dir_wc_reset(dk4_dir_wc_t *dptr)
592{
593#if	DK4_USE_ASSERT
594  assert(NULL != dptr);
595#endif
596  if (NULL != dptr) {
597    if ((NULL != dptr->s_dir) && (NULL != dptr->i_dir)) {
598      dk4sto_it_reset(dptr->i_dir);
599    }
600    if ((NULL != dptr->s_file) && (NULL != dptr->i_file)) {
601      dk4sto_it_reset(dptr->i_file);
602    }
603  }
604}
605
606
607
608const wchar_t *
609dk4dir_wc_get_path(dk4_dir_wc_t const *dptr)
610{
611  const wchar_t	*back	= NULL;
612#if	DK4_USE_ASSERT
613  assert(NULL != dptr);
614#endif
615  if (NULL != dptr) {
616    back = (const wchar_t *)(dptr->path);
617  }
618  return back;
619}
620
621
622
623const wchar_t *
624dk4dir_wc_next_dir(dk4_dir_wc_t *dptr)
625{
626  const wchar_t	*back = NULL;
627#if	DK4_USE_ASSERT
628  assert(NULL != dptr);
629#endif
630  if (NULL != dptr) {
631    if ((NULL != dptr->s_dir) && (NULL != dptr->i_dir)) {
632      back = (const wchar_t *)dk4sto_it_next(dptr->i_dir);
633    }
634  }
635  return back;
636}
637
638
639
640const wchar_t *
641dk4dir_wc_next_file(dk4_dir_wc_t *dptr)
642{
643  const wchar_t	*back = NULL;
644#if	DK4_USE_ASSERT
645  assert(NULL != dptr);
646#endif
647  if (NULL != dptr) {
648    if ((NULL != dptr->s_file) && (NULL != dptr->i_file)) {
649      back = (const wchar_t *)dk4sto_it_next(dptr->i_file);
650    }
651  }
652  return back;
653}
654
655
656
657void
658dk4dir_wc_skip_files(dk4_dir_wc_t *dptr)
659{
660#if	DK4_USE_ASSERT
661  assert(NULL != dptr);
662#endif
663  if (NULL != dptr) {
664    dk4dir_wc_clean_storage(dptr->s_file, dptr->i_file);
665    dptr->s_file = NULL; dptr->i_file = NULL;
666  }
667}
668
669
670
671size_t
672dk4dir_wc_get_max_entry_length(dk4_dir_wc_t const *dptr)
673{
674  size_t	 back = 0;
675#if	DK4_USE_ASSERT
676  assert(NULL != dptr);
677#endif
678  if (NULL != dptr) {
679    back = dptr->maxlen;
680  }
681  return back;
682}
683
684
685
686int
687dk4dir_wc_full_name_buffer(
688  wchar_t		*buffer,
689  size_t		 szbuf,
690  dk4_dir_wc_t	const	*pdir,
691  wchar_t	const	*fn,
692  dk4_er_t		*erp
693)
694{
695  int		 back	= 0;
696#if	DK4_USE_ASSERT
697  assert(NULL != pdir);
698  assert(NULL != fn);
699  assert(NULL != buffer);
700  assert(0 < szbuf);
701#endif
702  if ((NULL != buffer) && (NULL != pdir) && (NULL != fn) && (0 < szbuf)) {
703    back = dk4pathw_concatenate_buffer(buffer, szbuf, pdir->path, fn, erp);
704  }
705  else {
706    dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS);
707  }
708  return back;
709}
710
711
712
713wchar_t *
714dk4dir_wc_full_name_new(
715  dk4_dir_wc_t	const	*pdir,
716  wchar_t	const	*fn,
717  dk4_er_t	*	erp
718)
719{
720  wchar_t *back	= NULL;
721#if	DK4_USE_ASSERT
722  assert(NULL != pdir);
723  assert(NULL != fn);
724#endif
725  if ((NULL != pdir) && (NULL != fn)) {
726    back = dk4pathw_concatenate_new(pdir->path, fn, erp);
727  }
728  else {
729    dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS);
730  }
731  return back;
732}
733
734