1%%	options
2
3copyright owner	=	Dirk Krause
4copyright year	=	2015-xxxx
5SPDX-License-Identifier:	BSD-3-Clause
6
7
8%%	header
9
10/**	@file
11	String operations for path names
12	(8 bit characters).
13*/
14
15#ifndef DK4CONF_H_INCLUDED
16#if DK4_BUILDING_DKTOOLS4
17#include "dk4conf.h"
18#else
19#include <dktools-4/dk4conf.h>
20#endif
21#endif
22
23#ifndef DK4TYPES_H_INCLUDED
24#if DK4_BUILDING_DKTOOLS4
25#include <libdk4base/dk4types.h>
26#else
27#include <dktools-4/dk4types.h>
28#endif
29#endif
30
31#ifndef DK4ERROR_H_INCLUDED
32#if DK4_BUILDING_DKTOOLS4
33#include <libdk4base/dk4error.h>
34#else
35#include <dktools-4/dk4error.h>
36#endif
37#endif
38
39
40#ifdef __cplusplus
41extern "C" {
42#endif
43
44/**	Check whether a file name is an absolute path.
45	CRT on Windows: Not used.
46	@param	path	Path name to check.
47	@return	1 for absolute path, 0 for other path.
48*/
49int
50dk4path8_is_absolute(const char *path);
51
52/**	Check whether a file name is a relative path.
53	CRT on Windows: Not used.
54	@param	path	Path name to check.
55	@return	1 for absolute path, 0 for other path.
56*/
57int
58dk4path8_is_relative(const char *path);
59
60/**	Append path filename to path name already stored in buffer.
61	Resolve . and .. for current directory and parent directory.
62	CRT on Windows: Optional, disabling CRT degrades performance.
63	@param	buffer		Buffer already containing a path.
64	@param	sz		Buffer size.
65	@param	filename	Relative file name to append to buffer.
66	@param	erp		Error report, may be NULL.
67	@return	1 on success, 0 on error.
68
69	Error codes:
70	- DK4_E_INVALID_ARGUMENTS<br>
71	  if buffer or filename is NULL or sz is 0,
72	- DK4_E_MATH_OVERFLOW<br>
73	  if filename is too long,
74	- DK4_E_MEMORY<br>
75	  if allocation of a filename copy fails,
76	- DK4_E_SYNTAX<br>
77	  if too many .. in filename,
78	- DK4_E_BUFFER_TOO_SMALL<br>
79	  if buffer size is too small.
80*/
81int
82dk4path8_append(char *buffer, size_t sz, const char *filename, dk4_er_t *erp);
83
84/**	Find pointer to suffix.
85	CRT on Windows: Not used.
86	@param	filename	File name to find suffix for.
87	@param	erp		Error report, may be NULL.
88	@return	Pointer to suffix dot if found, NULL otherwise.
89
90	Error codes:
91	- DK4_E_INVALID_ARGUMENTS<br>
92	  if filename is NULL,
93	- DK4_E_NOT_FOUND<br>
94	  if the file name does not contain a suffix.
95*/
96char *
97dk4path8_get_suffix(const char *filename, dk4_er_t *erp);
98
99/**	Correct file name separators from slash to backslash on Windows,
100	vice versa on other systems.
101	CRT on Windows: Not used.
102	@param	filename	File name to correct.
103*/
104void
105dk4path8_correct_sep(char *filename);
106
107/**	Check whether file name needs expansion on Windows.
108	CRT on Windows: Not used.
109	@param	filename	File name to check.
110	@return	1 if expansion is necessary, 0 otherwise.
111*/
112int
113dk4path8_must_expand(const char *filename);
114
115/**	Create file name with specified suffix.
116	@param	pdst	Destination buffer.
117	@param	szdst	Size of pdst.
118	@param	srcname	Source file name.
119	@param	newsu	New file suffix.
120	@param	erp	Error report, may be NULL.
121	@return	1 on success, 0 on error.
122
123	Error codes:
124	- DK4_E_INVALID_ARGUMENTS<br>
125	  if pdst or srcname or newsu is NULL or szdst is 0,
126	- DK4_E_BUFFER_TOO_SMALL<br>
127	  if the dst buffer is too small.
128	- DK4_E_MATH_OVERFLOW<br>
129	  if a mathematical overflow occured in size calculation,
130*/
131int
132dk4path8_set_suffix(
133  char		*pdst,
134  size_t	 szdst,
135  char const	*srcname,
136  char const	*newsu,
137  dk4_er_t	*erp
138);
139
140/**	Create a dynamic copy of a file name with changed suffix.
141	@param	srcname	Old file name.
142	@param	newsu	New suffix.
143	@param	erp	Error report, may be NULL.
144	@return	Valid pointer to changed file name on success, NULL on error.
145	On success call dk4mem_free() on the pointer when no longer needed
146	to release the memory.
147
148	Error codes:
149	- DK4_E_INVALID_ARGUMENTS<br>
150	  if srcname or newsu is NULL,
151	- DK4_E_BUG<br>
152	  if a bug occured,
153	- DK4_E_MATH_OVERFLOW<br>
154	  if a mathematical overflow occured in size calculation,
155	- DK4_E_MEMORY_ALLOCATION_FAILED<br>
156	  with mem.elsize and mem.nelem set if there is not enough memory
157	  available.
158*/
159char *
160dk4path8_dup_change_suffix(
161  char const	*srcname,
162  char const	*newsu,
163  dk4_er_t	*erp
164);
165
166/**	Calculate buffer size required to concatenate a directory
167	name and a file name.
168	@param	dirname		Directory name.
169	@param	filename	File name.
170	@param	erp		Error report, may be NULL.
171	@return	Buffer size for concatenated names including the finalizer
172	byte on success, 0 on error.
173
174	Error codes:
175	- DK4_E_INVALID_ARGUMENTS<br>
176	  if dirname or filename is NULL,
177	- DK4_E_MATH_OVERFLOW<br>
178	  if the calculation results in a numeric overflow.
179*/
180size_t
181dk4path8_concatenate_size(
182  char const	*dirname,
183  char const	*filename,
184  dk4_er_t	*erp
185);
186
187/**	Concatenate a directory name and a file name into an existing buffer.
188	This function simply concatenates directory name and file name,
189	. and .. for current and parent directory are not resolved.
190	@param	buffer	Result buffer for combined file name.
191	@param	szbuf	Buffer size.
192	@param	dirn	Directory name.
193	@param	filen	File name.
194	@param	erp	Error report, may be NULL.
195	@return	1 on success, 0 on error.
196
197	Error codes:
198	- DK4_E_INVALID_ARGUMENTS<br>
199	  if buffer, dirn or filen is NULL or szbuf is 0,
200	- DK4_E_BUFFER_TOO_SMALL<br>
201	  if the buffer is too small.
202*/
203int
204dk4path8_concatenate_buffer(
205  char		*buffer,
206  size_t	 szbuf,
207  char const	*dirn,
208  char const	*filen,
209  dk4_er_t	*erp
210);
211
212/**	Concatenate a directory name and a file name into newly
213	allocated memory.
214	This function simply concatenates directory name and file name,
215	. and .. for current and parent directory are not resolved.
216	@param	dirn	Directory name.
217	@param	filen	File name.
218	@param	erp	Error report, may be NULL.
219	@return	Valid pointer to newly allocated memory containing the
220	concatenation on success, NULL on error.
221	On success use dk4mem_free() to release the memory when done
222	with it.
223
224	Error codes:
225	- DK4_E_INVALID_ARGUMENTS<br>
226	  if dirn or filen is NULL,
227	- DK4_E_MATH_OVERFLOW<br>
228	  if the size calculation results in a numeric overflow.
229	- DK4_E_MEMORY_ALLOCATION_FAILED<br>
230	  if the memory allocation failed,
231	- DK4_E_BUFFER_TOO_SMALL<br>
232	  if the buffer is too small.
233*/
234char *
235dk4path8_concatenate_new(
236  char const	*dirn,
237  char const	*filen,
238  dk4_er_t	*erp
239);
240
241#ifdef __cplusplus
242}
243#endif
244
245
246
247%%	module
248
249
250#include "dk4conf.h"
251#include <libdk4c/dk4path8.h>
252#include <libdk4base/dk4mem.h>
253#include <libdk4base/dk4str8.h>
254#include <libdk4ma/dk4maasz.h>
255
256#if DK4_HAVE_STRING_H
257#ifndef STRING_H_INCLUDED
258#include <string.h>
259#define	STRING_H_INCLUDED 1
260#endif
261#endif
262
263#if	DK4_HAVE_ASSERT_H
264#ifndef	ASSERT_H_INCLUDED
265#include <assert.h>
266#define	ASSERT_H_INCLUDED 1
267#endif
268#endif
269
270#include <libdk4base/dk4unused.h>
271
272
273
274$!trace-include
275
276
277
278/**	Constant keywords used by the dk4path8 module.
279*/
280static const char * const dk4path8_kw[] = {
281#if DK4_HAVE_BACKSLASH_AS_SEP
282/* 0 */	"\\",
283/* 1 */ "/",
284#else
285/* 0 */ "/",
286/* 1 */ "\\",
287#endif
288/* 2 */ ".",
289/* 3 */ "..",
290/* 4 */ ":"
291};
292
293
294
295/**	Get last character from a string.
296
297	CRT on Windows: Not used.
298	@param	str	String to test.
299	@return	Last character from string.
300*/
301static
302char
303dk4path8_lastchar(const char *str)
304{
305  char	back	=	'\0';
306#if	DK4_USE_ASSERT
307  assert(NULL != str);
308#endif
309  if (str) { while ('\0' != *str) { back = *(str++); } }
310  return back;
311}
312
313
314
315int
316dk4path8_is_absolute(const char *path)
317{
318  int		back	=	0;
319#if DK4_ON_WINDOWS
320  char		c;
321#endif
322#if	DK4_USE_ASSERT
323  assert(NULL != path);
324#endif
325  if (NULL != path) {
326#if DK4_ON_WINDOWS
327    c = *path;
328    if ( *(dk4path8_kw[0]) == c ) {
329      back = 1;
330    } else {
331      if ((('a' <= c) && ('z' >= c)) || (('A' <= c) && ('Z' >= c))) {
332        if ( *(dk4path8_kw[4]) == path[1] ) {
333	  c = path[2];
334	  if ( ( *(dk4path8_kw[0]) == c ) || ( '\0' == c ) ) {
335	    back = 1;
336	  }
337	}
338      }
339    }
340#else
341    if ( *(dk4path8_kw[0]) == *path ) {
342      back = 1;
343    }
344#endif
345  }
346  return back;
347}
348
349
350
351int
352dk4path8_is_relative(const char *path)
353{
354  int		back	=	0;
355#if	DK4_USE_ASSERT
356  assert(NULL != path);
357#endif
358  if (NULL != path) {
359    if (NULL != dk4str8_chr(path, *(dk4path8_kw[0]))) {
360      if (0 == dk4path8_is_absolute(path)) {
361        back = 1;
362      }
363    }
364  }
365  return back;
366}
367
368
369
370int
371dk4path8_append(char *buffer, size_t sz, const char *filename, dk4_er_t *erp)
372{
373  char		*pc;			/* Current path part to append */
374  char		*pn;			/* Next part to append */
375  char		*pd;			/* Part to delete */
376  char		*mycp	=	NULL;	/* Private copy */
377#if DK4_ON_WINDOWS
378  int		 ischr;
379#endif
380  int		 back	=	0;	/* Function result */
381  int		 failed	=	0;	/* Definitely failed */
382  char		 lch;			/* Last character from string */
383
384#if	DK4_USE_ASSERT
385  assert(NULL != buffer);
386  assert(0 < sz);
387  assert(NULL != filename);
388#endif
389  if ((NULL != buffer) && (NULL != filename) && (0 < sz)) {
390    mycp = dk4str8_dup(filename, erp);
391    if (NULL != mycp) {
392      pc = mycp;
393      while ((NULL != pc) && (0 == failed)) {
394        pn = dk4str8_chr(pc, *(dk4path8_kw[0]));
395	if (NULL != pn) { *(pn++) = '\0'; }
396	if (0 != dk4str8_cmp(pc, dk4path8_kw[2])) {
397	  if (0 == dk4str8_cmp(pc, dk4path8_kw[3])) {	/* Remove from buffer */
398	    pd = dk4str8_rchr(buffer, *(dk4path8_kw[0]));
399	    if (NULL != pd) {
400	      *pd = '\0';
401	      if (0 < dk4str8_len(&(pd[1]))) {
402#if DK4_ON_WINDOWS
403	        if (2 == dk4str8_len(buffer)) {
404	          ischr = 0;
405	          if (('A' <= buffer[0]) && ('Z' >= buffer[0])) {
406		    ischr = 1;
407		  }
408		  if (('a' <= buffer[0]) && ('z' >= buffer[0])) {
409		    ischr = 1;
410		  }
411		  if (1 == ischr) {
412		    if (':' == buffer[1]) {
413		      buffer[2] = *(dk4path8_kw[0]);
414		      buffer[3] = '\0';
415		    }
416		  }
417	        }
418#else
419	        if (0 == dk4str8_len(buffer)) {
420	          *pd = *(dk4path8_kw[0]);
421		  pd++;
422		  *pd = '\0';
423	        }
424#endif
425	      } else {
426	        failed = 1;
427		dk4error_set_simple_error_code(erp, DK4_E_SYNTAX);
428	      }
429	    } else {
430	      failed = 1;
431	      dk4error_set_simple_error_code(erp, DK4_E_SYNTAX);
432	    }
433	  } else {					/* Add to buffer */
434	    lch = dk4path8_lastchar(buffer);
435	    if (lch != *(dk4path8_kw[0])) {
436	      if (0 != dk4str8_cat_s(buffer, sz, dk4path8_kw[0], erp)) {
437	        if (0 == dk4str8_cat_s(buffer, sz, pc, erp)) {
438	          failed = 1;
439	        }
440	      } else {
441	        failed = 1;
442	      }
443	    } else {
444	      if (0 == dk4str8_cat_s(buffer, sz, pc, erp)) {
445	        failed = 1;
446	      }
447	    }
448	  }
449	}
450	pc = pn;
451      }
452      if (0 == failed) {
453        back = 1;
454      }
455      dk4mem_release(mycp);
456    }
457  } else {
458    dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS);
459  }
460  return back;
461}
462
463
464
465char *
466dk4path8_get_suffix(const char *filename, dk4_er_t *erp)
467{
468  const char		*back	= NULL;
469#if	DK4_USE_ASSERT
470  assert(NULL != filename);
471#endif
472  if (NULL != filename) {
473    while ('\0' != *filename) {
474      if (('\\' == *filename) || ('/' == *filename)) {
475        back = NULL;
476      } else {
477        if ('.' == *filename) {
478	  back = filename;
479	}
480      }
481      filename++;
482    }
483    if (NULL == back) {
484      dk4error_set_simple_error_code(erp, DK4_E_NOT_FOUND);
485    }
486  } else {
487    dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS);
488  }
489  return ((char *)back);
490}
491
492
493
494void
495dk4path8_correct_sep(char *filename)
496{
497#if	DK4_USE_ASSERT
498  assert(NULL != filename);
499#endif
500  if (NULL != filename) {
501    while ('\0' != *filename) {
502#if DK4_HAVE_BACKSLASH_AS_SEP
503      if ('/' == *filename) { *filename = '\\'; }
504#else
505      if ('\\' == *filename) { *filename = '/'; }
506#endif
507      filename++;
508    }
509  }
510}
511
512
513
514int
515dk4path8_must_expand(
516#if DK4_ON_WINDOWS
517  const char *filename
518#else
519  const char * DK4_ARG_UNUSED(filename)
520#endif
521)
522{
523#if DK4_ON_WINDOWS
524  int back = 0;
525#if	DK4_USE_ASSERT
526  assert(NULL != filename);
527#endif
528  if (NULL != filename) {
529    while(('\0' != *filename) && (0 == back)) {
530      switch (*filename) {
531        case '*': case '?': {
532	  back = 1;
533	} break;
534	default: {
535	  filename++;
536	} break;
537      }
538    }
539  }
540  return back;
541#else
542  DK4_UNUSED_ARG(filename)
543  return 0;
544#endif
545}
546
547
548
549int
550dk4path8_set_suffix(
551  char		*pdst,
552  size_t	 szdst,
553  char const	*srcname,
554  char const	*newsu,
555  dk4_er_t	*erp
556)
557{
558  char		*sp	= NULL;
559  int		 back	= 0;
560#if	DK4_USE_ASSERT
561  assert(NULL != pdst);
562  assert(0 < szdst);
563  assert(NULL != srcname);
564  assert(NULL != newsu);
565#endif
566  if ((NULL == pdst) || (NULL == srcname) || (NULL == newsu) || (0 == szdst)) {
567    dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS);
568    goto finished;
569  }
570  if (0 == dk4str8_cpy_s(pdst, szdst, srcname, erp)) {
571    goto finished;
572  }
573  sp = dk4path8_get_suffix(pdst, NULL);
574  if (NULL != sp) {
575    *sp = '\0';
576  }
577  back = dk4str8_cat_s(pdst, szdst, newsu, erp);
578
579  finished:
580  return back;
581}
582
583
584
585char *
586dk4path8_dup_change_suffix(
587  char const	*srcname,
588  char const	*newsu,
589  dk4_er_t	*erp
590)
591{
592  dk4_er_t	 er;
593  char		*sp;
594  char		*back	= NULL;
595  size_t	 lold;
596  size_t	 lsuff;
597  size_t	 lnew;
598
599#if	DK4_USE_ASSERT
600  assert(NULL != srcname);
601  assert(NULL != newsu);
602#endif
603  if ((NULL == srcname) || (NULL == newsu)) {
604    dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS);
605    goto finished;
606  }
607  sp = dk4path8_get_suffix(srcname, NULL);
608  lold = strlen(srcname);
609  lsuff = 0;
610  if (NULL != sp) {
611    lsuff = strlen(sp);
612  }
613  if (lsuff > lold) {
614    /* BUG */
615    dk4error_set_simple_error_code(erp, DK4_E_BUG);
616    goto finished;
617  }
618  lold -= lsuff;
619  lsuff = strlen(newsu);
620  dk4error_init(&er);
621  lnew = dk4ma_size_t_add(lold, lsuff, &er);
622  lnew = dk4ma_size_t_add(lnew, 1, &er);
623  if (DK4_E_NONE != er.ec) {
624    dk4error_copy(erp, &er);
625    goto finished;
626  }
627  back = dk4mem_new(char,lnew,erp);
628  if (NULL == back) {
629    goto finished;
630  }
631  /*	2018-04-04 Bugfix
632  	The new file name may be shorter than the original
633	file name, so we must not copy the original name
634	into the new buffer!
635  */
636#if 0
637  /*	2018-04-04 Performance
638	Performance should be better using strncpy.
639  */
640  for (i = 0; i < lold; i++) { back[i] = srcname[i]; }
641#endif
642#if 0
643  strncpy(back, srcname, lold);
644#endif
645  DK4_MEMCPY(back,srcname,lold);
646  back[lold] = '\0';
647  strcat(back, newsu);
648  finished:
649  return back;
650}
651
652
653
654size_t
655dk4path8_concatenate_size(
656  char const	*dirname,
657  char const	*filename,
658  dk4_er_t	*erp
659)
660{
661  dk4_er_t	 er;		/* Test for math overflow */
662  size_t	 back	= 0;	/* Function result */
663  size_t	 sldir	= 0;	/* Length of dirname */
664  size_t	 slfil	= 0;	/* Length of filename */
665  size_t	 toadd	= 2;	/* Path separator and finalizer byte */
666  $? "+ dk4path8_concatenate_size"
667#if	DK4_USE_ASSERT
668  assert(NULL != dirname);
669  assert(NULL != filename);
670#endif
671  if ((NULL != dirname) && (NULL != filename)) {
672    sldir = dk4str8_len(dirname);
673    slfil = dk4str8_len(filename);
674    if (0 < sldir) {
675      if (*(dk4path8_kw[0]) == dirname[sldir - 1]) { toadd = 1; }
676    }
677    dk4error_init(&er);
678    back = dk4ma_size_t_add( dk4ma_size_t_add(sldir, slfil, &er), toadd, &er );
679    if (DK4_E_NONE != er.ec) {
680      dk4error_copy(erp, &er);
681      back = 0;
682    }
683  }
684  else {
685    dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS);
686  }
687  $? "- dk4path8_concatenate_size %u", (unsigned)back
688  return back;
689}
690
691
692
693int
694dk4path8_concatenate_buffer(
695  char		*buffer,
696  size_t	 szbuf,
697  char const	*dirn,
698  char const	*filen,
699  dk4_er_t	*erp
700)
701{
702  size_t	 sld;
703  int		 back	= 0;
704  int		 sep	= 1;
705  $? "+ dk4path8_concatenate_buffer \"%!8s\" \"%!8s\"", TR_8STR(dirn), TR_8STR(filen)
706#if	DK4_USE_ASSERT
707  assert(NULL != buffer);
708  assert(0 < szbuf);
709  assert(NULL != dirn);
710  assert(NULL != filen);
711#endif
712  if ((NULL != buffer) && (NULL != dirn) && (NULL != filen) && (0 != szbuf)) {
713    sld = dk4str8_len(dirn);
714    if (0 < sld) { if (dirn[sld - 1] == *(dk4path8_kw[0])) { sep = 0; } }
715    back = dk4str8_cpy_s(buffer, szbuf, dirn, erp);
716    if (0 != back) {
717      if (0 != sep) {
718        back = dk4str8_cat_s(buffer, szbuf, dk4path8_kw[0], erp);
719      }
720    }
721    if (0 != back) {
722      back = dk4str8_cat_s(buffer, szbuf, filen, erp);
723    }
724  }
725  else {
726    dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS);
727  }
728  if ((0 == back) && (NULL != buffer) && (0 < szbuf)) { buffer[0] = '\0'; }
729  $? "- dk4path8_concatenate_buffer %d", back
730  return back;
731}
732
733
734
735char *
736dk4path8_concatenate_new(
737  char const	*dirn,
738  char const	*filen,
739  dk4_er_t	*erp
740)
741{
742  char		*back	= NULL;
743  size_t	 bl	= 0;
744  $? "+ dk4path8_concatenate_new dir=\"%!8s\" f=\"%!8s\"", TR_8STR(dirn), TR_8STR(filen)
745#if	DK4_USE_ASSERT
746  assert(NULL != dirn);
747  assert(NULL != filen);
748#endif
749  if ((NULL != dirn) && (NULL != filen)) {
750    bl = dk4path8_concatenate_size(dirn, filen, erp);
751    if (0 != bl) {
752      back = dk4mem_new(char,bl,erp);
753      if (NULL != back) {
754        if (0 == dk4path8_concatenate_buffer(back, bl, dirn, filen, erp)) {
755	  dk4mem_free(back);
756	  back = NULL;
757	}
758#if TRACE_DEBUG
759	if (NULL != back) {
760	  $? ". path = \"%!8s\"", back
761	}
762#endif
763      }
764    }
765  }
766  else {
767    dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS);
768  }
769  $? "- dk4path8_concatenate_new %d", TR_IPTR(back)
770  return back;
771}
772
773