/*
* 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 <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>
#include <Aliases.h>
/*
* 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);
}
syntax highlighted by Code2HTML, v. 0.9.1