428
+ − 1 /*
+ − 2 * realpath.c -- canonicalize pathname by removing symlinks
+ − 3 * Copyright (C) 1993 Rick Sladkey <jrs@world.std.com>
1116
+ − 4 * Copyright (C) 2001, 2002 Ben Wing.
428
+ − 5 *
+ − 6
+ − 7 This file is part of XEmacs.
+ − 8
+ − 9 XEmacs is free software; you can redistribute it and/or modify it
+ − 10 under the terms of the GNU General Public License as published by the
+ − 11 Free Software Foundation; either version 2, or (at your option) any
+ − 12 later version.
+ − 13
+ − 14 XEmacs is distributed in the hope that it will be useful, but WITHOUT
+ − 15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ − 16 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ − 17 for more details.
+ − 18
+ − 19 You should have received a copy of the GNU General Public License
+ − 20 along with XEmacs; see the file COPYING. If not, write to
+ − 21 the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ − 22 Boston, MA 02111-1307, USA. */
+ − 23
+ − 24 /* Synched up with: Not in FSF. */
+ − 25
771
+ − 26 /* This file has been Mule-ized, June 2001 by Ben Wing.
+ − 27
+ − 28 Everything in this file now works in terms of internal, not external,
+ − 29 data. This is the only way to be safe, and it makes the code cleaner. */
+ − 30
428
+ − 31 #include <config.h>
446
+ − 32 #include "lisp.h"
442
+ − 33
558
+ − 34 #include "sysfile.h"
1116
+ − 35 #include "sysdir.h"
428
+ − 36
771
+ − 37 #define MAX_READLINKS 32
+ − 38
1116
+ − 39 #ifdef WIN32_ANY
446
+ − 40 #include "syswindows.h"
+ − 41 #ifndef ELOOP
+ − 42 #define ELOOP 10062 /* = WSAELOOP in winsock.h */
+ − 43 #endif
1116
+ − 44 #endif
+ − 45
446
+ − 46 /* Length of start of absolute filename. */
+ − 47 static int
1116
+ − 48 abs_start (const Ibyte *name)
446
+ − 49 {
1116
+ − 50 #ifdef WIN32_ANY
446
+ − 51 if (isalpha (*name) && IS_DEVICE_SEP (name[1])
+ − 52 && IS_DIRECTORY_SEP (name[2]))
+ − 53 return 3;
+ − 54 else if (IS_DIRECTORY_SEP (*name))
+ − 55 return IS_DIRECTORY_SEP (name[1]) ? 2 : 1;
+ − 56 else
+ − 57 return 0;
1116
+ − 58 #else /* not WIN32_ANY */
+ − 59 return IS_DIRECTORY_SEP (*name) ? 1 : 0;
+ − 60 #endif
446
+ − 61 }
1116
+ − 62
+ − 63 /* Find real name of a file by resolving symbolic links and (under Windows)
+ − 64 looking up the correct case of the file as it appears on the file
+ − 65 system.
+ − 66
+ − 67 Under Windows, UNC servers and shares are lower-cased. Directories must
+ − 68 be given without trailing '/'. One day, this could read Win2K's reparse
+ − 69 points. */
+ − 70
+ − 71 static int
+ − 72 readlink_and_correct_case (const Ibyte *name, Ibyte *buf,
+ − 73 int size)
+ − 74 {
+ − 75 #ifndef WIN32_ANY
+ − 76 return qxe_readlink (name, buf, size);
+ − 77 #else
+ − 78 # ifdef CYGWIN
+ − 79 Ibyte *tmp;
+ − 80 int n = qxe_readlink (name, buf, size);
+ − 81 if (n >= 0 || errno != EINVAL)
+ − 82 return n;
+ − 83
+ − 84 /* The file may exist, but isn't a symlink. Try to find the
+ − 85 right name. */
+ − 86 tmp = (Ibyte *) ALLOCA (cygwin_posix_to_win32_path_list_buf_size
+ − 87 ((char *) name));
+ − 88 cygwin_posix_to_win32_path_list ((char *) name, (char *) tmp);
+ − 89 name = tmp;
+ − 90 # endif
+ − 91
+ − 92 {
+ − 93 int len = 0;
+ − 94 int err = 0;
+ − 95 const Ibyte *lastname;
+ − 96 int count = 0;
1204
+ − 97 const Ibyte *nn;
1116
+ − 98 DECLARE_EISTRING (result);
+ − 99
+ − 100 assert (*name);
+ − 101
+ − 102 /* Sort of check we have a valid filename. */
+ − 103 if (qxestrpbrk (name, "*?|<>\""))
+ − 104 {
+ − 105 errno = ENOENT;
+ − 106 return -1;
+ − 107 }
+ − 108 else if (qxestrlen (name) >= PATH_MAX)
+ − 109 {
+ − 110 errno = ENAMETOOLONG;
+ − 111 return -1;
+ − 112 }
+ − 113
+ − 114 /* Find start of filename */
+ − 115 lastname = name + qxestrlen (name);
+ − 116 while (lastname > name && !IS_DIRECTORY_SEP (lastname[-1]))
+ − 117 --lastname;
+ − 118
+ − 119 /* Count slashes in unc path */
+ − 120 if (abs_start (name) == 2)
1204
+ − 121 for (nn = name; *nn; nn++)
+ − 122 if (IS_DIRECTORY_SEP (*nn))
1116
+ − 123 count++;
+ − 124
+ − 125 if (count >= 2 && count < 4)
+ − 126 {
+ − 127 eicpy_rawz (result, lastname);
+ − 128 eilwr (result);
+ − 129 }
+ − 130 else
+ − 131 {
+ − 132 WIN32_FIND_DATAW find_data;
+ − 133 Extbyte *nameext;
+ − 134 HANDLE dir_handle;
+ − 135
+ − 136 C_STRING_TO_TSTR (name, nameext);
+ − 137 dir_handle = qxeFindFirstFile (nameext, &find_data);
+ − 138 if (dir_handle == INVALID_HANDLE_VALUE)
+ − 139 {
+ − 140 errno = ENOENT;
+ − 141 return -1;
+ − 142 }
+ − 143 eicpy_ext (result, (Extbyte *) find_data.cFileName, Qmswindows_tstr);
+ − 144 FindClose (dir_handle);
+ − 145 }
+ − 146
+ − 147 if ((len = eilen (result)) < size)
+ − 148 {
+ − 149 DECLARE_EISTRING (eilastname);
+ − 150
+ − 151 eicpy_rawz (eilastname, lastname);
+ − 152 if (eicmp_ei (eilastname, result) == 0)
+ − 153 /* Signal that the name is already OK. */
+ − 154 err = EINVAL;
+ − 155 else
+ − 156 memcpy (buf, eidata (result), len + 1);
+ − 157 }
+ − 158 else
+ − 159 err = ENAMETOOLONG;
+ − 160
+ − 161 errno = err;
+ − 162 return err ? -1 : len;
+ − 163 }
+ − 164 #endif /* WIN32_ANY */
+ − 165 }
446
+ − 166
771
+ − 167 /* Mule Note: This function works with and returns
+ − 168 internally-formatted strings. */
440
+ − 169
867
+ − 170 Ibyte *
+ − 171 qxe_realpath (const Ibyte *path, Ibyte *resolved_path)
428
+ − 172 {
867
+ − 173 Ibyte copy_path[PATH_MAX];
+ − 174 Ibyte *new_path = resolved_path;
+ − 175 Ibyte *max_path;
1116
+ − 176 #if defined (HAVE_READLINK) || defined (WIN32_ANY)
428
+ − 177 int readlinks = 0;
867
+ − 178 Ibyte link_path[PATH_MAX];
428
+ − 179 int n;
1116
+ − 180 int abslen = abs_start (path);
428
+ − 181 #endif
+ − 182
+ − 183 /* Make a copy of the source path since we may need to modify it. */
771
+ − 184 qxestrcpy (copy_path, path);
428
+ − 185 path = copy_path;
+ − 186 max_path = copy_path + PATH_MAX - 2;
446
+ − 187
819
+ − 188 if (0)
+ − 189 ;
1116
+ − 190 #ifdef WIN32_ANY
446
+ − 191 /* Check for c:/... or //server/... */
988
+ − 192 else if (abslen == 3 || abslen == 2)
428
+ − 193 {
462
+ − 194 /* Make sure drive letter is lowercased. */
1116
+ − 195 if (abslen == 3)
+ − 196 {
+ − 197 *new_path = tolower (*path);
+ − 198 new_path++;
+ − 199 path++;
+ − 200 abslen--;
+ − 201 }
988
+ − 202 /* Coerce directory chars. */
1116
+ − 203 while (abslen-- > 0)
+ − 204 {
+ − 205 if (IS_DIRECTORY_SEP (*path))
+ − 206 *new_path++ = DIRECTORY_SEP;
+ − 207 else
+ − 208 *new_path++ = *path;
+ − 209 path++;
+ − 210 }
428
+ − 211 }
819
+ − 212 #endif
+ − 213 #ifdef WIN32_NATIVE
446
+ − 214 /* No drive letter, but a beginning slash? Prepend drive letter. */
+ − 215 else if (abslen == 1)
428
+ − 216 {
771
+ − 217 get_initial_directory (new_path, PATH_MAX - 1);
428
+ − 218 new_path += 3;
+ − 219 path++;
+ − 220 }
446
+ − 221 /* Just a path name, prepend the current directory */
1116
+ − 222 else
428
+ − 223 {
771
+ − 224 get_initial_directory (new_path, PATH_MAX - 1);
+ − 225 new_path += qxestrlen (new_path);
446
+ − 226 if (!IS_DIRECTORY_SEP (new_path[-1]))
+ − 227 *new_path++ = DIRECTORY_SEP;
428
+ − 228 }
+ − 229 #else
771
+ − 230 /* If it's a relative pathname use get_initial_directory for starters. */
819
+ − 231 else if (abslen == 0)
428
+ − 232 {
771
+ − 233 get_initial_directory (new_path, PATH_MAX - 1);
+ − 234 new_path += qxestrlen (new_path);
446
+ − 235 if (!IS_DIRECTORY_SEP (new_path[-1]))
+ − 236 *new_path++ = DIRECTORY_SEP;
428
+ − 237 }
+ − 238 else
+ − 239 {
446
+ − 240 /* Copy first directory sep. May have two on cygwin. */
771
+ − 241 qxestrncpy (new_path, path, abslen);
446
+ − 242 new_path += abslen;
+ − 243 path += abslen;
428
+ − 244 }
+ − 245 #endif
+ − 246 /* Expand each slash-separated pathname component. */
+ − 247 while (*path != '\0')
+ − 248 {
+ − 249 /* Ignore stray "/". */
446
+ − 250 if (IS_DIRECTORY_SEP (*path))
428
+ − 251 {
+ − 252 path++;
+ − 253 continue;
+ − 254 }
+ − 255
+ − 256 if (*path == '.')
+ − 257 {
+ − 258 /* Ignore ".". */
446
+ − 259 if (path[1] == '\0' || IS_DIRECTORY_SEP (path[1]))
428
+ − 260 {
+ − 261 path++;
+ − 262 continue;
+ − 263 }
+ − 264
442
+ − 265 /* Handle ".." */
+ − 266 if (path[1] == '.' &&
446
+ − 267 (path[2] == '\0' || IS_DIRECTORY_SEP (path[2])))
428
+ − 268 {
442
+ − 269 path += 2;
428
+ − 270
442
+ − 271 /* Ignore ".." at root. */
1116
+ − 272 if (new_path == resolved_path + abs_start (resolved_path))
442
+ − 273 continue;
428
+ − 274
442
+ − 275 /* Handle ".." by backing up. */
446
+ − 276 --new_path;
+ − 277 while (!IS_DIRECTORY_SEP (new_path[-1]))
+ − 278 --new_path;
442
+ − 279 continue;
428
+ − 280 }
+ − 281 }
+ − 282
+ − 283 /* Safely copy the next pathname component. */
446
+ − 284 while (*path != '\0' && !IS_DIRECTORY_SEP (*path))
428
+ − 285 {
+ − 286 if (path > max_path)
+ − 287 {
+ − 288 errno = ENAMETOOLONG;
+ − 289 return NULL;
+ − 290 }
+ − 291 *new_path++ = *path++;
+ − 292 }
+ − 293
1116
+ − 294 #if defined (HAVE_READLINK) || defined (WIN32_ANY)
771
+ − 295 /* See if latest pathname component is a symlink or needs case
+ − 296 correction. */
428
+ − 297 *new_path = '\0';
771
+ − 298 n = readlink_and_correct_case (resolved_path, link_path, PATH_MAX - 1);
428
+ − 299
+ − 300 if (n < 0)
+ − 301 {
771
+ − 302 /* EINVAL means the file exists but isn't a symlink or doesn't
+ − 303 need case correction. */
1116
+ − 304 #ifdef WIN32_ANY
462
+ − 305 if (errno != EINVAL && errno != ENOENT)
+ − 306 #else
+ − 307 if (errno != EINVAL)
+ − 308 #endif
428
+ − 309 return NULL;
+ − 310 }
+ − 311 else
+ − 312 {
+ − 313 /* Protect against infinite loops. */
+ − 314 if (readlinks++ > MAX_READLINKS)
+ − 315 {
+ − 316 errno = ELOOP;
+ − 317 return NULL;
+ − 318 }
+ − 319
+ − 320 /* Note: readlink doesn't add the null byte. */
+ − 321 link_path[n] = '\0';
446
+ − 322
1116
+ − 323 if (abs_start (link_path) > 0)
428
+ − 324 /* Start over for an absolute symlink. */
1116
+ − 325 new_path = resolved_path + abs_start (link_path) - 1;
428
+ − 326 else
+ − 327 /* Otherwise back up over this component. */
446
+ − 328 for (--new_path; !IS_DIRECTORY_SEP (*new_path); --new_path)
+ − 329 assert (new_path > resolved_path);
428
+ − 330
+ − 331 /* Safe sex check. */
771
+ − 332 if (qxestrlen (path) + n >= PATH_MAX)
428
+ − 333 {
+ − 334 errno = ENAMETOOLONG;
+ − 335 return NULL;
+ − 336 }
+ − 337
+ − 338 /* Insert symlink contents into path. */
771
+ − 339 qxestrcat (link_path, path);
+ − 340 qxestrcpy (copy_path, link_path);
428
+ − 341 path = copy_path;
+ − 342 }
1116
+ − 343 #endif /* HAVE_READLINK || WIN32_ANY */
446
+ − 344 *new_path++ = DIRECTORY_SEP;
428
+ − 345 }
+ − 346
+ − 347 /* Delete trailing slash but don't whomp a lone slash. */
1116
+ − 348 if (new_path != resolved_path + abs_start (resolved_path) &&
771
+ − 349 IS_DIRECTORY_SEP (new_path[-1]))
428
+ − 350 new_path--;
+ − 351
+ − 352 /* Make sure it's null terminated. */
+ − 353 *new_path = '\0';
446
+ − 354
428
+ − 355 return resolved_path;
+ − 356 }