#include "tclInt.h"
#include "tclPort.h"
#include "tclMacInt.h"
#include <Aliases.h>
#include <Errors.h>
#include <Files.h>
#include <Gestalt.h>
#include <Processes.h>
#include <Strings.h>
#include <FSpCompat.h>
#include <MoreFiles.h>
#include <MoreFilesExtras.h>
static int initialized = 0;
#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)
typedef struct FileState {
short fileRef;
Tcl_Channel fileChan;
int watchMask;
int appendMode;
int volumeRef;
int pending;
struct FileState *nextPtr;
} FileState;
static FileState *firstFilePtr;
typedef struct FileEvent {
Tcl_Event header;
FileState *infoPtr;
} FileEvent;
static int CommonGetHandle _ANSI_ARGS_((ClientData instanceData,
int direction, ClientData *handlePtr));
static void CommonWatch _ANSI_ARGS_((ClientData instanceData,
int mask));
static int FileBlockMode _ANSI_ARGS_((ClientData instanceData,
int mode));
static void FileChannelExitHandler _ANSI_ARGS_((
ClientData clientData));
static void FileCheckProc _ANSI_ARGS_((ClientData clientData,
int flags));
static int FileClose _ANSI_ARGS_((ClientData instanceData,
Tcl_Interp *interp));
static int FileEventProc _ANSI_ARGS_((Tcl_Event *evPtr,
int flags));
static void FileInit _ANSI_ARGS_((void));
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 FileSeek _ANSI_ARGS_((ClientData instanceData,
long offset, int mode, int *errorCode));
static void FileSetupProc _ANSI_ARGS_((ClientData clientData,
int flags));
static int GetOpenMode _ANSI_ARGS_((Tcl_Interp *interp,
char *string));
static Tcl_Channel OpenFileChannel _ANSI_ARGS_((char *fileName, int mode,
int permissions, int *errorCodePtr));
static int StdIOBlockMode _ANSI_ARGS_((ClientData instanceData,
int mode));
static int StdIOClose _ANSI_ARGS_((ClientData instanceData,
Tcl_Interp *interp));
static int StdIOInput _ANSI_ARGS_((ClientData instanceData,
char *buf, int toRead, int *errorCode));
static int StdIOOutput _ANSI_ARGS_((ClientData instanceData,
char *buf, int toWrite, int *errorCode));
static int StdIOSeek _ANSI_ARGS_((ClientData instanceData,
long offset, int mode, int *errorCode));
static int StdReady _ANSI_ARGS_((ClientData instanceData,
int mask));
static Tcl_ChannelType consoleChannelType = {
"file",
StdIOBlockMode,
StdIOClose,
StdIOInput,
StdIOOutput,
StdIOSeek,
NULL,
NULL,
CommonWatch,
CommonGetHandle
};
static Tcl_ChannelType fileChannelType = {
"file",
FileBlockMode,
FileClose,
FileInput,
FileOutput,
FileSeek,
NULL,
NULL,
CommonWatch,
CommonGetHandle
};
typedef void (*TclGetStdChannelsProc) _ANSI_ARGS_((Tcl_Channel *stdinPtr,
Tcl_Channel *stdoutPtr, Tcl_Channel *stderrPtr));
TclGetStdChannelsProc getStdChannelsProc = NULL;
static Tcl_Channel stdinChannel = NULL;
static Tcl_Channel stdoutChannel = NULL;
static Tcl_Channel stderrChannel = NULL;
static void
FileInit()
{
initialized = 1;
firstFilePtr = NULL;
Tcl_CreateEventSource(FileSetupProc, FileCheckProc, NULL);
Tcl_CreateExitHandler(FileChannelExitHandler, NULL);
}
static void
FileChannelExitHandler(
ClientData clientData)
{
Tcl_DeleteEventSource(FileSetupProc, FileCheckProc, NULL);
initialized = 0;
}
void
FileSetupProc(
ClientData data,
int flags)
{
FileState *infoPtr;
Tcl_Time blockTime = { 0, 0 };
if (!(flags & TCL_FILE_EVENTS)) {
return;
}
for (infoPtr = firstFilePtr; infoPtr != NULL; infoPtr = infoPtr->nextPtr) {
if (infoPtr->watchMask) {
Tcl_SetMaxBlockTime(&blockTime);
break;
}
}
}
static void
FileCheckProc(
ClientData data,
int flags)
{
FileEvent *evPtr;
FileState *infoPtr;
int sentMsg = 0;
Tcl_Time blockTime = { 0, 0 };
if (!(flags & TCL_FILE_EVENTS)) {
return;
}
for (infoPtr = firstFilePtr; infoPtr != NULL; infoPtr = infoPtr->nextPtr) {
if (infoPtr->watchMask && !infoPtr->pending) {
infoPtr->pending = 1;
evPtr = (FileEvent *) ckalloc(sizeof(FileEvent));
evPtr->header.proc = FileEventProc;
evPtr->infoPtr = infoPtr;
Tcl_QueueEvent((Tcl_Event *) evPtr, TCL_QUEUE_TAIL);
}
}
}
static int
FileEventProc(
Tcl_Event *evPtr,
int flags)
{
FileEvent *fileEvPtr = (FileEvent *)evPtr;
FileState *infoPtr;
if (!(flags & TCL_FILE_EVENTS)) {
return 0;
}
for (infoPtr = firstFilePtr; infoPtr != NULL; infoPtr = infoPtr->nextPtr) {
if (fileEvPtr->infoPtr == infoPtr) {
infoPtr->pending = 0;
Tcl_NotifyChannel(infoPtr->fileChan, infoPtr->watchMask);
break;
}
}
return 1;
}
static int
StdIOBlockMode(
ClientData instanceData,
int mode)
{
if (mode == TCL_MODE_NONBLOCKING) {
return EFAULT;
}
return 0;
}
static int
StdIOClose(
ClientData instanceData,
Tcl_Interp *interp)
{
int fd, errorCode = 0;
fd = (int) ((FileState*)instanceData)->fileRef;
if (fd == 0) {
fd = 0;
stdinChannel = NULL;
} else if (fd == 1) {
stdoutChannel = NULL;
} else if (fd == 2) {
stderrChannel = NULL;
} else {
panic("recieved invalid std file");
}
if (close(fd) < 0) {
errorCode = errno;
}
return errorCode;
}
static int
CommonGetHandle(
ClientData instanceData,
int direction,
ClientData *handlePtr)
{
if ((direction == TCL_READABLE) || (direction == TCL_WRITABLE)) {
*handlePtr = (ClientData) ((FileState*)instanceData)->fileRef;
return TCL_OK;
}
return TCL_ERROR;
}
int
StdIOInput(
ClientData instanceData,
char *buf,
int bufSize,
int *errorCode)
{
int fd;
int bytesRead;
*errorCode = 0;
errno = 0;
fd = (int) ((FileState*)instanceData)->fileRef;
bytesRead = read(fd, buf, (size_t) bufSize);
if (bytesRead > -1) {
return bytesRead;
}
*errorCode = errno;
return -1;
}
static int
StdIOOutput(
ClientData instanceData,
char *buf,
int toWrite,
int *errorCode)
{
int written;
int fd;
*errorCode = 0;
errno = 0;
fd = (int) ((FileState*)instanceData)->fileRef;
written = write(fd, buf, (size_t) toWrite);
if (written > -1) {
return written;
}
*errorCode = errno;
return -1;
}
static int
StdIOSeek(
ClientData instanceData,
long offset,
int mode,
int *errorCodePtr)
{
int newLoc;
int fd;
*errorCodePtr = 0;
fd = (int) ((FileState*)instanceData)->fileRef;
newLoc = lseek(fd, offset, mode);
if (newLoc > -1) {
return newLoc;
}
*errorCodePtr = errno;
return -1;
}
int
Tcl_PidObjCmd(dummy, interp, objc, objv)
ClientData dummy;
Tcl_Interp *interp;
int objc;
Tcl_Obj *CONST *objv;
{
ProcessSerialNumber psn;
char buf[20];
Tcl_Channel chan;
Tcl_Obj *resultPtr;
if (objc > 2) {
Tcl_WrongNumArgs(interp, 1, objv, "?channelId?");
return TCL_ERROR;
}
if (objc == 1) {
resultPtr = Tcl_GetObjResult(interp);
GetCurrentProcess(&psn);
sprintf(buf, "0x%08x%08x", psn.highLongOfPSN, psn.lowLongOfPSN);
Tcl_SetStringObj(resultPtr, buf, -1);
} else {
chan = Tcl_GetChannel(interp, Tcl_GetStringFromObj(objv[1], NULL),
NULL);
if (chan == (Tcl_Channel) NULL) {
return TCL_ERROR;
}
}
return TCL_OK;
}
Tcl_Channel
TclGetDefaultStdChannel(
int type)
{
Tcl_Channel channel = NULL;
int fd = 0;
int mode = 0;
char *bufMode = NULL;
char channelName[20];
int channelPermissions;
FileState *fileState;
switch (type) {
case TCL_STDIN:
fd = 0;
channelPermissions = TCL_READABLE;
bufMode = "line";
break;
case TCL_STDOUT:
fd = 1;
channelPermissions = TCL_WRITABLE;
bufMode = "line";
break;
case TCL_STDERR:
fd = 2;
channelPermissions = TCL_WRITABLE;
bufMode = "none";
break;
default:
panic("TclGetDefaultStdChannel: Unexpected channel type");
break;
}
sprintf(channelName, "console%d", (int) fd);
fileState = (FileState *) ckalloc((unsigned) sizeof(FileState));
channel = Tcl_CreateChannel(&consoleChannelType, channelName,
(ClientData) fileState, channelPermissions);
fileState->fileChan = channel;
fileState->fileRef = fd;
Tcl_SetChannelOption(NULL, channel, "-translation", "cr");
Tcl_SetChannelOption(NULL, channel, "-buffering", bufMode);
return channel;
}
Tcl_Channel
TclpOpenFileChannel(
Tcl_Interp *interp,
char *fileName,
char *modeString,
int permissions)
{
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;
}
static Tcl_Channel
OpenFileChannel(
char *fileName,
int mode,
int permissions,
int *errorCodePtr)
{
int channelPermissions;
Tcl_Channel chan;
char macPermision;
FSSpec fileSpec;
OSErr err;
short fileRef;
FileState *fileState;
char channelName[64];
switch (mode & (TCL_RDONLY | TCL_WRONLY | TCL_RDWR)) {
case TCL_RDWR:
channelPermissions = (TCL_READABLE | TCL_WRITABLE);
macPermision = fsRdWrShPerm;
break;
case TCL_WRONLY:
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;
fileState->pending = 0;
fileState->watchMask = 0;
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;
}
static int
FileBlockMode(
ClientData instanceData,
int mode)
{
return 0;
}
static int
FileClose(
ClientData instanceData,
Tcl_Interp *interp)
{
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;
}
int
FileInput(
ClientData instanceData,
char *buffer,
int bufSize,
int *errorCodePtr)
{
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;
}
static int
FileOutput(
ClientData instanceData,
char *buffer,
int toWrite,
int *errorCodePtr)
{
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;
}
static int
FileSeek(
ClientData instanceData,
long offset,
int mode,
int *errorCodePtr)
{
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;
}
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;
}
static void
CommonWatch(
ClientData instanceData,
int mask)
{
FileState **nextPtrPtr, *ptr;
FileState *infoPtr = (FileState *) instanceData;
int oldMask = infoPtr->watchMask;
if (!initialized) {
FileInit();
}
infoPtr->watchMask = mask;
if (infoPtr->watchMask) {
if (!oldMask) {
infoPtr->nextPtr = firstFilePtr;
firstFilePtr = infoPtr;
}
} else {
if (oldMask) {
for (nextPtrPtr = &firstFilePtr, ptr = *nextPtrPtr;
ptr != NULL;
nextPtrPtr = &ptr->nextPtr, ptr = *nextPtrPtr) {
if (infoPtr == ptr) {
*nextPtrPtr = ptr->nextPtr;
break;
}
}
}
}
}
static int
GetOpenMode(
Tcl_Interp *interp,
char *string)
{
int mode, modeArgc, c, i, gotRW;
char **modeArgv, *flag;
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;
}
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;
}