184 lines
6.6 KiB
C
184 lines
6.6 KiB
C
|
/* 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;
|
||
|
}
|