Attachment "tclUnixFCmd.c" to
ticket [929534ffff]
added by
tauvan
2004-04-05 10:43:46.
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;
}