diff --git a/qse/include/qse/cmn/path.h b/qse/include/qse/cmn/path.h index ce38ab06..e78cf747 100644 --- a/qse/include/qse/cmn/path.h +++ b/qse/include/qse/cmn/path.h @@ -39,6 +39,16 @@ # define qse_basename(path) qse_wcsbasename(path) #endif +enum qse_canonpath_flag_t +{ + /** if the final output is . logically, return an empty path */ + QSE_CANONPATH_EMPTYSINGLEDOT = (1 << 0), + /** keep the .. segment in the path name */ + QSE_CANONPATH_KEEPDOUBLEDOTS = (1 << 1), + /** drop a trailing separator even if the source contains one */ + QSE_CANONPATH_DROPTRAILINGSEP = (1 << 2) +}; + #ifdef __cplusplus extern "C" { #endif @@ -100,10 +110,14 @@ int qse_isdrivecurpath ( * qse_printf (QSE_T("%s\n")); // prints /usr/bin/sh * @endcode * - * Note that the output is empty returning 0 if the input @a path is empty, - * whereas a single period is produced if canonicalization results in the - * current directory logically without a single period in the input @a path. - * For example, dir/.. is canonicalized to a single period. + * If #QSE_CANONPATH_EMPTYSINGLEDOT is clear in the @a flags, a single dot + * is produced if the input @path resolves to the current directory logically. + * For example, dir/.. is canonicalized to a single period; If it is set, + * an empty string is produced. Even a single period as an input produces + * an empty string if it is set. + * + * The output is empty returning 0 regardless of @a flags if the input + * @a path is empty. * * The caller must ensure that it is large enough to hold the resulting * canonical path before calling because this function does not check the @@ -117,7 +131,8 @@ int qse_isdrivecurpath ( */ qse_size_t qse_canonpath ( const qse_char_t* path, - qse_char_t* canon + qse_char_t* canon, + int flags ); #ifdef __cplusplus diff --git a/qse/include/qse/fs/dir.h b/qse/include/qse/fs/dir.h index 94e366d0..eb0760cc 100644 --- a/qse/include/qse/fs/dir.h +++ b/qse/include/qse/fs/dir.h @@ -67,6 +67,15 @@ struct qse_dir_t typedef struct qse_dir_t qse_dir_t; +enum qse_dir_option_t +{ + /**< don't follow a symbolic link in qse_dir_change() */ + QSE_DIR_NOFOLLOW = (1 << 0), + + /**< check directories against file system in qse_dir_change() */ + QSE_DIR_REALPATH = (1 << 1) +}; + #ifdef __cplusplus extern "C" { #endif diff --git a/qse/lib/cmn/path-canon.c b/qse/lib/cmn/path-canon.c index 045f9bc6..8c687570 100644 --- a/qse/lib/cmn/path-canon.c +++ b/qse/lib/cmn/path-canon.c @@ -61,7 +61,7 @@ int qse_isdrivecurpath (const qse_char_t* path) return 0; } -qse_size_t qse_canonpath (const qse_char_t* path, qse_char_t* canon) +qse_size_t qse_canonpath (const qse_char_t* path, qse_char_t* canon, int flags) { const qse_char_t* ptr; qse_char_t* dst; @@ -155,7 +155,8 @@ qse_size_t qse_canonpath (const qse_char_t* path, qse_char_t* canon) { /* eat up . */ } - else if (seglen == 2 && seg[0] == QSE_T('.') && seg[1] == QSE_T('.')) + else if (!(flags & QSE_CANONPATH_KEEPDOUBLEDOTS) && + seglen == 2 && seg[0] == QSE_T('.') && seg[1] == QSE_T('.')) { /* eat up the previous segment */ qse_char_t* tmp; @@ -242,11 +243,13 @@ qse_size_t qse_canonpath (const qse_char_t* path, qse_char_t* canon) } while (1); - if (dst > non_root_start && ISSEP(dst[-1]) && !ISSEP(ptr[-1])) + if (dst > non_root_start && ISSEP(dst[-1]) && + ((flags & QSE_CANONPATH_DROPTRAILINGSEP) || !ISSEP(ptr[-1]))) { /* if the canoncal path composed so far ends with a separator * and the original path didn't end with the separator, delete - * the ending separator. + * the ending separator. + * also delete it if QSE_CANONPATH_DROPTRAILINGSEP is set. * * dst > non_root_start: * there is at least 1 character after the root directory part. @@ -267,11 +270,14 @@ qse_size_t qse_canonpath (const qse_char_t* path, qse_char_t* canon) if (canon_len <= 0) { - /* when resolving to a single dot, a trailing separator is not - * retained though the orignal path name contains it. */ - canon[0] = QSE_T('.'); - canon[1] = QSE_T('\0'); - canon_len = 1; + if (!(flags & QSE_CANONPATH_EMPTYSINGLEDOT)) + { + /* when resolving to a single dot, a trailing separator is not + * retained though the orignal path name contains it. */ + canon[0] = QSE_T('.'); + canon[1] = QSE_T('\0'); + canon_len = 1; + } } else { diff --git a/qse/lib/fs/dir.c b/qse/lib/fs/dir.c index 225aac5a..4915d4c7 100644 --- a/qse/lib/fs/dir.c +++ b/qse/lib/fs/dir.c @@ -264,7 +264,7 @@ int qse_dir_change (qse_dir_t* dir, const qse_char_t* name) return -1; } - idx = qse_canonpath (dirname, dirname); + idx = qse_canonpath (dirname, dirname, 0); /* Put an asterisk after canonicalization to prevent side-effects. * otherwise, .\* would be transformed to * by qse_canonpath() */ dirname[idx-1] = QSE_T('*'); @@ -323,7 +323,7 @@ int qse_dir_change (qse_dir_t* dir, const qse_char_t* name) return -1; } - qse_canonpath (dirname, dirname); + qse_canonpath (dirname, dirname, 0); #if defined(QSE_CHAR_IS_MCHAR) mdirname = dirname; diff --git a/qse/samples/cmn/path01.c b/qse/samples/cmn/path01.c index 02169721..6965ad76 100644 --- a/qse/samples/cmn/path01.c +++ b/qse/samples/cmn/path01.c @@ -9,6 +9,18 @@ int path_main (int argc, qse_char_t* argv[]) { qse_char_t* canon; qse_size_t len; + int i; + int options[] = + { + 0, + QSE_CANONPATH_EMPTYSINGLEDOT, + QSE_CANONPATH_KEEPDOUBLEDOTS, + QSE_CANONPATH_DROPTRAILINGSEP, + QSE_CANONPATH_EMPTYSINGLEDOT | QSE_CANONPATH_KEEPDOUBLEDOTS, + QSE_CANONPATH_EMPTYSINGLEDOT | QSE_CANONPATH_DROPTRAILINGSEP, + QSE_CANONPATH_KEEPDOUBLEDOTS | QSE_CANONPATH_DROPTRAILINGSEP, + QSE_CANONPATH_EMPTYSINGLEDOT | QSE_CANONPATH_KEEPDOUBLEDOTS | QSE_CANONPATH_DROPTRAILINGSEP + }; if (argc != 2) { @@ -16,21 +28,32 @@ int path_main (int argc, qse_char_t* argv[]) return -1; } -/* - canon = QSE_MMGR_ALLOC (QSE_MMGR_GETDFL(), (qse_strlen(argv[1]) + 1) * QSE_SIZEOF(*canon)); - if (canon == QSE_NULL) + canon = QSE_MMGR_ALLOC ( + QSE_MMGR_GETDFL(), (qse_strlen(argv[1]) + 1) * QSE_SIZEOF(*canon)); + if (!canon) { + QSE_MMGR_FREE (QSE_MMGR_GETDFL(), canon); qse_fprintf (QSE_STDERR, QSE_T("Error: out of memory\n")); return -1; } - len = qse_canonpath (argv[1], canon); - qse_printf (QSE_T("[%s] => [%s] %d chars\n"), argv[1], canon, (int)len); - QSE_MMGR_FREE (QSE_MMGR_GETDFL(), canon); -*/ + for (i = 0; i < QSE_COUNTOF(options); i++) + { + len = qse_canonpath (argv[1], canon, options[i]); + qse_printf (QSE_T("OPT[%c%c%c] [%s]: [%5d] [%s]\n"), + ((options[i] & QSE_CANONPATH_EMPTYSINGLEDOT)? QSE_T('E'): QSE_T(' ')), + ((options[i] & QSE_CANONPATH_KEEPDOUBLEDOTS)? QSE_T('K'): QSE_T(' ')), + ((options[i] & QSE_CANONPATH_DROPTRAILINGSEP)? QSE_T('D'): QSE_T(' ')), + argv[1], (int)len, canon); + } + + QSE_MMGR_FREE (QSE_MMGR_GETDFL(), canon); + +#if 0 qse_printf (QSE_T("[%s] => "), argv[1]); - len = qse_canonpath (argv[1], argv[1]); + len = qse_canonpath (argv[1], argv[1], 0); qse_printf (QSE_T("[%s] %d chars\n"), argv[1], (int)len); +#endif return 0; }