(dir_name_r): Fix typo: int -> size_t.
[gnulib.git] / lib / dirname.c
1 /* dirname.c -- return all but the last element in a path
2    Copyright (C) 1990, 1998, 2000 Free Software Foundation, Inc.
3
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 2, or (at your option)
7    any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, write to the Free Software Foundation,
16    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
17
18 #if HAVE_CONFIG_H
19 # include <config.h>
20 #endif
21
22 #ifdef STDC_HEADERS
23 # include <stdlib.h>
24 #else
25 char *malloc ();
26 #endif
27 #if defined STDC_HEADERS || defined HAVE_STRING_H
28 # include <string.h>
29 #else
30 # include <strings.h>
31 # ifndef strrchr
32 #  define strrchr rindex
33 # endif
34 #endif
35 #include <assert.h>
36
37 #ifndef HAVE_DECL_MEMRCHR
38 "this configure-time declaration test was not run"
39 #endif
40 #if !HAVE_DECL_MEMRCHR
41 void *memrchr ();
42 #endif
43
44 #include "dirname.h"
45
46 #ifndef ISSLASH
47 # define ISSLASH(C) ((C) == '/')
48 #endif
49
50 #define BACKSLASH_IS_PATH_SEPARATOR ISSLASH ('\\')
51
52 /* Return the length of `dirname (PATH)' and set *RESULT
53    to point to PATH or to `"."', as appropriate.
54    Works properly even if there are trailing slashes
55    (by effectively ignoring them).  */
56 size_t
57 dir_name_r (const char *path, const char **result)
58 {
59   char *slash;
60   size_t length;                /* Length of result, not including NUL.  */
61
62   slash = strrchr (path, '/');
63   if (BACKSLASH_IS_PATH_SEPARATOR)
64     {
65       char *b = strrchr (path, '\\');
66       if (b && slash < b)
67         slash = b;
68     }
69
70   /* If the last byte of PATH is a slash, decrement SLASH until it's
71      pointing at the leftmost in a sequence of trailing slashes.  */
72   if (slash && slash[1] == 0)
73     {
74       while (path < slash && ISSLASH (slash[-1]))
75         {
76           --slash;
77         }
78
79       if (path < slash)
80         {
81           slash = memrchr (path, '/', slash - path);
82           if (BACKSLASH_IS_PATH_SEPARATOR)
83             {
84               char *b = memrchr (path, '\\', slash - path);
85               if (b && slash < b)
86                 slash = b;
87             }
88         }
89     }
90
91   if (slash == 0)
92     {
93       /* File is in the current directory.  */
94       path = ".";
95       length = 1;
96     }
97   else
98     {
99       /* Remove any trailing slashes from the result.  */
100       if (BACKSLASH_IS_PATH_SEPARATOR)
101         {
102           const char *lim = ((path[0] >= 'A' && path[0] <= 'z'
103                               && path[1] == ':')
104                              ? path + 2 : path);
105
106           /* If canonicalized "d:/path", leave alone the root case "d:/".  */
107           while (slash > lim && ISSLASH (*slash))
108             --slash;
109         }
110       else
111         {
112           while (slash > path && ISSLASH (*slash))
113             --slash;
114         }
115
116       length = slash - path + 1;
117     }
118
119   *result = path;
120   return length;
121 }
122
123 /* Return the leading directories part of PATH,
124    allocated with malloc.  If out of memory, return 0.
125    Works properly even if there are trailing slashes
126    (by effectively ignoring them).  */
127
128 char *
129 dir_name (const char *path)
130 {
131   const char *result;
132   size_t length = dir_name_r (path, &result);
133   char *newpath = (char *) malloc (length + 1);
134   if (newpath == 0)
135     return 0;
136   strncpy (newpath, result, length);
137   newpath[length] = 0;
138   return newpath;
139 }
140
141 #ifdef TEST_DIRNAME
142 /*
143
144 Run the test like this (expect no output):
145   gcc -DHAVE_CONFIG_H -DTEST_DIRNAME -I.. -O -Wall memrchr.c dirname.c
146   sed -n '/^BEGIN-DATA$/,/^END-DATA$/p' dirname.c|grep -v DATA|./a.out
147
148 BEGIN-DATA
149 foo//// .
150 bar/foo//// bar
151 foo/ .
152 / /
153 . .
154 a .
155 END-DATA
156
157 */
158
159 # define MAX_BUFF_LEN 1024
160 # include <stdio.h>
161 # include <stdlib.h>
162
163 int
164 main ()
165 {
166   char buff[MAX_BUFF_LEN + 1];
167
168   buff[MAX_BUFF_LEN] = 0;
169   while (fgets (buff, MAX_BUFF_LEN, stdin) && buff[0])
170     {
171       char path[MAX_BUFF_LEN];
172       char expected_result[MAX_BUFF_LEN];
173       char *result;
174       sscanf (buff, "%s %s", path, expected_result);
175       result = dir_name (path);
176       if (strcmp (result, expected_result))
177         printf ("%s: got %s, expected %s\n", path, result, expected_result);
178     }
179   exit (0);
180
181 }
182 #endif