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. */
2367
+ − 86 tmp =
+ − 87 alloca_ibytes (cygwin_posix_to_win32_path_list_buf_size ((char *) name));
1116
+ − 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 }
2421
+ − 108 else if (qxestrlen (name) >= PATH_MAX_INTERNAL)
1116
+ − 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 {
2421
+ − 173 Ibyte copy_path[PATH_MAX_INTERNAL];
867
+ − 174 Ibyte *new_path = resolved_path;
+ − 175 Ibyte *max_path;
1116
+ − 176 #if defined (HAVE_READLINK) || defined (WIN32_ANY)
428
+ − 177 int readlinks = 0;
2421
+ − 178 Ibyte link_path[PATH_MAX_INTERNAL];
428
+ − 179 int n;
1116
+ − 180 int abslen = abs_start (path);
428
+ − 181 #endif
+ − 182
1760
+ − 183 restart:
+ − 184
428
+ − 185 /* Make a copy of the source path since we may need to modify it. */
771
+ − 186 qxestrcpy (copy_path, path);
428
+ − 187 path = copy_path;
2421
+ − 188 max_path = copy_path + PATH_MAX_INTERNAL - 2;
446
+ − 189
819
+ − 190 if (0)
+ − 191 ;
1116
+ − 192 #ifdef WIN32_ANY
446
+ − 193 /* Check for c:/... or //server/... */
988
+ − 194 else if (abslen == 3 || abslen == 2)
428
+ − 195 {
462
+ − 196 /* Make sure drive letter is lowercased. */
1116
+ − 197 if (abslen == 3)
+ − 198 {
+ − 199 *new_path = tolower (*path);
+ − 200 new_path++;
+ − 201 path++;
+ − 202 abslen--;
+ − 203 }
988
+ − 204 /* Coerce directory chars. */
1116
+ − 205 while (abslen-- > 0)
+ − 206 {
+ − 207 if (IS_DIRECTORY_SEP (*path))
+ − 208 *new_path++ = DIRECTORY_SEP;
+ − 209 else
+ − 210 *new_path++ = *path;
+ − 211 path++;
+ − 212 }
428
+ − 213 }
819
+ − 214 #endif
+ − 215 #ifdef WIN32_NATIVE
446
+ − 216 /* No drive letter, but a beginning slash? Prepend drive letter. */
+ − 217 else if (abslen == 1)
428
+ − 218 {
2421
+ − 219 get_initial_directory (new_path, PATH_MAX_INTERNAL - 1);
428
+ − 220 new_path += 3;
+ − 221 path++;
+ − 222 }
446
+ − 223 /* Just a path name, prepend the current directory */
1116
+ − 224 else
428
+ − 225 {
2421
+ − 226 get_initial_directory (new_path, PATH_MAX_INTERNAL - 1);
771
+ − 227 new_path += qxestrlen (new_path);
446
+ − 228 if (!IS_DIRECTORY_SEP (new_path[-1]))
+ − 229 *new_path++ = DIRECTORY_SEP;
428
+ − 230 }
+ − 231 #else
771
+ − 232 /* If it's a relative pathname use get_initial_directory for starters. */
819
+ − 233 else if (abslen == 0)
428
+ − 234 {
2421
+ − 235 get_initial_directory (new_path, PATH_MAX_INTERNAL - 1);
771
+ − 236 new_path += qxestrlen (new_path);
446
+ − 237 if (!IS_DIRECTORY_SEP (new_path[-1]))
+ − 238 *new_path++ = DIRECTORY_SEP;
428
+ − 239 }
+ − 240 else
+ − 241 {
446
+ − 242 /* Copy first directory sep. May have two on cygwin. */
771
+ − 243 qxestrncpy (new_path, path, abslen);
446
+ − 244 new_path += abslen;
+ − 245 path += abslen;
428
+ − 246 }
+ − 247 #endif
+ − 248 /* Expand each slash-separated pathname component. */
+ − 249 while (*path != '\0')
+ − 250 {
+ − 251 /* Ignore stray "/". */
446
+ − 252 if (IS_DIRECTORY_SEP (*path))
428
+ − 253 {
+ − 254 path++;
+ − 255 continue;
+ − 256 }
+ − 257
+ − 258 if (*path == '.')
+ − 259 {
+ − 260 /* Ignore ".". */
446
+ − 261 if (path[1] == '\0' || IS_DIRECTORY_SEP (path[1]))
428
+ − 262 {
+ − 263 path++;
+ − 264 continue;
+ − 265 }
+ − 266
442
+ − 267 /* Handle ".." */
+ − 268 if (path[1] == '.' &&
446
+ − 269 (path[2] == '\0' || IS_DIRECTORY_SEP (path[2])))
428
+ − 270 {
442
+ − 271 path += 2;
428
+ − 272
442
+ − 273 /* Ignore ".." at root. */
1116
+ − 274 if (new_path == resolved_path + abs_start (resolved_path))
442
+ − 275 continue;
428
+ − 276
442
+ − 277 /* Handle ".." by backing up. */
446
+ − 278 --new_path;
+ − 279 while (!IS_DIRECTORY_SEP (new_path[-1]))
+ − 280 --new_path;
442
+ − 281 continue;
428
+ − 282 }
+ − 283 }
+ − 284
+ − 285 /* Safely copy the next pathname component. */
446
+ − 286 while (*path != '\0' && !IS_DIRECTORY_SEP (*path))
428
+ − 287 {
+ − 288 if (path > max_path)
+ − 289 {
+ − 290 errno = ENAMETOOLONG;
+ − 291 return NULL;
+ − 292 }
+ − 293 *new_path++ = *path++;
+ − 294 }
+ − 295
1116
+ − 296 #if defined (HAVE_READLINK) || defined (WIN32_ANY)
771
+ − 297 /* See if latest pathname component is a symlink or needs case
+ − 298 correction. */
428
+ − 299 *new_path = '\0';
2421
+ − 300 n = readlink_and_correct_case (resolved_path, link_path, PATH_MAX_INTERNAL - 1);
428
+ − 301
+ − 302 if (n < 0)
+ − 303 {
771
+ − 304 /* EINVAL means the file exists but isn't a symlink or doesn't
+ − 305 need case correction. */
1116
+ − 306 #ifdef WIN32_ANY
462
+ − 307 if (errno != EINVAL && errno != ENOENT)
+ − 308 #else
+ − 309 if (errno != EINVAL)
+ − 310 #endif
428
+ − 311 return NULL;
+ − 312 }
+ − 313 else
+ − 314 {
+ − 315 /* Protect against infinite loops. */
+ − 316 if (readlinks++ > MAX_READLINKS)
+ − 317 {
+ − 318 errno = ELOOP;
+ − 319 return NULL;
+ − 320 }
+ − 321
+ − 322 /* Note: readlink doesn't add the null byte. */
+ − 323 link_path[n] = '\0';
446
+ − 324
1760
+ − 325 abslen = abs_start (link_path);
+ − 326 if (abslen > 0)
+ − 327 {
+ − 328 /* Start over for an absolute symlink. */
+ − 329 new_path = resolved_path;
+ − 330 qxestrcat (link_path, path);
+ − 331 path = link_path;
+ − 332 goto restart;
+ − 333 }
+ − 334
+ − 335 /* Otherwise back up over this component. */
+ − 336 for (--new_path; !IS_DIRECTORY_SEP (*new_path); --new_path)
+ − 337 assert (new_path > resolved_path);
428
+ − 338
+ − 339 /* Safe sex check. */
2421
+ − 340 if (qxestrlen (path) + n >= PATH_MAX_INTERNAL)
428
+ − 341 {
+ − 342 errno = ENAMETOOLONG;
+ − 343 return NULL;
+ − 344 }
+ − 345
+ − 346 /* Insert symlink contents into path. */
771
+ − 347 qxestrcat (link_path, path);
+ − 348 qxestrcpy (copy_path, link_path);
428
+ − 349 path = copy_path;
+ − 350 }
1116
+ − 351 #endif /* HAVE_READLINK || WIN32_ANY */
446
+ − 352 *new_path++ = DIRECTORY_SEP;
428
+ − 353 }
+ − 354
+ − 355 /* Delete trailing slash but don't whomp a lone slash. */
1116
+ − 356 if (new_path != resolved_path + abs_start (resolved_path) &&
771
+ − 357 IS_DIRECTORY_SEP (new_path[-1]))
428
+ − 358 new_path--;
+ − 359
+ − 360 /* Make sure it's null terminated. */
+ − 361 *new_path = '\0';
446
+ − 362
428
+ − 363 return resolved_path;
+ − 364 }