%% options copyright owner = Dirk Krause copyright year = 2015-xxxx SPDX-License-Identifier: BSD-3-Clause %% header /** @file String operations for path names (wchar_t characters). */ #ifndef DK4CONF_H_INCLUDED #if DK4_BUILDING_DKTOOLS4 #include "dk4conf.h" #else #include #endif #endif #ifndef DK4TYPES_H_INCLUDED #if DK4_BUILDING_DKTOOLS4 #include "dk4types.h" #else #include #endif #endif #ifndef DK4ERROR_H_INCLUDED #if DK4_BUILDING_DKTOOLS4 #include "dk4error.h" #else #include #endif #endif #ifdef __cplusplus extern "C" { #endif /** Check whether a file name is an absolute path. CRT on Windows: Not used. @param path Path name to check. @return 1 for absolute path, 0 for other path. */ int dk4pathw_is_absolute(const wchar_t *path); /** Check whether a file name is a relative path. CRT on Windows: Not used. @param path Path name to check. @return 1 for absolute path, 0 for other path. */ int dk4pathw_is_relative(const wchar_t *path); /** Append path filename to path name already stored in buffer. Resolve . and .. for current directory and parent directory. CRT on Windows: Optional, disabling CRT degrades performance. @param buffer Buffer already containing a path. @param sz Buffer size. @param filename Relative file name to append to buffer. @param erp Error report, may be NULL. @return 1 on success, 0 on error. Error codes: - DK4_E_INVALID_ARGUMENTS
if buffer or filename is NULL or sz is 0, - DK4_E_MATH_OVERFLOW
if filename is too long, - DK4_E_MEMORY
if allocation of a filename copy fails, - DK4_E_SYNTAX
if too many .. in filename, - DK4_E_BUFFER_TOO_SMALL
if buffer size is too small. */ int dk4pathw_append( wchar_t *buffer, size_t sz, const wchar_t *filename, dk4_er_t *erp ); /** Find pointer to suffix. CRT on Windows: Not used. @param filename File name to find suffix for. @param erp Error report, may be NULL. @return Pointer to suffix dot if found, NULL otherwise. Error codes: - DK4_E_INVALID_ARGUMENTS
if filename is NULL, - DK4_E_NOT_FOUND
if the file name does not contain a suffix. */ wchar_t * dk4pathw_get_suffix(const wchar_t *filename, dk4_er_t *erp); /** Correct file name separators from slash to backslash on Windows, vice versa on other systems. CRT on Windows: Not used. @param filename File name to correct. */ void dk4pathw_correct_sep(wchar_t *filename); /** Check whether file name needs expansion on Windows. CRT on Windows: Not used. @param filename File name to check. @return 1 if expansion is necessary, 0 otherwise. */ int dk4pathw_must_expand(const wchar_t *filename); /** Create file name with specified suffix. @param pdst Destination buffer. @param szdst Size of pdst (number of wchar_t). @param srcname Source file name. @param newsu New file suffix. @param erp Error report, may be NULL. @return 1 on success, 0 on error. Error codes: - DK4_E_INVALID_ARGUMENTS
if pdst or srcname or newsu is NULL or szdst is 0, - DK4_E_BUFFER_TOO_SMALL
if the dst buffer is too small. - DK4_E_MATH_OVERFLOW
if a mathematical overflow occured in size calculation, */ int dk4pathw_set_suffix( wchar_t *pdst, size_t szdst, wchar_t const *srcname, wchar_t const *newsu, dk4_er_t *erp ); /** Create a dynamic copy of a file name with changed suffix. @param srcname Old file name. @param newsu New suffix. @param erp Error report, may be NULL. @return Valid pointer to changed file name on success, NULL on error. On success call dk4mem_free() on the pointer when no longer needed to release the memory. Error codes: - DK4_E_INVALID_ARGUMENTS
if srcname or newsu is NULL, - DK4_E_BUG
if a bug occured, - DK4_E_MATH_OVERFLOW
if a mathematical overflow occured in size calculation, - DK4_E_MEMORY_ALLOCATION_FAILED
with mem.elsize and mem.nelem set if there is not enough memory available. */ wchar_t * dk4pathw_dup_change_suffix( wchar_t const *srcname, wchar_t const *newsu, dk4_er_t *erp ); /** Calculate buffer size required to concatenate a directory name and a file name. @param dirname Directory name. @param filename File name. @param erp Error report, may be NULL. @return Buffer size for concatenated names including the finalizer byte on success, 0 on error. Error codes: - DK4_E_INVALID_ARGUMENTS
if dirname or filename is NULL, - DK4_E_MATH_OVERFLOW
if the calculation results in a numeric overflow. */ size_t dk4pathw_concatenate_size( wchar_t const *dirname, wchar_t const *filename, dk4_er_t *erp ); /** Concatenate a directory name and a file name into an existing buffer. This function simply concatenates directory name and file name, . and .. for current and parent directory are not resolved. @param buffer Result buffer for combined file name. @param szbuf Buffer size (number of wchar_t). @param dirn Directory name. @param filen File name. @param erp Error report, may be NULL. @return 1 on success, 0 on error. Error codes: - DK4_E_INVALID_ARGUMENTS
if buffer, dirn or filen is NULL or szbuf is 0, - DK4_E_BUFFER_TOO_SMALL
if the buffer is too small. */ int dk4pathw_concatenate_buffer( wchar_t *buffer, size_t szbuf, wchar_t const *dirn, wchar_t const *filen, dk4_er_t *erp ); /** Concatenate a directory name and a file name into newly allocated memory. This function simply concatenates directory name and file name, . and .. for current and parent directory are not resolved. @param dirn Directory name. @param filen File name. @param erp Error report, may be NULL. @return Valid pointer to newly allocated memory containing the concatenation on success, NULL on error. On success use dk4mem_free() to release the memory when done with it. Error codes: - DK4_E_INVALID_ARGUMENTS
if dirn or filen is NULL, - DK4_E_MATH_OVERFLOW
if the size calculation results in a numeric overflow. - DK4_E_MEMORY_ALLOCATION_FAILED
if the memory allocation failed, - DK4_E_BUFFER_TOO_SMALL
if the buffer is too small. */ wchar_t * dk4pathw_concatenate_new( wchar_t const *dirn, wchar_t const *filen, dk4_er_t *erp ); #ifdef __cplusplus } #endif %% module #include "dk4conf.h" #include "dk4pathw.h" #include "dk4mem.h" #include "dk4strw.h" #include "dk4maasz.h" #if DK4_HAVE_STRING_H #ifndef STRING_H_INCLUDED #include #define STRING_H_INCLUDED 1 #endif #endif #if DK4_HAVE_WCHAR_H #ifndef WCHAR_H_INCLUDED #include #define WCHAR_H_INCLUDED 1 #endif #endif #if DK4_HAVE_ASSERT_H #ifndef ASSERT_H_INCLUDED #include #define ASSERT_H_INCLUDED 1 #endif #endif #include "dk4unused.h" $!trace-include /** Constant keywords used by the dk4pathw module. */ static const wchar_t * const dk4pathw_kw[] = { #if DK4_HAVE_BACKSLASH_AS_SEP /* 0 */ L"\\", /* 1 */ L"/", #else /* 0 */ L"/", /* 1 */ L"\\", #endif /* 2 */ L".", /* 3 */ L"..", /* 4 */ L":" }; /** Get last character from a string. @param str String to test. @return Last character from string. */ static wchar_t dk4pathw_lastchar(const wchar_t *str) { wchar_t back = '\0'; #if DK4_USE_ASSERT assert(NULL != str); #endif if (str) { while (L'\0' != *str) { back = *(str++); } } return back; } int dk4pathw_is_absolute(const wchar_t *path) { int back = 0; #if DK4_ON_WINDOWS wchar_t c; #endif #if DK4_USE_ASSERT assert(NULL != path); #endif if (NULL != path) { #if DK4_ON_WINDOWS c = *path; if ( *(dk4pathw_kw[0]) == c ) { back = 1; } else { if (((L'a' <= c) && (L'z' >= c)) || ((L'A' <= c) && (L'Z' >= c))) { if ( *(dk4pathw_kw[4]) == path[1] ) { c = path[2]; if ( ( *(dk4pathw_kw[0]) == c ) || ( L'\0' == c ) ) { back = 1; } } } } #else if ( *(dk4pathw_kw[0]) == *path ) { back = 1; } #endif } return back; } int dk4pathw_is_relative(const wchar_t *path) { int back = 0; #if DK4_USE_ASSERT assert(NULL != path); #endif if (NULL != path) { if (NULL != dk4strw_chr(path, *(dk4pathw_kw[0]))) { if (0 == dk4pathw_is_absolute(path)) { back = 1; } } } return back; } int dk4pathw_append( wchar_t *buffer, size_t sz, const wchar_t *filename, dk4_er_t *erp ) { wchar_t *pc; /* Current path part to append */ wchar_t *pn; /* Next part to append */ wchar_t *pd; /* Part to delete */ wchar_t *mycp = NULL; /* Private copy */ #if DK4_ON_WINDOWS int ischr; #endif int back = 0; /* Function result */ int failed = 0; /* Definitely failed */ wchar_t lch; /* Last character from string */ #if DK4_USE_ASSERT assert(NULL != buffer); assert(0 < sz); assert(NULL != filename); #endif if ((NULL != buffer) && (NULL != filename) && (0 < sz)) { mycp = dk4strw_dup(filename, erp); if (NULL != mycp) { pc = mycp; while ((NULL != pc) && (0 == failed)) { pn = dk4strw_chr(pc, *(dk4pathw_kw[0])); if (NULL != pn) { *(pn++) = L'\0'; } if (0 != dk4strw_cmp(pc, dk4pathw_kw[2])) { if (0 == dk4strw_cmp(pc, dk4pathw_kw[3])) { /* Remove from buffer */ pd = dk4strw_rchr(buffer, *(dk4pathw_kw[0])); if (NULL != pd) { *pd = L'\0'; if (0 < dk4strw_len(&(pd[1]))) { #if DK4_ON_WINDOWS if (2 == dk4strw_len(buffer)) { ischr = 0; if ((L'A' <= buffer[0]) && (L'Z' >= buffer[0])) { ischr = 1; } if ((L'a' <= buffer[0]) && (L'z' >= buffer[0])) { ischr = 1; } if (1 == ischr) { if (L':' == buffer[1]) { buffer[2] = *(dk4pathw_kw[0]); buffer[3] = L'\0'; } } } #else if (0 == dk4strw_len(buffer)) { *pd = *(dk4pathw_kw[0]); pd++; *pd = L'\0'; } #endif } else { failed = 1; dk4error_set_simple_error_code(erp, DK4_E_SYNTAX); } } else { failed = 1; dk4error_set_simple_error_code(erp, DK4_E_SYNTAX); } } else { /* Add to buffer */ lch = dk4pathw_lastchar(buffer); if (lch != *(dk4pathw_kw[0])) { if (0 != dk4strw_cat_s(buffer, sz, dk4pathw_kw[0], erp)) { if (0 == dk4strw_cat_s(buffer, sz, pc, erp)) { failed = 1; } } else { failed = 1; } } else { if (0 == dk4strw_cat_s(buffer, sz, pc, erp)) { failed = 1; } } } } pc = pn; } if (0 == failed) { back = 1; } dk4mem_release(mycp); } } else { dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS); } return back; } wchar_t * dk4pathw_get_suffix(const wchar_t *filename, dk4_er_t *erp) { const wchar_t *back = NULL; #if DK4_USE_ASSERT assert(NULL != filename); #endif if (NULL != filename) { while (L'\0' != *filename) { if ((L'\\' == *filename) || (L'/' == *filename)) { back = NULL; } else { if (L'.' == *filename) { back = filename; } } filename++; } } else { dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS); } return ((wchar_t *)back); } void dk4pathw_correct_sep(wchar_t *filename) { #if DK4_USE_ASSERT assert(NULL != filename); #endif if (NULL != filename) { while (L'\0' != *filename) { #if DK4_HAVE_BACKSLASH_AS_SEP if (L'/' == *filename) { *filename = L'\\'; } #else if (L'\\' == *filename) { *filename = L'/'; } #endif filename++; } } } int dk4pathw_must_expand( #if DK4_ON_WINDOWS const wchar_t *filename #else const wchar_t * DK4_ARG_UNUSED(filename) #endif ) { #if DK4_ON_WINDOWS int back = 0; #if DK4_USE_ASSERT assert(NULL != filename); #endif if (NULL != filename) { if (L'\\' == filename[0]) { if (L'\\' == filename[1]) { if (L'?' == filename[2]) { if (L'\\' == filename[3]) { filename = &(filename[4]); } } } } while((L'\0' != *filename) && (0 == back)) { if (L'*' == *filename) { back = 1; } else { if (L'?' == *filename) { back = 1; } else { filename++; } } } } return back; #else DK4_UNUSED_ARG(filename) return 0; #endif } int dk4pathw_set_suffix( wchar_t *pdst, size_t szdst, wchar_t const *srcname, wchar_t const *newsu, dk4_er_t *erp ) { wchar_t *sp = NULL; int back = 0; #if DK4_USE_ASSERT assert(NULL != pdst); assert(0 < szdst); assert(NULL != srcname); assert(NULL != newsu); #endif if ((NULL == pdst) || (NULL == srcname) || (NULL == newsu) || (0 == szdst)) { dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS); goto finished; } if (0 == dk4strw_cpy_s(pdst, szdst, srcname, erp)) { goto finished; } sp = dk4pathw_get_suffix(pdst, NULL); if (NULL != sp) { *sp = L'\0'; } back = dk4strw_cat_s(pdst, szdst, newsu, erp); finished: return back; } wchar_t * dk4pathw_dup_change_suffix( wchar_t const *srcname, wchar_t const *newsu, dk4_er_t *erp ) { dk4_er_t er; wchar_t *sp; wchar_t *back = NULL; size_t lold; size_t lsuff; size_t lnew; #if 0 size_t i; #endif #if DK4_USE_ASSERT assert(NULL != srcname); assert(NULL != newsu); #endif if ((NULL == srcname) || (NULL == newsu)) { dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS); goto finished; } sp = dk4pathw_get_suffix(srcname, NULL); lold = dk4strw_len(srcname); lsuff = 0; if (NULL != sp) { lsuff = dk4strw_len(sp); } if (lsuff > lold) { /* BUG */ dk4error_set_simple_error_code(erp, DK4_E_BUG); goto finished; } lold -= lsuff; lsuff = dk4strw_len(newsu); dk4error_init(&er); lnew = dk4ma_size_t_add(lold, lsuff, &er); lnew = dk4ma_size_t_add(lnew, 1, &er); if (DK4_E_NONE != er.ec) { dk4error_copy(erp, &er); goto finished; } back = dk4mem_new(wchar_t,lnew,erp); if (NULL == back) { goto finished; } #if 0 /* 2018-04-04 Bugfix The new file name may be shorter than the original file name, so we must not copy the original name into the new buffer! */ dk4strw_cpy_s(back, lnew, srcname, NULL); sp = dk4pathw_get_suffix(back, NULL); if (NULL != sp) { *sp = L'\0'; } #endif #if 0 for (i = 0; i < lold; i++) { back[i] = srcname[i]; } #endif DK4_MEMCPY(back,srcname,(lold*sizeof(wchar_t))); back[lold] = L'\0'; dk4strw_cat_s(back, lnew, newsu, NULL); finished: return back; } size_t dk4pathw_concatenate_size( wchar_t const *dirname, wchar_t const *filename, dk4_er_t *erp ) { dk4_er_t er; size_t back = 0; size_t sldir = 0; size_t slfil = 0; size_t toadd = 2; #if DK4_USE_ASSERT assert(NULL != dirname); assert(NULL != filename); #endif if ((NULL != dirname) && (NULL != filename)) { sldir = dk4strw_len(dirname); slfil = dk4strw_len(filename); if (0 < sldir) { if (*(dk4pathw_kw[0]) == dirname[sldir - 1]) { toadd = 1; } } dk4error_init(&er); back = dk4ma_size_t_add( dk4ma_size_t_add(sldir, slfil, &er), toadd, &er ); if (DK4_E_NONE != er.ec) { dk4error_copy(erp, &er); back = 0; } } else { dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS); } return back; } int dk4pathw_concatenate_buffer( wchar_t *buffer, size_t szbuf, wchar_t const *dirn, wchar_t const *filen, dk4_er_t *erp ) { size_t sld; int back = 0; int sep = 1; #if DK4_USE_ASSERT assert(NULL != buffer); assert(0 < szbuf); assert(NULL != dirn); assert(NULL != filen); #endif if ((NULL != buffer) && (NULL != dirn) && (NULL != filen) && (0 != szbuf)) { sld = dk4strw_len(dirn); if (0 < sld) { if (dirn[sld - 1] == *(dk4pathw_kw[0])) { sep = 0; } } back = dk4strw_cpy_s(buffer, szbuf, dirn, erp); if (0 != back) { if (0 != sep) { back = dk4strw_cat_s(buffer, szbuf, dk4pathw_kw[0], erp); } } if (0 != back) { back = dk4strw_cat_s(buffer, szbuf, filen, erp); } } else { dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS); } if ((0 == back) && (NULL != buffer) && (0 < szbuf)) { buffer[0] = L'\0'; } return back; } wchar_t * dk4pathw_concatenate_new( wchar_t const *dirn, wchar_t const *filen, dk4_er_t *erp ) { wchar_t *back = NULL; size_t bl = 0; #if DK4_USE_ASSERT assert(NULL != dirn); assert(NULL != filen); #endif if ((NULL != dirn) && (NULL != filen)) { bl = dk4pathw_concatenate_size(dirn, filen, erp); if (0 != bl) { back = dk4mem_new(wchar_t,bl,erp); if (NULL != back) { if (0 == dk4pathw_concatenate_buffer(back, bl, dirn, filen, erp)) { dk4mem_free(back); back = NULL; } } } } else { dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS); } return back; }