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