4 * Implements the Macintosh specific portions of the file manipulation
5 * subcommands of the "file" command.
7 * Copyright (c) 1996-1998 Sun Microsystems, Inc.
9 * See the file "license.terms" for information on usage and redistribution
10 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
17 #include "tclMacInt.h"
19 #include <FSpCompat.h>
20 #include <MoreFilesExtras.h>
24 #include <DirectoryCopy.h>
31 * Callback for the file attributes code.
34 static int GetFileFinderAttributes _ANSI_ARGS_((Tcl_Interp *interp,
35 int objIndex, Tcl_Obj *fileName,
36 Tcl_Obj **attributePtrPtr));
37 static int GetFileReadOnly _ANSI_ARGS_((Tcl_Interp *interp,
38 int objIndex, Tcl_Obj *fileName,
39 Tcl_Obj **readOnlyPtrPtr));
40 static int SetFileFinderAttributes _ANSI_ARGS_((Tcl_Interp *interp,
41 int objIndex, Tcl_Obj *fileName,
42 Tcl_Obj *attributePtr));
43 static int SetFileReadOnly _ANSI_ARGS_((Tcl_Interp *interp,
44 int objIndex, Tcl_Obj *fileName,
45 Tcl_Obj *readOnlyPtr));
48 * These are indeces into the tclpFileAttrsStrings table below.
51 #define MAC_CREATOR_ATTRIBUTE 0
52 #define MAC_HIDDEN_ATTRIBUTE 1
53 #define MAC_READONLY_ATTRIBUTE 2
54 #define MAC_TYPE_ATTRIBUTE 3
57 * Global variables for the file attributes code.
60 CONST char *tclpFileAttrStrings[] = {"-creator", "-hidden", "-readonly",
61 "-type", (char *) NULL};
62 CONST TclFileAttrProcs tclpFileAttrProcs[] = {
63 {GetFileFinderAttributes, SetFileFinderAttributes},
64 {GetFileFinderAttributes, SetFileFinderAttributes},
65 {GetFileReadOnly, SetFileReadOnly},
66 {GetFileFinderAttributes, SetFileFinderAttributes}};
69 * File specific static data
72 static long startSeed = 248923489;
75 * Prototypes for procedure only used in this file
78 static pascal Boolean CopyErrHandler _ANSI_ARGS_((OSErr error,
79 short failedOperation,
80 short srcVRefNum, long srcDirID,
81 ConstStr255Param srcName, short dstVRefNum,
82 long dstDirID,ConstStr255Param dstName));
83 static int DoCopyDirectory _ANSI_ARGS_((CONST char *src,
84 CONST char *dst, Tcl_DString *errorPtr));
85 static int DoCopyFile _ANSI_ARGS_((CONST char *src,
87 static int DoCreateDirectory _ANSI_ARGS_((CONST char *path));
88 static int DoDeleteFile _ANSI_ARGS_((CONST char *path));
89 static int DoRemoveDirectory _ANSI_ARGS_((CONST char *path,
90 int recursive, Tcl_DString *errorPtr));
91 static int DoRenameFile _ANSI_ARGS_((CONST char *src,
93 OSErr FSpGetFLockCompat _ANSI_ARGS_((const FSSpec *specPtr,
95 static OSErr GetFileSpecs _ANSI_ARGS_((CONST char *path,
96 FSSpec *pathSpecPtr, FSSpec *dirSpecPtr,
97 Boolean *pathExistsPtr,
98 Boolean *pathIsDirectoryPtr));
99 static OSErr MoveRename _ANSI_ARGS_((const FSSpec *srcSpecPtr,
100 const FSSpec *dstSpecPtr, StringPtr copyName));
101 static int Pstrequal _ANSI_ARGS_((ConstStr255Param stringA,
102 ConstStr255Param stringB));
105 *---------------------------------------------------------------------------
107 * TclpObjRenameFile, DoRenameFile --
109 * Changes the name of an existing file or directory, from src to dst.
110 * If src and dst refer to the same file or directory, does nothing
111 * and returns success. Otherwise if dst already exists, it will be
112 * deleted and replaced by src subject to the following conditions:
113 * If src is a directory, dst may be an empty directory.
114 * If src is a file, dst may be a file.
115 * In any other situation where dst already exists, the rename will
119 * If the directory was successfully created, returns TCL_OK.
120 * Otherwise the return value is TCL_ERROR and errno is set to
121 * indicate the error. Some possible values for errno are:
123 * EACCES: src or dst parent directory can't be read and/or written.
124 * EEXIST: dst is a non-empty directory.
125 * EINVAL: src is a root directory or dst is a subdirectory of src.
126 * EISDIR: dst is a directory, but src is not.
127 * ENOENT: src doesn't exist. src or dst is "".
128 * ENOTDIR: src is a directory, but dst is not.
129 * EXDEV: src and dst are on different filesystems.
132 * The implementation of rename may allow cross-filesystem renames,
133 * but the caller should be prepared to emulate it with copy and
134 * delete if errno is EXDEV.
136 *---------------------------------------------------------------------------
140 TclpObjRenameFile(srcPathPtr, destPathPtr)
142 Tcl_Obj *destPathPtr;
144 return DoRenameFile(Tcl_FSGetNativePath(srcPathPtr),
145 Tcl_FSGetNativePath(destPathPtr));
150 CONST char *src, /* Pathname of file or dir to be renamed
152 CONST char *dst) /* New pathname of file or directory
155 FSSpec srcFileSpec, dstFileSpec, dstDirSpec;
158 Boolean srcIsDirectory, dstIsDirectory, dstExists, dstLocked;
160 err = FSpLLocationFromPath(strlen(src), src, &srcFileSpec);
162 FSpGetDirectoryID(&srcFileSpec, &srcID, &srcIsDirectory);
165 err = GetFileSpecs(dst, &dstFileSpec, &dstDirSpec, &dstExists,
169 if (dstExists == 0) {
170 err = MoveRename(&srcFileSpec, &dstDirSpec, dstFileSpec.name);
173 err = FSpGetFLockCompat(&dstFileSpec, &dstLocked);
175 FSpRstFLockCompat(&dstFileSpec);
179 if (srcIsDirectory) {
180 if (dstIsDirectory) {
182 * The following call will remove an empty directory. If it
183 * fails, it's because it wasn't empty.
186 if (DoRemoveDirectory(dst, 0, NULL) != TCL_OK) {
191 * Now that that empty directory is gone, we can try
192 * renaming src. If that fails, we'll put this empty
193 * directory back, for completeness.
196 err = MoveRename(&srcFileSpec, &dstDirSpec, dstFileSpec.name);
198 FSpDirCreateCompat(&dstFileSpec, smSystemScript, &dummy);
200 FSpSetFLockCompat(&dstFileSpec);
208 if (dstIsDirectory) {
213 * Overwrite existing file by:
215 * 1. Rename existing file to temp name.
216 * 2. Rename old file to new name.
217 * 3. If success, delete temp file. If failure,
218 * put temp file back to old name.
224 err = GenerateUniqueName(dstFileSpec.vRefNum, &startSeed,
225 dstFileSpec.parID, dstFileSpec.parID, tmpName);
227 err = FSpRenameCompat(&dstFileSpec, tmpName);
230 err = FSMakeFSSpecCompat(dstFileSpec.vRefNum,
231 dstFileSpec.parID, tmpName, &tmpFileSpec);
234 err = MoveRename(&srcFileSpec, &dstDirSpec,
238 FSpDeleteCompat(&tmpFileSpec);
240 FSpDeleteCompat(&dstFileSpec);
241 FSpRenameCompat(&tmpFileSpec, dstFileSpec.name);
243 FSpSetFLockCompat(&dstFileSpec);
252 errno = TclMacOSErrorToPosixError(err);
259 *--------------------------------------------------------------------------
263 * Helper function for TclpRenameFile. Renames a file or directory
264 * into the same directory or another directory. The target name
265 * must not already exist in the destination directory.
267 * Don't use FSpMoveRenameCompat because it doesn't work with
268 * directories or with locked files.
271 * Returns a mac error indicating the cause of the failure.
274 * Creates a temp file in the target directory to handle a rename
275 * between directories.
277 *--------------------------------------------------------------------------
282 const FSSpec *srcFileSpecPtr, /* Source object. */
283 const FSSpec *dstDirSpecPtr, /* Destination directory. */
284 StringPtr copyName) /* New name for object in destination
289 Boolean srcIsDir, dstIsDir;
291 FSSpec dstFileSpec, srcDirSpec, tmpSrcFileSpec, tmpDstFileSpec;
294 if (srcFileSpecPtr->parID == 1) {
296 * Trying to rename a volume.
301 if (srcFileSpecPtr->vRefNum != dstDirSpecPtr->vRefNum) {
303 * Renaming across volumes.
308 err = FSpGetFLockCompat(srcFileSpecPtr, &locked);
310 FSpRstFLockCompat(srcFileSpecPtr);
313 err = FSpGetDirectoryID(dstDirSpecPtr, &dstID, &dstIsDir);
316 if (srcFileSpecPtr->parID == dstID) {
318 * Renaming object within directory.
321 err = FSpRenameCompat(srcFileSpecPtr, copyName);
324 if (Pstrequal(srcFileSpecPtr->name, copyName)) {
326 * Moving object to another directory (under same name).
329 err = FSpCatMoveCompat(srcFileSpecPtr, dstDirSpecPtr);
332 err = FSpGetDirectoryID(srcFileSpecPtr, &srcID, &srcIsDir);
336 * Fullblown: rename source object to temp name, move temp to
337 * dest directory, and rename temp to target.
340 err = GenerateUniqueName(srcFileSpecPtr->vRefNum, &startSeed,
341 srcFileSpecPtr->parID, dstID, tmpName);
342 FSMakeFSSpecCompat(srcFileSpecPtr->vRefNum, srcFileSpecPtr->parID,
343 tmpName, &tmpSrcFileSpec);
344 FSMakeFSSpecCompat(dstDirSpecPtr->vRefNum, dstID, tmpName,
348 err = FSpRenameCompat(srcFileSpecPtr, tmpName);
351 err = FSpCatMoveCompat(&tmpSrcFileSpec, dstDirSpecPtr);
353 err = FSpRenameCompat(&tmpDstFileSpec, copyName);
357 FSMakeFSSpecCompat(srcFileSpecPtr->vRefNum, srcFileSpecPtr->parID,
359 FSpCatMoveCompat(&tmpDstFileSpec, &srcDirSpec);
361 FSpRenameCompat(&tmpSrcFileSpec, srcFileSpecPtr->name);
365 if (locked != false) {
367 FSMakeFSSpecCompat(dstDirSpecPtr->vRefNum,
368 dstID, copyName, &dstFileSpec);
369 FSpSetFLockCompat(&dstFileSpec);
371 FSpSetFLockCompat(srcFileSpecPtr);
378 *---------------------------------------------------------------------------
380 * TclpObjCopyFile, DoCopyFile --
382 * Copy a single file (not a directory). If dst already exists and
383 * is not a directory, it is removed.
386 * If the file was successfully copied, returns TCL_OK. Otherwise
387 * the return value is TCL_ERROR and errno is set to indicate the
388 * error. Some possible values for errno are:
390 * EACCES: src or dst parent directory can't be read and/or written.
391 * EISDIR: src or dst is a directory.
392 * ENOENT: src doesn't exist. src or dst is "".
395 * This procedure will also copy symbolic links, block, and
396 * character devices, and fifos. For symbolic links, the links
397 * themselves will be copied and not what they point to. For the
398 * other special file types, the directory entry will be copied and
399 * not the contents of the device that it refers to.
401 *---------------------------------------------------------------------------
405 TclpObjCopyFile(srcPathPtr, destPathPtr)
407 Tcl_Obj *destPathPtr;
409 return DoCopyFile(Tcl_FSGetNativePath(srcPathPtr),
410 Tcl_FSGetNativePath(destPathPtr));
415 CONST char *src, /* Pathname of file to be copied (native). */
416 CONST char *dst) /* Pathname of file to copy to (native). */
419 Boolean dstExists, dstIsDirectory, dstLocked;
420 FSSpec srcFileSpec, dstFileSpec, dstDirSpec, tmpFileSpec;
423 err = FSpLLocationFromPath(strlen(src), src, &srcFileSpec);
425 err = GetFileSpecs(dst, &dstFileSpec, &dstDirSpec, &dstExists,
429 if (dstIsDirectory) {
433 err = FSpGetFLockCompat(&dstFileSpec, &dstLocked);
435 FSpRstFLockCompat(&dstFileSpec);
442 dstErr = GenerateUniqueName(dstFileSpec.vRefNum, &startSeed, dstFileSpec.parID,
443 dstFileSpec.parID, tmpName);
444 if (dstErr == noErr) {
445 dstErr = FSpRenameCompat(&dstFileSpec, tmpName);
449 err = FSpFileCopy(&srcFileSpec, &dstDirSpec,
450 (StringPtr) dstFileSpec.name, NULL, 0, true);
452 if ((dstExists != false) && (dstErr == noErr)) {
453 FSMakeFSSpecCompat(dstFileSpec.vRefNum, dstFileSpec.parID,
454 tmpName, &tmpFileSpec);
457 * Delete backup file.
460 FSpDeleteCompat(&tmpFileSpec);
464 * Restore backup file.
467 FSpDeleteCompat(&dstFileSpec);
468 FSpRenameCompat(&tmpFileSpec, dstFileSpec.name);
470 FSpSetFLockCompat(&dstFileSpec);
476 errno = TclMacOSErrorToPosixError(err);
483 *---------------------------------------------------------------------------
485 * TclpObjDeleteFile, DoDeleteFile --
487 * Removes a single file (not a directory).
490 * If the file was successfully deleted, returns TCL_OK. Otherwise
491 * the return value is TCL_ERROR and errno is set to indicate the
492 * error. Some possible values for errno are:
494 * EACCES: a parent directory can't be read and/or written.
495 * EISDIR: path is a directory.
496 * ENOENT: path doesn't exist or is "".
499 * The file is deleted, even if it is read-only.
501 *---------------------------------------------------------------------------
505 TclpObjDeleteFile(pathPtr)
508 return DoDeleteFile(Tcl_FSGetNativePath(pathPtr));
513 CONST char *path) /* Pathname of file to be removed (native). */
520 err = FSpLLocationFromPath(strlen(path), path, &fileSpec);
523 * Since FSpDeleteCompat will delete an empty directory, make sure
524 * that this isn't a directory first.
527 FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory);
528 if (isDirectory == true) {
533 err = FSpDeleteCompat(&fileSpec);
534 if (err == fLckdErr) {
535 FSpRstFLockCompat(&fileSpec);
536 err = FSpDeleteCompat(&fileSpec);
538 FSpSetFLockCompat(&fileSpec);
542 errno = TclMacOSErrorToPosixError(err);
549 *---------------------------------------------------------------------------
551 * TclpObjCreateDirectory, DoCreateDirectory --
553 * Creates the specified directory. All parent directories of the
554 * specified directory must already exist. The directory is
555 * automatically created with permissions so that user can access
556 * the new directory and create new files or subdirectories in it.
559 * If the directory was successfully created, returns TCL_OK.
560 * Otherwise the return value is TCL_ERROR and errno is set to
561 * indicate the error. Some possible values for errno are:
563 * EACCES: a parent directory can't be read and/or written.
564 * EEXIST: path already exists.
565 * ENOENT: a parent directory doesn't exist.
568 * A directory is created with the current umask, except that
569 * permission for u+rwx will always be added.
571 *---------------------------------------------------------------------------
575 TclpObjCreateDirectory(pathPtr)
578 return DoCreateDirectory(Tcl_FSGetNativePath(pathPtr));
583 CONST char *path) /* Pathname of directory to create (native). */
589 err = FSpLocationFromPath(strlen(path), path, &dirSpec);
591 err = dupFNErr; /* EEXIST. */
592 } else if (err == fnfErr) {
593 err = FSpDirCreateCompat(&dirSpec, smSystemScript, &outDirID);
597 errno = TclMacOSErrorToPosixError(err);
604 *---------------------------------------------------------------------------
606 * TclpObjCopyDirectory, DoCopyDirectory --
608 * Recursively copies a directory. The target directory dst must
609 * not already exist. Note that this function does not merge two
610 * directory hierarchies, even if the target directory is an an
614 * If the directory was successfully copied, returns TCL_OK.
615 * Otherwise the return value is TCL_ERROR, errno is set to indicate
616 * the error, and the pathname of the file that caused the error
617 * is stored in errorPtr. See TclpCreateDirectory and TclpCopyFile
618 * for a description of possible values for errno.
621 * An exact copy of the directory hierarchy src will be created
622 * with the name dst. If an error occurs, the error will
623 * be returned immediately, and remaining files will not be
626 *---------------------------------------------------------------------------
630 TclpObjCopyDirectory(srcPathPtr, destPathPtr, errorPtr)
632 Tcl_Obj *destPathPtr;
637 ret = DoCopyDirectory(Tcl_FSGetNativePath(srcPathPtr),
638 Tcl_FSGetNativePath(destPathPtr), &ds);
640 *errorPtr = Tcl_NewStringObj(Tcl_DStringValue(&ds), -1);
641 Tcl_DStringFree(&ds);
642 Tcl_IncrRefCount(*errorPtr);
649 CONST char *src, /* Pathname of directory to be copied
651 CONST char *dst, /* Pathname of target directory (Native). */
652 Tcl_DString *errorPtr) /* If non-NULL, uninitialized or free
653 * DString filled with UTF-8 name of file
657 long srcID, tmpDirID;
658 FSSpec srcFileSpec, dstFileSpec, dstDirSpec, tmpDirSpec, tmpFileSpec;
659 Boolean srcIsDirectory, srcLocked;
660 Boolean dstIsDirectory, dstExists;
663 err = FSpLocationFromPath(strlen(src), src, &srcFileSpec);
665 err = FSpGetDirectoryID(&srcFileSpec, &srcID, &srcIsDirectory);
668 if (srcIsDirectory == false) {
669 err = afpObjectTypeErr; /* ENOTDIR. */
673 err = GetFileSpecs(dst, &dstFileSpec, &dstDirSpec, &dstExists,
677 if (dstIsDirectory == false) {
678 err = afpObjectTypeErr; /* ENOTDIR. */
680 err = dupFNErr; /* EEXIST. */
686 if ((srcFileSpec.vRefNum == dstFileSpec.vRefNum) &&
687 (srcFileSpec.parID == dstFileSpec.parID) &&
688 (Pstrequal(srcFileSpec.name, dstFileSpec.name) != 0)) {
690 * Copying on top of self. No-op.
697 * This algorthm will work making a copy of the source directory in
698 * the current directory with a new name, in a new directory with the
699 * same name, and in a new directory with a new name:
701 * 1. Make dstDir/tmpDir.
702 * 2. Copy srcDir/src to dstDir/tmpDir/src
703 * 3. Rename dstDir/tmpDir/src to dstDir/tmpDir/dst (if necessary).
704 * 4. CatMove dstDir/tmpDir/dst to dstDir/dst.
705 * 5. Remove dstDir/tmpDir.
708 err = FSpGetFLockCompat(&srcFileSpec, &srcLocked);
710 FSpRstFLockCompat(&srcFileSpec);
713 err = GenerateUniqueName(dstFileSpec.vRefNum, &startSeed, dstFileSpec.parID,
714 dstFileSpec.parID, tmpName);
717 FSMakeFSSpecCompat(dstFileSpec.vRefNum, dstFileSpec.parID,
718 tmpName, &tmpDirSpec);
719 err = FSpDirCreateCompat(&tmpDirSpec, smSystemScript, &tmpDirID);
722 err = FSpDirectoryCopy(&srcFileSpec, &tmpDirSpec, NULL, NULL, 0, true,
727 * Even if the Copy failed, Rename/Move whatever did get copied to the
728 * appropriate final destination, if possible.
733 if (Pstrequal(srcFileSpec.name, dstFileSpec.name) == 0) {
734 err = FSMakeFSSpecCompat(tmpDirSpec.vRefNum, tmpDirID,
735 srcFileSpec.name, &tmpFileSpec);
737 err = FSpRenameCompat(&tmpFileSpec, dstFileSpec.name);
741 err = FSMakeFSSpecCompat(tmpDirSpec.vRefNum, tmpDirID,
742 dstFileSpec.name, &tmpFileSpec);
745 err = FSpCatMoveCompat(&tmpFileSpec, &dstDirSpec);
749 FSpSetFLockCompat(&dstFileSpec);
753 FSpDeleteCompat(&tmpDirSpec);
755 if (saveErr != noErr) {
761 errno = TclMacOSErrorToPosixError(err);
762 if (errorPtr != NULL) {
763 Tcl_ExternalToUtfDString(NULL, dst, -1, errorPtr);
771 *----------------------------------------------------------------------
775 * This procedure is called from the MoreFiles procedure
776 * FSpDirectoryCopy whenever an error occurs.
779 * False if the condition should not be considered an error, true
783 * Since FSpDirectoryCopy() is called only after removing any
784 * existing target directories, there shouldn't be any errors.
786 *----------------------------------------------------------------------
789 static pascal Boolean
791 OSErr error, /* Error that occured */
792 short failedOperation, /* operation that caused the error */
793 short srcVRefNum, /* volume ref number of source */
794 long srcDirID, /* directory id of source */
795 ConstStr255Param srcName, /* name of source */
796 short dstVRefNum, /* volume ref number of dst */
797 long dstDirID, /* directory id of dst */
798 ConstStr255Param dstName) /* name of dst directory */
804 *---------------------------------------------------------------------------
806 * TclpObjRemoveDirectory, DoRemoveDirectory --
808 * Removes directory (and its contents, if the recursive flag is set).
811 * If the directory was successfully removed, returns TCL_OK.
812 * Otherwise the return value is TCL_ERROR, errno is set to indicate
813 * the error, and the pathname of the file that caused the error
814 * is stored in errorPtr. Some possible values for errno are:
816 * EACCES: path directory can't be read and/or written.
817 * EEXIST: path is a non-empty directory.
818 * EINVAL: path is a root directory.
819 * ENOENT: path doesn't exist or is "".
820 * ENOTDIR: path is not a directory.
823 * Directory removed. If an error occurs, the error will be returned
824 * immediately, and remaining files will not be deleted.
826 *---------------------------------------------------------------------------
830 TclpObjRemoveDirectory(pathPtr, recursive, errorPtr)
837 ret = DoRemoveDirectory(Tcl_FSGetNativePath(pathPtr),recursive, &ds);
839 *errorPtr = Tcl_NewStringObj(Tcl_DStringValue(&ds), -1);
840 Tcl_DStringFree(&ds);
841 Tcl_IncrRefCount(*errorPtr);
848 CONST char *path, /* Pathname of directory to be removed
850 int recursive, /* If non-zero, removes directories that
851 * are nonempty. Otherwise, will only remove
852 * empty directories. */
853 Tcl_DString *errorPtr) /* If non-NULL, uninitialized or free
854 * DString filled with UTF-8 name of file
867 err = FSpLocationFromPath(strlen(path), path, &fileSpec);
873 * Since FSpDeleteCompat will delete a file, make sure this isn't
878 FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory);
879 if (isDirectory == 0) {
884 err = FSpDeleteCompat(&fileSpec);
885 if (err == fLckdErr) {
887 FSpRstFLockCompat(&fileSpec);
888 err = FSpDeleteCompat(&fileSpec);
893 if (err != fBsyErr) {
897 if (recursive == 0) {
899 * fBsyErr means one of three things: file busy, directory not empty,
900 * or working directory control block open. Determine if directory
901 * is empty. If directory is not empty, return EEXIST.
904 pb.hFileInfo.ioVRefNum = fileSpec.vRefNum;
905 pb.hFileInfo.ioDirID = dirID;
906 pb.hFileInfo.ioNamePtr = (StringPtr) fileName;
907 pb.hFileInfo.ioFDirIndex = 1;
908 if (PBGetCatInfoSync(&pb) == noErr) {
909 err = dupFNErr; /* EEXIST */
915 * DeleteDirectory removes a directory and all its contents, including
916 * any locked files. There is no interface to get the name of the
917 * file that caused the error, if an error occurs deleting this tree,
918 * unless we rewrite DeleteDirectory ourselves.
921 err = DeleteDirectory(fileSpec.vRefNum, dirID, NULL);
925 if (errorPtr != NULL) {
926 Tcl_UtfToExternalDString(NULL, path, -1, errorPtr);
929 FSpSetFLockCompat(&fileSpec);
931 errno = TclMacOSErrorToPosixError(err);
938 *---------------------------------------------------------------------------
942 * Gets FSSpecs for the specified path and its parent directory.
945 * The return value is noErr if there was no error getting FSSpecs,
946 * otherwise it is an error describing the problem. Fills buffers
947 * with information, as above.
952 *---------------------------------------------------------------------------
957 CONST char *path, /* The path to query. */
958 FSSpec *pathSpecPtr, /* Filled with information about path. */
959 FSSpec *dirSpecPtr, /* Filled with information about path's
960 * parent directory. */
961 Boolean *pathExistsPtr, /* Set to true if path actually exists,
962 * false if it doesn't or there was an
963 * error reading the specified path. */
964 Boolean *pathIsDirectoryPtr)/* Set to true if path is itself a directory,
965 * otherwise false. */
974 *pathExistsPtr = false;
975 *pathIsDirectoryPtr = false;
977 Tcl_DStringInit(&buffer);
978 Tcl_SplitPath(path, &argc, &argv);
982 dirName = Tcl_JoinPath(argc - 1, argv, &buffer);
984 err = FSpLocationFromPath(strlen(dirName), dirName, dirSpecPtr);
985 Tcl_DStringFree(&buffer);
986 ckfree((char *) argv);
989 err = FSpLocationFromPath(strlen(path), path, pathSpecPtr);
991 *pathExistsPtr = true;
992 err = FSpGetDirectoryID(pathSpecPtr, &d, pathIsDirectoryPtr);
993 } else if (err == fnfErr) {
1001 *-------------------------------------------------------------------------
1003 * FSpGetFLockCompat --
1005 * Determines if there exists a software lock on the specified
1006 * file. The software lock could prevent the file from being
1010 * Standard macintosh error code.
1016 *-------------------------------------------------------------------------
1021 const FSSpec *specPtr, /* File to query. */
1022 Boolean *lockedPtr) /* Set to true if file is locked, false
1023 * if it isn't or there was an error reading
1024 * specified file. */
1029 pb.hFileInfo.ioVRefNum = specPtr->vRefNum;
1030 pb.hFileInfo.ioDirID = specPtr->parID;
1031 pb.hFileInfo.ioNamePtr = (StringPtr) specPtr->name;
1032 pb.hFileInfo.ioFDirIndex = 0;
1034 err = PBGetCatInfoSync(&pb);
1035 if ((err == noErr) && (pb.hFileInfo.ioFlAttrib & 0x01)) {
1044 *----------------------------------------------------------------------
1048 * Pascal string compare.
1051 * Returns 1 if strings equal, 0 otherwise.
1056 *----------------------------------------------------------------------
1061 ConstStr255Param stringA, /* Pascal string A */
1062 ConstStr255Param stringB) /* Pascal string B */
1067 for (i = 0; i <= len; i++) {
1068 if (*stringA++ != *stringB++) {
1076 *----------------------------------------------------------------------
1078 * GetFileFinderAttributes --
1080 * Returns a Tcl_Obj containing the value of a file attribute
1081 * which is part of the FInfo record. Which attribute is controlled
1085 * Returns a standard TCL error. If the return value is TCL_OK,
1086 * the new creator or file type object is put into attributePtrPtr.
1087 * The object will have ref count 0. If there is an error,
1088 * attributePtrPtr is not touched.
1091 * A new object is allocated if the file is valid.
1093 *----------------------------------------------------------------------
1097 GetFileFinderAttributes(
1098 Tcl_Interp *interp, /* The interp to report errors with. */
1099 int objIndex, /* The index of the attribute option. */
1100 Tcl_Obj *fileName, /* The name of the file (UTF-8). */
1101 Tcl_Obj **attributePtrPtr) /* A pointer to return the object with. */
1108 native=Tcl_FSGetNativePath(fileName);
1109 err = FSpLLocationFromPath(strlen(native),
1113 err = FSpGetFInfo(&fileSpec, &finfo);
1118 case MAC_CREATOR_ATTRIBUTE:
1119 *attributePtrPtr = Tcl_NewOSTypeObj(finfo.fdCreator);
1121 case MAC_HIDDEN_ATTRIBUTE:
1122 *attributePtrPtr = Tcl_NewBooleanObj(finfo.fdFlags
1125 case MAC_TYPE_ATTRIBUTE:
1126 *attributePtrPtr = Tcl_NewOSTypeObj(finfo.fdType);
1129 } else if (err == fnfErr) {
1131 Boolean isDirectory = 0;
1133 err = FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory);
1134 if ((err == noErr) && isDirectory) {
1135 if (objIndex == MAC_HIDDEN_ATTRIBUTE) {
1136 *attributePtrPtr = Tcl_NewBooleanObj(0);
1138 *attributePtrPtr = Tcl_NewOSTypeObj('Fldr');
1144 errno = TclMacOSErrorToPosixError(err);
1145 Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
1146 "could not read \"", Tcl_GetString(fileName), "\": ",
1147 Tcl_PosixError(interp), (char *) NULL);
1154 *----------------------------------------------------------------------
1156 * GetFileReadOnly --
1158 * Returns a Tcl_Obj containing a Boolean value indicating whether
1159 * or not the file is read-only. The object will have ref count 0.
1160 * This procedure just checks the Finder attributes; it does not
1161 * check AppleShare sharing attributes.
1164 * Returns a standard TCL error. If the return value is TCL_OK,
1165 * the new creator type object is put into readOnlyPtrPtr.
1166 * If there is an error, readOnlyPtrPtr is not touched.
1169 * A new object is allocated if the file is valid.
1171 *----------------------------------------------------------------------
1176 Tcl_Interp *interp, /* The interp to report errors with. */
1177 int objIndex, /* The index of the attribute. */
1178 Tcl_Obj *fileName, /* The name of the file (UTF-8). */
1179 Tcl_Obj **readOnlyPtrPtr) /* A pointer to return the object with. */
1183 CInfoPBRec paramBlock;
1186 native=Tcl_FSGetNativePath(fileName);
1187 err = FSpLLocationFromPath(strlen(native),
1192 paramBlock.hFileInfo.ioCompletion = NULL;
1193 paramBlock.hFileInfo.ioNamePtr = fileSpec.name;
1194 paramBlock.hFileInfo.ioVRefNum = fileSpec.vRefNum;
1195 paramBlock.hFileInfo.ioFDirIndex = 0;
1196 paramBlock.hFileInfo.ioDirID = fileSpec.parID;
1197 err = PBGetCatInfo(¶mBlock, 0);
1201 * For some unknown reason, the Mac does not give
1202 * symbols for the bits in the ioFlAttrib field.
1206 *readOnlyPtrPtr = Tcl_NewBooleanObj(
1207 paramBlock.hFileInfo.ioFlAttrib & 1);
1212 errno = TclMacOSErrorToPosixError(err);
1213 Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
1214 "could not read \"", Tcl_GetString(fileName), "\": ",
1215 Tcl_PosixError(interp), (char *) NULL);
1222 *----------------------------------------------------------------------
1224 * SetFileFinderAttributes --
1226 * Sets the file to the creator or file type given by attributePtr.
1227 * objIndex determines whether the creator or file type is set.
1230 * Returns a standard TCL error.
1233 * The file's attribute is set.
1235 *----------------------------------------------------------------------
1239 SetFileFinderAttributes(
1240 Tcl_Interp *interp, /* The interp to report errors with. */
1241 int objIndex, /* The index of the attribute. */
1242 Tcl_Obj *fileName, /* The name of the file (UTF-8). */
1243 Tcl_Obj *attributePtr) /* The command line object. */
1250 native=Tcl_FSGetNativePath(fileName);
1251 err = FSpLLocationFromPath(strlen(native),
1255 err = FSpGetFInfo(&fileSpec, &finfo);
1260 case MAC_CREATOR_ATTRIBUTE:
1261 if (Tcl_GetOSTypeFromObj(interp, attributePtr,
1262 &finfo.fdCreator) != TCL_OK) {
1266 case MAC_HIDDEN_ATTRIBUTE: {
1269 if (Tcl_GetBooleanFromObj(interp, attributePtr, &hidden)
1274 finfo.fdFlags |= kIsInvisible;
1276 finfo.fdFlags &= ~kIsInvisible;
1280 case MAC_TYPE_ATTRIBUTE:
1281 if (Tcl_GetOSTypeFromObj(interp, attributePtr,
1282 &finfo.fdType) != TCL_OK) {
1287 err = FSpSetFInfo(&fileSpec, &finfo);
1288 } else if (err == fnfErr) {
1290 Boolean isDirectory = 0;
1292 err = FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory);
1293 if ((err == noErr) && isDirectory) {
1294 Tcl_Obj *resultPtr = Tcl_GetObjResult(interp);
1295 Tcl_AppendStringsToObj(resultPtr, "cannot set ",
1296 tclpFileAttrStrings[objIndex], ": \"",
1297 Tcl_GetString(fileName), "\" is a directory", (char *) NULL);
1303 errno = TclMacOSErrorToPosixError(err);
1304 Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
1305 "could not read \"", Tcl_GetString(fileName), "\": ",
1306 Tcl_PosixError(interp), (char *) NULL);
1313 *----------------------------------------------------------------------
1315 * SetFileReadOnly --
1317 * Sets the file to be read-only according to the Boolean value
1318 * given by hiddenPtr.
1321 * Returns a standard TCL error.
1324 * The file's attribute is set.
1326 *----------------------------------------------------------------------
1331 Tcl_Interp *interp, /* The interp to report errors with. */
1332 int objIndex, /* The index of the attribute. */
1333 Tcl_Obj *fileName, /* The name of the file (UTF-8). */
1334 Tcl_Obj *readOnlyPtr) /* The command line object. */
1338 HParamBlockRec paramBlock;
1342 native=Tcl_FSGetNativePath(fileName);
1343 err = FSpLLocationFromPath(strlen(native),
1347 if (Tcl_GetBooleanFromObj(interp, readOnlyPtr, &hidden) != TCL_OK) {
1351 paramBlock.fileParam.ioCompletion = NULL;
1352 paramBlock.fileParam.ioNamePtr = fileSpec.name;
1353 paramBlock.fileParam.ioVRefNum = fileSpec.vRefNum;
1354 paramBlock.fileParam.ioDirID = fileSpec.parID;
1356 err = PBHSetFLock(¶mBlock, 0);
1358 err = PBHRstFLock(¶mBlock, 0);
1362 if (err == fnfErr) {
1364 Boolean isDirectory = 0;
1365 err = FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory);
1366 if ((err == noErr) && isDirectory) {
1367 Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
1368 "cannot set a directory to read-only when File Sharing is turned off",
1377 errno = TclMacOSErrorToPosixError(err);
1378 Tcl_AppendStringsToObj(Tcl_GetObjResult(interp),
1379 "could not read \"", Tcl_GetString(fileName), "\": ",
1380 Tcl_PosixError(interp), (char *) NULL);
1387 *---------------------------------------------------------------------------
1389 * TclpObjListVolumes --
1391 * Lists the currently mounted volumes
1394 * The list of volumes.
1399 *---------------------------------------------------------------------------
1402 TclpObjListVolumes(void)
1406 OSErr theError = noErr;
1407 Tcl_Obj *resultPtr, *elemPtr;
1411 resultPtr = Tcl_NewObj();
1415 * 1) The Mac volumes are enumerated by the ioVolIndex parameter of
1416 * the HParamBlockRec. They run through the integers contiguously,
1418 * 2) PBHGetVInfoSync returns an error when you ask for a volume index
1419 * that does not exist.
1424 pb.volumeParam.ioNamePtr = (StringPtr) &name;
1425 pb.volumeParam.ioVolIndex = volIndex;
1427 theError = PBHGetVInfoSync(&pb);
1429 if ( theError != noErr ) {
1433 Tcl_ExternalToUtfDString(NULL, (CONST char *)&name[1], name[0], &dstr);
1434 elemPtr = Tcl_NewStringObj(Tcl_DStringValue(&dstr),
1435 Tcl_DStringLength(&dstr));
1436 Tcl_AppendToObj(elemPtr, ":", 1);
1437 Tcl_ListObjAppendElement(NULL, resultPtr, elemPtr);
1439 Tcl_DStringFree(&dstr);
1444 Tcl_IncrRefCount(resultPtr);
1449 *---------------------------------------------------------------------------
1451 * TclpObjNormalizePath --
1453 * This function scans through a path specification and replaces
1454 * it, in place, with a normalized version. On MacOS, this means
1455 * resolving all aliases present in the path and replacing the head of
1456 * pathPtr with the absolute case-sensitive path to the last file or
1457 * directory that could be validated in the path.
1460 * The new 'nextCheckpoint' value, giving as far as we could
1461 * understand in the path.
1464 * The pathPtr string, which must contain a valid path, is
1465 * possibly modified in place.
1467 *---------------------------------------------------------------------------
1471 TclpObjNormalizePath(interp, pathPtr, nextCheckpoint)
1476 #define MAXMACFILENAMELEN 31 /* assumed to be < sizeof(StrFileName) */
1478 StrFileName fileName;
1479 StringPtr fileNamePtr;
1480 int fileNameLen,newPathLen;
1481 Handle newPathHandle;
1485 Boolean isDirectory;
1486 Boolean wasAlias=FALSE;
1487 FSSpec fileSpec, lastFileSpec;
1489 Tcl_DString nativeds;
1492 int firstCheckpoint=nextCheckpoint, lastCheckpoint;
1494 char *path = Tcl_GetStringFromObj(pathPtr,&origPathLen);
1499 * check if substring to first ':' after initial
1500 * nextCheckpoint is a valid relative or absolute
1501 * path to a directory, if not we return without
1502 * normalizing anything
1506 cur = path[nextCheckpoint];
1507 if (cur == ':' || cur == 0) {
1509 /* jump over separator */
1510 nextCheckpoint++; cur = path[nextCheckpoint];
1512 Tcl_UtfToExternalDString(NULL,path,nextCheckpoint,&nativeds);
1513 err = FSpLLocationFromPath(Tcl_DStringLength(&nativeds),
1514 Tcl_DStringValue(&nativeds),
1516 Tcl_DStringFree(&nativeds);
1518 lastFileSpec=fileSpec;
1519 err = ResolveAliasFile(&fileSpec, true, &isDirectory,
1522 err = FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory);
1523 currDirValid = ((err == noErr) && isDirectory);
1524 vRefNum = fileSpec.vRefNum;
1533 /* can't determine root dir, bail out */
1534 return firstCheckpoint;
1539 * Now vRefNum and dirID point to a valid
1540 * directory, so walk the rest of the path
1541 * ( code adapted from FSpLocationFromPath() )
1544 lastCheckpoint=nextCheckpoint;
1546 cur = path[nextCheckpoint];
1547 if (cur == ':' || cur == 0) {
1548 fileNameLen=nextCheckpoint-lastCheckpoint;
1549 fileNamePtr=fileName;
1550 if(fileNameLen==0) {
1553 * special case for empty dirname i.e. encountered
1554 * a '::' path component: get parent dir of currDir
1557 strcpy((char *) fileName + 1, "::");
1561 * empty filename, i.e. want FSSpec for currDir
1566 Tcl_UtfToExternalDString(NULL,&path[lastCheckpoint],
1567 fileNameLen,&nativeds);
1568 fileNameLen=Tcl_DStringLength(&nativeds);
1569 if(fileNameLen > MAXMACFILENAMELEN) {
1572 fileName[0]=fileNameLen;
1573 strncpy((char *) fileName + 1, Tcl_DStringValue(&nativeds),
1576 Tcl_DStringFree(&nativeds);
1579 err=FSMakeFSSpecCompat(vRefNum, dirID, fileNamePtr, &fileSpec);
1583 * this can occur if trying to get parent of a root
1584 * volume via '::' or when using an illegal
1585 * filename; revert to last checkpoint and stop
1586 * processing path further
1588 err=FSMakeFSSpecCompat(vRefNum, dirID, NULL, &fileSpec);
1590 /* should never happen, bail out */
1591 return firstCheckpoint;
1593 nextCheckpoint=lastCheckpoint;
1594 cur = path[lastCheckpoint];
1596 break; /* arrived at nonexistent file or dir */
1598 /* fileSpec could point to an alias, resolve it */
1599 lastFileSpec=fileSpec;
1600 err = ResolveAliasFile(&fileSpec, true, &isDirectory,
1602 if (err != noErr || !isDirectory) {
1603 break; /* fileSpec doesn't point to a dir */
1606 if (cur == 0) break; /* arrived at end of path */
1608 /* fileSpec points to possibly nonexisting subdirectory; validate */
1609 err = FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory);
1610 if (err != noErr || !isDirectory) {
1611 break; /* fileSpec doesn't point to existing dir */
1613 vRefNum = fileSpec.vRefNum;
1615 /* found a new valid subdir in path, continue processing path */
1616 lastCheckpoint=nextCheckpoint+1;
1623 fileSpec=lastFileSpec;
1626 * fileSpec now points to a possibly nonexisting file or dir
1627 * inside a valid dir; get full path name to it
1630 err=FSpPathFromLocation(&fileSpec, &newPathLen, &newPathHandle);
1632 return firstCheckpoint; /* should not see any errors here, bail out */
1635 HLock(newPathHandle);
1636 Tcl_ExternalToUtfDString(NULL,*newPathHandle,newPathLen,&nativeds);
1638 /* not at end, append remaining path */
1639 if ( newPathLen==0 || (*(*newPathHandle+(newPathLen-1))!=':' && path[nextCheckpoint] !=':')) {
1640 Tcl_DStringAppend(&nativeds, ":" , 1);
1642 Tcl_DStringAppend(&nativeds, &path[nextCheckpoint],
1643 strlen(&path[nextCheckpoint]));
1645 DisposeHandle(newPathHandle);
1647 fileNameLen=Tcl_DStringLength(&nativeds);
1648 Tcl_SetStringObj(pathPtr,Tcl_DStringValue(&nativeds),fileNameLen);
1649 Tcl_DStringFree(&nativeds);
1651 return nextCheckpoint+(fileNameLen-origPathLen);