1/* pathphys.c -- Return pathname with all symlinks expanded. */ 2 3/* Copyright (C) 2000 Free Software Foundation, Inc. 4 5 This file is part of GNU Bash, the Bourne Again SHell. 6 7 Bash is free software; you can redistribute it and/or modify it under 8 the terms of the GNU General Public License as published by the Free 9 Software Foundation; either version 2, or (at your option) any later 10 version. 11 12 Bash is distributed in the hope that it will be useful, but WITHOUT ANY 13 WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 15 for more details. 16 17 You should have received a copy of the GNU General Public License along 18 with Bash; see the file COPYING. If not, write to the Free Software 19 Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ 20 21#include <config.h> 22 23#include <bashtypes.h> 24#ifndef _MINIX 25# include <sys/param.h> 26#endif 27#include <posixstat.h> 28 29#if defined (HAVE_UNISTD_H) 30# include <unistd.h> 31#endif 32 33#include <filecntl.h> 34#include <bashansi.h> 35#include <stdio.h> 36#include <chartypes.h> 37#include <errno.h> 38 39#include "shell.h" 40 41#if !defined (MAXSYMLINKS) 42# define MAXSYMLINKS 32 43#endif 44 45#if !defined (errno) 46extern int errno; 47#endif /* !errno */ 48 49extern char *get_working_directory __P((char *)); 50 51static int 52_path_readlink (path, buf, bufsiz) 53 char *path; 54 char *buf; 55 int bufsiz; 56{ 57#ifdef HAVE_READLINK 58 return readlink (path, buf, bufsiz); 59#else 60 errno = EINVAL; 61 return -1; 62#endif 63} 64 65/* Look for ROOTEDPATH, PATHSEP, DIRSEP, and ISDIRSEP in ../../general.h */ 66 67#define DOUBLE_SLASH(p) ((p[0] == '/') && (p[1] == '/') && p[2] != '/') 68 69/* 70 * Return PATH with all symlinks expanded in newly-allocated memory. 71 * This always gets an absolute pathname. 72 */ 73 74char * 75sh_physpath (path, flags) 76 char *path; 77 int flags; 78{ 79 char tbuf[PATH_MAX+1], linkbuf[PATH_MAX+1]; 80 char *result, *p, *q, *qsave, *qbase, *workpath; 81 int double_slash_path, linklen, nlink; 82 83 linklen = strlen (path); 84 85#if 0 86 /* First sanity check -- punt immediately if the name is too long. */ 87 if (linklen >= PATH_MAX) 88 return (savestring (path)); 89#endif 90 91 nlink = 0; 92 q = result = (char *)xmalloc (PATH_MAX + 1); 93 94 /* Even if we get something longer than PATH_MAX, we might be able to 95 shorten it, so we try. */ 96 if (linklen >= PATH_MAX) 97 workpath = savestring (path); 98 else 99 { 100 workpath = (char *)xmalloc (PATH_MAX + 1); 101 strcpy (workpath, path); 102 } 103 104 /* This always gets an absolute pathname. */ 105 106 /* POSIX.2 says to leave a leading `//' alone. On cygwin, we skip over any 107 leading `x:' (dos drive name). */ 108#if defined (__CYGWIN__) 109 qbase = (ISALPHA((unsigned char)workpath[0]) && workpath[1] == ':') ? workpath + 3 : workpath + 1; 110#else 111 qbase = workpath + 1; 112#endif 113 double_slash_path = DOUBLE_SLASH (workpath); 114 qbase += double_slash_path; 115 116 for (p = workpath; p < qbase; ) 117 *q++ = *p++; 118 qbase = q; 119 120 /* 121 * invariants: 122 * qbase points to the portion of the result path we want to modify 123 * p points at beginning of path element we're considering. 124 * q points just past the last path element we wrote (no slash). 125 * 126 * XXX -- need to fix error checking for too-long pathnames 127 */ 128 129 while (*p) 130 { 131 if (ISDIRSEP(p[0])) /* null element */ 132 p++; 133 else if(p[0] == '.' && PATHSEP(p[1])) /* . and ./ */ 134 p += 1; /* don't count the separator in case it is nul */ 135 else if (p[0] == '.' && p[1] == '.' && PATHSEP(p[2])) /* .. and ../ */ 136 { 137 p += 2; /* skip `..' */ 138 if (q > qbase) 139 { 140 while (--q > qbase && ISDIRSEP(*q) == 0) 141 ; 142 } 143 } 144 else /* real path element */ 145 { 146 /* add separator if not at start of work portion of result */ 147 qsave = q; 148 if (q != qbase) 149 *q++ = DIRSEP; 150 while (*p && (ISDIRSEP(*p) == 0)) 151 { 152 if (q - result >= PATH_MAX) 153 { 154#ifdef ENAMETOOLONG 155 errno = ENAMETOOLONG; 156#else 157 errno = EINVAL; 158#endif 159 goto error; 160 } 161 162 *q++ = *p++; 163 } 164 165 *q = '\0'; 166 167 linklen = _path_readlink (result, linkbuf, PATH_MAX); 168 if (linklen < 0) /* if errno == EINVAL, it's not a symlink */ 169 { 170 if (errno != EINVAL) 171 goto error; 172 continue; 173 } 174 175 /* It's a symlink, and the value is in LINKBUF. */ 176 nlink++; 177 if (nlink > MAXSYMLINKS) 178 { 179#ifdef ELOOP 180 errno = ELOOP; 181#else 182 errno = EINVAL; 183#endif 184error: 185 free (result); 186 free (workpath); 187 return ((char *)NULL); 188 } 189 190 linkbuf[linklen] = '\0'; 191 192 /* If the new path length would overrun PATH_MAX, punt now. */ 193 if ((strlen (p) + linklen + 2) >= PATH_MAX) 194 { 195#ifdef ENAMETOOLONG 196 errno = ENAMETOOLONG; 197#else 198 errno = EINVAL; 199#endif 200 goto error; 201 } 202 203 /* Form the new pathname by copying the link value to a temporary 204 buffer and appending the rest of `workpath'. Reset p to point 205 to the start of the rest of the path. If the link value is an 206 absolute pathname, reset p, q, and qbase. If not, reset p 207 and q. */ 208 strcpy (tbuf, linkbuf); 209 tbuf[linklen] = '/'; 210 strcpy (tbuf + linklen, p); 211 strcpy (workpath, tbuf); 212 213 if (ABSPATH(linkbuf)) 214 { 215 q = result; 216 /* Duplicating some code here... */ 217#if defined (__CYGWIN__) 218 qbase = (ISALPHA((unsigned char)workpath[0]) && workpath[1] == ':') ? workpath + 3 : workpath + 1; 219#else 220 qbase = workpath + 1; 221#endif 222 double_slash_path = DOUBLE_SLASH (workpath); 223 qbase += double_slash_path; 224 225 for (p = workpath; p < qbase; ) 226 *q++ = *p++; 227 qbase = q; 228 } 229 else 230 { 231 p = workpath; 232 q = qsave; 233 } 234 } 235 } 236 237 *q = '\0'; 238 free (workpath); 239 240 /* If the result starts with `//', but the original path does not, we 241 can turn the // into /. Because of how we set `qbase', this should never 242 be true, but it's a sanity check. */ 243 if (DOUBLE_SLASH(result) && double_slash_path == 0) 244 { 245 if (result[2] == '\0') /* short-circuit for bare `//' */ 246 result[1] = '\0'; 247 else 248 memmove(result, result + 1, strlen(result + 1) + 1); 249 } 250 251 return (result); 252} 253 254char * 255sh_realpath (pathname, resolved) 256 const char *pathname; 257 char *resolved; 258{ 259 char *tdir, *wd; 260 261 if (pathname == 0 || *pathname == '\0') 262 { 263 errno = (pathname == 0) ? EINVAL : ENOENT; 264 return ((char *)NULL); 265 } 266 267 if (ABSPATH (pathname) == 0) 268 { 269 wd = get_working_directory ("sh_realpath"); 270 if (wd == 0) 271 return ((char *)NULL); 272 tdir = sh_makepath ((char *)pathname, wd, 0); 273 free (wd); 274 } 275 else 276 tdir = savestring (pathname); 277 278 wd = sh_physpath (tdir, 0); 279 free (tdir); 280 281 if (resolved == 0) 282 return (wd); 283 284 if (wd) 285 { 286 strncpy (resolved, wd, PATH_MAX - 1); 287 resolved[PATH_MAX - 1] = '\0'; 288 free (wd); 289 return resolved; 290 } 291 else 292 { 293 resolved[0] = '\0'; 294 return wd; 295 } 296} 297