/**************************************************************************** * PROJECT: File Interface * FILE: sqFilePluginBasicPrims.c * CONTENT: * * AUTHOR: * ADDRESS: * EMAIL: ] * RCSID: $Id: sqFilePluginBasicPrims.c 2953 2014-06-06 23:36:45Z lewis $ * * NOTES: See change log below. * 2005-03-26 IKP fix unaligned accesses to file[Size] members * 2004-06-10 IKP 64-bit cleanliness * 1/28/02 Tim remove non-ansi stuff * unistd.h ftello fseeko ftruncate & fileno macro-ise use of sqFTruncate to avoid non-ansi * 1/22/2002 JMM Use squeakFileOffsetType versus off_t * *****************************************************************************/ /* The basic prim code for file operations. See also the platform specific * files typically named 'sq{blah}Directory.c' for details of the directory * handling code. Note that the win32 platform #defines NO_STD_FILE_SUPPORT * and thus bypasses this file */ #include #include "sq.h" #ifndef NO_STD_FILE_SUPPORT #include "FilePlugin.h" /*** The state of a file is kept in the following structure, which is stored directly in a Squeak bytes object. NOTE: The Squeak side is responsible for creating an object with enough room to store sizeof(SQFile) bytes. The session ID is used to detect stale file objects-- files that were still open when an image was written. The file pointer of such files is meaningless. Files are always opened in binary mode; Smalltalk code does (or someday will do) line-end conversion if needed. Writeable files are opened read/write. The stdio spec requires that a positioning operation be done when switching between reading and writing of a read/write filestream. The lastOp field records whether the last operation was a read or write operation, allowing this positioning operation to be done automatically if needed. typedef struct { int sessionID; File *file; squeakFileOffsetType fileSize; //JMM Nov 8th 2001 64bits we hope char writable; char lastOp; // 0 = uncommitted, 1 = read, 2 = write // char lastChar; // one character peek for stdin // char isStdioStream; } SQFile; ***/ /*** Constants ***/ #define UNCOMMITTED 0 #define READ_OP 1 #define WRITE_OP 2 #ifndef SEEK_SET #define SEEK_SET 0 #define SEEK_CUR 1 #define SEEK_END 2 #endif /*** Variables ***/ int thisSession = 0; extern struct VirtualMachine * interpreterProxy; /* Since SQFile instaces are held on the heap in 32-bit-aligned byte arrays we * may need to use memcpy to avoid alignment faults. */ #if DOUBLE_WORD_ALIGNMENT static void setFile(SQFile *f, FILE *file) { void *in= (void *)&file; void *out= (void *)&f->file; memcpy(out, in, sizeof(FILE *)); } #else # define setFile(f,fileptr) ((f)->file = (fileptr)) #endif #if DOUBLE_WORD_ALIGNMENT static void setSize(SQFile *f, squeakFileOffsetType size) { void *in= (void *)&size; void *out= (void *)&f->fileSize; memcpy(out, in, sizeof(squeakFileOffsetType)); } #else # define setSize(f,size) ((f)->fileSize = (size)) #endif #if DOUBLE_WORD_ALIGNMENT static FILE *getFile(SQFile *f) { FILE *file; void *in= (void *)&f->file; void *out= (void *)&file; memcpy(out, in, sizeof(FILE *)); return file; } #else # define getFile(f) ((FILE *)((f)->file)) #endif #if DOUBLE_WORD_ALIGNMENT static squeakFileOffsetType getSize(SQFile *f) { squeakFileOffsetType size; void *in= (void *)&f->fileSize; void *out= (void *)&size; memcpy(out, in, sizeof(squeakFileOffsetType)); return size; } #else # define getSize(f) ((f)->fileSize) #endif #if 0 # define pentry(func) do { int fn = fileno(getFile(f)); if (f->isStdioStream) printf("\n"#func "(%s) %lld %d\n", fn == 0 ? "in" : fn == 1 ? "out" : "err", (long long)ftell(getFile(f)), f->lastChar); } while (0) # define pexit(expr) (f->isStdioStream && printf("\n\t^"#expr " %lld %d\n", (long long)(sqFileValid(f) ? ftell(getFile(f)) : -1), f->lastChar)), expr # define pfail() printf("\tFAIL\n"); #else # define pentry(func) 0 # define pexit(expr) expr # define pfail() 0 #endif sqInt sqFileAtEnd(SQFile *f) { /* Return true if the file's read/write head is at the end of the file. */ if (!sqFileValid(f)) return interpreterProxy->success(false); pentry(sqFileAtEnd); if (f->isStdioStream) return pexit(feof(getFile(f))); return ftell(getFile(f)) >= getSize(f); } sqInt sqFileClose(SQFile *f) { /* Close the given file. */ if (!sqFileValid(f)) return interpreterProxy->success(false); fclose(getFile(f)); setFile(f, 0); f->sessionID = 0; f->writable = false; setSize(f, 0); f->lastOp = UNCOMMITTED; return 1; } sqInt sqFileDeleteNameSize(char* sqFileName, sqInt sqFileNameSize) { char cFileName[1000]; int err; if (sqFileNameSize >= 1000) { return interpreterProxy->success(false); } /* copy the file name into a null-terminated C string */ interpreterProxy->ioFilenamefromStringofLengthresolveAliases(cFileName, sqFileName, sqFileNameSize, false); err = remove(cFileName); if (err) { return interpreterProxy->success(false); } return 1; } squeakFileOffsetType sqFileGetPosition(SQFile *f) { /* Return the current position of the file's read/write head. */ squeakFileOffsetType position; if (!sqFileValid(f)) return interpreterProxy->success(false); pentry(sqFileGetPosition); if (f->isStdioStream && !f->writable) return pexit(f->lastChar == EOF ? 0 : 1); position = ftell(getFile(f)); if (position == -1) return interpreterProxy->success(false); return position; } sqInt sqFileInit(void) { /* Create a session ID that is unlikely to be repeated. Zero is never used for a valid session number. Should be called once at startup time. */ #if VM_PROXY_MINOR > 6 thisSession = interpreterProxy->getThisSessionID(); #else thisSession = ioLowResMSecs() + time(NULL); if (thisSession == 0) thisSession = 1; /* don't use 0 */ #endif return 1; } sqInt sqFileShutdown(void) { return 1; } sqInt sqFileOpen(SQFile *f, char* sqFileName, sqInt sqFileNameSize, sqInt writeFlag) { /* Opens the given file using the supplied sqFile structure to record its state. Fails with no side effects if f is already open. Files are always opened in binary mode; Squeak must take care of any line-end character mapping. */ char cFileName[1001]; /* don't open an already open file */ if (sqFileValid(f)) return interpreterProxy->success(false); /* copy the file name into a null-terminated C string */ if (sqFileNameSize > 1000) { return interpreterProxy->success(false); } interpreterProxy->ioFilenamefromStringofLengthresolveAliases(cFileName, sqFileName, sqFileNameSize, true); if (writeFlag) { /* First try to open an existing file read/write: */ setFile(f, fopen(cFileName, "r+b")); if (getFile(f) == NULL) { /* Previous call fails if file does not exist. In that case, try opening it in write mode to create a new, empty file. */ setFile(f, fopen(cFileName, "w+b")); /* and if w+b fails, try ab to open a write-only file in append mode, not wb which opens a write-only file but overwrites its contents. */ if (getFile(f) == NULL) setFile(f, fopen(cFileName, "ab")); if (getFile(f) != NULL) { /* New file created, set Mac file characteristics */ char type[4],creator[4]; dir_GetMacFileTypeAndCreator(sqFileName, sqFileNameSize, type, creator); if (strncmp(type,"BINA",4) == 0 || strncmp(type,"????",4) == 0 || *(int *)type == 0 ) dir_SetMacFileTypeAndCreator(sqFileName, sqFileNameSize,"TEXT","R*ch"); } else { /* If the file could not be opened read/write and if a new file could not be created, then it may be that the file exists but does not permit read access. Try opening as a write only file, opened for append to preserve existing file contents. */ setFile(f, fopen(cFileName, "ab")); if (getFile(f) == NULL) { return interpreterProxy->success(false); } } } f->writable = true; } else { setFile(f, fopen(cFileName, "rb")); f->writable = false; } if (getFile(f) == NULL) { f->sessionID = 0; setSize(f, 0); return interpreterProxy->success(false); } else { FILE *file= getFile(f); f->sessionID = thisSession; /* compute and cache file size */ fseek(file, 0, SEEK_END); setSize(f, ftell(file)); fseek(file, 0, SEEK_SET); } f->lastOp = UNCOMMITTED; return 1; } /* * Fill-in files with handles for stdin, stdout and stderr as available and * answer a bit-mask of the availability, 1 corresponding to stdin, 2 to stdout * and 4 to stderr, with 0 on error or unavailablity. */ sqInt sqFileStdioHandlesInto(SQFile files[3]) { #if defined(_IONBF) && 0 if (isatty(fileno(stdin))) # if 0 setvbuf(stdin,0,_IONBF,1); # else setvbuf(stdin,0,_IOFBF,0); # endif #endif files[0].sessionID = thisSession; files[0].file = stdin; files[0].fileSize = 0; files[0].writable = false; files[0].lastOp = READ_OP; files[0].isStdioStream = true; files[0].lastChar = EOF; files[1].sessionID = thisSession; files[1].file = stdout; files[1].fileSize = 0; files[1].writable = true; files[1].isStdioStream = true; files[1].lastChar = EOF; files[1].lastOp = WRITE_OP; files[2].sessionID = thisSession; files[2].file = stderr; files[2].fileSize = 0; files[2].writable = true; files[2].isStdioStream = true; files[2].lastChar = EOF; files[2].lastOp = WRITE_OP; return 7; } size_t sqFileReadIntoAt(SQFile *f, size_t count, char* byteArrayIndex, size_t startIndex) { /* Read count bytes from the given file into byteArray starting at startIndex. byteArray is the address of the first byte of a Squeak bytes object (e.g. String or ByteArray). startIndex is a zero-based index; that is a startIndex of 0 starts writing at the first byte of byteArray. */ char *dst; size_t bytesRead; FILE *file; #if COGMTVM sqInt myThreadIndex; #endif if (!sqFileValid(f)) return interpreterProxy->success(false); pentry(sqFileReadIntoAt); file = getFile(f); if (f->writable) { if (f->isStdioStream) return interpreterProxy->success(false); if (f->lastOp == WRITE_OP) fseek(file, 0, SEEK_CUR); /* seek between writing and reading */ } dst = byteArrayIndex + startIndex; #if COGMTVM if (f->isStdioStream) { if (interpreterProxy->isInMemory((sqInt)f) && interpreterProxy->isYoung((sqInt)f) || interpreterProxy->isInMemory((sqInt)dst) && interpreterProxy->isYoung((sqInt)dst)) { interpreterProxy->primitiveFailFor(PrimErrObjectMayMove); return 0; } myThreadIndex = interpreterProxy->disownVM(DisownVMLockOutFullGC); } #endif do { clearerr(file); bytesRead = fread(dst, 1, count, file); } while (bytesRead <= 0 && ferror(file) && errno == EINTR); #if COGMTVM if (f->isStdioStream) interpreterProxy->ownVM(myThreadIndex); #endif /* support for skipping back 1 character for stdio streams */ if (f->isStdioStream) if (bytesRead > 0) f->lastChar = dst[bytesRead-1]; f->lastOp = READ_OP; return pexit(bytesRead); } sqInt sqFileRenameOldSizeNewSize(char* oldNameIndex, sqInt oldNameSize, char* newNameIndex, sqInt newNameSize) { char cOldName[1000], cNewName[1000]; int err; if ((oldNameSize >= 1000) || (newNameSize >= 1000)) { return interpreterProxy->success(false); } /* copy the file names into null-terminated C strings */ interpreterProxy->ioFilenamefromStringofLengthresolveAliases(cOldName, oldNameIndex, oldNameSize, false); interpreterProxy->ioFilenamefromStringofLengthresolveAliases(cNewName, newNameIndex, newNameSize, false); err = rename(cOldName, cNewName); if (err) { return interpreterProxy->success(false); } return 1; } sqInt sqFileSetPosition(SQFile *f, squeakFileOffsetType position) { /* Set the file's read/write head to the given position. */ if (!sqFileValid(f)) return interpreterProxy->success(false); if (f->isStdioStream) { pentry(sqFileSetPosition); /* support one character of pushback for stdio streams. */ if (!f->writable && f->lastChar != EOF) { squeakFileOffsetType currentPos = f->lastChar == EOF ? 0 : 1; if (currentPos == position) return pexit(1); if (currentPos - 1 == position) { ungetc(f->lastChar, getFile(f)); f->lastChar = EOF; return pexit(1); } } pfail(); return interpreterProxy->success(false); } fseek(getFile(f), position, SEEK_SET); f->lastOp = UNCOMMITTED; return 1; } squeakFileOffsetType sqFileSize(SQFile *f) { /* Return the length of the given file. */ if (!sqFileValid(f)) return interpreterProxy->success(false); if (f->isStdioStream) return interpreterProxy->success(false); return getSize(f); } sqInt sqFileFlush(SQFile *f) { if (!sqFileValid(f)) return interpreterProxy->success(false); pentry(sqFileFlush); fflush(getFile(f)); return 1; } sqInt sqFileTruncate(SQFile *f,squeakFileOffsetType offset) { if (!sqFileValid(f)) return interpreterProxy->success(false); if (sqFTruncate(getFile(f), offset)) return interpreterProxy->success(false); setSize(f, ftell(getFile(f))); return 1; } sqInt sqFileValid(SQFile *f) { return ( (f != NULL) && (getFile(f) != NULL) && (f->sessionID == thisSession)); } size_t sqFileWriteFromAt(SQFile *f, size_t count, char* byteArrayIndex, size_t startIndex) { /* Write count bytes to the given writable file starting at startIndex in the given byteArray. (See comment in sqFileReadIntoAt for interpretation of byteArray and startIndex). */ char *src; size_t bytesWritten; squeakFileOffsetType position; FILE *file; if (!(sqFileValid(f) && f->writable)) return interpreterProxy->success(false); pentry(sqFileWriteFromAt); file = getFile(f); if (f->lastOp == READ_OP) fseek(file, 0, SEEK_CUR); /* seek between reading and writing */ src = byteArrayIndex + startIndex; bytesWritten = fwrite(src, 1, count, file); position = ftell(file); if (position > getSize(f)) { setSize(f, position); /* update file size */ } if (bytesWritten != count) { interpreterProxy->success(false); } f->lastOp = WRITE_OP; return pexit(bytesWritten); } sqInt sqFileThisSession() { return thisSession; } #endif /* NO_STD_FILE_SUPPORT */