1607 lines
40 KiB
C
1607 lines
40 KiB
C
|
/*
|
|||
|
* tclMacFile.c --
|
|||
|
*
|
|||
|
* This file implements the channel drivers for Macintosh
|
|||
|
* files. It also comtains Macintosh version of other Tcl
|
|||
|
* functions that deal with the file system.
|
|||
|
*
|
|||
|
* Copyright (c) 1995-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: @(#) tclMacFile.c 1.49 96/10/10 10:11:36
|
|||
|
*/
|
|||
|
|
|||
|
/*
|
|||
|
* Note: This code eventually needs to support async I/O. In doing this
|
|||
|
* we will need to keep track of all current async I/O. If exit to shell
|
|||
|
* is called - we shouldn't exit until all asyc I/O completes.
|
|||
|
*/
|
|||
|
|
|||
|
#include "tclInt.h"
|
|||
|
#include "tclPort.h"
|
|||
|
#include "tclMacInt.h"
|
|||
|
#include <Aliases.h>
|
|||
|
#include <Errors.h>
|
|||
|
#include <Processes.h>
|
|||
|
#include <Strings.h>
|
|||
|
#include <Types.h>
|
|||
|
#include <MoreFiles.h>
|
|||
|
#include <MoreFilesExtras.h>
|
|||
|
#include <FSpCompat.h>
|
|||
|
|
|||
|
/*
|
|||
|
* The following are flags returned by GetOpenMode. They
|
|||
|
* are or'd together to determine how opening and handling
|
|||
|
* a file should occur.
|
|||
|
*/
|
|||
|
|
|||
|
#define TCL_RDONLY (1<<0)
|
|||
|
#define TCL_WRONLY (1<<1)
|
|||
|
#define TCL_RDWR (1<<2)
|
|||
|
#define TCL_CREAT (1<<3)
|
|||
|
#define TCL_TRUNC (1<<4)
|
|||
|
#define TCL_APPEND (1<<5)
|
|||
|
#define TCL_ALWAYS_APPEND (1<<6)
|
|||
|
#define TCL_EXCL (1<<7)
|
|||
|
#define TCL_NOCTTY (1<<8)
|
|||
|
#define TCL_NONBLOCK (1<<9)
|
|||
|
#define TCL_RW_MODES (TCL_RDONLY|TCL_WRONLY|TCL_RDWR)
|
|||
|
|
|||
|
/*
|
|||
|
* This structure describes per-instance state of a
|
|||
|
* macintosh file based channel.
|
|||
|
*/
|
|||
|
|
|||
|
typedef struct FileState {
|
|||
|
short fileRef; /* Macintosh file reference number. */
|
|||
|
Tcl_Channel fileChan; /* Pointer to the channel for this file. */
|
|||
|
int appendMode; /* Flag to tell if in O_APPEND mode or not. */
|
|||
|
int volumeRef; /* Flag to tell if in O_APPEND mode or not. */
|
|||
|
} FileState;
|
|||
|
|
|||
|
/*
|
|||
|
* The variable below caches the name of the current working directory
|
|||
|
* in order to avoid repeated calls to getcwd. The string is malloc-ed.
|
|||
|
* NULL means the cache needs to be refreshed.
|
|||
|
*/
|
|||
|
|
|||
|
static char *currentDir = NULL;
|
|||
|
|
|||
|
/*
|
|||
|
* Static routines for this file:
|
|||
|
*/
|
|||
|
|
|||
|
static int FileBlockMode _ANSI_ARGS_((ClientData instanceData,
|
|||
|
int mode));
|
|||
|
static int FileClose _ANSI_ARGS_((ClientData instanceData,
|
|||
|
Tcl_Interp *interp));
|
|||
|
static Tcl_File FileGet _ANSI_ARGS_((ClientData instanceData,
|
|||
|
int direction));
|
|||
|
static int FileInput _ANSI_ARGS_((ClientData instanceData,
|
|||
|
char *buf, int toRead, int *errorCode));
|
|||
|
static int FileOutput _ANSI_ARGS_((ClientData instanceData,
|
|||
|
char *buf, int toWrite, int *errorCode));
|
|||
|
static int FileReady _ANSI_ARGS_((ClientData instanceData,
|
|||
|
int mask));
|
|||
|
static int FileSeek _ANSI_ARGS_((ClientData instanceData,
|
|||
|
long offset, int mode, int *errorCode));
|
|||
|
static void FileWatch _ANSI_ARGS_((ClientData instanceData,
|
|||
|
int mask));
|
|||
|
static int GetOpenMode _ANSI_ARGS_((Tcl_Interp *interp,
|
|||
|
char *string));
|
|||
|
static Tcl_Channel OpenFileChannel _ANSI_ARGS_((char *fileName, int mode,
|
|||
|
int permissions, int *errorCodePtr));
|
|||
|
|
|||
|
/*
|
|||
|
* This variable describes the channel type structure for file based IO.
|
|||
|
*/
|
|||
|
|
|||
|
static Tcl_ChannelType fileChannelType = {
|
|||
|
"file", /* Type name. */
|
|||
|
FileBlockMode, /* Set blocking or
|
|||
|
* non-blocking mode.*/
|
|||
|
FileClose, /* Close proc. */
|
|||
|
FileInput, /* Input proc. */
|
|||
|
FileOutput, /* Output proc. */
|
|||
|
FileSeek, /* Seek proc. */
|
|||
|
NULL, /* Set option proc. */
|
|||
|
NULL, /* Get option proc. */
|
|||
|
FileWatch, /* Initialize notifier. */
|
|||
|
FileReady, /* Are there events? */
|
|||
|
FileGet /* Get Tcl_Files out of channel. */
|
|||
|
};
|
|||
|
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclChdir --
|
|||
|
*
|
|||
|
* Change the current working directory.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* The result is a standard Tcl result. If an error occurs and
|
|||
|
* interp isn't NULL, an error message is left in interp->result.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* The working directory for this application is changed. Also
|
|||
|
* the cache maintained used by TclGetCwd is deallocated and
|
|||
|
* set to NULL.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
TclChdir(interp, dirName)
|
|||
|
Tcl_Interp *interp; /* If non NULL, used for error reporting. */
|
|||
|
char *dirName; /* Path to new working directory. */
|
|||
|
{
|
|||
|
FSSpec spec;
|
|||
|
OSErr err;
|
|||
|
Boolean isFolder;
|
|||
|
long dirID;
|
|||
|
|
|||
|
if (currentDir != NULL) {
|
|||
|
ckfree(currentDir);
|
|||
|
currentDir = NULL;
|
|||
|
}
|
|||
|
|
|||
|
err = FSpLocationFromPath(strlen(dirName), dirName, &spec);
|
|||
|
if (err != noErr) {
|
|||
|
errno = ENOENT;
|
|||
|
goto chdirError;
|
|||
|
}
|
|||
|
|
|||
|
err = FSpGetDirectoryID(&spec, &dirID, &isFolder);
|
|||
|
if (err != noErr) {
|
|||
|
errno = ENOENT;
|
|||
|
goto chdirError;
|
|||
|
}
|
|||
|
|
|||
|
if (isFolder != true) {
|
|||
|
errno = ENOTDIR;
|
|||
|
goto chdirError;
|
|||
|
}
|
|||
|
|
|||
|
err = FSpSetDefaultDir(&spec);
|
|||
|
if (err != noErr) {
|
|||
|
switch (err) {
|
|||
|
case afpAccessDenied:
|
|||
|
errno = EACCES;
|
|||
|
break;
|
|||
|
default:
|
|||
|
errno = ENOENT;
|
|||
|
}
|
|||
|
goto chdirError;
|
|||
|
}
|
|||
|
|
|||
|
return TCL_OK;
|
|||
|
chdirError:
|
|||
|
if (interp != NULL) {
|
|||
|
Tcl_AppendResult(interp, "couldn't change working directory to \"",
|
|||
|
dirName, "\": ", Tcl_PosixError(interp), (char *) NULL);
|
|||
|
}
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclGetCwd --
|
|||
|
*
|
|||
|
* Return the path name of the current working directory.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* The result is the full path name of the current working
|
|||
|
* directory, or NULL if an error occurred while figuring it
|
|||
|
* out. If an error occurs and interp isn't NULL, an error
|
|||
|
* message is left in interp->result.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* The path name is cached to avoid having to recompute it
|
|||
|
* on future calls; if it is already cached, the cached
|
|||
|
* value is returned.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
char *
|
|||
|
TclGetCwd(interp)
|
|||
|
Tcl_Interp *interp; /* If non NULL, used for error reporting. */
|
|||
|
{
|
|||
|
FSSpec theSpec;
|
|||
|
int length;
|
|||
|
Handle pathHandle = NULL;
|
|||
|
|
|||
|
if (currentDir == NULL) {
|
|||
|
if (FSpGetDefaultDir(&theSpec) != noErr) {
|
|||
|
if (interp != NULL) {
|
|||
|
interp->result = "error getting working directory name";
|
|||
|
}
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
if (FSpPathFromLocation(&theSpec, &length, &pathHandle) != noErr) {
|
|||
|
if (interp != NULL) {
|
|||
|
interp->result = "error getting working directory name";
|
|||
|
}
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
HLock(pathHandle);
|
|||
|
currentDir = (char *) ckalloc((unsigned) (length + 1));
|
|||
|
strcpy(currentDir, *pathHandle);
|
|||
|
HUnlock(pathHandle);
|
|||
|
DisposeHandle(pathHandle);
|
|||
|
}
|
|||
|
return currentDir;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_WaitPid --
|
|||
|
*
|
|||
|
* Fakes a call to wait pid.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Always returns -1.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* None.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
Tcl_WaitPid(pid, statPtr, options)
|
|||
|
int pid;
|
|||
|
int *statPtr;
|
|||
|
int options;
|
|||
|
{
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclCreateTempFile --
|
|||
|
*
|
|||
|
* This function creates a temporary file initialized with an
|
|||
|
* optional string, and returns a file handle with the file pointer
|
|||
|
* at the beginning of the file.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* A handle to a file.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* None.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_File
|
|||
|
TclCreateTempFile(contents, namePtr)
|
|||
|
char *contents; /* String to write into temp file, or NULL. */
|
|||
|
Tcl_DString *namePtr; /* If non-NULL, pointer to initialized
|
|||
|
* DString that is filled with the name of
|
|||
|
* the temp file that was created. */
|
|||
|
{
|
|||
|
char fileName[L_tmpnam];
|
|||
|
Tcl_File file;
|
|||
|
int length = (contents == NULL) ? 0 : strlen(contents);
|
|||
|
|
|||
|
tmpnam(fileName);
|
|||
|
file = TclOpenFile(fileName, O_RDWR|O_CREAT|O_TRUNC);
|
|||
|
unlink(fileName);
|
|||
|
|
|||
|
if ((file != NULL) && (length > 0)) {
|
|||
|
int fd = (int)Tcl_GetFileInfo(file, NULL);
|
|||
|
while (1) {
|
|||
|
if (write(fd, contents, length) != -1) {
|
|||
|
break;
|
|||
|
} else if (errno != EINTR) {
|
|||
|
close(fd);
|
|||
|
Tcl_FreeFile(file);
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
}
|
|||
|
lseek(fd, 0, SEEK_SET);
|
|||
|
}
|
|||
|
if (namePtr != NULL) {
|
|||
|
Tcl_DStringAppend(namePtr, fileName, -1);
|
|||
|
}
|
|||
|
return file;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_FindExecutable --
|
|||
|
*
|
|||
|
* This procedure computes the absolute path name of the current
|
|||
|
* application, given its argv[0] value. However, this
|
|||
|
* implementation doesn't use of need the argv[0] value. NULL
|
|||
|
* may be passed in its place.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* None.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* The variable tclExecutableName gets filled in with the file
|
|||
|
* name for the application, if we figured it out. If we couldn't
|
|||
|
* figure it out, Tcl_FindExecutable is set to NULL.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
void
|
|||
|
Tcl_FindExecutable(argv0)
|
|||
|
char *argv0; /* The value of the application's argv[0]. */
|
|||
|
{
|
|||
|
ProcessSerialNumber psn;
|
|||
|
ProcessInfoRec info;
|
|||
|
Str63 appName;
|
|||
|
FSSpec fileSpec;
|
|||
|
int pathLength;
|
|||
|
Handle pathName = NULL;
|
|||
|
OSErr err;
|
|||
|
|
|||
|
GetCurrentProcess(&psn);
|
|||
|
info.processInfoLength = sizeof(ProcessInfoRec);
|
|||
|
info.processName = appName;
|
|||
|
info.processAppSpec = &fileSpec;
|
|||
|
GetProcessInformation(&psn, &info);
|
|||
|
|
|||
|
if (tclExecutableName != NULL) {
|
|||
|
ckfree(tclExecutableName);
|
|||
|
tclExecutableName = NULL;
|
|||
|
}
|
|||
|
|
|||
|
err = FSpPathFromLocation(&fileSpec, &pathLength, &pathName);
|
|||
|
|
|||
|
tclExecutableName = (char *) ckalloc((unsigned) pathLength + 1);
|
|||
|
HLock(pathName);
|
|||
|
strcpy(tclExecutableName, *pathName);
|
|||
|
HUnlock(pathName);
|
|||
|
DisposeHandle(pathName);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclGetUserHome --
|
|||
|
*
|
|||
|
* This function takes the passed in user name and finds the
|
|||
|
* corresponding home directory specified in the password file.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* On a Macintosh we always return a NULL.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* None.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
char *
|
|||
|
TclGetUserHome(name, bufferPtr)
|
|||
|
char *name; /* User name to use to find home directory. */
|
|||
|
Tcl_DString *bufferPtr; /* May be used to hold result. Must not hold
|
|||
|
* anything at the time of the call, and need
|
|||
|
* not even be initialized. */
|
|||
|
{
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclMatchFiles --
|
|||
|
*
|
|||
|
* This routine is used by the globbing code to search a
|
|||
|
* directory for all files which match a given pattern.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* If the tail argument is NULL, then the matching files are
|
|||
|
* added to the interp->result. Otherwise, TclDoGlob is called
|
|||
|
* recursively for each matching subdirectory. The return value
|
|||
|
* is a standard Tcl result indicating whether an error occurred
|
|||
|
* in globbing.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* None.
|
|||
|
*
|
|||
|
*---------------------------------------------------------------------- */
|
|||
|
|
|||
|
int
|
|||
|
TclMatchFiles(interp, separators, dirPtr, pattern, tail)
|
|||
|
Tcl_Interp *interp; /* Interpreter to receive results. */
|
|||
|
char *separators; /* Directory separators to pass to TclDoGlob. */
|
|||
|
Tcl_DString *dirPtr; /* Contains path to directory to search. */
|
|||
|
char *pattern; /* Pattern to match against. */
|
|||
|
char *tail; /* Pointer to end of pattern. Tail must
|
|||
|
* point to a location in pattern. */
|
|||
|
{
|
|||
|
char *dirName, *patternEnd = tail;
|
|||
|
char savedChar;
|
|||
|
int result = TCL_OK;
|
|||
|
int baseLength = Tcl_DStringLength(dirPtr);
|
|||
|
CInfoPBRec pb;
|
|||
|
OSErr err;
|
|||
|
FSSpec dirSpec;
|
|||
|
Boolean isDirectory;
|
|||
|
long dirID;
|
|||
|
short itemIndex;
|
|||
|
Str255 fileName;
|
|||
|
|
|||
|
|
|||
|
/*
|
|||
|
* Make sure that the directory part of the name really is a
|
|||
|
* directory.
|
|||
|
*/
|
|||
|
|
|||
|
dirName = dirPtr->string;
|
|||
|
FSpLocationFromPath(strlen(dirName), dirName, &dirSpec);
|
|||
|
err = FSpGetDirectoryID(&dirSpec, &dirID, &isDirectory);
|
|||
|
if ((err != noErr) || !isDirectory) {
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Now open the directory for reading and iterate over the contents.
|
|||
|
*/
|
|||
|
|
|||
|
pb.hFileInfo.ioVRefNum = dirSpec.vRefNum;
|
|||
|
pb.hFileInfo.ioDirID = dirID;
|
|||
|
pb.hFileInfo.ioNamePtr = (StringPtr) fileName;
|
|||
|
pb.hFileInfo.ioFDirIndex = itemIndex = 1;
|
|||
|
|
|||
|
/*
|
|||
|
* Clean up the end of the pattern and the tail pointer. Leave
|
|||
|
* the tail pointing to the first character after the path separator
|
|||
|
* following the pattern, or NULL. Also, ensure that the pattern
|
|||
|
* is null-terminated.
|
|||
|
*/
|
|||
|
|
|||
|
if (*tail == '\\') {
|
|||
|
tail++;
|
|||
|
}
|
|||
|
if (*tail == '\0') {
|
|||
|
tail = NULL;
|
|||
|
} else {
|
|||
|
tail++;
|
|||
|
}
|
|||
|
savedChar = *patternEnd;
|
|||
|
*patternEnd = '\0';
|
|||
|
|
|||
|
while (1) {
|
|||
|
pb.hFileInfo.ioFDirIndex = itemIndex;
|
|||
|
pb.hFileInfo.ioDirID = dirID;
|
|||
|
err = PBGetCatInfoSync(&pb);
|
|||
|
if (err != noErr) {
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Now check to see if the file matches. If there are more
|
|||
|
* characters to be processed, then ensure matching files are
|
|||
|
* directories before calling TclDoGlob. Otherwise, just add
|
|||
|
* the file to the result.
|
|||
|
*/
|
|||
|
|
|||
|
p2cstr(fileName);
|
|||
|
if (Tcl_StringMatch((char *) fileName, pattern)) {
|
|||
|
Tcl_DStringSetLength(dirPtr, baseLength);
|
|||
|
Tcl_DStringAppend(dirPtr, (char *) fileName, -1);
|
|||
|
if (tail == NULL) {
|
|||
|
if ((dirPtr->length > 1) &&
|
|||
|
(strchr(dirPtr->string+1, ':') == NULL)) {
|
|||
|
Tcl_AppendElement(interp, dirPtr->string+1);
|
|||
|
} else {
|
|||
|
Tcl_AppendElement(interp, dirPtr->string);
|
|||
|
}
|
|||
|
} else if ((pb.hFileInfo.ioFlAttrib & ioDirMask) != 0) {
|
|||
|
Tcl_DStringAppend(dirPtr, ":", 1);
|
|||
|
result = TclDoGlob(interp, separators, dirPtr, tail);
|
|||
|
if (result != TCL_OK) {
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
itemIndex++;
|
|||
|
}
|
|||
|
*patternEnd = savedChar;
|
|||
|
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclMacStat --
|
|||
|
*
|
|||
|
* This function replaces the library version of stat. The stat
|
|||
|
* function provided by most Mac compiliers is rather broken and
|
|||
|
* incomplete.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* See stat documentation.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* See stat documentation.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
TclMacStat(path, buf)
|
|||
|
char *path;
|
|||
|
struct stat *buf;
|
|||
|
{
|
|||
|
HFileInfo fpb;
|
|||
|
HVolumeParam vpb;
|
|||
|
OSErr err;
|
|||
|
FSSpec fileSpec;
|
|||
|
Boolean isDirectory;
|
|||
|
long dirID;
|
|||
|
|
|||
|
err = FSpLocationFromPath(strlen(path), path, &fileSpec);
|
|||
|
if (err != noErr) {
|
|||
|
errno = TclMacOSErrorToPosixError(err);
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Fill the fpb & vpb struct up with info about file or directory.
|
|||
|
*/
|
|||
|
|
|||
|
FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory);
|
|||
|
vpb.ioVRefNum = fpb.ioVRefNum = fileSpec.vRefNum;
|
|||
|
vpb.ioNamePtr = fpb.ioNamePtr = fileSpec.name;
|
|||
|
if (isDirectory) {
|
|||
|
fpb.ioDirID = fileSpec.parID;
|
|||
|
} else {
|
|||
|
fpb.ioDirID = dirID;
|
|||
|
}
|
|||
|
|
|||
|
fpb.ioFDirIndex = 0;
|
|||
|
err = PBGetCatInfoSync((CInfoPBPtr)&fpb);
|
|||
|
if (err == noErr) {
|
|||
|
vpb.ioVolIndex = 0;
|
|||
|
err = PBHGetVInfoSync((HParmBlkPtr)&vpb);
|
|||
|
if (err == noErr && buf != NULL) {
|
|||
|
/*
|
|||
|
* Files are always readable by everyone.
|
|||
|
*/
|
|||
|
|
|||
|
buf->st_mode = S_IRUSR | S_IRGRP | S_IROTH;
|
|||
|
|
|||
|
/*
|
|||
|
* Use the Volume Info & File Info to fill out stat buf.
|
|||
|
*/
|
|||
|
if (fpb.ioFlAttrib & 0x10) {
|
|||
|
buf->st_mode |= S_IFDIR;
|
|||
|
buf->st_nlink = 2;
|
|||
|
} else {
|
|||
|
buf->st_nlink = 1;
|
|||
|
if (fpb.ioFlFndrInfo.fdFlags & 0x8000) {
|
|||
|
buf->st_mode |= S_IFLNK;
|
|||
|
} else {
|
|||
|
buf->st_mode |= S_IFREG;
|
|||
|
}
|
|||
|
}
|
|||
|
if ((fpb.ioFlAttrib & 0x10) || (fpb.ioFlFndrInfo.fdType == 'APPL')) {
|
|||
|
/*
|
|||
|
* Directories and applications are executable by everyone.
|
|||
|
*/
|
|||
|
|
|||
|
buf->st_mode |= S_IXUSR | S_IXGRP | S_IXOTH;
|
|||
|
}
|
|||
|
if ((fpb.ioFlAttrib & 0x01) == 0){
|
|||
|
/*
|
|||
|
* If not locked, then everyone has write acces.
|
|||
|
*/
|
|||
|
|
|||
|
buf->st_mode |= S_IWUSR | S_IWGRP | S_IWOTH;
|
|||
|
}
|
|||
|
buf->st_ino = fpb.ioDirID;
|
|||
|
buf->st_dev = fpb.ioVRefNum;
|
|||
|
buf->st_uid = -1;
|
|||
|
buf->st_gid = -1;
|
|||
|
buf->st_rdev = 0;
|
|||
|
buf->st_size = fpb.ioFlLgLen;
|
|||
|
buf->st_atime = buf->st_mtime = fpb.ioFlMdDat;
|
|||
|
buf->st_ctime = fpb.ioFlCrDat;
|
|||
|
buf->st_blksize = vpb.ioVAlBlkSiz;
|
|||
|
buf->st_blocks = (buf->st_size + buf->st_blksize - 1) / buf->st_blksize;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (err != noErr) {
|
|||
|
errno = TclMacOSErrorToPosixError(err);
|
|||
|
}
|
|||
|
|
|||
|
return (err == noErr ? 0 : -1);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclMacReadlink --
|
|||
|
*
|
|||
|
* This function replaces the library version of readlink.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* See readlink documentation.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* None.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
TclMacReadlink(path, buf, size)
|
|||
|
char *path;
|
|||
|
char *buf;
|
|||
|
int size;
|
|||
|
{
|
|||
|
HFileInfo fpb;
|
|||
|
OSErr err;
|
|||
|
FSSpec fileSpec;
|
|||
|
Boolean isDirectory;
|
|||
|
Boolean wasAlias;
|
|||
|
long dirID;
|
|||
|
char fileName[256];
|
|||
|
char *end;
|
|||
|
Handle theString = NULL;
|
|||
|
int pathSize;
|
|||
|
|
|||
|
/*
|
|||
|
* Remove ending colons if they exist.
|
|||
|
*/
|
|||
|
while ((strlen(path) != 0) && (path[strlen(path) - 1] == ':')) {
|
|||
|
path[strlen(path) - 1] = NULL;
|
|||
|
}
|
|||
|
|
|||
|
if (strchr(path, ':') == NULL) {
|
|||
|
strcpy(fileName, path);
|
|||
|
path = NULL;
|
|||
|
} else {
|
|||
|
end = strrchr(path, ':') + 1;
|
|||
|
strcpy(fileName, end);
|
|||
|
*end = NULL;
|
|||
|
}
|
|||
|
c2pstr(fileName);
|
|||
|
|
|||
|
/*
|
|||
|
* Create the file spec for the directory of the file
|
|||
|
* we want to look at.
|
|||
|
*/
|
|||
|
if (path != NULL) {
|
|||
|
err = FSpLocationFromPath(strlen(path), path, &fileSpec);
|
|||
|
if (err != noErr) {
|
|||
|
errno = EINVAL;
|
|||
|
return -1;
|
|||
|
}
|
|||
|
} else {
|
|||
|
FSMakeFSSpecCompat(0, 0, NULL, &fileSpec);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Fill the fpb struct up with info about file or directory.
|
|||
|
*/
|
|||
|
FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory);
|
|||
|
fpb.ioVRefNum = fileSpec.vRefNum;
|
|||
|
fpb.ioDirID = dirID;
|
|||
|
fpb.ioNamePtr = (StringPtr) fileName;
|
|||
|
|
|||
|
fpb.ioFDirIndex = 0;
|
|||
|
err = PBGetCatInfoSync((CInfoPBPtr)&fpb);
|
|||
|
if (err != noErr) {
|
|||
|
errno = TclMacOSErrorToPosixError(err);
|
|||
|
return -1;
|
|||
|
} else {
|
|||
|
if (fpb.ioFlAttrib & 0x10) {
|
|||
|
errno = EINVAL;
|
|||
|
return -1;
|
|||
|
} else {
|
|||
|
if (fpb.ioFlFndrInfo.fdFlags & 0x8000) {
|
|||
|
/*
|
|||
|
* The file is a link!
|
|||
|
*/
|
|||
|
} else {
|
|||
|
errno = EINVAL;
|
|||
|
return -1;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* If we are here it's really a link - now find out
|
|||
|
* where it points to.
|
|||
|
*/
|
|||
|
err = FSMakeFSSpecCompat(fileSpec.vRefNum, dirID, (StringPtr) fileName, &fileSpec);
|
|||
|
if (err == noErr) {
|
|||
|
err = ResolveAliasFile(&fileSpec, true, &isDirectory, &wasAlias);
|
|||
|
}
|
|||
|
if ((err == fnfErr) || wasAlias) {
|
|||
|
err = FSpPathFromLocation(&fileSpec, &pathSize, &theString);
|
|||
|
if ((err != noErr) || (pathSize > size)) {
|
|||
|
DisposeHandle(theString);
|
|||
|
errno = ENAMETOOLONG;
|
|||
|
return -1;
|
|||
|
}
|
|||
|
} else {
|
|||
|
errno = EINVAL;
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
strncpy(buf, *theString, pathSize);
|
|||
|
DisposeHandle(theString);
|
|||
|
|
|||
|
return pathSize;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclMacAccess --
|
|||
|
*
|
|||
|
* This function replaces the library version of access. The
|
|||
|
* access function provided by most Mac compiliers is rather
|
|||
|
* broken or incomplete.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* See access documentation.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* See access documentation.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
TclMacAccess(path, mode)
|
|||
|
const char *path;
|
|||
|
int mode;
|
|||
|
{
|
|||
|
HFileInfo fpb;
|
|||
|
HVolumeParam vpb;
|
|||
|
OSErr err;
|
|||
|
FSSpec fileSpec;
|
|||
|
Boolean isDirectory;
|
|||
|
long dirID;
|
|||
|
int full_mode = 0;
|
|||
|
|
|||
|
err = FSpLocationFromPath(strlen(path), (char *) path, &fileSpec);
|
|||
|
if (err != noErr) {
|
|||
|
errno = TclMacOSErrorToPosixError(err);
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Fill the fpb & vpb struct up with info about file or directory.
|
|||
|
*/
|
|||
|
FSpGetDirectoryID(&fileSpec, &dirID, &isDirectory);
|
|||
|
vpb.ioVRefNum = fpb.ioVRefNum = fileSpec.vRefNum;
|
|||
|
vpb.ioNamePtr = fpb.ioNamePtr = fileSpec.name;
|
|||
|
if (isDirectory) {
|
|||
|
fpb.ioDirID = fileSpec.parID;
|
|||
|
} else {
|
|||
|
fpb.ioDirID = dirID;
|
|||
|
}
|
|||
|
|
|||
|
fpb.ioFDirIndex = 0;
|
|||
|
err = PBGetCatInfoSync((CInfoPBPtr)&fpb);
|
|||
|
if (err == noErr) {
|
|||
|
vpb.ioVolIndex = 0;
|
|||
|
err = PBHGetVInfoSync((HParmBlkPtr)&vpb);
|
|||
|
if (err == noErr) {
|
|||
|
/*
|
|||
|
* Use the Volume Info & File Info to determine
|
|||
|
* access information. If we have got this far
|
|||
|
* we know the directory is searchable or the file
|
|||
|
* exists. (We have F_OK)
|
|||
|
*/
|
|||
|
|
|||
|
/*
|
|||
|
* Check to see if the volume is hardware or
|
|||
|
* software locked. If so we arn't W_OK.
|
|||
|
*/
|
|||
|
if (mode & W_OK) {
|
|||
|
if ((vpb.ioVAtrb & 0x0080) || (vpb.ioVAtrb & 0x8000)) {
|
|||
|
errno = EROFS;
|
|||
|
return -1;
|
|||
|
}
|
|||
|
if (fpb.ioFlAttrib & 0x01) {
|
|||
|
errno = EACCES;
|
|||
|
return -1;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Directories are always searchable and executable. But only
|
|||
|
* files of type 'APPL' are executable.
|
|||
|
*/
|
|||
|
if (!(fpb.ioFlAttrib & 0x10) && (mode & X_OK)
|
|||
|
&& (fpb.ioFlFndrInfo.fdType != 'APPL')) {
|
|||
|
return -1;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (err != noErr) {
|
|||
|
errno = TclMacOSErrorToPosixError(err);
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclMacFOpenHack --
|
|||
|
*
|
|||
|
* This function replaces fopen. It supports paths with alises.
|
|||
|
* Note, remember to undefine the fopen macro!
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* See fopen documentation.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* See fopen documentation.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
#undef fopen
|
|||
|
FILE *
|
|||
|
TclMacFOpenHack(path, mode)
|
|||
|
const char *path;
|
|||
|
const char *mode;
|
|||
|
{
|
|||
|
OSErr err;
|
|||
|
FSSpec fileSpec;
|
|||
|
Handle pathString = NULL;
|
|||
|
int size;
|
|||
|
FILE * f;
|
|||
|
|
|||
|
err = FSpLocationFromPath(strlen(path), (char *) path, &fileSpec);
|
|||
|
if ((err != noErr) && (err != fnfErr)) {
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
err = FSpPathFromLocation(&fileSpec, &size, &pathString);
|
|||
|
if ((err != noErr) && (err != fnfErr)) {
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
HLock(pathString);
|
|||
|
f = fopen(*pathString, mode);
|
|||
|
HUnlock(pathString);
|
|||
|
DisposeHandle(pathString);
|
|||
|
return f;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_OpenFileChannel --
|
|||
|
*
|
|||
|
* Open an File based channel on Unix systems.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* The new channel or NULL. If NULL, the output argument
|
|||
|
* errorCodePtr is set to a POSIX error.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* May open the channel and may cause creation of a file on the
|
|||
|
* file system.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_Channel
|
|||
|
Tcl_OpenFileChannel(interp, fileName, modeString, permissions)
|
|||
|
Tcl_Interp *interp; /* Interpreter for error reporting;
|
|||
|
* can be NULL. */
|
|||
|
char *fileName; /* Name of file to open. */
|
|||
|
char *modeString; /* A list of POSIX open modes or
|
|||
|
* a string such as "rw". */
|
|||
|
int permissions; /* If the open involves creating a
|
|||
|
* file, with what modes to create
|
|||
|
* it? */
|
|||
|
{
|
|||
|
Tcl_Channel chan;
|
|||
|
int mode;
|
|||
|
char *nativeName;
|
|||
|
Tcl_DString buffer;
|
|||
|
int errorCode;
|
|||
|
|
|||
|
mode = GetOpenMode(interp, modeString);
|
|||
|
if (mode == -1) {
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
nativeName = Tcl_TranslateFileName(interp, fileName, &buffer);
|
|||
|
if (nativeName == NULL) {
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
chan = OpenFileChannel(nativeName, mode, permissions, &errorCode);
|
|||
|
Tcl_DStringFree(&buffer);
|
|||
|
|
|||
|
if (chan == NULL) {
|
|||
|
Tcl_SetErrno(errorCode);
|
|||
|
if (interp != (Tcl_Interp *) NULL) {
|
|||
|
Tcl_AppendResult(interp, "couldn't open \"", fileName, "\": ",
|
|||
|
Tcl_PosixError(interp), (char *) NULL);
|
|||
|
}
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
return chan;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* OpenFileChannel--
|
|||
|
*
|
|||
|
* Opens a Macintosh file and creates a Tcl channel to control it.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* A Tcl channel.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Will open a Macintosh file.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static Tcl_Channel
|
|||
|
OpenFileChannel(fileName, mode, permissions, errorCodePtr)
|
|||
|
char *fileName; /* Name of file to open. */
|
|||
|
int mode; /* Mode for opening file. */
|
|||
|
int permissions; /* If the open involves creating a
|
|||
|
* file, with what modes to create
|
|||
|
* it? */
|
|||
|
int *errorCodePtr; /* Where to store error code. */
|
|||
|
{
|
|||
|
int channelPermissions;
|
|||
|
Tcl_Channel chan;
|
|||
|
char macPermision;
|
|||
|
FSSpec fileSpec;
|
|||
|
OSErr err;
|
|||
|
short fileRef;
|
|||
|
FileState *fileState;
|
|||
|
char channelName[64];
|
|||
|
|
|||
|
/*
|
|||
|
* Note we use fsRdWrShPerm instead of fsRdWrPerm which allows shared
|
|||
|
* writes on a file. This isn't common on a mac but is common with
|
|||
|
* Windows and UNIX and the feature is used by Tcl.
|
|||
|
*/
|
|||
|
|
|||
|
switch (mode & (TCL_RDONLY | TCL_WRONLY | TCL_RDWR)) {
|
|||
|
case TCL_RDWR:
|
|||
|
channelPermissions = (TCL_READABLE | TCL_WRITABLE);
|
|||
|
macPermision = fsRdWrShPerm;
|
|||
|
break;
|
|||
|
case TCL_WRONLY:
|
|||
|
/*
|
|||
|
* Mac's fsRdPerm permission actually defaults to fsRdWrPerm because
|
|||
|
* the Mac OS doesn't realy support write only access. We explicitly
|
|||
|
* set the permission fsRdWrShPerm so that we can have shared write
|
|||
|
* access.
|
|||
|
*/
|
|||
|
channelPermissions = TCL_WRITABLE;
|
|||
|
macPermision = fsRdWrShPerm;
|
|||
|
break;
|
|||
|
case TCL_RDONLY:
|
|||
|
default:
|
|||
|
channelPermissions = TCL_READABLE;
|
|||
|
macPermision = fsRdPerm;
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
err = FSpLocationFromPath(strlen(fileName), fileName, &fileSpec);
|
|||
|
if ((err != noErr) && (err != fnfErr)) {
|
|||
|
*errorCodePtr = errno = TclMacOSErrorToPosixError(err);
|
|||
|
Tcl_SetErrno(errno);
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
if ((err == fnfErr) && (mode & TCL_CREAT)) {
|
|||
|
err = HCreate(fileSpec.vRefNum, fileSpec.parID, fileSpec.name, 'MPW ', 'TEXT');
|
|||
|
if (err != noErr) {
|
|||
|
*errorCodePtr = errno = TclMacOSErrorToPosixError(err);
|
|||
|
Tcl_SetErrno(errno);
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
} else if ((mode & TCL_CREAT) && (mode & TCL_EXCL)) {
|
|||
|
*errorCodePtr = errno = EEXIST;
|
|||
|
Tcl_SetErrno(errno);
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
err = HOpenDF(fileSpec.vRefNum, fileSpec.parID, fileSpec.name, macPermision, &fileRef);
|
|||
|
if (err != noErr) {
|
|||
|
*errorCodePtr = errno = TclMacOSErrorToPosixError(err);
|
|||
|
Tcl_SetErrno(errno);
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
if (mode & TCL_TRUNC) {
|
|||
|
SetEOF(fileRef, 0);
|
|||
|
}
|
|||
|
|
|||
|
sprintf(channelName, "file%d", (int) fileRef);
|
|||
|
fileState = (FileState *) ckalloc((unsigned) sizeof(FileState));
|
|||
|
chan = Tcl_CreateChannel(&fileChannelType, channelName,
|
|||
|
(ClientData) fileState, channelPermissions);
|
|||
|
if (chan == (Tcl_Channel) NULL) {
|
|||
|
*errorCodePtr = errno = EFAULT;
|
|||
|
Tcl_SetErrno(errno);
|
|||
|
FSClose(fileRef);
|
|||
|
ckfree((char *) fileState);
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
fileState->fileChan = chan;
|
|||
|
fileState->volumeRef = fileSpec.vRefNum;
|
|||
|
fileState->fileRef = fileRef;
|
|||
|
if (mode & TCL_ALWAYS_APPEND) {
|
|||
|
fileState->appendMode = true;
|
|||
|
} else {
|
|||
|
fileState->appendMode = false;
|
|||
|
}
|
|||
|
|
|||
|
if ((mode & TCL_ALWAYS_APPEND) || (mode & TCL_APPEND)) {
|
|||
|
if (Tcl_Seek(chan, 0, SEEK_END) < 0) {
|
|||
|
*errorCodePtr = errno = EFAULT;
|
|||
|
Tcl_SetErrno(errno);
|
|||
|
Tcl_Close(NULL, chan);
|
|||
|
FSClose(fileRef);
|
|||
|
ckfree((char *) fileState);
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return chan;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* FileBlockMode --
|
|||
|
*
|
|||
|
* Set blocking or non-blocking mode on channel. Macintosh files
|
|||
|
* can never really be set to blocking or non-blocking modes.
|
|||
|
* However, we don't generate an error - we just return success.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* 0 if successful, errno when failed.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Sets the device into blocking or non-blocking mode.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
FileBlockMode(instanceData, mode)
|
|||
|
ClientData instanceData; /* Unused. */
|
|||
|
int mode; /* The mode to set. */
|
|||
|
{
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* FileClose --
|
|||
|
*
|
|||
|
* Closes the IO channel.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* 0 if successful, the value of errno if failed.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Closes the physical channel
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
FileClose(instanceData, interp)
|
|||
|
ClientData instanceData; /* Unused. */
|
|||
|
Tcl_Interp *interp; /* Unused. */
|
|||
|
{
|
|||
|
FileState *fileState = (FileState *) instanceData;
|
|||
|
int errorCode = 0;
|
|||
|
OSErr err;
|
|||
|
|
|||
|
err = FSClose(fileState->fileRef);
|
|||
|
FlushVol(NULL, fileState->volumeRef);
|
|||
|
if (err != noErr) {
|
|||
|
errorCode = errno = TclMacOSErrorToPosixError(err);
|
|||
|
panic("error during file close");
|
|||
|
}
|
|||
|
|
|||
|
ckfree((char *) fileState);
|
|||
|
Tcl_SetErrno(errorCode);
|
|||
|
return errorCode;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* FileGet --
|
|||
|
*
|
|||
|
* Called from Tcl_GetChannelFile to retrieve Tcl_Files from inside
|
|||
|
* a file based channel.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* The appropriate Tcl_File or NULL if not present.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* None.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static Tcl_File
|
|||
|
FileGet(instanceData, direction)
|
|||
|
ClientData instanceData; /* The file state. */
|
|||
|
int direction; /* Which Tcl_File to retrieve? */
|
|||
|
{
|
|||
|
FileState *fileState = (FileState *) instanceData;
|
|||
|
|
|||
|
if ((direction == TCL_READABLE) || (direction == TCL_WRITABLE)) {
|
|||
|
return (Tcl_File) fileState->fileRef;
|
|||
|
}
|
|||
|
return (Tcl_File) NULL;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* FileInput --
|
|||
|
*
|
|||
|
* Reads input from the IO channel into the buffer given. Returns
|
|||
|
* count of how many bytes were actually read, and an error indication.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* A count of how many bytes were read is returned and an error
|
|||
|
* indication is returned in an output argument.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Reads input from the actual channel.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
FileInput(instanceData, buffer, bufSize, errorCodePtr)
|
|||
|
ClientData instanceData; /* Unused. */
|
|||
|
char *buffer; /* Where to store data read. */
|
|||
|
int bufSize; /* How much space is available
|
|||
|
* in the buffer? */
|
|||
|
int *errorCodePtr; /* Where to store error code. */
|
|||
|
{
|
|||
|
FileState *fileState = (FileState *) instanceData;
|
|||
|
OSErr err;
|
|||
|
long length = bufSize;
|
|||
|
|
|||
|
*errorCodePtr = 0;
|
|||
|
errno = 0;
|
|||
|
err = FSRead(fileState->fileRef, &length, buffer);
|
|||
|
if ((err == noErr) || (err == eofErr)) {
|
|||
|
return length;
|
|||
|
} else {
|
|||
|
switch (err) {
|
|||
|
case ioErr:
|
|||
|
*errorCodePtr = errno = EIO;
|
|||
|
case afpAccessDenied:
|
|||
|
*errorCodePtr = errno = EACCES;
|
|||
|
default:
|
|||
|
*errorCodePtr = errno = EINVAL;
|
|||
|
}
|
|||
|
return -1;
|
|||
|
}
|
|||
|
*errorCodePtr = errno;
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* FileOutput--
|
|||
|
*
|
|||
|
* Writes the given output on the IO channel. Returns count of how
|
|||
|
* many characters were actually written, and an error indication.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* A count of how many characters were written is returned and an
|
|||
|
* error indication is returned in an output argument.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Writes output on the actual channel.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
FileOutput(instanceData, buffer, toWrite, errorCodePtr)
|
|||
|
ClientData instanceData; /* Unused. */
|
|||
|
char *buffer; /* The data buffer. */
|
|||
|
int toWrite; /* How many bytes to write? */
|
|||
|
int *errorCodePtr; /* Where to store error code. */
|
|||
|
{
|
|||
|
FileState *fileState = (FileState *) instanceData;
|
|||
|
long length = toWrite;
|
|||
|
OSErr err;
|
|||
|
|
|||
|
*errorCodePtr = 0;
|
|||
|
errno = 0;
|
|||
|
|
|||
|
if (fileState->appendMode == true) {
|
|||
|
FileSeek(instanceData, 0, SEEK_END, errorCodePtr);
|
|||
|
*errorCodePtr = 0;
|
|||
|
}
|
|||
|
|
|||
|
err = FSWrite(fileState->fileRef, &length, buffer);
|
|||
|
if (err == noErr) {
|
|||
|
err = FlushFile(fileState->fileRef);
|
|||
|
} else {
|
|||
|
*errorCodePtr = errno = TclMacOSErrorToPosixError(err);
|
|||
|
return -1;
|
|||
|
}
|
|||
|
return length;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* FileReady --
|
|||
|
*
|
|||
|
* Called by the notifier to check whether events of interest are
|
|||
|
* present on the channel. On the Macintosh all files are always
|
|||
|
* considered to be readable and writeable.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Returns OR-ed combination of TCL_READABLE, TCL_WRITABLE and
|
|||
|
* TCL_EXCEPTION to indicate which events of interest are present.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* None.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
FileReady(instanceData, mask)
|
|||
|
ClientData instanceData; /* The file state. */
|
|||
|
int mask; /* Events of interest; an OR-ed
|
|||
|
* combination of TCL_READABLE,
|
|||
|
* TCL_WRITABLE and TCL_EXCEPTION. */
|
|||
|
{
|
|||
|
return (TCL_READABLE | TCL_WRITABLE);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* FileSeek --
|
|||
|
*
|
|||
|
* Seeks on an IO channel. Returns the new position.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* -1 if failed, the new position if successful. If failed, it
|
|||
|
* also sets *errorCodePtr to the error code.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Moves the location at which the channel will be accessed in
|
|||
|
* future operations.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
FileSeek(instanceData, offset, mode, errorCodePtr)
|
|||
|
ClientData instanceData; /* Unused. */
|
|||
|
long offset; /* Offset to seek to. */
|
|||
|
int mode; /* Relative to where
|
|||
|
* should we seek? */
|
|||
|
int *errorCodePtr; /* To store error code. */
|
|||
|
{
|
|||
|
FileState *fileState = (FileState *) instanceData;
|
|||
|
IOParam pb;
|
|||
|
OSErr err;
|
|||
|
|
|||
|
*errorCodePtr = 0;
|
|||
|
pb.ioCompletion = NULL;
|
|||
|
pb.ioRefNum = fileState->fileRef;
|
|||
|
if (mode == SEEK_SET) {
|
|||
|
pb.ioPosMode = fsFromStart;
|
|||
|
} else if (mode == SEEK_END) {
|
|||
|
pb.ioPosMode = fsFromLEOF;
|
|||
|
} else if (mode == SEEK_CUR) {
|
|||
|
err = PBGetFPosSync((ParmBlkPtr) &pb);
|
|||
|
if (pb.ioResult == noErr) {
|
|||
|
if (offset == 0) {
|
|||
|
return pb.ioPosOffset;
|
|||
|
}
|
|||
|
offset += pb.ioPosOffset;
|
|||
|
}
|
|||
|
pb.ioPosMode = fsFromStart;
|
|||
|
}
|
|||
|
pb.ioPosOffset = offset;
|
|||
|
err = PBSetFPosSync((ParmBlkPtr) &pb);
|
|||
|
if (pb.ioResult == noErr){
|
|||
|
return pb.ioPosOffset;
|
|||
|
} else if (pb.ioResult == eofErr) {
|
|||
|
long currentEOF, newEOF;
|
|||
|
long buffer, i, length;
|
|||
|
|
|||
|
err = PBGetEOFSync((ParmBlkPtr) &pb);
|
|||
|
currentEOF = (long) pb.ioMisc;
|
|||
|
if (mode == SEEK_SET) {
|
|||
|
newEOF = offset;
|
|||
|
} else if (mode == SEEK_END) {
|
|||
|
newEOF = offset + currentEOF;
|
|||
|
} else if (mode == SEEK_CUR) {
|
|||
|
err = PBGetFPosSync((ParmBlkPtr) &pb);
|
|||
|
newEOF = offset + pb.ioPosOffset;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Write 0's to the new EOF.
|
|||
|
*/
|
|||
|
pb.ioPosOffset = 0;
|
|||
|
pb.ioPosMode = fsFromLEOF;
|
|||
|
err = PBGetFPosSync((ParmBlkPtr) &pb);
|
|||
|
length = 1;
|
|||
|
buffer = 0;
|
|||
|
for (i = 0; i < (newEOF - currentEOF); i++) {
|
|||
|
err = FSWrite(fileState->fileRef, &length, &buffer);
|
|||
|
}
|
|||
|
err = PBGetFPosSync((ParmBlkPtr) &pb);
|
|||
|
if (pb.ioResult == noErr){
|
|||
|
return pb.ioPosOffset;
|
|||
|
}
|
|||
|
}
|
|||
|
*errorCodePtr = errno = TclMacOSErrorToPosixError(err);
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* FileWatch --
|
|||
|
*
|
|||
|
* Initialize the notifier to watch Tcl_Files from this channel.
|
|||
|
* This doesn't do anything on the Macintosh.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* None.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* None.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
FileWatch(instanceData, mask)
|
|||
|
ClientData instanceData; /* The file state. */
|
|||
|
int mask; /* Events of interest; an OR-ed
|
|||
|
* combination of TCL_READABLE,
|
|||
|
* TCL_WRITABLE and TCL_EXCEPTION. */
|
|||
|
{
|
|||
|
Tcl_Time timeout = { 0, 0 };
|
|||
|
|
|||
|
/*
|
|||
|
* Currently, files are always ready under the Macintosh,
|
|||
|
* so we just set a 0 timeout. Since there s no notification
|
|||
|
* scheme - we just set the timeout time to zero.
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_SetMaxBlockTime(&timeout);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclMacOSErrorToPosixError --
|
|||
|
*
|
|||
|
* Given a Macintosh OSErr return the appropiate POSIX error.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* A Posix error.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* None.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
TclMacOSErrorToPosixError(error)
|
|||
|
int error; /* A Macintosh error. */
|
|||
|
{
|
|||
|
switch (error) {
|
|||
|
case noErr:
|
|||
|
return 0;
|
|||
|
case bdNamErr:
|
|||
|
return ENAMETOOLONG;
|
|||
|
case afpObjectTypeErr:
|
|||
|
return ENOTDIR;
|
|||
|
case fnfErr:
|
|||
|
case dirNFErr:
|
|||
|
return ENOENT;
|
|||
|
case dupFNErr:
|
|||
|
return EEXIST;
|
|||
|
case dirFulErr:
|
|||
|
case dskFulErr:
|
|||
|
return ENOSPC;
|
|||
|
case fBsyErr:
|
|||
|
return EBUSY;
|
|||
|
case tmfoErr:
|
|||
|
return ENFILE;
|
|||
|
case fLckdErr:
|
|||
|
case permErr:
|
|||
|
case afpAccessDenied:
|
|||
|
return EACCES;
|
|||
|
case wPrErr:
|
|||
|
case vLckdErr:
|
|||
|
return EROFS;
|
|||
|
case badMovErr:
|
|||
|
return EINVAL;
|
|||
|
case diffVolErr:
|
|||
|
return EXDEV;
|
|||
|
default:
|
|||
|
return EINVAL;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* GetOpenMode --
|
|||
|
*
|
|||
|
* Description:
|
|||
|
* Computes a POSIX mode mask from a given string and also sets
|
|||
|
* a flag to indicate whether the caller should seek to EOF during
|
|||
|
* opening of the file.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* On success, returns mode to pass to "open". If an error occurs, the
|
|||
|
* returns -1 and if interp is not NULL, sets interp->result to an
|
|||
|
* error message.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Sets the integer referenced by seekFlagPtr to 1 if the caller
|
|||
|
* should seek to EOF during opening the file.
|
|||
|
*
|
|||
|
* Special note:
|
|||
|
* This code is based on a prototype implementation contributed
|
|||
|
* by Mark Diekhans.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
GetOpenMode(interp, string)
|
|||
|
Tcl_Interp *interp; /* Interpreter to use for error
|
|||
|
* reporting - may be NULL. */
|
|||
|
char *string; /* Mode string, e.g. "r+" or
|
|||
|
* "RDONLY CREAT". */
|
|||
|
{
|
|||
|
int mode, modeArgc, c, i, gotRW;
|
|||
|
char **modeArgv, *flag;
|
|||
|
|
|||
|
/*
|
|||
|
* Check for the simpler fopen-like access modes (e.g. "r"). They
|
|||
|
* are distinguished from the POSIX access modes by the presence
|
|||
|
* of a lower-case first letter.
|
|||
|
*/
|
|||
|
|
|||
|
mode = 0;
|
|||
|
if (islower(UCHAR(string[0]))) {
|
|||
|
switch (string[0]) {
|
|||
|
case 'r':
|
|||
|
mode = TCL_RDONLY;
|
|||
|
break;
|
|||
|
case 'w':
|
|||
|
mode = TCL_WRONLY|TCL_CREAT|TCL_TRUNC;
|
|||
|
break;
|
|||
|
case 'a':
|
|||
|
mode = TCL_WRONLY|TCL_CREAT|TCL_APPEND;
|
|||
|
break;
|
|||
|
default:
|
|||
|
error:
|
|||
|
if (interp != (Tcl_Interp *) NULL) {
|
|||
|
Tcl_AppendResult(interp,
|
|||
|
"illegal access mode \"", string, "\"",
|
|||
|
(char *) NULL);
|
|||
|
}
|
|||
|
return -1;
|
|||
|
}
|
|||
|
if (string[1] == '+') {
|
|||
|
mode &= ~(TCL_RDONLY|TCL_WRONLY);
|
|||
|
mode |= TCL_RDWR;
|
|||
|
if (string[2] != 0) {
|
|||
|
goto error;
|
|||
|
}
|
|||
|
} else if (string[1] != 0) {
|
|||
|
goto error;
|
|||
|
}
|
|||
|
return mode;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* The access modes are specified using a list of POSIX modes
|
|||
|
* such as TCL_CREAT.
|
|||
|
*/
|
|||
|
|
|||
|
if (Tcl_SplitList(interp, string, &modeArgc, &modeArgv) != TCL_OK) {
|
|||
|
if (interp != (Tcl_Interp *) NULL) {
|
|||
|
Tcl_AddErrorInfo(interp,
|
|||
|
"\n while processing open access modes \"");
|
|||
|
Tcl_AddErrorInfo(interp, string);
|
|||
|
Tcl_AddErrorInfo(interp, "\"");
|
|||
|
}
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
gotRW = 0;
|
|||
|
for (i = 0; i < modeArgc; i++) {
|
|||
|
flag = modeArgv[i];
|
|||
|
c = flag[0];
|
|||
|
if ((c == 'R') && (strcmp(flag, "RDONLY") == 0)) {
|
|||
|
mode = (mode & ~TCL_RW_MODES) | TCL_RDONLY;
|
|||
|
gotRW = 1;
|
|||
|
} else if ((c == 'W') && (strcmp(flag, "WRONLY") == 0)) {
|
|||
|
mode = (mode & ~TCL_RW_MODES) | TCL_WRONLY;
|
|||
|
gotRW = 1;
|
|||
|
} else if ((c == 'R') && (strcmp(flag, "RDWR") == 0)) {
|
|||
|
mode = (mode & ~TCL_RW_MODES) | TCL_RDWR;
|
|||
|
gotRW = 1;
|
|||
|
} else if ((c == 'A') && (strcmp(flag, "APPEND") == 0)) {
|
|||
|
mode |= TCL_ALWAYS_APPEND;
|
|||
|
} else if ((c == 'C') && (strcmp(flag, "CREAT") == 0)) {
|
|||
|
mode |= TCL_CREAT;
|
|||
|
} else if ((c == 'E') && (strcmp(flag, "EXCL") == 0)) {
|
|||
|
mode |= TCL_EXCL;
|
|||
|
} else if ((c == 'N') && (strcmp(flag, "NOCTTY") == 0)) {
|
|||
|
mode |= TCL_NOCTTY;
|
|||
|
} else if ((c == 'N') && (strcmp(flag, "NONBLOCK") == 0)) {
|
|||
|
mode |= TCL_NONBLOCK;
|
|||
|
} else if ((c == 'T') && (strcmp(flag, "TRUNC") == 0)) {
|
|||
|
mode |= TCL_TRUNC;
|
|||
|
} else {
|
|||
|
if (interp != (Tcl_Interp *) NULL) {
|
|||
|
Tcl_AppendResult(interp, "invalid access mode \"", flag,
|
|||
|
"\": must be RDONLY, WRONLY, RDWR, APPEND, CREAT",
|
|||
|
" EXCL, NOCTTY, NONBLOCK, or TRUNC", (char *) NULL);
|
|||
|
}
|
|||
|
ckfree((char *) modeArgv);
|
|||
|
return -1;
|
|||
|
}
|
|||
|
}
|
|||
|
ckfree((char *) modeArgv);
|
|||
|
if (!gotRW) {
|
|||
|
if (interp != (Tcl_Interp *) NULL) {
|
|||
|
Tcl_AppendResult(interp, "access mode must include either",
|
|||
|
" RDONLY, WRONLY, or RDWR", (char *) NULL);
|
|||
|
}
|
|||
|
return -1;
|
|||
|
}
|
|||
|
return mode;
|
|||
|
}
|