1 1.1 christos /* Return the canonical absolute name of a given file. 2 1.1 christos Copyright (C) 1996-2005 Free Software Foundation, Inc. 3 1.1 christos 4 1.1 christos This program is free software; you can redistribute it and/or modify 5 1.1 christos it under the terms of the GNU General Public License as published by 6 1.1 christos the Free Software Foundation; either version 2, or (at your option) 7 1.1 christos any later version. 8 1.1 christos 9 1.1 christos This program is distributed in the hope that it will be useful, 10 1.1 christos but WITHOUT ANY WARRANTY; without even the implied warranty of 11 1.1 christos MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 1.1 christos GNU General Public License for more details. 13 1.1 christos 14 1.1 christos You should have received a copy of the GNU General Public License 15 1.1 christos along with this program; see the file COPYING. 16 1.1 christos If not, write to the Free Software Foundation, 17 1.1 christos 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ 18 1.2 christos #include <sys/cdefs.h> 19 1.2 christos __RCSID("$NetBSD: canonicalize.c,v 1.2 2016/05/17 14:00:09 christos Exp $"); 20 1.2 christos 21 1.1 christos 22 1.1 christos #ifdef HAVE_CONFIG_H 23 1.1 christos # include <config.h> 24 1.1 christos #endif 25 1.1 christos 26 1.1 christos #include "canonicalize.h" 27 1.1 christos 28 1.1 christos #ifdef STDC_HEADERS 29 1.1 christos # include <stdlib.h> 30 1.1 christos #else 31 1.1 christos void free (); 32 1.1 christos #endif 33 1.1 christos 34 1.1 christos #if defined STDC_HEADERS || defined HAVE_STRING_H 35 1.1 christos # include <string.h> 36 1.1 christos #else 37 1.1 christos # include <strings.h> 38 1.1 christos #endif 39 1.1 christos 40 1.1 christos #if HAVE_SYS_PARAM_H 41 1.1 christos # include <sys/param.h> 42 1.1 christos #endif 43 1.1 christos 44 1.1 christos #include <sys/stat.h> 45 1.1 christos 46 1.1 christos #if HAVE_UNISTD_H 47 1.1 christos # include <unistd.h> 48 1.1 christos #endif 49 1.1 christos 50 1.1 christos #include <errno.h> 51 1.1 christos #include <stddef.h> 52 1.1 christos 53 1.1 christos #include "cycle-check.h" 54 1.1 christos #include "filenamecat.h" 55 1.1 christos #include "stat-macros.h" 56 1.1 christos #include "xalloc.h" 57 1.1 christos #include "xgetcwd.h" 58 1.1 christos 59 1.1 christos #ifndef __set_errno 60 1.1 christos # define __set_errno(Val) errno = (Val) 61 1.1 christos #endif 62 1.1 christos 63 1.1 christos #include "pathmax.h" 64 1.1 christos #include "xreadlink.h" 65 1.1 christos 66 1.1 christos #if !HAVE_CANONICALIZE_FILE_NAME 67 1.1 christos /* Return the canonical absolute name of file NAME. A canonical name 68 1.1 christos does not contain any `.', `..' components nor any repeated file name 69 1.1 christos separators ('/') or symlinks. All components must exist. 70 1.1 christos The result is malloc'd. */ 71 1.1 christos 72 1.1 christos char * 73 1.1 christos canonicalize_file_name (const char *name) 74 1.1 christos { 75 1.1 christos # if HAVE_RESOLVEPATH 76 1.1 christos 77 1.1 christos char *resolved, *extra_buf = NULL; 78 1.1 christos size_t resolved_size; 79 1.1 christos ssize_t resolved_len; 80 1.1 christos 81 1.1 christos if (name == NULL) 82 1.1 christos { 83 1.1 christos __set_errno (EINVAL); 84 1.1 christos return NULL; 85 1.1 christos } 86 1.1 christos 87 1.1 christos if (name[0] == '\0') 88 1.1 christos { 89 1.1 christos __set_errno (ENOENT); 90 1.1 christos return NULL; 91 1.1 christos } 92 1.1 christos 93 1.1 christos /* All known hosts with resolvepath (e.g. Solaris 7) don't turn 94 1.1 christos relative names into absolute ones, so prepend the working 95 1.1 christos directory if the file name is not absolute. */ 96 1.1 christos if (name[0] != '/') 97 1.1 christos { 98 1.1 christos char *wd; 99 1.1 christos 100 1.1 christos if (!(wd = xgetcwd ())) 101 1.1 christos return NULL; 102 1.1 christos 103 1.1 christos extra_buf = file_name_concat (wd, name, NULL); 104 1.1 christos name = extra_buf; 105 1.1 christos free (wd); 106 1.1 christos } 107 1.1 christos 108 1.1 christos resolved_size = strlen (name); 109 1.1 christos while (1) 110 1.1 christos { 111 1.1 christos resolved_size = 2 * resolved_size + 1; 112 1.1 christos resolved = xmalloc (resolved_size); 113 1.1 christos resolved_len = resolvepath (name, resolved, resolved_size); 114 1.1 christos if (resolved_len < 0) 115 1.1 christos { 116 1.1 christos free (resolved); 117 1.1 christos free (extra_buf); 118 1.1 christos return NULL; 119 1.1 christos } 120 1.1 christos if (resolved_len < resolved_size) 121 1.1 christos break; 122 1.1 christos free (resolved); 123 1.1 christos } 124 1.1 christos 125 1.1 christos free (extra_buf); 126 1.1 christos 127 1.1 christos /* NUL-terminate the resulting name. */ 128 1.1 christos resolved[resolved_len] = '\0'; 129 1.1 christos 130 1.1 christos return resolved; 131 1.1 christos 132 1.1 christos # else 133 1.1 christos 134 1.1 christos return canonicalize_filename_mode (name, CAN_EXISTING); 135 1.1 christos 136 1.1 christos # endif /* !HAVE_RESOLVEPATH */ 137 1.1 christos } 138 1.1 christos #endif /* !HAVE_CANONICALIZE_FILE_NAME */ 139 1.1 christos 140 1.1 christos /* Return the canonical absolute name of file NAME. A canonical name 141 1.1 christos does not contain any `.', `..' components nor any repeated file name 142 1.1 christos separators ('/') or symlinks. Whether components must exist 143 1.1 christos or not depends on canonicalize mode. The result is malloc'd. */ 144 1.1 christos 145 1.1 christos char * 146 1.1 christos canonicalize_filename_mode (const char *name, canonicalize_mode_t can_mode) 147 1.1 christos { 148 1.1 christos char *rname, *dest, *extra_buf = NULL; 149 1.1 christos char const *start; 150 1.1 christos char const *end; 151 1.1 christos char const *rname_limit; 152 1.1 christos size_t extra_len = 0; 153 1.1 christos struct cycle_check_state cycle_state; 154 1.1 christos 155 1.1 christos if (name == NULL) 156 1.1 christos { 157 1.1 christos __set_errno (EINVAL); 158 1.1 christos return NULL; 159 1.1 christos } 160 1.1 christos 161 1.1 christos if (name[0] == '\0') 162 1.1 christos { 163 1.1 christos __set_errno (ENOENT); 164 1.1 christos return NULL; 165 1.1 christos } 166 1.1 christos 167 1.1 christos if (name[0] != '/') 168 1.1 christos { 169 1.1 christos rname = xgetcwd (); 170 1.1 christos if (!rname) 171 1.1 christos return NULL; 172 1.1 christos dest = strchr (rname, '\0'); 173 1.1 christos if (dest - rname < PATH_MAX) 174 1.1 christos { 175 1.1 christos char *p = xrealloc (rname, PATH_MAX); 176 1.1 christos dest = p + (dest - rname); 177 1.1 christos rname = p; 178 1.1 christos rname_limit = rname + PATH_MAX; 179 1.1 christos } 180 1.1 christos else 181 1.1 christos { 182 1.1 christos rname_limit = dest; 183 1.1 christos } 184 1.1 christos } 185 1.1 christos else 186 1.1 christos { 187 1.1 christos rname = xmalloc (PATH_MAX); 188 1.1 christos rname_limit = rname + PATH_MAX; 189 1.1 christos rname[0] = '/'; 190 1.1 christos dest = rname + 1; 191 1.1 christos } 192 1.1 christos 193 1.1 christos cycle_check_init (&cycle_state); 194 1.1 christos for (start = end = name; *start; start = end) 195 1.1 christos { 196 1.1 christos /* Skip sequence of multiple file name separators. */ 197 1.1 christos while (*start == '/') 198 1.1 christos ++start; 199 1.1 christos 200 1.1 christos /* Find end of component. */ 201 1.1 christos for (end = start; *end && *end != '/'; ++end) 202 1.1 christos /* Nothing. */; 203 1.1 christos 204 1.1 christos if (end - start == 0) 205 1.1 christos break; 206 1.1 christos else if (end - start == 1 && start[0] == '.') 207 1.1 christos /* nothing */; 208 1.1 christos else if (end - start == 2 && start[0] == '.' && start[1] == '.') 209 1.1 christos { 210 1.1 christos /* Back up to previous component, ignore if at root already. */ 211 1.1 christos if (dest > rname + 1) 212 1.1 christos while ((--dest)[-1] != '/'); 213 1.1 christos } 214 1.1 christos else 215 1.1 christos { 216 1.1 christos struct stat st; 217 1.1 christos 218 1.1 christos if (dest[-1] != '/') 219 1.1 christos *dest++ = '/'; 220 1.1 christos 221 1.1 christos if (dest + (end - start) >= rname_limit) 222 1.1 christos { 223 1.1 christos ptrdiff_t dest_offset = dest - rname; 224 1.1 christos size_t new_size = rname_limit - rname; 225 1.1 christos 226 1.1 christos if (end - start + 1 > PATH_MAX) 227 1.1 christos new_size += end - start + 1; 228 1.1 christos else 229 1.1 christos new_size += PATH_MAX; 230 1.1 christos rname = xrealloc (rname, new_size); 231 1.1 christos rname_limit = rname + new_size; 232 1.1 christos 233 1.1 christos dest = rname + dest_offset; 234 1.1 christos } 235 1.1 christos 236 1.1 christos dest = memcpy (dest, start, end - start); 237 1.1 christos dest += end - start; 238 1.1 christos *dest = '\0'; 239 1.1 christos 240 1.1 christos if (lstat (rname, &st) != 0) 241 1.1 christos { 242 1.1 christos if (can_mode == CAN_EXISTING) 243 1.1 christos goto error; 244 1.1 christos if (can_mode == CAN_ALL_BUT_LAST && *end) 245 1.1 christos goto error; 246 1.1 christos st.st_mode = 0; 247 1.1 christos } 248 1.1 christos 249 1.1 christos if (S_ISLNK (st.st_mode)) 250 1.1 christos { 251 1.1 christos char *buf; 252 1.1 christos size_t n, len; 253 1.1 christos 254 1.1 christos if (cycle_check (&cycle_state, &st)) 255 1.1 christos { 256 1.1 christos __set_errno (ELOOP); 257 1.1 christos if (can_mode == CAN_MISSING) 258 1.1 christos continue; 259 1.1 christos else 260 1.1 christos goto error; 261 1.1 christos } 262 1.1 christos 263 1.1 christos buf = xreadlink (rname, st.st_size); 264 1.1 christos if (!buf) 265 1.1 christos { 266 1.1 christos if (can_mode == CAN_MISSING) 267 1.1 christos continue; 268 1.1 christos else 269 1.1 christos goto error; 270 1.1 christos } 271 1.1 christos 272 1.1 christos n = strlen (buf); 273 1.1 christos len = strlen (end); 274 1.1 christos 275 1.1 christos if (!extra_len) 276 1.1 christos { 277 1.1 christos extra_len = 278 1.1 christos ((n + len + 1) > PATH_MAX) ? (n + len + 1) : PATH_MAX; 279 1.1 christos extra_buf = xmalloc (extra_len); 280 1.1 christos } 281 1.1 christos else if ((n + len + 1) > extra_len) 282 1.1 christos { 283 1.1 christos extra_len = n + len + 1; 284 1.1 christos extra_buf = xrealloc (extra_buf, extra_len); 285 1.1 christos } 286 1.1 christos 287 1.1 christos /* Careful here, end may be a pointer into extra_buf... */ 288 1.1 christos memmove (&extra_buf[n], end, len + 1); 289 1.1 christos name = end = memcpy (extra_buf, buf, n); 290 1.1 christos 291 1.1 christos if (buf[0] == '/') 292 1.1 christos dest = rname + 1; /* It's an absolute symlink */ 293 1.1 christos else 294 1.1 christos /* Back up to previous component, ignore if at root already: */ 295 1.1 christos if (dest > rname + 1) 296 1.1 christos while ((--dest)[-1] != '/'); 297 1.1 christos 298 1.1 christos free (buf); 299 1.1 christos } 300 1.1 christos else 301 1.1 christos { 302 1.1 christos if (!S_ISDIR (st.st_mode) && *end && (can_mode != CAN_MISSING)) 303 1.1 christos { 304 1.1 christos errno = ENOTDIR; 305 1.1 christos goto error; 306 1.1 christos } 307 1.1 christos } 308 1.1 christos } 309 1.1 christos } 310 1.1 christos if (dest > rname + 1 && dest[-1] == '/') 311 1.1 christos --dest; 312 1.1 christos *dest = '\0'; 313 1.1 christos 314 1.1 christos free (extra_buf); 315 1.1 christos return rname; 316 1.1 christos 317 1.1 christos error: 318 1.1 christos free (extra_buf); 319 1.1 christos free (rname); 320 1.1 christos return NULL; 321 1.1 christos } 322