diff options
Diffstat (limited to 'mac/tclMacFCmd.c')
-rw-r--r-- | mac/tclMacFCmd.c | 1408 |
1 files changed, 1408 insertions, 0 deletions
diff --git a/mac/tclMacFCmd.c b/mac/tclMacFCmd.c new file mode 100644 index 0000000..0dcac1c --- /dev/null +++ b/mac/tclMacFCmd.c @@ -0,0 +1,1408 @@ +/* + * tclMacFCmd.c -- + * + * Implements the Macintosh specific portions of the file manipulation + * subcommands of the "file" command. + * + * Copyright (c) 1996-1997 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * SCCS: @(#) tclMacFCmd.c 1.22 97/05/20 15:44:26 + */ + +#include "tclInt.h" +#include "tclMac.h" +#include "tclMacInt.h" +#include "tclPort.h" +#include <FSpCompat.h> +#include <MoreFilesExtras.h> +#include <Strings.h> +#include <Errors.h> +#include <FileCopy.h> +#include <DirectoryCopy.h> +#include <Script.h> +#include <string.h> +#include <Finder.h> + +/* + * Callback for the file attributes code. + */ + +static int GetFileFinderAttributes _ANSI_ARGS_((Tcl_Interp *interp, + int objIndex, char *fileName, + Tcl_Obj **attributePtrPtr)); +static int GetFileReadOnly _ANSI_ARGS_((Tcl_Interp *interp, + int objIndex, char *fileName, + Tcl_Obj **readOnlyPtrPtr)); +static int SetFileFinderAttributes _ANSI_ARGS_((Tcl_Interp *interp, + int objIndex, char *fileName, + Tcl_Obj *attributePtr)); +static int SetFileReadOnly _ANSI_ARGS_((Tcl_Interp *interp, + int objIndex, char *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. + */ + +char *tclpFileAttrStrings[] = {"-creator", "-hidden", "-readonly", + "-type", (char *) NULL}; +CONST TclFileAttrProcs tclpFileAttrProcs[] = { + {GetFileFinderAttributes, SetFileFinderAttributes}, + {GetFileFinderAttributes, SetFileFinderAttributes}, + {GetFileReadOnly, SetFileReadOnly}, + {GetFileFinderAttributes, SetFileFinderAttributes}}; + + +/* + * Prototypes for procedure only used in this file + */ + +static pascal Boolean CopyErrHandler _ANSI_ARGS_((OSErr error, + short failedOperation, + short srcVRefNum, long srcDirID, + StringPtr srcName, short dstVRefNum, + long dstDirID,StringPtr dstName)); +OSErr FSpGetFLockCompat _ANSI_ARGS_((const FSSpec *specPtr, + Boolean *lockedPtr)); +static OSErr GenerateUniqueName _ANSI_ARGS_((short vRefNum, + long dirID1, long dirID2, Str31 uniqueName)); +static OSErr GetFileSpecs _ANSI_ARGS_((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)); + +/* + *--------------------------------------------------------------------------- + * + * TclpRenameFile -- + * + * 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 +TclpRenameFile( + char *src, /* Pathname of file or dir to be renamed. */ + char *dst) /* New pathname for file or directory. */ +{ + FSSpec srcFileSpec, dstFileSpec, dstDirSpec; + OSErr err; + long srcID, dummy; + Boolean srcIsDirectory, dstIsDirectory, dstExists, dstLocked; + + err = FSpLocationFromPath(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 (TclpRemoveDirectory(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, + 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; +} + +/* + *--------------------------------------------------------------------------- + * + * TclpCopyFile -- + * + * 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 +TclpCopyFile( + char *src, /* Pathname of file to be copied. */ + char *dst) /* Pathname of file to copy to. */ +{ + OSErr err, dstErr; + Boolean dstExists, dstIsDirectory, dstLocked; + FSSpec srcFileSpec, dstFileSpec, dstDirSpec, tmpFileSpec; + Str31 tmpName; + + err = FSpLocationFromPath(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, 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; +} + +/* + *--------------------------------------------------------------------------- + * + * 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 +TclpDeleteFile( + char *path) /* Pathname of file to be removed. */ +{ + OSErr err; + FSSpec fileSpec; + Boolean isDirectory; + long dirID; + + err = FSpLocationFromPath(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; +} + +/* + *--------------------------------------------------------------------------- + * + * TclpCreateDirectory -- + * + * 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 +TclpCreateDirectory( + char *path) /* Pathname of directory to create. */ +{ + 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; +} + +/* + *--------------------------------------------------------------------------- + * + * TclpCopyDirectory -- + * + * 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 +TclpCopyDirectory( + char *src, /* Pathname of directory to be copied. */ + char *dst, /* Pathname of target directory. */ + Tcl_DString *errorPtr) /* If non-NULL, initialized DString for + * error reporting. */ +{ + 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, 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, 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_DStringAppend(errorPtr, dst, -1); + } + 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 */ + StringPtr srcName, /* name of source */ + short dstVRefNum, /* volume ref number of dst */ + long dstDirID, /* directory id of dst */ + StringPtr dstName) /* name of dst directory */ +{ + return true; +} + +/* + *--------------------------------------------------------------------------- + * + * TclpRemoveDirectory -- + * + * 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 +TclpRemoveDirectory( + char *path, /* Pathname of directory to be removed. */ + int recursive, /* If non-zero, removes directories that + * are nonempty. Otherwise, will only remove + * empty directories. */ + Tcl_DString *errorPtr) /* If non-NULL, initialized DString for + * error reporting. */ +{ + 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_DStringAppend(errorPtr, path, -1); + } + if (locked) { + FSpSetFLockCompat(&fileSpec); + } + 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, + 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; +} + +/* + *--------------------------------------------------------------------------- + * + * GetFileSpecs -- + * + * Generate a filename that is not in either of the two specified + * directories (on the same volume). + * + * Results: + * Standard macintosh error. On success, uniqueName is filled with + * the name of the temporary file. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +static OSErr +GenerateUniqueName( + short vRefNum, /* Volume on which the following directories + * are located. */ + long dirID1, /* ID of first directory. */ + long dirID2, /* ID of second directory. May be the same + * as the first. */ + Str31 uniqueName) /* Filled with filename for a file that is + * not located in either of the above two + * directories. */ +{ + OSErr err; + long i; + CInfoPBRec pb; + static unsigned char hexStr[16] = "0123456789ABCDEF"; + static long startSeed = 248923489; + + pb.hFileInfo.ioVRefNum = vRefNum; + pb.hFileInfo.ioFDirIndex = 0; + pb.hFileInfo.ioNamePtr = uniqueName; + + while (1) { + startSeed++; + pb.hFileInfo.ioNamePtr[0] = 8; + for (i = 1; i <= 8; i++) { + pb.hFileInfo.ioNamePtr[i] = hexStr[((startSeed >> ((8-i)*4)) & 0xf)]; + } + pb.hFileInfo.ioDirID = dirID1; + err = PBGetCatInfoSync(&pb); + if (err == fnfErr) { + if (dirID1 != dirID2) { + pb.hFileInfo.ioDirID = dirID2; + err = PBGetCatInfoSync(&pb); + } + if (err == fnfErr) { + return noErr; + } + } + if (err == noErr) { + continue; + } + return err; + } +} + +/* + *--------------------------------------------------------------------------- + * + * 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( + 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. */ +{ + char *dirName; + OSErr err; + int argc; + 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. */ + char *fileName, /* The name of the file. */ + Tcl_Obj **attributePtrPtr) /* A pointer to return the object with. */ +{ + OSErr err; + FSSpec fileSpec; + FInfo finfo; + + err = FSpLocationFromPath(strlen(fileName), fileName, &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), + "couldn't get attributes for file \"", 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. */ + char *fileName, /* The name of the file. */ + Tcl_Obj **readOnlyPtrPtr) /* A pointer to return the object with. */ +{ + OSErr err; + FSSpec fileSpec; + CInfoPBRec paramBlock; + + err = FSpLocationFromPath(strlen(fileName), fileName, &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), + "couldn't get attributes for file \"", 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. */ + char *fileName, /* The name of the file. */ + Tcl_Obj *attributePtr) /* The command line object. */ +{ + OSErr err; + FSSpec fileSpec; + FInfo finfo; + + err = FSpLocationFromPath(strlen(fileName), fileName, &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], ": \"", + fileName, "\" is a directory", (char *) NULL); + return TCL_ERROR; + } + } + + if (err != noErr) { + errno = TclMacOSErrorToPosixError(err); + Tcl_AppendStringsToObj(Tcl_GetObjResult(interp), + "couldn't set attributes for file \"", 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. */ + char *fileName, /* The name of the file. */ + Tcl_Obj *readOnlyPtr) /* The command line object. */ +{ + OSErr err; + FSSpec fileSpec; + HParamBlockRec paramBlock; + int hidden; + + err = FSpLocationFromPath(strlen(fileName), fileName, &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), + "couldn't set attributes for file \"", fileName, "\": ", + Tcl_PosixError(interp), (char *) NULL); + return TCL_ERROR; + } + return TCL_OK; +} + +/* + *--------------------------------------------------------------------------- + * + * TclpListVolumes -- + * + * Lists the currently mounted volumes + * + * Results: + * A standard Tcl result. Will always be TCL_OK, since there is no way + * that this command can fail. Also, the interpreter's result is set to + * the list of volumes. + * + * Side effects: + * None + * + *--------------------------------------------------------------------------- + */ + +int +TclpListVolumes( + Tcl_Interp *interp) /* Interpreter to which to pass the volume list */ +{ + HParamBlockRec pb; + Str255 name; + OSErr theError = noErr; + Tcl_Obj *resultPtr, *elemPtr; + short volIndex = 1; + + 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; + } + + elemPtr = Tcl_NewStringObj((char *) name + 1, (int) name[0]); + Tcl_AppendToObj(elemPtr, ":", 1); + Tcl_ListObjAppendElement(interp, resultPtr, elemPtr); + + volIndex++; + } + + Tcl_SetObjResult(interp, resultPtr); + return TCL_OK; +} + |