static int findPath(const char *orgPath, char **retPath, int *idx) { struct stat curStatBuf; char *pathBuffer = *retPath; int pBdex = 0, oPdex = *idx, redex; int oPlen = strlen(orgPath); errno = 0; if (pathBuffer == NULL) { pathBuffer = (char *)malloc(MAXPATHLEN); bzero(pathBuffer, MAXPATHLEN); *retPath = pathBuffer; } // find the first '/' startAt should point to '/' if (orgPath[oPdex] != '/') { free(pathBuffer); *retPath = NULL; return 0; } if (oPdex) { oPdex--; while (oPdex > 0 && orgPath[oPdex] != '/') oPdex--; while (pBdex < oPdex) pathBuffer[pBdex++] = orgPath[pBdex]; } // should be pointing @ first replacement '/' in pB & oP // ie... redex = pBdex = oPdex. now find endex oPdex = pBdex; findnext: redex = oPdex + 1; do { pathBuffer[pBdex] = orgPath[oPdex]; pBdex++; oPdex++; } while (orgPath[oPdex] != '/' && oPdex < oPlen); // should have path to validate. redex pts @ replacement loc // opdex, bpdex, @ cur termination pts. // pb not terminated with '/' // if opdex == oplen then we're done (no '/' termination) if (oPdex == oPlen) { *idx = oPlen; return 0; } lstat(pathBuffer, &curStatBuf); if (S_ISLNK(curStatBuf.st_mode)) { char *newName = NULL; int normPathLen, i; normPathLen = readlink(pathBuffer, (&pathBuffer[redex]), (MAXPATHLEN - redex)); pathBuffer[redex + normPathLen] = 0; if (pathBuffer[redex] == '/') { pBdex = 0; do { pathBuffer[pBdex] = pathBuffer[redex]; pBdex++; redex++; } while (--normPathLen > 0); redex = pBdex + 1; } else { pBdex = redex + normPathLen; } do { pathBuffer[pBdex] = orgPath[oPdex]; pBdex++; oPdex++; } while (oPdex < oPlen); pathBuffer[pBdex] = 0; i = redex - 1; findPath(pathBuffer, &newName, &i); free(pathBuffer); *retPath = newName; *idx = i; return 0; } if (errno) { *idx = redex - 1; free(pathBuffer); *retPath = NULL; return 0; } goto findnext; return 0; } /* *--------------------------------------------------------------------------- * * TclpObjNormalizePath -- * * This function scans through a path specification and replaces * it, in place, with a normalized version. A normalized version * is one in which all symlinks in the path are replaced with * their expanded form (except a symlink at the very end of the * path). * * Results: * The new 'nextCheckpoint' value, giving as far as we could * understand in the path. * * Side effects: * The pathPtr string, is modified. * *--------------------------------------------------------------------------- */ int TclpObjNormalizePath(interp, pathPtr, nextCheckpoint) Tcl_Interp *interp; Tcl_Obj *pathPtr; int nextCheckpoint; { int pathLen; char *currentPathEndPosition = NULL; char *path = Tcl_GetStringFromObj(pathPtr, &pathLen); #if 1 findPath(path, ¤tPathEndPosition, &nextCheckpoint); if (currentPathEndPosition) { Tcl_DString ds; Tcl_ExternalToUtfDString(NULL, currentPathEndPosition, nextCheckpoint, &ds); Tcl_SetStringObj(pathPtr, Tcl_DStringValue(&ds), Tcl_DStringLength(&ds)); Tcl_DStringFree(&ds); } #else #ifndef NO_REALPATH char normPath[MAXPATHLEN]; Tcl_DString ds; CONST char *nativePath; #endif /* !NO_REALPATH */ /* * We add '1' here because if nextCheckpoint is zero we know * that '/' exists, and if it isn't zero, it must point at * a directory separator which we also know exists. */ currentPathEndPosition = path + nextCheckpoint; if (*currentPathEndPosition == '/') { currentPathEndPosition++; } #ifndef NO_REALPATH /* For speed, try to get the entire path in one go */ if (nextCheckpoint == 0) { char *lastDir = strrchr(currentPathEndPosition, '/'); if (lastDir != NULL) { nativePath = Tcl_UtfToExternalDString(NULL, path, lastDir - path, &ds); if (Realpath(nativePath, normPath) != NULL) { nextCheckpoint = lastDir - path; goto wholeStringOk; } } } /* Else do it the slow way */ #endif /* !NO_REALPATH */ while (1) { char cur = *currentPathEndPosition; if ((cur == '/') && (path != currentPathEndPosition)) { /* Reached directory separator */ Tcl_DString ds; CONST char *nativePath; int accessOk; nativePath = Tcl_UtfToExternalDString(NULL, path, currentPathEndPosition - path, &ds); accessOk = access(nativePath, F_OK); Tcl_DStringFree(&ds); if (accessOk != 0) { /* File doesn't exist */ break; } /* Update the acceptable point */ nextCheckpoint = currentPathEndPosition - path; } else if (cur == 0) { /* Reached end of string */ break; } currentPathEndPosition++; } /* * We should really now convert this to a canonical path. We do * that with 'realpath' if we have it available. Otherwise we could * step through every single path component, checking whether it is a * symlink, but that would be a lot of work, and most modern OSes * have 'realpath'. */ #ifndef NO_REALPATH /* * If we only had '/foo' or '/' then we never increment nextCheckpoint * and we don't need or want to go through 'Realpath'. Also, on some * platforms, passing an empty string to 'Realpath' will give us the * normalized pwd, which is not what we want at all! */ if (nextCheckpoint == 0) return 0; nativePath = Tcl_UtfToExternalDString(NULL, path, nextCheckpoint, &ds); if (Realpath(nativePath, normPath) != NULL) { int newNormLen; wholeStringOk: newNormLen = strlen(normPath); if ((newNormLen == Tcl_DStringLength(&ds)) && (strcmp(normPath, nativePath) == 0)) { /* String is unchanged */ Tcl_DStringFree(&ds); if (path[nextCheckpoint] != '\0') { nextCheckpoint++; } return nextCheckpoint; } /* * Free up the native path and put in its place the * converted, normalized path. */ Tcl_DStringFree(&ds); Tcl_ExternalToUtfDString(NULL, normPath, (int) newNormLen, &ds); if (path[nextCheckpoint] != '\0') { /* not at end, append remaining path */ int normLen = Tcl_DStringLength(&ds); Tcl_DStringAppend(&ds, path + nextCheckpoint, pathLen - nextCheckpoint); /* * We recognise up to and including the directory * separator. */ nextCheckpoint = normLen + 1; } else { /* We recognise the whole string */ nextCheckpoint = Tcl_DStringLength(&ds); } /* * Overwrite with the normalized path. */ Tcl_SetStringObj(pathPtr, Tcl_DStringValue(&ds), Tcl_DStringLength(&ds)); } Tcl_DStringFree(&ds); #endif /* !NO_REALPATH */ #endif /* 0 or 1 */ return nextCheckpoint; }