Line data Source code
1 : /* -at() version of realpath()
2 : Copyright (C) 2012 L. A. F. Pereira
3 :
4 : Based on: return the canonical absolute name of a given file.
5 : Copyright (C) 1996-2002,2004,2005,2006,2008 Free Software Foundation, Inc.
6 : This file is part of the GNU C Library.
7 :
8 : The GNU C Library is free software; you can redistribute it and/or
9 : modify it under the terms of the GNU Lesser General Public
10 : License as published by the Free Software Foundation; either
11 : version 2.1 of the License, or (at your option) any later version.
12 :
13 : The GNU C Library is distributed in the hope that it will be useful,
14 : but WITHOUT ANY WARRANTY; without even the implied warranty of
15 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 : Lesser General Public License for more details.
17 :
18 : You should have received a copy of the GNU Lesser General Public
19 : License along with the GNU C Library; if not, see
20 : <http://www.gnu.org/licenses/>. */
21 :
22 : #define _GNU_SOURCE
23 : #include <assert.h>
24 : #include <errno.h>
25 : #include <fcntl.h>
26 : #include <limits.h>
27 : #include <stddef.h>
28 : #include <stdlib.h>
29 : #include <string.h>
30 : #include <string.h>
31 : #include <sys/param.h>
32 : #include <sys/stat.h>
33 : #include <unistd.h>
34 :
35 : #include "lwan-private.h"
36 :
37 : char *
38 35 : realpathat2(int dirfd, char *dirfdpath, const char *name, char *resolved,
39 : struct stat *st)
40 : {
41 : char *rpath, *dest, extra_buf[PATH_MAX];
42 : const char *start, *end, *rpath_limit;
43 35 : int num_links = 0;
44 : ptrdiff_t dirfdlen;
45 : char *pathat;
46 :
47 35 : if (UNLIKELY(name == NULL)) {
48 : /* As per Single Unix Specification V2 we must return an error if
49 : either parameter is a null pointer. We extend this to allow
50 : the RESOLVED parameter to be NULL in case the we are expected to
51 : allocate the room for the return value. */
52 0 : errno = EINVAL;
53 0 : return NULL;
54 : }
55 :
56 : /* If any of the additional parameters are null, or if the name to
57 : resolve the real path is an absolute path, use the standard
58 : realpath() routine. */
59 35 : if (UNLIKELY(dirfd < 0 || dirfdpath == NULL || name[0] == '/'))
60 0 : return realpath(name, resolved);
61 :
62 35 : if (name[0] == '\0') {
63 4 : if (UNLIKELY(fstat(dirfd, st) < 0))
64 0 : return NULL;
65 4 : if (LIKELY(!resolved))
66 0 : return strdup(dirfdpath);
67 4 : return strcpy(resolved, dirfdpath);
68 : }
69 :
70 31 : if (LIKELY(!resolved)) {
71 0 : rpath = malloc(PATH_MAX);
72 0 : if (UNLIKELY(!rpath))
73 0 : return NULL;
74 : } else
75 31 : rpath = resolved;
76 31 : rpath_limit = rpath + PATH_MAX;
77 :
78 31 : strcpy(rpath, dirfdpath);
79 31 : dest = rpath + strlen(rpath);
80 31 : dirfdlen = dest - rpath;
81 :
82 59 : for (start = end = name; *start; start = end) {
83 : int n;
84 :
85 : /* Skip sequence of multiple path-separators. */
86 39 : while (*start == '/')
87 4 : ++start;
88 :
89 : /* Find end of path component. */
90 236 : for (end = start; *end && *end != '/'; ++end)
91 : /* Nothing. */ ;
92 :
93 35 : if (end - start == 0)
94 2 : break;
95 33 : else if (end - start == 1 && start[0] == '.')
96 : /* nothing */ ;
97 33 : else if (end - start == 2 && start[0] == '.' && start[1] == '.') {
98 : /* Back up to previous component, ignore if at root already. */
99 0 : if (dest > rpath + 1)
100 0 : while ((--dest)[-1] != '/');
101 : } else {
102 : size_t new_size;
103 :
104 33 : if (dest[-1] != '/')
105 2 : *dest++ = '/';
106 :
107 33 : if (dest + (end - start) >= rpath_limit) {
108 0 : ptrdiff_t dest_offset = dest - rpath;
109 : char *new_rpath;
110 :
111 0 : if (UNLIKELY(resolved != NULL)) {
112 0 : errno = ENAMETOOLONG;
113 0 : if (dest > rpath + 1)
114 0 : dest--;
115 0 : *dest = '\0';
116 0 : goto error;
117 : }
118 :
119 0 : new_size = (size_t)(rpath_limit - rpath);
120 0 : if (end - start + 1 > PATH_MAX)
121 0 : new_size += (size_t)(end - start + 1);
122 : else
123 0 : new_size += PATH_MAX;
124 0 : new_rpath = (char *) realloc(rpath, new_size);
125 0 : if (UNLIKELY(new_rpath == NULL))
126 0 : goto error;
127 0 : rpath = new_rpath;
128 0 : rpath_limit = rpath + new_size;
129 :
130 0 : dest = rpath + dest_offset;
131 : }
132 :
133 33 : dest = mempmove(dest, start, (size_t)(end - start));
134 33 : *dest = '\0';
135 :
136 33 : if ((dirfdlen == 1 && *dirfdpath == '/') ||
137 33 : strncmp(rpath, dirfdpath, (size_t)dirfdlen)) {
138 0 : pathat = rpath;
139 : } else {
140 33 : pathat = rpath + dirfdlen;
141 : }
142 33 : if (UNLIKELY(*pathat == '\0'))
143 0 : pathat = rpath;
144 :
145 33 : if (UNLIKELY(fstatat(dirfd, pathat, st, AT_SYMLINK_NOFOLLOW) < 0))
146 5 : goto error;
147 :
148 28 : if (UNLIKELY(S_ISLNK(st->st_mode))) {
149 : char buf[PATH_MAX];
150 : size_t len;
151 :
152 0 : if (UNLIKELY(++num_links > MAXSYMLINKS)) {
153 0 : errno = ELOOP;
154 0 : goto error;
155 : }
156 :
157 0 : n = (int)readlinkat(dirfd, pathat, buf, PATH_MAX - 1);
158 0 : if (UNLIKELY(n < 0))
159 0 : goto error;
160 0 : buf[n] = '\0';
161 :
162 0 : len = strlen(end);
163 0 : if (UNLIKELY((size_t)(PATH_MAX - n) <= len)) {
164 0 : errno = ENAMETOOLONG;
165 0 : goto error;
166 : }
167 :
168 : /* Careful here, end may be a pointer into extra_buf... */
169 0 : memmove(&extra_buf[n], end, len + 1);
170 0 : end = memcpy(extra_buf, buf, (size_t)n);
171 :
172 0 : if (buf[0] == '/')
173 0 : dest = rpath + 1; /* It's an absolute symlink */
174 : else
175 : /* Back up to previous component, ignore if at root already: */
176 0 : if (dest > rpath + 1)
177 0 : while ((--dest)[-1] != '/');
178 28 : } else if (UNLIKELY(!S_ISDIR(st->st_mode) && *end != '\0')) {
179 0 : errno = ENOTDIR;
180 0 : goto error;
181 : }
182 : }
183 : }
184 :
185 26 : if (dest > rpath + 1 && dest[-1] == '/')
186 0 : --dest;
187 26 : *dest = '\0';
188 :
189 26 : assert(resolved == NULL || resolved == rpath);
190 26 : return rpath;
191 :
192 5 : error:
193 5 : assert(resolved == NULL || resolved == rpath);
194 5 : if (resolved == NULL)
195 0 : free(rpath);
196 5 : return NULL;
197 : }
198 :
199 : char *
200 0 : realpathat(int dirfd, char *dirfdpath, const char *name, char *resolved)
201 : {
202 : struct stat st;
203 0 : return realpathat2(dirfd, dirfdpath, name, resolved, &st);
204 : }
|