Tcl Source Code

Artifact [b73576e612]
Login

Artifact b73576e612d6b1c282a66161fecc5cb83988c4b5:

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, &currentPathEndPosition, &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;
}