/* * tclMacFCmd.c -- * * Implements the Macintosh specific portions of the file manipulation * subcommands of the "file" command. * * Copyright (c) 1996-1998 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * * RCS: @(#) $Id: tclMacFCmd.c,v 1.19 2003/02/04 17:06:51 vincentdarley Exp $ */ #include "tclInt.h" #include "tclMac.h" #include "tclMacInt.h" #include "tclPort.h" #include #include #include #include #include #include #include #include #include #include /* * Callback for the file attributes code. */ static int GetFileFinderAttributes _ANSI_ARGS_((Tcl_Interp *interp, int objIndex, Tcl_Obj *fileName, Tcl_Obj **attributePtrPtr)); static int GetFileReadOnly _ANSI_ARGS_((Tcl_Interp *interp, int objIndex, Tcl_Obj *fileName, Tcl_Obj **readOnlyPtrPtr)); static int SetFileFinderAttributes _ANSI_ARGS_((Tcl_Interp *interp, int objIndex, Tcl_Obj *fileName, Tcl_Obj *attributePtr)); static int SetFileReadOnly _ANSI_ARGS_((Tcl_Interp *interp, int objIndex, Tcl_Obj *fileName, Tcl_Obj *readOnlyPtr)); /* * These are indeces into the tclpFileAttrsStrings table below. */ #define MAC_CREATOR_ATTRIBUTE 0 #define MAC_HIDDEN_ATTRIBUTE 1 #define MAC_READONLY_ATTRIBUTE 2 #define MAC_TYPE_ATTRIBUTE 3 /* * Global variables for the file attributes code. */ CONST char *tclpFileAttrStrings[] = {"-creator", "-hidden", "-readonly", "-type", (char *) NULL}; CONST TclFileAttrProcs tclpFileAttrProcs[] = { {GetFileFinderAttributes, SetFileFinderAttributes}, {GetFileFinderAttributes, SetFileFinderAttributes}, {GetFileReadOnly, SetFileReadOnly}, {GetFileFinderAttributes, SetFileFinderAttributes}}; /* * File specific static data */ static long startSeed = 248923489; /* * Prototypes for procedure only used in this file */ static pascal Boolean CopyErrHandler _ANSI_ARGS_((OSErr error, short failedOperation, short srcVRefNum, long srcDirID, ConstStr255Param srcName, short dstVRefNum, long dstDirID,ConstStr255Param dstName)); static int DoCopyDirectory _ANSI_ARGS_((CONST char *src, CONST char *dst, Tcl_DString *errorPtr)); static int DoCopyFile _ANSI_ARGS_((CONST char *src, CONST char *dst)); static int DoCreateDirectory _ANSI_ARGS_((CONST char *path)); static int DoRemoveDirectory _ANSI_ARGS_((CONST char *path, int recursive, Tcl_DString *errorPtr)); static int DoRenameFile _ANSI_ARGS_((CONST char *src, CONST char *dst)); OSErr FSpGetFLockCompat _ANSI_ARGS_((const FSSpec *specPtr, Boolean *lockedPtr)); static OSErr GetFileSpecs _ANSI_ARGS_((CONST char *path, FSSpec *pathSpecPtr, FSSpec *dirSpecPtr, Boolean *pathExistsPtr, Boolean *pathIsDirectoryPtr)); static OSErr MoveRename _ANSI_ARGS_((const FSSpec *srcSpecPtr, const FSSpec *dstSpecPtr, StringPtr copyName)); static int Pstrequal _ANSI_ARGS_((ConstStr255Param stringA, ConstStr255Param stringB)); /* *--------------------------------------------------------------------------- * * TclpObjRenameFile, DoRenameFile -- * * Changes the name of an existing file or directory, from src to dst. * If src and dst refer to the same file or directory, does nothing * and returns success. Otherwise if dst already exists, it will be * deleted and replaced by src subject to the following conditions: * If src is a directory, dst may be an empty directory. * If src is a file, dst may be a file. * In any other situation where dst already exists, the rename will * fail. * * Results: * If the directory was successfully created, returns TCL_OK. * Otherwise the return value is TCL_ERROR and errno is set to * indicate the error. Some possible values for errno are: * * EACCES: src or dst parent directory can't be read and/or written. * EEXIST: dst is a non-empty directory. * EINVAL: src is a root directory or dst is a subdirectory of src. * EISDIR: dst is a directory, but src is not. * ENOENT: src doesn't exist. src or dst is "". * ENOTDIR: src is a directory, but dst is not. * EXDEV: src and dst are on different filesystems. * * Side effects: * The implementation of rename may allow cross-filesystem renames, * but the caller should be prepared to emulate it with copy and * delete if errno is EXDEV. * *--------------------------------------------------------------------------- */ int TclpObjRenameFile(srcPathPtr, destPathPtr) Tcl_Obj *srcPathPtr; Tcl_Obj *destPathPtr; { return DoRenameFile(Tcl_FSGetNativePath(srcPathPtr), Tcl_FSGetNativePath(destPathPtr)); } static int DoRenameFile( CONST char *src, /* Pathname of file or dir to be renamed * (native). */ CONST char *dst) /* New pathname of file or directory * (native). */ { FSSpec srcFileSpec, dstFileSpec, dstDirSpec; OSErr err; long srcID, dummy; Boolean srcIsDirectory, dstIsDirectory, dstExists, dstLocked; err = FSpLLocationFromPath(strlen(src), src, &srcFileSpec); if (err == noErr) { FSpGetDirectoryID(&srcFileSpec, &srcID, &srcIsDirectory); } if (err == noErr) { err = GetFileSpecs(dst, &dstFileSpec, &dstDirSpec, &dstExists, &dstIsDirectory); } if (err == noErr) { if (dstExists == 0) { err = MoveRename(&srcFileSpec, &dstDirSpec, dstFileSpec.name); goto end; } err = FSpGetFLockCompat(&dstFileSpec, &dstLocked); if (dstLocked) { FSpRstFLockCompat(&dstFileSpec); } } if (err == noErr) { if (srcIsDirectory) { if (dstIsDirectory) { /* * The following call will remove an empty directory. If it * fails, it's because it wasn't empty. */ if (DoRemoveDirectory(dst, 0, NULL) != TCL_OK) { return TCL_ERROR; } /* * Now that that empty directory is gone, we can try * renaming src. If that fails, we'll put this empty * directory back, for completeness. */ err = MoveRename(&srcFileSpec, &dstDirSpec, dstFileSpec.name); if (err != noErr) { FSpDirCreateCompat(&dstFileSpec, smSystemScript, &dummy); if (dstLocked) { FSpSetFLockCompat(&dstFileSpec); } } } else { errno = ENOTDIR; return TCL_ERROR; } } else { if (dstIsDirectory) { errno = EISDIR; return TCL_ERROR; } else { /* * Overwrite existing file by: * * 1. Rename existing file to temp name. * 2. Rename old file to new name. * 3. If success, delete temp file. If failure, * put temp file back to old name. */ Str31 tmpName; FSSpec tmpFileSpec; err = GenerateUniqueName(dstFileSpec.vRefNum, &startSeed, dstFileSpec.parID, dstFileSpec.parID, tmpName); if (err == noErr) { err = FSpRenameCompat(&dstFileSpec, tmpName); } if (err == noErr) { err = FSMakeFSSpecCompat(dstFileSpec.vRefNum, dstFileSpec.parID, tmpName, &tmpFileSpec); } if (err == noErr) { err = MoveRename(&srcFileSpec, &dstDirSpec, dstFileSpec.name); } if (err == noErr) { FSpDeleteCompat(&tmpFileSpec); } else { FSpDeleteCompat(&dstFileSpec); FSpRenameCompat(&tmpFileSpec, dstFileSpec.name); if (dstLocked) { FSpSetFLockCompat(&dstFileSpec); } } } } } end: if (err != noErr) { errno = TclMacOSErrorToPosixError(err); return TCL_ERROR; } return TCL_OK; } /* *-------------------------------------------------------------------------- * * MoveRename -- * * Helper function for TclpRenameFile. Renames a file or directory * into the same directory or another directory. The target name * must not already exist in the destination directory. * * Don't use FSpMoveRenameCompat because it doesn't work with * directories or with locked files. * * Results: * Returns a mac error indicating the cause of the failure. * * Side effects: * Creates a temp file in the target directory to handle a rename * between directories. * *-------------------------------------------------------------------------- */ static OSErr MoveRename( const FSSpec *srcFileSpecPtr, /* Source object. */ const FSSpec *dstDirSpecPtr, /* Destination directory. */ StringPtr copyName) /* New name for object in destination * directory. */ { OSErr err; long srcID, dstID; Boolean srcIsDir, dstIsDir; Str31 tmpName; FSSpec dstFileSpec, srcDirSpec, tmpSrcFileSpec, tmpDstFileSpec; Boolean locked; if (srcFileSpecPtr->parID == 1) { /* * Trying to rename a volume. */ return badMovErr; } if (srcFileSpecPtr->vRefNum != dstDirSpecPtr->vRefNum) { /* * Renaming across volumes. */ return diffVolErr; } err = FSpGetFLockCompat(srcFileSpecPtr, &locked); if (locked) { FSpRstFLockCompat(srcFileSpecPtr); } if (err == noErr) { err = FSpGetDirectoryID(dstDirSpecPtr, &dstID, &dstIsDir); } if (err == noErr) { if (srcFileSpecPtr->parID == dstID) { /* * Renaming object within directory. */ err = FSpRenameCompat(srcFileSpecPtr, copyName); goto done; } if (Pstrequal(srcFileSpecPtr->name, copyName)) { /* * Moving object to another directory (under same name). */ err = FSpCatMoveCompat(srcFileSpecPtr, dstDirSpecPtr); goto done; } err = FSpGetDirectoryID(srcFileSpecPtr, &srcID, &srcIsDir); } if (err == noErr) { /* * Fullblown: rename source object to temp name, move temp to * dest directory, and rename temp to target. */ err = GenerateUniqueName(srcFileSpecPtr->vRefNum, &startSeed, srcFileSpecPtr->parID, dstID, tmpName); FSMakeFSSpecCompat(srcFileSpecPtr->vRefNum, srcFileSpecPtr->parID, tmpName, &tmpSrcFileSpec); FSMakeFSSpecCompat(dstDirSpecPtr->vRefNum, dstID, tmpName, &tmpDstFileSpec); } if (err == noErr) { err = FSpRenameCompat(srcFileSpecPtr, tmpName); } if (err == noErr) { err = FSpCatMoveCompat(&tmpSrcFileSpec, dstDirSpecPtr); if (err == noErr) { err = FSpRenameCompat(&tmpDstFileSpec, copyName); if (err == noErr) { goto done; } FSMakeFSSpecCompat(srcFileSpecPtr->vRefNum, srcFileSpecPtr->parID, NULL, &srcDirSpec); FSpCatMoveCompat(&tmpDstFileSpec, &srcDirSpec); } FSpRenameCompat(&tmpSrcFileSpec, srcFileSpecPtr->name); } done: if (locked != false) { if (err == noErr) { FSMakeFSSpecCompat(dstDirSpecPtr->vRefNum, dstID, copyName, &dstFileSpec); FSpSetFLockCompat(&dstFileSpec); } else { FSpSetFLockCompat(srcFileSpecPtr); } } return err; } /* *--------------------------------------------------------------------------- * * TclpObjCopyFile, DoCopyFile -- * * Copy a single file (not a directory). If dst already exists and * is not a directory, it is removed. * * Results: * If the file was successfully copied, returns TCL_OK. Otherwise * the return value is TCL_ERROR and errno is set to indicate the * error. Some possible values for errno are: * * EACCES: src or dst parent directory can't be read and/or written. * EISDIR: src or dst is a directory. * ENOENT: src doesn't exist. src or dst is "". * * Side effects: * This procedure will also copy symbolic links, block, and * character devices, and fifos. For symbolic links, the links * themselves will be copied and not what they point to. For the * other special file types, the directory entry will be copied and * not the contents of the device that it refers to. * *--------------------------------------------------------------------------- */ int TclpObjCopyFile(srcPathPtr, destPathPtr) Tcl_Obj *srcPathPtr; Tcl_Obj *destPathPtr; { return DoCopyFile(Tcl_FSGetNativePath(srcPathPtr), Tcl_FSGetNativePath(destPathPtr)); } static int DoCopyFile( CONST char *src, /* Pathname of file to be copied (native). */ CONST char *dst) /* Pathname of file to copy to (native). */ { OSErr err, dstErr; Boolean dstExists, dstIsDirectory, dstLocked; FSSpec srcFileSpec, dstFileSpec, dstDirSpec, tmpFileSpec; Str31 tmpName; err = FSpLLocationFromPath(strlen(src), src, &srcFileSpec); if (err == noErr) { err = GetFileSpecs(dst, &dstFileSpec, &dstDirSpec, &dstExists, &dstIsDirectory); } if (dstExists) { if (dstIsDirectory) { errno = EISDIR; return TCL_ERROR; } err = FSpGetFLockCompat(&dstFileSpec, &dstLocked); if (dstLocked) { FSpRstFLockCompat(&dstFileSpec); } /* * Backup dest file. */ dstErr = GenerateUniqueName(dstFileSpec.vRefNum, &startSeed, dstFileSpec.parID, dstFileSpec.parID, tmpName); if (dstErr == noErr) { dstErr = FSpRenameCompat(&dstFileSpec, tmpName); } } if (err == noErr) { err = FSpFileCopy(&srcFileSpec, &dstDirSpec, (StringPtr) dstFileSpec.name, NULL, 0, true); } if ((dstExists != false) && (dstErr == noErr)) { FSMakeFSSpecCompat(dstFileSpec.vRefNum, dstFileSpec.parID, tmpName, &tmpFileSpec); if (err == noErr) { /* * Delete backup file. */ FSpDeleteCompat(&tmpFileSpec); } else { /* * Restore backup file. */ FSpDeleteCompat(&dstFileSpec); FSpRenameCompat(&tmpFileSpec, dstFileSpec.name); if (dstLocked) { FSpSetFLockCompat(&dstFileSpec); } } } if (err != noErr) { errno = TclMacOSErrorToPosixError(err); return TCL_ERROR; } return TCL_OK; } /* *--------------------------------------------------------------------------- * * TclpObjDeleteFile, TclpDeleteFile -- * * Removes a single file (not a directory). * * Results: * If the file was successfully deleted, returns TCL_OK. Otherwise * the return value is TCL_ERROR and errno is set to indicate the * error. Some possible values for errno are: * * EACCES: a parent directory can't be read and/or written. * EISDIR: path is a directory. * ENOENT: path doesn't exist or is "". * * Side effects: * The file is deleted, even if it is read-only. * *--------------------------------------------------------------------------- */ int TclpObjDeleteFile(pathPtr) Tcl_Obj *pathPtr; { return TclpDeleteFile(Tcl_FSGetNativePath(pathPtr)); } int TclpDeleteFile( CONST char *path) /* Pathname of file to be removed (native). */ { OSErr err; FSSpec fileSpec; Boolean isDirectory; long dirID; err = FSpLLocationFromPath(strlen(path), path, &fileSpec); if (err == noErr) { /* * Since FSpDeleteCompat will delete an empty directory, make sure * that this isn't a directory first. */ FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory); if (isDirectory == true) { errno = EISDIR; return TCL_ERROR; } } err = FSpDeleteCompat(&fileSpec); if (err == fLckdErr) { FSpRstFLockCompat(&fileSpec); err = FSpDeleteCompat(&fileSpec); if (err != noErr) { FSpSetFLockCompat(&fileSpec); } } if (err != noErr) { errno = TclMacOSErrorToPosixError(err); return TCL_ERROR; } return TCL_OK; } /* *--------------------------------------------------------------------------- * * TclpObjCreateDirectory, DoCreateDirectory -- * * Creates the specified directory. All parent directories of the * specified directory must already exist. The directory is * automatically created with permissions so that user can access * the new directory and create new files or subdirectories in it. * * Results: * If the directory was successfully created, returns TCL_OK. * Otherwise the return value is TCL_ERROR and errno is set to * indicate the error. Some possible values for errno are: * * EACCES: a parent directory can't be read and/or written. * EEXIST: path already exists. * ENOENT: a parent directory doesn't exist. * * Side effects: * A directory is created with the current umask, except that * permission for u+rwx will always be added. * *--------------------------------------------------------------------------- */ int TclpObjCreateDirectory(pathPtr) Tcl_Obj *pathPtr; { return DoCreateDirectory(Tcl_FSGetNativePath(pathPtr)); } static int DoCreateDirectory( CONST char *path) /* Pathname of directory to create (native). */ { OSErr err; FSSpec dirSpec; long outDirID; err = FSpLocationFromPath(strlen(path), path, &dirSpec); if (err == noErr) { err = dupFNErr; /* EEXIST. */ } else if (err == fnfErr) { err = FSpDirCreateCompat(&dirSpec, smSystemScript, &outDirID); } if (err != noErr) { errno = TclMacOSErrorToPosixError(err); return TCL_ERROR; } return TCL_OK; } /* *--------------------------------------------------------------------------- * * TclpObjCopyDirectory, DoCopyDirectory -- * * Recursively copies a directory. The target directory dst must * not already exist. Note that this function does not merge two * directory hierarchies, even if the target directory is an an * empty directory. * * Results: * If the directory was successfully copied, returns TCL_OK. * Otherwise the return value is TCL_ERROR, errno is set to indicate * the error, and the pathname of the file that caused the error * is stored in errorPtr. See TclpCreateDirectory and TclpCopyFile * for a description of possible values for errno. * * Side effects: * An exact copy of the directory hierarchy src will be created * with the name dst. If an error occurs, the error will * be returned immediately, and remaining files will not be * processed. * *--------------------------------------------------------------------------- */ int TclpObjCopyDirectory(srcPathPtr, destPathPtr, errorPtr) Tcl_Obj *srcPathPtr; Tcl_Obj *destPathPtr; Tcl_Obj **errorPtr; { Tcl_DString ds; int ret; ret = DoCopyDirectory(Tcl_FSGetNativePath(srcPathPtr), Tcl_FSGetNativePath(destPathPtr), &ds); if (ret != TCL_OK) { *errorPtr = Tcl_NewStringObj(Tcl_DStringValue(&ds), -1); Tcl_DStringFree(&ds); Tcl_IncrRefCount(*errorPtr); } return ret; } static int DoCopyDirectory( CONST char *src, /* Pathname of directory to be copied * (Native). */ CONST char *dst, /* Pathname of target directory (Native). */ Tcl_DString *errorPtr) /* If non-NULL, uninitialized or free * DString filled with UTF-8 name of file * causing error. */ { OSErr err, saveErr; long srcID, tmpDirID; FSSpec srcFileSpec, dstFileSpec, dstDirSpec, tmpDirSpec, tmpFileSpec; Boolean srcIsDirectory, srcLocked; Boolean dstIsDirectory, dstExists; Str31 tmpName; err = FSpLocationFromPath(strlen(src), src, &srcFileSpec); if (err == noErr) { err = FSpGetDirectoryID(&srcFileSpec, &srcID, &srcIsDirectory); } if (err == noErr) { if (srcIsDirectory == false) { err = afpObjectTypeErr; /* ENOTDIR. */ } } if (err == noErr) { err = GetFileSpecs(dst, &dstFileSpec, &dstDirSpec, &dstExists, &dstIsDirectory); } if (dstExists) { if (dstIsDirectory == false) { err = afpObjectTypeErr; /* ENOTDIR. */ } else { err = dupFNErr; /* EEXIST. */ } } if (err != noErr) { goto done; } if ((srcFileSpec.vRefNum == dstFileSpec.vRefNum) && (srcFileSpec.parID == dstFileSpec.parID) && (Pstrequal(srcFileSpec.name, dstFileSpec.name) != 0)) { /* * Copying on top of self. No-op. */ goto done; } /* * This algorthm will work making a copy of the source directory in * the current directory with a new name, in a new directory with the * same name, and in a new directory with a new name: * * 1. Make dstDir/tmpDir. * 2. Copy srcDir/src to dstDir/tmpDir/src * 3. Rename dstDir/tmpDir/src to dstDir/tmpDir/dst (if necessary). * 4. CatMove dstDir/tmpDir/dst to dstDir/dst. * 5. Remove dstDir/tmpDir. */ err = FSpGetFLockCompat(&srcFileSpec, &srcLocked); if (srcLocked) { FSpRstFLockCompat(&srcFileSpec); } if (err == noErr) { err = GenerateUniqueName(dstFileSpec.vRefNum, &startSeed, dstFileSpec.parID, dstFileSpec.parID, tmpName); } if (err == noErr) { FSMakeFSSpecCompat(dstFileSpec.vRefNum, dstFileSpec.parID, tmpName, &tmpDirSpec); err = FSpDirCreateCompat(&tmpDirSpec, smSystemScript, &tmpDirID); } if (err == noErr) { err = FSpDirectoryCopy(&srcFileSpec, &tmpDirSpec, NULL, NULL, 0, true, CopyErrHandler); } /* * Even if the Copy failed, Rename/Move whatever did get copied to the * appropriate final destination, if possible. */ saveErr = err; err = noErr; if (Pstrequal(srcFileSpec.name, dstFileSpec.name) == 0) { err = FSMakeFSSpecCompat(tmpDirSpec.vRefNum, tmpDirID, srcFileSpec.name, &tmpFileSpec); if (err == noErr) { err = FSpRenameCompat(&tmpFileSpec, dstFileSpec.name); } } if (err == noErr) { err = FSMakeFSSpecCompat(tmpDirSpec.vRefNum, tmpDirID, dstFileSpec.name, &tmpFileSpec); } if (err == noErr) { err = FSpCatMoveCompat(&tmpFileSpec, &dstDirSpec); } if (err == noErr) { if (srcLocked) { FSpSetFLockCompat(&dstFileSpec); } } FSpDeleteCompat(&tmpDirSpec); if (saveErr != noErr) { err = saveErr; } done: if (err != noErr) { errno = TclMacOSErrorToPosixError(err); if (errorPtr != NULL) { Tcl_ExternalToUtfDString(NULL, dst, -1, errorPtr); } return TCL_ERROR; } return TCL_OK; } /* *---------------------------------------------------------------------- * * CopyErrHandler -- * * This procedure is called from the MoreFiles procedure * FSpDirectoryCopy whenever an error occurs. * * Results: * False if the condition should not be considered an error, true * otherwise. * * Side effects: * Since FSpDirectoryCopy() is called only after removing any * existing target directories, there shouldn't be any errors. * *---------------------------------------------------------------------- */ static pascal Boolean CopyErrHandler( OSErr error, /* Error that occured */ short failedOperation, /* operation that caused the error */ short srcVRefNum, /* volume ref number of source */ long srcDirID, /* directory id of source */ ConstStr255Param srcName, /* name of source */ short dstVRefNum, /* volume ref number of dst */ long dstDirID, /* directory id of dst */ ConstStr255Param dstName) /* name of dst directory */ { return true; } /* *--------------------------------------------------------------------------- * * TclpObjRemoveDirectory, DoRemoveDirectory -- * * Removes directory (and its contents, if the recursive flag is set). * * Results: * If the directory was successfully removed, returns TCL_OK. * Otherwise the return value is TCL_ERROR, errno is set to indicate * the error, and the pathname of the file that caused the error * is stored in errorPtr. Some possible values for errno are: * * EACCES: path directory can't be read and/or written. * EEXIST: path is a non-empty directory. * EINVAL: path is a root directory. * ENOENT: path doesn't exist or is "". * ENOTDIR: path is not a directory. * * Side effects: * Directory removed. If an error occurs, the error will be returned * immediately, and remaining files will not be deleted. * *--------------------------------------------------------------------------- */ int TclpObjRemoveDirectory(pathPtr, recursive, errorPtr) Tcl_Obj *pathPtr; int recursive; Tcl_Obj **errorPtr; { Tcl_DString ds; int ret; ret = DoRemoveDirectory(Tcl_FSGetNativePath(pathPtr),recursive, &ds); if (ret != TCL_OK) { *errorPtr = Tcl_NewStringObj(Tcl_DStringValue(&ds), -1); Tcl_DStringFree(&ds); Tcl_IncrRefCount(*errorPtr); } return ret; } static int DoRemoveDirectory( CONST char *path, /* Pathname of directory to be removed * (native). */ int recursive, /* If non-zero, removes directories that * are nonempty. Otherwise, will only remove * empty directories. */ Tcl_DString *errorPtr) /* If non-NULL, uninitialized or free * DString filled with UTF-8 name of file * causing error. */ { OSErr err; FSSpec fileSpec; long dirID; int locked; Boolean isDirectory; CInfoPBRec pb; Str255 fileName; locked = 0; err = FSpLocationFromPath(strlen(path), path, &fileSpec); if (err != noErr) { goto done; } /* * Since FSpDeleteCompat will delete a file, make sure this isn't * a file first. */ isDirectory = 1; FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory); if (isDirectory == 0) { errno = ENOTDIR; return TCL_ERROR; } err = FSpDeleteCompat(&fileSpec); if (err == fLckdErr) { locked = 1; FSpRstFLockCompat(&fileSpec); err = FSpDeleteCompat(&fileSpec); } if (err == noErr) { return TCL_OK; } if (err != fBsyErr) { goto done; } if (recursive == 0) { /* * fBsyErr means one of three things: file busy, directory not empty, * or working directory control block open. Determine if directory * is empty. If directory is not empty, return EEXIST. */ pb.hFileInfo.ioVRefNum = fileSpec.vRefNum; pb.hFileInfo.ioDirID = dirID; pb.hFileInfo.ioNamePtr = (StringPtr) fileName; pb.hFileInfo.ioFDirIndex = 1; if (PBGetCatInfoSync(&pb) == noErr) { err = dupFNErr; /* EEXIST */ goto done; } } /* * DeleteDirectory removes a directory and all its contents, including * any locked files. There is no interface to get the name of the * file that caused the error, if an error occurs deleting this tree, * unless we rewrite DeleteDirectory ourselves. */ err = DeleteDirectory(fileSpec.vRefNum, dirID, NULL); done: if (err != noErr) { if (errorPtr != NULL) { Tcl_UtfToExternalDString(NULL, path, -1, errorPtr); } if (locked) { FSpSetFLockCompat(&fileSpec); } errno = TclMacOSErrorToPosixError(err); return TCL_ERROR; } return TCL_OK; } /* *--------------------------------------------------------------------------- * * GetFileSpecs -- * * Gets FSSpecs for the specified path and its parent directory. * * Results: * The return value is noErr if there was no error getting FSSpecs, * otherwise it is an error describing the problem. Fills buffers * with information, as above. * * Side effects: * None. * *--------------------------------------------------------------------------- */ static OSErr GetFileSpecs( CONST char *path, /* The path to query. */ FSSpec *pathSpecPtr, /* Filled with information about path. */ FSSpec *dirSpecPtr, /* Filled with information about path's * parent directory. */ Boolean *pathExistsPtr, /* Set to true if path actually exists, * false if it doesn't or there was an * error reading the specified path. */ Boolean *pathIsDirectoryPtr)/* Set to true if path is itself a directory, * otherwise false. */ { CONST char *dirName; OSErr err; int argc; CONST char **argv; long d; Tcl_DString buffer; *pathExistsPtr = false; *pathIsDirectoryPtr = false; Tcl_DStringInit(&buffer); Tcl_SplitPath(path, &argc, &argv); if (argc == 1) { dirName = ":"; } else { dirName = Tcl_JoinPath(argc - 1, argv, &buffer); } err = FSpLocationFromPath(strlen(dirName), dirName, dirSpecPtr); Tcl_DStringFree(&buffer); ckfree((char *) argv); if (err == noErr) { err = FSpLocationFromPath(strlen(path), path, pathSpecPtr); if (err == noErr) { *pathExistsPtr = true; err = FSpGetDirectoryID(pathSpecPtr, &d, pathIsDirectoryPtr); } else if (err == fnfErr) { err = noErr; } } return err; } /* *------------------------------------------------------------------------- * * FSpGetFLockCompat -- * * Determines if there exists a software lock on the specified * file. The software lock could prevent the file from being * renamed or moved. * * Results: * Standard macintosh error code. * * Side effects: * None. * * *------------------------------------------------------------------------- */ OSErr FSpGetFLockCompat( const FSSpec *specPtr, /* File to query. */ Boolean *lockedPtr) /* Set to true if file is locked, false * if it isn't or there was an error reading * specified file. */ { CInfoPBRec pb; OSErr err; pb.hFileInfo.ioVRefNum = specPtr->vRefNum; pb.hFileInfo.ioDirID = specPtr->parID; pb.hFileInfo.ioNamePtr = (StringPtr) specPtr->name; pb.hFileInfo.ioFDirIndex = 0; err = PBGetCatInfoSync(&pb); if ((err == noErr) && (pb.hFileInfo.ioFlAttrib & 0x01)) { *lockedPtr = true; } else { *lockedPtr = false; } return err; } /* *---------------------------------------------------------------------- * * Pstrequal -- * * Pascal string compare. * * Results: * Returns 1 if strings equal, 0 otherwise. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int Pstrequal ( ConstStr255Param stringA, /* Pascal string A */ ConstStr255Param stringB) /* Pascal string B */ { int i, len; len = *stringA; for (i = 0; i <= len; i++) { if (*stringA++ != *stringB++) { return 0; } } return 1; } /* *---------------------------------------------------------------------- * * GetFileFinderAttributes -- * * Returns a Tcl_Obj containing the value of a file attribute * which is part of the FInfo record. Which attribute is controlled * by objIndex. * * Results: * Returns a standard TCL error. If the return value is TCL_OK, * the new creator or file type object is put into attributePtrPtr. * The object will have ref count 0. If there is an error, * attributePtrPtr is not touched. * * Side effects: * A new object is allocated if the file is valid. * *---------------------------------------------------------------------- */ static int GetFileFinderAttributes( Tcl_Interp *interp, /* The interp to report errors with. */ int objIndex, /* The index of the attribute option. */ Tcl_Obj *fileName, /* The name of the file (UTF-8). */ Tcl_Obj **attributePtrPtr) /* A pointer to return the object with. */ { OSErr err; FSSpec fileSpec; FInfo finfo; CONST char *native; native=Tcl_FSGetNativePath(fileName); err = FSpLLocationFromPath(strlen(native), native, &fileSpec); if (err == noErr) { err = FSpGetFInfo(&fileSpec, &finfo); } if (err == noErr) { switch (objIndex) { case MAC_CREATOR_ATTRIBUTE: *attributePtrPtr = Tcl_NewOSTypeObj(finfo.fdCreator); break; case MAC_HIDDEN_ATTRIBUTE: *attributePtrPtr = Tcl_NewBooleanObj(finfo.fdFlags & kIsInvisible); break; case MAC_TYPE_ATTRIBUTE: *attributePtrPtr = Tcl_NewOSTypeObj(finfo.fdType); break; } } else if (err == fnfErr) { long dirID; Boolean isDirectory = 0; err = FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory); if ((err == noErr) && isDirectory) { if (objIndex == MAC_HIDDEN_ATTRIBUTE) { *attributePtrPtr = Tcl_NewBooleanObj(0); } else { *attributePtrPtr = Tcl_NewOSTypeObj('Fldr'); } } } if (err != noErr) { errno = TclMacOSErrorToPosixError(err); Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "could not read \"", Tcl_GetString(fileName), "\": ", Tcl_PosixError(interp), (char *) NULL); return TCL_ERROR; } return TCL_OK; } /* *---------------------------------------------------------------------- * * GetFileReadOnly -- * * Returns a Tcl_Obj containing a Boolean value indicating whether * or not the file is read-only. The object will have ref count 0. * This procedure just checks the Finder attributes; it does not * check AppleShare sharing attributes. * * Results: * Returns a standard TCL error. If the return value is TCL_OK, * the new creator type object is put into readOnlyPtrPtr. * If there is an error, readOnlyPtrPtr is not touched. * * Side effects: * A new object is allocated if the file is valid. * *---------------------------------------------------------------------- */ static int GetFileReadOnly( Tcl_Interp *interp, /* The interp to report errors with. */ int objIndex, /* The index of the attribute. */ Tcl_Obj *fileName, /* The name of the file (UTF-8). */ Tcl_Obj **readOnlyPtrPtr) /* A pointer to return the object with. */ { OSErr err; FSSpec fileSpec; CInfoPBRec paramBlock; CONST char *native; native=Tcl_FSGetNativePath(fileName); err = FSpLLocationFromPath(strlen(native), native, &fileSpec); if (err == noErr) { if (err == noErr) { paramBlock.hFileInfo.ioCompletion = NULL; paramBlock.hFileInfo.ioNamePtr = fileSpec.name; paramBlock.hFileInfo.ioVRefNum = fileSpec.vRefNum; paramBlock.hFileInfo.ioFDirIndex = 0; paramBlock.hFileInfo.ioDirID = fileSpec.parID; err = PBGetCatInfo(¶mBlock, 0); if (err == noErr) { /* * For some unknown reason, the Mac does not give * symbols for the bits in the ioFlAttrib field. * 1 -> locked. */ *readOnlyPtrPtr = Tcl_NewBooleanObj( paramBlock.hFileInfo.ioFlAttrib & 1); } } } if (err != noErr) { errno = TclMacOSErrorToPosixError(err); Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "could not read \"", Tcl_GetString(fileName), "\": ", Tcl_PosixError(interp), (char *) NULL); return TCL_ERROR; } return TCL_OK; } /* *---------------------------------------------------------------------- * * SetFileFinderAttributes -- * * Sets the file to the creator or file type given by attributePtr. * objIndex determines whether the creator or file type is set. * * Results: * Returns a standard TCL error. * * Side effects: * The file's attribute is set. * *---------------------------------------------------------------------- */ static int SetFileFinderAttributes( Tcl_Interp *interp, /* The interp to report errors with. */ int objIndex, /* The index of the attribute. */ Tcl_Obj *fileName, /* The name of the file (UTF-8). */ Tcl_Obj *attributePtr) /* The command line object. */ { OSErr err; FSSpec fileSpec; FInfo finfo; CONST char *native; native=Tcl_FSGetNativePath(fileName); err = FSpLLocationFromPath(strlen(native), native, &fileSpec); if (err == noErr) { err = FSpGetFInfo(&fileSpec, &finfo); } if (err == noErr) { switch (objIndex) { case MAC_CREATOR_ATTRIBUTE: if (Tcl_GetOSTypeFromObj(interp, attributePtr, &finfo.fdCreator) != TCL_OK) { return TCL_ERROR; } break; case MAC_HIDDEN_ATTRIBUTE: { int hidden; if (Tcl_GetBooleanFromObj(interp, attributePtr, &hidden) != TCL_OK) { return TCL_ERROR; } if (hidden) { finfo.fdFlags |= kIsInvisible; } else { finfo.fdFlags &= ~kIsInvisible; } break; } case MAC_TYPE_ATTRIBUTE: if (Tcl_GetOSTypeFromObj(interp, attributePtr, &finfo.fdType) != TCL_OK) { return TCL_ERROR; } break; } err = FSpSetFInfo(&fileSpec, &finfo); } else if (err == fnfErr) { long dirID; Boolean isDirectory = 0; err = FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory); if ((err == noErr) && isDirectory) { Tcl_Obj *resultPtr = Tcl_GetObjResult(interp); Tcl_AppendStringsToObj(resultPtr, "cannot set ", tclpFileAttrStrings[objIndex], ": \"", Tcl_GetString(fileName), "\" is a directory", (char *) NULL); return TCL_ERROR; } } if (err != noErr) { errno = TclMacOSErrorToPosixError(err); Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "could not read \"", Tcl_GetString(fileName), "\": ", Tcl_PosixError(interp), (char *) NULL); return TCL_ERROR; } return TCL_OK; } /* *---------------------------------------------------------------------- * * SetFileReadOnly -- * * Sets the file to be read-only according to the Boolean value * given by hiddenPtr. * * Results: * Returns a standard TCL error. * * Side effects: * The file's attribute is set. * *---------------------------------------------------------------------- */ static int SetFileReadOnly( Tcl_Interp *interp, /* The interp to report errors with. */ int objIndex, /* The index of the attribute. */ Tcl_Obj *fileName, /* The name of the file (UTF-8). */ Tcl_Obj *readOnlyPtr) /* The command line object. */ { OSErr err; FSSpec fileSpec; HParamBlockRec paramBlock; int hidden; CONST char *native; native=Tcl_FSGetNativePath(fileName); err = FSpLLocationFromPath(strlen(native), native, &fileSpec); if (err == noErr) { if (Tcl_GetBooleanFromObj(interp, readOnlyPtr, &hidden) != TCL_OK) { return TCL_ERROR; } paramBlock.fileParam.ioCompletion = NULL; paramBlock.fileParam.ioNamePtr = fileSpec.name; paramBlock.fileParam.ioVRefNum = fileSpec.vRefNum; paramBlock.fileParam.ioDirID = fileSpec.parID; if (hidden) { err = PBHSetFLock(¶mBlock, 0); } else { err = PBHRstFLock(¶mBlock, 0); } } if (err == fnfErr) { long dirID; Boolean isDirectory = 0; err = FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory); if ((err == noErr) && isDirectory) { Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "cannot set a directory to read-only when File Sharing is turned off", (char *) NULL); return TCL_ERROR; } else { err = fnfErr; } } if (err != noErr) { errno = TclMacOSErrorToPosixError(err); Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), "could not read \"", Tcl_GetString(fileName), "\": ", Tcl_PosixError(interp), (char *) NULL); return TCL_ERROR; } return TCL_OK; } /* *--------------------------------------------------------------------------- * * TclpObjListVolumes -- * * Lists the currently mounted volumes * * Results: * The list of volumes. * * Side effects: * None * *--------------------------------------------------------------------------- */ Tcl_Obj* TclpObjListVolumes(void) { HParamBlockRec pb; Str255 name; OSErr theError = noErr; Tcl_Obj *resultPtr, *elemPtr; short volIndex = 1; Tcl_DString dstr; resultPtr = Tcl_NewObj(); /* * We use two facts: * 1) The Mac volumes are enumerated by the ioVolIndex parameter of * the HParamBlockRec. They run through the integers contiguously, * starting at 1. * 2) PBHGetVInfoSync returns an error when you ask for a volume index * that does not exist. * */ while ( 1 ) { pb.volumeParam.ioNamePtr = (StringPtr) &name; pb.volumeParam.ioVolIndex = volIndex; theError = PBHGetVInfoSync(&pb); if ( theError != noErr ) { break; } Tcl_ExternalToUtfDString(NULL, (CONST char *)&name[1], name[0], &dstr); elemPtr = Tcl_NewStringObj(Tcl_DStringValue(&dstr), Tcl_DStringLength(&dstr)); Tcl_AppendToObj(elemPtr, ":", 1); Tcl_ListObjAppendElement(NULL, resultPtr, elemPtr); Tcl_DStringFree(&dstr); volIndex++; } Tcl_IncrRefCount(resultPtr); return resultPtr; } /* *--------------------------------------------------------------------------- * * TclpObjNormalizePath -- * * This function scans through a path specification and replaces * it, in place, with a normalized version. On MacOS, this means * resolving all aliases present in the path and replacing the head of * pathPtr with the absolute case-sensitive path to the last file or * directory that could be validated in the path. * * Results: * The new 'nextCheckpoint' value, giving as far as we could * understand in the path. * * Side effects: * The pathPtr string, which must contain a valid path, is * possibly modified in place. * *--------------------------------------------------------------------------- */ int TclpObjNormalizePath(interp, pathPtr, nextCheckpoint) Tcl_Interp *interp; Tcl_Obj *pathPtr; int nextCheckpoint; { #define MAXMACFILENAMELEN 31 /* assumed to be < sizeof(StrFileName) */ StrFileName fileName; StringPtr fileNamePtr; int fileNameLen,newPathLen; Handle newPathHandle; OSErr err; short vRefNum; long dirID; Boolean isDirectory; Boolean wasAlias=FALSE; FSSpec fileSpec, lastFileSpec; Tcl_DString nativeds; char cur; int firstCheckpoint=nextCheckpoint, lastCheckpoint; int origPathLen; char *path = Tcl_GetStringFromObj(pathPtr,&origPathLen); { int currDirValid=0; /* * check if substring to first ':' after initial * nextCheckpoint is a valid relative or absolute * path to a directory, if not we return without * normalizing anything */ while (1) { cur = path[nextCheckpoint]; if (cur == ':' || cur == 0) { if (cur == ':') { /* jump over separator */ nextCheckpoint++; cur = path[nextCheckpoint]; } Tcl_UtfToExternalDString(NULL,path,nextCheckpoint,&nativeds); err = FSpLLocationFromPath(Tcl_DStringLength(&nativeds), Tcl_DStringValue(&nativeds), &fileSpec); Tcl_DStringFree(&nativeds); if (err == noErr) { lastFileSpec=fileSpec; err = ResolveAliasFile(&fileSpec, true, &isDirectory, &wasAlias); if (err == noErr) { err = FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory); currDirValid = ((err == noErr) && isDirectory); vRefNum = fileSpec.vRefNum; } } break; } nextCheckpoint++; } if(!currDirValid) { /* can't determine root dir, bail out */ return firstCheckpoint; } } /* * Now vRefNum and dirID point to a valid * directory, so walk the rest of the path * ( code adapted from FSpLocationFromPath() ) */ lastCheckpoint=nextCheckpoint; while (1) { cur = path[nextCheckpoint]; if (cur == ':' || cur == 0) { fileNameLen=nextCheckpoint-lastCheckpoint; fileNamePtr=fileName; if(fileNameLen==0) { if (cur == ':') { /* * special case for empty dirname i.e. encountered * a '::' path component: get parent dir of currDir */ fileName[0]=2; strcpy((char *) fileName + 1, "::"); lastCheckpoint--; } else { /* * empty filename, i.e. want FSSpec for currDir */ fileNamePtr=NULL; } } else { Tcl_UtfToExternalDString(NULL,&path[lastCheckpoint], fileNameLen,&nativeds); fileNameLen=Tcl_DStringLength(&nativeds); if(fileNameLen > MAXMACFILENAMELEN) { err = bdNamErr; } else { fileName[0]=fileNameLen; strncpy((char *) fileName + 1, Tcl_DStringValue(&nativeds), fileNameLen); } Tcl_DStringFree(&nativeds); } if(err == noErr) err=FSMakeFSSpecCompat(vRefNum, dirID, fileNamePtr, &fileSpec); if(err != noErr) { if(err != fnfErr) { /* * this can occur if trying to get parent of a root * volume via '::' or when using an illegal * filename; revert to last checkpoint and stop * processing path further */ err=FSMakeFSSpecCompat(vRefNum, dirID, NULL, &fileSpec); if(err != noErr) { /* should never happen, bail out */ return firstCheckpoint; } nextCheckpoint=lastCheckpoint; cur = path[lastCheckpoint]; } break; /* arrived at nonexistent file or dir */ } else { /* fileSpec could point to an alias, resolve it */ lastFileSpec=fileSpec; err = ResolveAliasFile(&fileSpec, true, &isDirectory, &wasAlias); if (err != noErr || !isDirectory) { break; /* fileSpec doesn't point to a dir */ } } if (cur == 0) break; /* arrived at end of path */ /* fileSpec points to possibly nonexisting subdirectory; validate */ err = FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory); if (err != noErr || !isDirectory) { break; /* fileSpec doesn't point to existing dir */ } vRefNum = fileSpec.vRefNum; /* found a new valid subdir in path, continue processing path */ lastCheckpoint=nextCheckpoint+1; } wasAlias=FALSE; nextCheckpoint++; } if (wasAlias) fileSpec=lastFileSpec; /* * fileSpec now points to a possibly nonexisting file or dir * inside a valid dir; get full path name to it */ err=FSpPathFromLocation(&fileSpec, &newPathLen, &newPathHandle); if(err != noErr) { return firstCheckpoint; /* should not see any errors here, bail out */ } HLock(newPathHandle); Tcl_ExternalToUtfDString(NULL,*newPathHandle,newPathLen,&nativeds); if (cur != 0) { /* not at end, append remaining path */ if ( newPathLen==0 || (*(*newPathHandle+(newPathLen-1))!=':' && path[nextCheckpoint] !=':')) { Tcl_DStringAppend(&nativeds, ":" , 1); } Tcl_DStringAppend(&nativeds, &path[nextCheckpoint], strlen(&path[nextCheckpoint])); } DisposeHandle(newPathHandle); fileNameLen=Tcl_DStringLength(&nativeds); Tcl_SetStringObj(pathPtr,Tcl_DStringValue(&nativeds),fileNameLen); Tcl_DStringFree(&nativeds); return nextCheckpoint+(fileNameLen-origPathLen); }