• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* dirname.c
2  *
3  * $Id: dirname.c,v 1.2 2007/03/08 23:15:58 keithmarshall Exp $
4  *
5  * Provides an implementation of the "dirname" function, conforming
6  * to SUSv3, with extensions to accommodate Win32 drive designators,
7  * and suitable for use on native Microsoft(R) Win32 platforms.
8  *
9  * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
10  *
11  * This is free software.  You may redistribute and/or modify it as you
12  * see fit, without restriction of copyright.
13  *
14  * This software is provided "as is", in the hope that it may be useful,
15  * but WITHOUT WARRANTY OF ANY KIND, not even any implied warranty of
16  * MERCHANTABILITY, nor of FITNESS FOR ANY PARTICULAR PURPOSE.  At no
17  * time will the author accept any form of liability for any damages,
18  * however caused, resulting from the use of this software.
19  *
20  */
21 
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <libgen.h>
26 #include <locale.h>
27 
28 #ifndef __cdecl  /* If compiling on any non-Win32 platform ... */
29 #define __cdecl  /* this may not be defined.                   */
30 #endif
31 
32 char * __cdecl
dirname(char * path)33 dirname(char *path)
34 {
35   static char *retfail = NULL;
36   size_t len;
37   /* to handle path names for files in multibyte character locales,
38    * we need to set up LC_CTYPE to match the host file system locale.  */
39   char *locale = setlocale (LC_CTYPE, NULL);
40 
41   if (locale != NULL)
42     locale = strdup (locale);
43   setlocale (LC_CTYPE, "");
44 
45   if (path && *path)
46     {
47       /* allocate sufficient local storage space,
48        * in which to create a wide character reference copy of path.  */
49       wchar_t refcopy[1 + (len = mbstowcs (NULL, path, 0))];
50       /* create the wide character reference copy of path */
51       wchar_t *refpath = refcopy;
52 
53       len = mbstowcs (refpath, path, len);
54       refcopy[len] = L'\0';
55       /* SUSv3 identifies a special case, where path is exactly equal to "//";
56        * (we will also accept "\\" in the Win32 context, but not "/\" or "\/",
57        *  and neither will we consider paths with an initial drive designator).
58        * For this special case, SUSv3 allows the implementation to choose to
59        * return "/" or "//", (or "\" or "\\", since this is Win32); we will
60        * simply return the path unchanged, (i.e. "//" or "\\").  */
61       if (len > 1 && (refpath[0] == L'/' || refpath[0] == L'\\'))
62         {
63 	  if (refpath[1] == refpath[0] && refpath[2] == L'\0')
64 	    {
65 	      setlocale (LC_CTYPE, locale);
66 	      free (locale);
67 	      return path;
68 	    }
69         }
70       /* For all other cases ...
71        * step over the drive designator, if present ...  */
72       else if (len > 1 && refpath[1] == L':')
73         {
74 	  /* FIXME: maybe should confirm *refpath is a valid drive designator.  */
75 	  refpath += 2;
76         }
77       /* check again, just to ensure we still have a non-empty path name ... */
78       if (*refpath)
79         {
80 #	undef  basename
81 #	define basename __the_basename		/* avoid shadowing. */
82 	  /* reproduce the scanning logic of the "basename" function
83 	   * to locate the basename component of the current path string,
84 	   * (but also remember where the dirname component starts).  */
85 	  wchar_t *refname, *basename;
86 	  for (refname = basename = refpath; *refpath; ++refpath)
87 	    {
88 	      if (*refpath == L'/' || *refpath == L'\\')
89 	        {
90 		  /* we found a dir separator ...
91 		   * step over it, and any others which immediately follow it.  */
92 		  while (*refpath == L'/' || *refpath == L'\\')
93 		    ++refpath;
94 		  /* if we didn't reach the end of the path string ... */
95 		  if (*refpath)
96 		    /* then we have a new candidate for the base name.  */
97 		    basename = refpath;
98 		  else
99 		    /* we struck an early termination of the path string,
100 		     * with trailing dir separators following the base name,
101 		     * so break out of the for loop, to avoid overrun.  */
102 		    break;
103 	        }
104 	    }
105 	  /* now check,
106 	   * to confirm that we have distinct dirname and basename components.  */
107 	  if (basename > refname)
108 	    {
109 	      /* and, when we do ...
110 	       * backtrack over all trailing separators on the dirname component,
111 	       * (but preserve exactly two initial dirname separators, if identical),
112 	       * and add a NUL terminator in their place.  */
113 	      do --basename;
114 	      while (basename > refname && (*basename == L'/' || *basename == L'\\'));
115 	      if (basename == refname && (refname[0] == L'/' || refname[0] == L'\\')
116 		  && refname[1] == refname[0] && refname[2] != L'/' && refname[2] != L'\\')
117 		++basename;
118 	      *++basename = L'\0';
119 	      /* if the resultant dirname begins with EXACTLY two dir separators,
120 	       * AND both are identical, then we preserve them.  */
121 	      refpath = refcopy;
122 	      while ((*refpath == L'/' || *refpath == L'\\'))
123 		++refpath;
124 	      if ((refpath - refcopy) > 2 || refcopy[1] != refcopy[0])
125 		refpath = refcopy;
126 	      /* and finally ...
127 	       * we remove any residual, redundantly duplicated separators from the dirname,
128 	       * reterminate, and return it.  */
129 	      refname = refpath;
130 	      while (*refpath)
131 	        {
132 		  if ((*refname++ = *refpath) == L'/' || *refpath++ == L'\\')
133 		    {
134 		      while (*refpath == L'/' || *refpath == L'\\')
135 			++refpath;
136 		    }
137 	        }
138 	      *refname = L'\0';
139 	      /* finally ...
140 	       * transform the resolved dirname back into the multibyte char domain,
141 	       * restore the caller's locale, and return the resultant dirname.  */
142 	      if ((len = wcstombs( path, refcopy, len )) != (size_t)(-1))
143 		path[len] = '\0';
144 	    }
145 	  else
146 	    {
147 	      /* either there were no dirname separators in the path name,
148 	       * or there was nothing else ...  */
149 	      if (*refname == L'/' || *refname == L'\\')
150 	        {
151 		  /* it was all separators, so return one.  */
152 		  ++refname;
153 	        }
154 	      else
155 	        {
156 		  /* there were no separators, so return '.'.  */
157 		  *refname++ = L'.';
158 	        }
159 	      /* add a NUL terminator, in either case,
160 	       * then transform to the multibyte char domain,
161 	       * using our own buffer.  */
162 	      *refname = L'\0';
163 	      retfail = realloc (retfail, len = 1 + wcstombs (NULL, refcopy, 0));
164 	      wcstombs (path = retfail, refcopy, len);
165 	    }
166 	  /* restore caller's locale, clean up, and return the resolved dirname.  */
167 	  setlocale (LC_CTYPE, locale);
168 	  free (locale);
169 	  return path;
170         }
171 #	undef  basename
172     }
173   /* path is NULL, or an empty string; default return value is "." ...
174    * return this in our own buffer, regenerated by wide char transform,
175    * in case the caller trashed it after a previous call.
176    */
177   retfail = realloc (retfail, len = 1 + wcstombs (NULL, L".", 0));
178   wcstombs (retfail, L".", len);
179   /* restore caller's locale, clean up, and return the default dirname.  */
180   setlocale (LC_CTYPE, locale);
181   free (locale);
182   return retfail;
183 }
184