| /* dirname.c | |
| * | |
| * $Id: dirname.c,v 1.2 2007/03/08 23:15:58 keithmarshall Exp $ | |
| * | |
| * Provides an implementation of the "dirname" function, conforming | |
| * to SUSv3, with extensions to accommodate Win32 drive designators, | |
| * and suitable for use on native Microsoft(R) Win32 platforms. | |
| * | |
| * Written by Keith Marshall <keithmarshall@users.sourceforge.net> | |
| * | |
| * This is free software. You may redistribute and/or modify it as you | |
| * see fit, without restriction of copyright. | |
| * | |
| * This software is provided "as is", in the hope that it may be useful, | |
| * but WITHOUT WARRANTY OF ANY KIND, not even any implied warranty of | |
| * MERCHANTABILITY, nor of FITNESS FOR ANY PARTICULAR PURPOSE. At no | |
| * time will the author accept any form of liability for any damages, | |
| * however caused, resulting from the use of this software. | |
| * | |
| */ | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <libgen.h> | |
| #include <locale.h> | |
| #ifndef __cdecl /* If compiling on any non-Win32 platform ... */ | |
| #define __cdecl /* this may not be defined. */ | |
| #endif | |
| char * __cdecl | |
| dirname(char *path) | |
| { | |
| static char *retfail = NULL; | |
| size_t len; | |
| /* to handle path names for files in multibyte character locales, | |
| * we need to set up LC_CTYPE to match the host file system locale. */ | |
| char *locale = setlocale (LC_CTYPE, NULL); | |
| if (locale != NULL) | |
| locale = strdup (locale); | |
| setlocale (LC_CTYPE, ""); | |
| if (path && *path) | |
| { | |
| /* allocate sufficient local storage space, | |
| * in which to create a wide character reference copy of path. */ | |
| wchar_t refcopy[1 + (len = mbstowcs (NULL, path, 0))]; | |
| /* create the wide character reference copy of path */ | |
| wchar_t *refpath = refcopy; | |
| len = mbstowcs (refpath, path, len); | |
| refcopy[len] = L'\0'; | |
| /* SUSv3 identifies a special case, where path is exactly equal to "//"; | |
| * (we will also accept "\\" in the Win32 context, but not "/\" or "\/", | |
| * and neither will we consider paths with an initial drive designator). | |
| * For this special case, SUSv3 allows the implementation to choose to | |
| * return "/" or "//", (or "\" or "\\", since this is Win32); we will | |
| * simply return the path unchanged, (i.e. "//" or "\\"). */ | |
| if (len > 1 && (refpath[0] == L'/' || refpath[0] == L'\\')) | |
| { | |
| if (refpath[1] == refpath[0] && refpath[2] == L'\0') | |
| { | |
| setlocale (LC_CTYPE, locale); | |
| free (locale); | |
| return path; | |
| } | |
| } | |
| /* For all other cases ... | |
| * step over the drive designator, if present ... */ | |
| else if (len > 1 && refpath[1] == L':') | |
| { | |
| /* FIXME: maybe should confirm *refpath is a valid drive designator. */ | |
| refpath += 2; | |
| } | |
| /* check again, just to ensure we still have a non-empty path name ... */ | |
| if (*refpath) | |
| { | |
| # undef basename | |
| # define basename __the_basename /* avoid shadowing. */ | |
| /* reproduce the scanning logic of the "basename" function | |
| * to locate the basename component of the current path string, | |
| * (but also remember where the dirname component starts). */ | |
| wchar_t *refname, *basename; | |
| for (refname = basename = refpath; *refpath; ++refpath) | |
| { | |
| if (*refpath == L'/' || *refpath == L'\\') | |
| { | |
| /* we found a dir separator ... | |
| * step over it, and any others which immediately follow it. */ | |
| while (*refpath == L'/' || *refpath == L'\\') | |
| ++refpath; | |
| /* if we didn't reach the end of the path string ... */ | |
| if (*refpath) | |
| /* then we have a new candidate for the base name. */ | |
| basename = refpath; | |
| else | |
| /* we struck an early termination of the path string, | |
| * with trailing dir separators following the base name, | |
| * so break out of the for loop, to avoid overrun. */ | |
| break; | |
| } | |
| } | |
| /* now check, | |
| * to confirm that we have distinct dirname and basename components. */ | |
| if (basename > refname) | |
| { | |
| /* and, when we do ... | |
| * backtrack over all trailing separators on the dirname component, | |
| * (but preserve exactly two initial dirname separators, if identical), | |
| * and add a NUL terminator in their place. */ | |
| do --basename; | |
| while (basename > refname && (*basename == L'/' || *basename == L'\\')); | |
| if (basename == refname && (refname[0] == L'/' || refname[0] == L'\\') | |
| && refname[1] == refname[0] && refname[2] != L'/' && refname[2] != L'\\') | |
| ++basename; | |
| *++basename = L'\0'; | |
| /* if the resultant dirname begins with EXACTLY two dir separators, | |
| * AND both are identical, then we preserve them. */ | |
| refpath = refcopy; | |
| while ((*refpath == L'/' || *refpath == L'\\')) | |
| ++refpath; | |
| if ((refpath - refcopy) > 2 || refcopy[1] != refcopy[0]) | |
| refpath = refcopy; | |
| /* and finally ... | |
| * we remove any residual, redundantly duplicated separators from the dirname, | |
| * reterminate, and return it. */ | |
| refname = refpath; | |
| while (*refpath) | |
| { | |
| if ((*refname++ = *refpath) == L'/' || *refpath++ == L'\\') | |
| { | |
| while (*refpath == L'/' || *refpath == L'\\') | |
| ++refpath; | |
| } | |
| } | |
| *refname = L'\0'; | |
| /* finally ... | |
| * transform the resolved dirname back into the multibyte char domain, | |
| * restore the caller's locale, and return the resultant dirname. */ | |
| if ((len = wcstombs( path, refcopy, len )) != (size_t)(-1)) | |
| path[len] = '\0'; | |
| } | |
| else | |
| { | |
| /* either there were no dirname separators in the path name, | |
| * or there was nothing else ... */ | |
| if (*refname == L'/' || *refname == L'\\') | |
| { | |
| /* it was all separators, so return one. */ | |
| ++refname; | |
| } | |
| else | |
| { | |
| /* there were no separators, so return '.'. */ | |
| *refname++ = L'.'; | |
| } | |
| /* add a NUL terminator, in either case, | |
| * then transform to the multibyte char domain, | |
| * using our own buffer. */ | |
| *refname = L'\0'; | |
| retfail = realloc (retfail, len = 1 + wcstombs (NULL, refcopy, 0)); | |
| wcstombs (path = retfail, refcopy, len); | |
| } | |
| /* restore caller's locale, clean up, and return the resolved dirname. */ | |
| setlocale (LC_CTYPE, locale); | |
| free (locale); | |
| return path; | |
| } | |
| # undef basename | |
| } | |
| /* path is NULL, or an empty string; default return value is "." ... | |
| * return this in our own buffer, regenerated by wide char transform, | |
| * in case the caller trashed it after a previous call. | |
| */ | |
| retfail = realloc (retfail, len = 1 + wcstombs (NULL, L".", 0)); | |
| wcstombs (retfail, L".", len); | |
| /* restore caller's locale, clean up, and return the default dirname. */ | |
| setlocale (LC_CTYPE, locale); | |
| free (locale); | |
| return retfail; | |
| } |