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