4 * This file implements the channel drivers for Macintosh
5 * files. It also comtains Macintosh version of other Tcl
6 * functions that deal with the file system.
8 * Copyright (c) 1995-1997 Sun Microsystems, Inc.
10 * See the file "license.terms" for information on usage and redistribution
11 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
17 * Note: This code eventually needs to support async I/O. In doing this
18 * we will need to keep track of all current async I/O. If exit to shell
19 * is called - we shouldn't exit until all asyc I/O completes.
24 #include "tclMacInt.h"
27 #include <Processes.h>
30 #include <MoreFiles.h>
31 #include <MoreFilesExtras.h>
32 #include <FSpCompat.h>
35 * Static variables used by the TclpStat function.
37 static int initalized = false;
38 static long gmt_offset;
41 * The variable below caches the name of the current working directory
42 * in order to avoid repeated calls to getcwd. The string is malloc-ed.
43 * NULL means the cache needs to be refreshed.
46 static char *currentDir = NULL;
49 *----------------------------------------------------------------------
53 * Change the current working directory.
56 * The result is a standard Tcl result. If an error occurs and
57 * interp isn't NULL, an error message is left in interp->result.
60 * The working directory for this application is changed. Also
61 * the cache maintained used by TclGetCwd is deallocated and
64 *----------------------------------------------------------------------
69 Tcl_Interp *interp, /* If non NULL, used for error reporting. */
70 char *dirName) /* Path to new working directory. */
77 if (currentDir != NULL) {
82 err = FSpLocationFromPath(strlen(dirName), dirName, &spec);
88 err = FSpGetDirectoryID(&spec, &dirID, &isFolder);
94 if (isFolder != true) {
99 err = FSpSetDefaultDir(&spec);
102 case afpAccessDenied:
113 if (interp != NULL) {
114 Tcl_AppendResult(interp, "couldn't change working directory to \"",
115 dirName, "\": ", Tcl_PosixError(interp), (char *) NULL);
121 *----------------------------------------------------------------------
125 * Return the path name of the current working directory.
128 * The result is the full path name of the current working
129 * directory, or NULL if an error occurred while figuring it
130 * out. If an error occurs and interp isn't NULL, an error
131 * message is left in interp->result.
134 * The path name is cached to avoid having to recompute it
135 * on future calls; if it is already cached, the cached
138 *----------------------------------------------------------------------
143 Tcl_Interp *interp) /* If non NULL, used for error reporting. */
147 Handle pathHandle = NULL;
149 if (currentDir == NULL) {
150 if (FSpGetDefaultDir(&theSpec) != noErr) {
151 if (interp != NULL) {
152 interp->result = "error getting working directory name";
156 if (FSpPathFromLocation(&theSpec, &length, &pathHandle) != noErr) {
157 if (interp != NULL) {
158 interp->result = "error getting working directory name";
163 currentDir = (char *) ckalloc((unsigned) (length + 1));
164 strcpy(currentDir, *pathHandle);
166 DisposeHandle(pathHandle);
172 *----------------------------------------------------------------------
176 * Fakes a call to wait pid.
184 *----------------------------------------------------------------------
197 *----------------------------------------------------------------------
199 * Tcl_FindExecutable --
201 * This procedure computes the absolute path name of the current
202 * application, given its argv[0] value. However, this
203 * implementation doesn't use of need the argv[0] value. NULL
204 * may be passed in its place.
210 * The variable tclExecutableName gets filled in with the file
211 * name for the application, if we figured it out. If we couldn't
212 * figure it out, Tcl_FindExecutable is set to NULL.
214 *----------------------------------------------------------------------
219 char *argv0) /* The value of the application's argv[0]. */
221 ProcessSerialNumber psn;
226 Handle pathName = NULL;
229 GetCurrentProcess(&psn);
230 info.processInfoLength = sizeof(ProcessInfoRec);
231 info.processName = appName;
232 info.processAppSpec = &fileSpec;
233 GetProcessInformation(&psn, &info);
235 if (tclExecutableName != NULL) {
236 ckfree(tclExecutableName);
237 tclExecutableName = NULL;
240 err = FSpPathFromLocation(&fileSpec, &pathLength, &pathName);
242 tclExecutableName = (char *) ckalloc((unsigned) pathLength + 1);
244 strcpy(tclExecutableName, *pathName);
246 DisposeHandle(pathName);
250 *----------------------------------------------------------------------
254 * This function takes the passed in user name and finds the
255 * corresponding home directory specified in the password file.
258 * On a Macintosh we always return a NULL.
263 *----------------------------------------------------------------------
268 char *name, /* User name to use to find home directory. */
269 Tcl_DString *bufferPtr) /* May be used to hold result. Must not hold
270 * anything at the time of the call, and need
271 * not even be initialized. */
277 *----------------------------------------------------------------------
281 * This routine is used by the globbing code to search a
282 * directory for all files which match a given pattern.
285 * If the tail argument is NULL, then the matching files are
286 * added to the interp->result. Otherwise, TclDoGlob is called
287 * recursively for each matching subdirectory. The return value
288 * is a standard Tcl result indicating whether an error occurred
294 *---------------------------------------------------------------------- */
298 Tcl_Interp *interp, /* Interpreter to receive results. */
299 char *separators, /* Directory separators to pass to TclDoGlob. */
300 Tcl_DString *dirPtr, /* Contains path to directory to search. */
301 char *pattern, /* Pattern to match against. */
302 char *tail) /* Pointer to end of pattern. Tail must
303 * point to a location in pattern. */
305 char *dirName, *patternEnd = tail;
308 int baseLength = Tcl_DStringLength(dirPtr);
319 * Make sure that the directory part of the name really is a
323 dirName = dirPtr->string;
324 FSpLocationFromPath(strlen(dirName), dirName, &dirSpec);
325 err = FSpGetDirectoryID(&dirSpec, &dirID, &isDirectory);
326 if ((err != noErr) || !isDirectory) {
331 * Now open the directory for reading and iterate over the contents.
334 pb.hFileInfo.ioVRefNum = dirSpec.vRefNum;
335 pb.hFileInfo.ioDirID = dirID;
336 pb.hFileInfo.ioNamePtr = (StringPtr) fileName;
337 pb.hFileInfo.ioFDirIndex = itemIndex = 1;
340 * Clean up the end of the pattern and the tail pointer. Leave
341 * the tail pointing to the first character after the path separator
342 * following the pattern, or NULL. Also, ensure that the pattern
343 * is null-terminated.
354 savedChar = *patternEnd;
358 pb.hFileInfo.ioFDirIndex = itemIndex;
359 pb.hFileInfo.ioDirID = dirID;
360 err = PBGetCatInfoSync(&pb);
366 * Now check to see if the file matches. If there are more
367 * characters to be processed, then ensure matching files are
368 * directories before calling TclDoGlob. Otherwise, just add
369 * the file to the result.
373 if (Tcl_StringMatch((char *) fileName, pattern)) {
374 Tcl_DStringSetLength(dirPtr, baseLength);
375 Tcl_DStringAppend(dirPtr, (char *) fileName, -1);
377 if ((dirPtr->length > 1) &&
378 (strchr(dirPtr->string+1, ':') == NULL)) {
379 Tcl_AppendElement(interp, dirPtr->string+1);
381 Tcl_AppendElement(interp, dirPtr->string);
383 } else if ((pb.hFileInfo.ioFlAttrib & ioDirMask) != 0) {
384 Tcl_DStringAppend(dirPtr, ":", 1);
385 result = TclDoGlob(interp, separators, dirPtr, tail);
386 if (result != TCL_OK) {
394 *patternEnd = savedChar;
400 *----------------------------------------------------------------------
404 * This function replaces the library version of stat. The stat
405 * function provided by most Mac compiliers is rather broken and
409 * See stat documentation.
412 * See stat documentation.
414 *----------------------------------------------------------------------
429 err = FSpLocationFromPath(strlen(path), path, &fileSpec);
431 errno = TclMacOSErrorToPosixError(err);
436 * Fill the fpb & vpb struct up with info about file or directory.
439 FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory);
440 vpb.ioVRefNum = fpb.ioVRefNum = fileSpec.vRefNum;
441 vpb.ioNamePtr = fpb.ioNamePtr = fileSpec.name;
443 fpb.ioDirID = fileSpec.parID;
449 err = PBGetCatInfoSync((CInfoPBPtr)&fpb);
452 err = PBHGetVInfoSync((HParmBlkPtr)&vpb);
453 if (err == noErr && buf != NULL) {
455 * Files are always readable by everyone.
458 buf->st_mode = S_IRUSR | S_IRGRP | S_IROTH;
461 * Use the Volume Info & File Info to fill out stat buf.
463 if (fpb.ioFlAttrib & 0x10) {
464 buf->st_mode |= S_IFDIR;
468 if (fpb.ioFlFndrInfo.fdFlags & 0x8000) {
469 buf->st_mode |= S_IFLNK;
471 buf->st_mode |= S_IFREG;
474 if ((fpb.ioFlAttrib & 0x10) || (fpb.ioFlFndrInfo.fdType == 'APPL')) {
476 * Directories and applications are executable by everyone.
479 buf->st_mode |= S_IXUSR | S_IXGRP | S_IXOTH;
481 if ((fpb.ioFlAttrib & 0x01) == 0){
483 * If not locked, then everyone has write acces.
486 buf->st_mode |= S_IWUSR | S_IWGRP | S_IWOTH;
488 buf->st_ino = fpb.ioDirID;
489 buf->st_dev = fpb.ioVRefNum;
493 buf->st_size = fpb.ioFlLgLen;
494 buf->st_blksize = vpb.ioVAlBlkSiz;
495 buf->st_blocks = (buf->st_size + buf->st_blksize - 1)
499 * The times returned by the Mac file system are in the
500 * local time zone. We convert them to GMT so that the
501 * epoch starts from GMT. This is also consistant with
502 * what is returned from "clock seconds".
504 if (initalized == false) {
508 gmt_offset = loc.u.gmtDelta & 0x00ffffff;
509 if (gmt_offset & 0x00800000) {
510 gmt_offset = gmt_offset | 0xff000000;
514 buf->st_atime = buf->st_mtime = fpb.ioFlMdDat - gmt_offset;
515 buf->st_ctime = fpb.ioFlCrDat - gmt_offset;
521 errno = TclMacOSErrorToPosixError(err);
524 return (err == noErr ? 0 : -1);
528 *----------------------------------------------------------------------
532 * This function replaces the library version of readlink.
535 * See readlink documentation.
540 *----------------------------------------------------------------------
557 Handle theString = NULL;
561 * Remove ending colons if they exist.
563 while ((strlen(path) != 0) && (path[strlen(path) - 1] == ':')) {
564 path[strlen(path) - 1] = NULL;
567 if (strchr(path, ':') == NULL) {
568 strcpy(fileName, path);
571 end = strrchr(path, ':') + 1;
572 strcpy(fileName, end);
578 * Create the file spec for the directory of the file
579 * we want to look at.
582 err = FSpLocationFromPath(strlen(path), path, &fileSpec);
588 FSMakeFSSpecCompat(0, 0, NULL, &fileSpec);
592 * Fill the fpb struct up with info about file or directory.
594 FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory);
595 fpb.ioVRefNum = fileSpec.vRefNum;
597 fpb.ioNamePtr = (StringPtr) fileName;
600 err = PBGetCatInfoSync((CInfoPBPtr)&fpb);
602 errno = TclMacOSErrorToPosixError(err);
605 if (fpb.ioFlAttrib & 0x10) {
609 if (fpb.ioFlFndrInfo.fdFlags & 0x8000) {
611 * The file is a link!
621 * If we are here it's really a link - now find out
622 * where it points to.
624 err = FSMakeFSSpecCompat(fileSpec.vRefNum, dirID, (StringPtr) fileName, &fileSpec);
626 err = ResolveAliasFile(&fileSpec, true, &isDirectory, &wasAlias);
628 if ((err == fnfErr) || wasAlias) {
629 err = FSpPathFromLocation(&fileSpec, &pathSize, &theString);
630 if ((err != noErr) || (pathSize > size)) {
631 DisposeHandle(theString);
632 errno = ENAMETOOLONG;
640 strncpy(buf, *theString, pathSize);
641 DisposeHandle(theString);
647 *----------------------------------------------------------------------
651 * This function replaces the library version of access. The
652 * access function provided by most Mac compiliers is rather
653 * broken or incomplete.
656 * See access documentation.
659 * See access documentation.
661 *----------------------------------------------------------------------
677 err = FSpLocationFromPath(strlen(path), (char *) path, &fileSpec);
679 errno = TclMacOSErrorToPosixError(err);
684 * Fill the fpb & vpb struct up with info about file or directory.
686 FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory);
687 vpb.ioVRefNum = fpb.ioVRefNum = fileSpec.vRefNum;
688 vpb.ioNamePtr = fpb.ioNamePtr = fileSpec.name;
690 fpb.ioDirID = fileSpec.parID;
696 err = PBGetCatInfoSync((CInfoPBPtr)&fpb);
699 err = PBHGetVInfoSync((HParmBlkPtr)&vpb);
702 * Use the Volume Info & File Info to determine
703 * access information. If we have got this far
704 * we know the directory is searchable or the file
705 * exists. (We have F_OK)
709 * Check to see if the volume is hardware or
710 * software locked. If so we arn't W_OK.
713 if ((vpb.ioVAtrb & 0x0080) || (vpb.ioVAtrb & 0x8000)) {
717 if (fpb.ioFlAttrib & 0x01) {
724 * Directories are always searchable and executable. But only
725 * files of type 'APPL' are executable.
727 if (!(fpb.ioFlAttrib & 0x10) && (mode & X_OK)
728 && (fpb.ioFlFndrInfo.fdType != 'APPL')) {
735 errno = TclMacOSErrorToPosixError(err);
743 *----------------------------------------------------------------------
747 * This function replaces fopen. It supports paths with alises.
748 * Note, remember to undefine the fopen macro!
751 * See fopen documentation.
754 * See fopen documentation.
756 *----------------------------------------------------------------------
767 Handle pathString = NULL;
771 err = FSpLocationFromPath(strlen(path), (char *) path, &fileSpec);
772 if ((err != noErr) && (err != fnfErr)) {
775 err = FSpPathFromLocation(&fileSpec, &size, &pathString);
776 if ((err != noErr) && (err != fnfErr)) {
781 f = fopen(*pathString, mode);
783 DisposeHandle(pathString);
788 *----------------------------------------------------------------------
790 * TclMacOSErrorToPosixError --
792 * Given a Macintosh OSErr return the appropiate POSIX error.
800 *----------------------------------------------------------------------
804 TclMacOSErrorToPosixError(
805 int error) /* A Macintosh error. */
812 case afpObjectTypeErr:
828 case afpAccessDenied: