eddbe97a2b6eecb657af049218c4f6d9347df77c
[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 the directory part of PATH.
53    Set *RESULT to point to PATH or to `"."', as appropriate.  */
54 size_t
55 dir_name_r (const char *path, const char **result)
56 {
57   char *slash;
58   int length;                   /* Length of result, not including NUL.  */
59
60   slash = strrchr (path, '/');
61   if (BACKSLASH_IS_PATH_SEPARATOR)
62     {
63       char *b = strrchr (path, '\\');
64       if (b && slash < b)
65         slash = b;
66     }
67
68   /* If the last byte of PATH is a slash, decrement SLASH until it's
69      pointing at the leftmost in a sequence of trailing slashes.  */
70   if (slash && slash[1] == 0)
71     {
72       while (path < slash && ISSLASH (slash[-1]))
73         {
74           --slash;
75         }
76
77       if (path < slash)
78         {
79           slash = memrchr (path, '/', slash - path);
80           if (BACKSLASH_IS_PATH_SEPARATOR)
81             {
82               char *b = memrchr (path, '\\', slash - path);
83               if (b && slash < b)
84                 slash = b;
85             }
86         }
87     }
88
89   if (slash == 0)
90     {
91       /* File is in the current directory.  */
92       path = ".";
93       length = 1;
94     }
95   else
96     {
97       /* Remove any trailing slashes from the result.  */
98       if (BACKSLASH_IS_PATH_SEPARATOR)
99         {
100           const char *lim = ((path[0] >= 'A' && path[0] <= 'z'
101                               && path[1] == ':')
102                              ? path + 2 : path);
103
104           /* If canonicalized "d:/path", leave alone the root case "d:/".  */
105           while (slash > lim && ISSLASH (*slash))
106             --slash;
107         }
108       else
109         {
110           while (slash > path && ISSLASH (*slash))
111             --slash;
112         }
113
114       length = slash - path + 1;
115     }
116
117   *result = path;
118   return length;
119 }
120
121 /* Return the leading directories part of PATH,
122    allocated with malloc.  If out of memory, return 0.
123    Works properly even if there are trailing slashes
124    (by effectively ignoring them).  */
125
126 char *
127 dir_name (const char *path)
128 {
129   const char *result;
130   size_t length = dir_name_r (path, &result);
131   char *newpath = (char *) malloc (length + 1);
132   if (newpath == 0)
133     return 0;
134   strncpy (newpath, result, length);
135   newpath[length] = 0;
136   return newpath;
137 }
138
139 #ifdef TEST_DIRNAME
140 /*
141
142 Run the test like this (expect no output):
143   gcc -DHAVE_CONFIG_H -DTEST_DIRNAME -I.. -O -Wall memrchr.c dirname.c
144   sed -n '/^BEGIN-DATA$/,/^END-DATA$/p' dirname.c|grep -v DATA|./a.out
145
146 BEGIN-DATA
147 foo//// .
148 bar/foo//// bar
149 foo/ .
150 / /
151 . .
152 a .
153 END-DATA
154
155 */
156
157 # define MAX_BUFF_LEN 1024
158 # include <stdio.h>
159 # include <stdlib.h>
160
161 int
162 main ()
163 {
164   char buff[MAX_BUFF_LEN + 1];
165
166   buff[MAX_BUFF_LEN] = 0;
167   while (fgets (buff, MAX_BUFF_LEN, stdin) && buff[0])
168     {
169       char path[MAX_BUFF_LEN];
170       char expected_result[MAX_BUFF_LEN];
171       char *result;
172       sscanf (buff, "%s %s", path, expected_result);
173       result = dir_name (path);
174       if (strcmp (result, expected_result))
175         printf ("%s: got %s, expected %s\n", path, result, expected_result);
176     }
177   exit (0);
178
179 }
180 #endif