/* 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 * * 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 #include #include #include #include #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; }