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