1015 lines
29 KiB
C
1015 lines
29 KiB
C
/*
|
||
* tclMacFCmd.c --
|
||
*
|
||
* Implements the Macintosh specific portions of the file manipulation
|
||
* subcommands of the "file" command.
|
||
*
|
||
* Copyright (c) 1996 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.11 96/10/10 10:13:49
|
||
*/
|
||
|
||
#include "tclInt.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>
|
||
|
||
/*
|
||
* Prototypes for procedure only used in this file
|
||
*/
|
||
|
||
OSErr FSpGetFLockCompat(const FSSpec *specPtr,
|
||
Boolean *lockedPtr);
|
||
|
||
static OSErr GenerateUniqueName(short vRefNum, long dirID1,
|
||
long dirID2, Str31 uniqueName);
|
||
static OSErr GetFileSpecs(char *path, FSSpec *pathSpecPtr,
|
||
FSSpec *dirSpecPtr, Boolean *pathExistsPtr,
|
||
Boolean *pathIsDirectoryPtr);
|
||
static OSErr MoveRename(const FSSpec *srcSpecPtr,
|
||
const FSSpec *dstSpecPtr, StringPtr copyName);
|
||
static int Pstrequal(ConstStr255Param stringA,
|
||
ConstStr255Param stringB);
|
||
|
||
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 */
|
||
) ;
|
||
|
||
|
||
/*
|
||
*---------------------------------------------------------------------------
|
||
*
|
||
* 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;
|
||
}
|
||
|
||
|