/*#io JFile ioDoc( docCopyright("Steve Dekorte", 2004) docLicense("BSD revised") docObject("JFile") docDescription("A journaled file.") */ #include "JFile.h" #include "Common.h" // for Win32 snprintf #include #include #include #include #include "PortableTruncate.h" //#define DEBUG 1 void JFile_sync(JFile *self); void JFile_readHeader(JFile *self); void JFile_writeHeader(JFile *self); void JFile_commitFinished(JFile *self); int JFile_isCommitted(JFile *self); JFile *JFile_new(void) { JFile *self = (JFile *)calloc(1, sizeof(JFile)); //JFile_setPath_(self, "default"); return self; } void JFile_free(JFile *self) { JFile_close(self); if (self->path) free(self->path); if (self->logPath) free(self->logPath); if (self->buf) free(self->buf); free(self); } void JFile_setPath_(JFile *self, const char *path) { self->path = strcpy((char *)realloc(self->path, strlen(path)+1), path); self->logPath = strcpy((char *)realloc(self->logPath, strlen(path)+5), path); strcat(self->logPath, ".log"); } char *JFile_fileName(JFile *self) { #ifdef __WIN32__ char *fileName = strrchr(self->path, '\\'); #else char *fileName = strrchr(self->path, '/'); #endif return fileName ? fileName : self->path; } void JFile_setLogPath_(JFile *self, const char *path) { char *fileName = JFile_fileName(self); self->logPath = strcpy((char *)realloc(self->logPath, strlen(path) + strlen(fileName)+10), path); if (self->logPath[strlen(self->logPath)-1] != '/') strcat(self->logPath, "/"); strcat(self->logPath, fileName); strcat(self->logPath, ".log"); } void JFile_setPath_withExtension_(JFile *self, const char *path, const char *ext) { size_t length = strlen(path) + 1 + strlen(ext) + 1; char *s = (char *)calloc(1, length); strcat(s, path); strcat(s, "."); strcat(s, ext); JFile_setPath_(self, s); free(s); } char *JFile_path(JFile *self) { return self->path; } void JFile_open(JFile *self) { JFile_openWithoutCommitCompletion(self); if (JFile_isCommitted(self)) { JFile_sync(self); } } void JFile_openWithoutCommitCompletion(JFile *self) { self->file = fopen(self->path, "r+b"); if (!self->file) { self->file = fopen(self->path, "wb"); fclose(self->file); self->file = fopen(self->path, "r+b"); } fseek(self->file, 0, SEEK_END); self->maxPos = ftell(self->file); self->log = fopen(self->logPath, "r+b"); /*printf("%s\n", self->logPath);*/ if (!self->log) { self->log = fopen(self->logPath, "wb"); fclose(self->log); self->log = fopen(self->logPath, "r+b"); } self->header.state = JFILE_UNCOMMITTED; self->header.writeCount = 0; JFile_readHeader(self); // would normally auto commit here self->logPos = sizeof(JFileLogHeader); } void JFile_close(JFile *self) { if (self->file) { fclose(self->file); self->file = NULL; } if (self->log) { fclose(self->log); self->log = NULL; } } void JFile_delete(JFile *self) { remove(self->path); remove(self->logPath); } size_t JFile_fwrite(JFile *self, void *buf, size_t size, size_t nobjs) { size_t result; size_t total = size * nobjs; #ifdef DEBUG if (total == 4 && strcmp(self->path, "default.udbData")) { printf("%s at %i writing int %i\n", self->path, self->pos, *((int *)(buf))); } #endif fseek(self->log, self->logPos, SEEK_SET); fwrite(&(self->pos), sizeof(long), 1, self->log); fwrite(&total, sizeof(size_t), 1, self->log); result = fwrite(buf, size, nobjs, self->log); self->pos += total; if (self->pos > self->maxPos) self->maxPos = self->pos; self->logPos = ftell(self->log); self->needsSync = 1; self->header.writeCount ++; return result; } size_t JFile_fread(JFile *self, void *buf, size_t size, size_t nobjs) { size_t result; // JFile_sync(self); will leave file pos in proper place fseek(self->file, self->pos, SEEK_SET); //printf("JFile read at %i %i\n", self->pos, ftell(self->file)); result = fread(buf, size, nobjs, self->file); self->pos = ftell(self->file); #ifdef DEBUG if (size == 4 && strcmp(self->path, "default.udbData")) { printf("%s at %i read int %i\n", self->path, self->pos, *((int *)(buf))); } #endif return result; } int JFile_fseek(JFile *self, long offset, int origin) { int result = fseek(self->file, offset, origin); self->pos = ftell(self->file); return result; } int JFile_fputc(JFile *self, int i) { unsigned char c = i; JFile_fwrite(self, &c, 1, 1); return i; } void JFile_writeInt_(JFile *self, int v) { JFile_fwrite(self, &v, sizeof(int), 1); } int JFile_readInt(JFile *self) { int v; JFile_fread(self, &v, sizeof(int), 1); return v; } void JFile_setPosition_(JFile *self, long pos) { self->pos = pos; } long JFile_position(JFile *self) { return self->pos; } long JFile_setPositionToEnd(JFile *self) { self->pos = self->maxPos; return self->pos; } #ifdef JFILE_SUPPORTS_MMAP #include #include #include #include #include size_t JFile_mmapfread(JFile *self, void *buf, size_t size, size_t nobjs) { size_t result; int fd = fileno(self->file); size_t readSize = size * nobjs; size_t mapSize = self->pos + readSize; char *m = (char *)mmap(NULL, mapSize, PROT_READ, MAP_SHARED, fd, 0); if (m == MAP_FAILED || (int)m == -1) { perror("mmap"); exit(-1); } memcpy(buf, m + self->pos, readSize); munmap(m, mapSize); self->pos += readSize; result = readSize; return result; } void mmapread(void *buf, size_t size, size_t nobjs, char *m) { size_t length = size * nobjs; memcpy(buf, m, length); m += length; } void JFile_sync(JFile *self) { if (self->needsSync) { int i; long pos; size_t total; char *mroot, *m; size_t mapSize = self->logPos; int fd = fileno(self->log); //printf("mapSize = %i\n", (int)mapSize); JFile_flushLog(self); self->logPos = sizeof(JFileLogHeader); fseek(self->log, self->logPos, SEEK_SET); mroot = (char *)mmap(NULL, mapSize, PROT_READ, MAP_SHARED, fd, 0); m = mroot; assert(m != MAP_FAILED || (int)m != -1); for (i = 0; i < self->header.writeCount; i ++) { //fread(&pos, sizeof(long), 1, self->log); memcpy(&pos, m, sizeof(long)); m += sizeof(long); //fread(&total, sizeof(size_t), 1, self->log); memcpy(&total, m, sizeof(size_t)); m += sizeof(size_t); self->buf = (unsigned char *)realloc(self->buf, total); //fread(self->buf, total, 1, self->log); memcpy(self->buf, m, total); m += total; fseek(self->file, pos, SEEK_SET); fwrite(self->buf, total, 1, self->file); } munmap(mroot, mapSize); self->needsSync = 0; JFile_flushFile(self);; fseek(self->file, self->pos, SEEK_SET); fseek(self->log, self->logPos, SEEK_SET); JFile_commitFinished(self); #ifdef DEBUG printf("--------------------------------------------------\n"); #endif } } #else void JFile_sync(JFile *self) { if (self->needsSync) { int i; long pos; size_t total; //JFile_flushLog(self); // needed? self->logPos = sizeof(JFileLogHeader); fseek(self->log, self->logPos, SEEK_SET); #ifdef DEBUG if (strcmp(self->path, "default.udbData") != 0) { printf("------- %s - commiting %i writes -----\n", self->path, self->header.writeCount); } #endif for (i = 0; i < self->header.writeCount; i ++) { fread(&pos, sizeof(long), 1, self->log); fread(&total, sizeof(size_t), 1, self->log); self->buf = (unsigned char *)realloc(self->buf, total); fread(self->buf, total, 1, self->log); #ifdef DEBUG if (total == 4 && strcmp(self->path, "default.udbData")) { printf("%s at %i committing int %i\n", self->path, pos, *((int *)(self->buf))); } #endif fseek(self->file, pos, SEEK_SET); fwrite(self->buf, total, 1, self->file); } self->needsSync = 0; JFile_flushFile(self);; fseek(self->file, self->pos, SEEK_SET); fseek(self->log, self->logPos, SEEK_SET); JFile_commitFinished(self); // begin will do this! #ifdef DEBUG printf("--------------------------------------------------\n"); #endif } } #endif // header ------------------------ void JFile_readHeader(JFile *self) { rewind(self->log); fread((unsigned char *)(&(self->header)), sizeof(JFileLogHeader), 1, self->log); } void JFile_writeHeader(JFile *self) { rewind(self->log); fwrite((unsigned char *)(&(self->header)), sizeof(JFileLogHeader), 1, self->log); JFile_flushLog(self); } void JFile_begin(JFile *self) { self->header.state = JFILE_UNCOMMITTED; self->header.writeCount = 0; JFile_writeHeader(self); //JFile_sync(self); } void JFile_commit(JFile *self) { self->header.state = JFILE_COMMITTED; JFile_writeHeader(self); JFile_sync(self); } void JFile_clipLog(JFile *self) { fclose(self->log); truncate(self->logPath, sizeof(JFileLogHeader)); self->log = fopen(self->logPath, "r+b"); } void JFile_commitFinished(JFile *self) { /*JFile_clipLog(self);*/ self->header.state = JFILE_UNCOMMITTED; self->header.writeCount = 0; JFile_writeHeader(self); } int JFile_isCommitted(JFile *self) { JFile_readHeader(self); return (self->header.state == JFILE_COMMITTED); } void JFile_truncate_(JFile *self, off_t size) { long pos = self->pos; JFile_commit(self); JFile_close(self); //printf("truncate(%s, %.0f)\n", self->path, (float)size); truncate(self->path, size); JFile_open(self); fseek(self->file, pos, SEEK_SET); } int JFile_Test(void) { JFile *t = JFile_new(); JFile_setPath_(t, "testing"); JFile_open(t); JFile_begin(t); { int i; for (i = 0; i < 3; i ++) { char s[128]; snprintf(s, 128, "foobar-%i", i); printf("write: %s\n", s); JFile_fwrite(t, s, strlen(s), 1); } } printf("commit\n"); JFile_commit(t); printf("done\n"); JFile_close(t); JFile_free(t); return 0; } /* See: http://lists.apple.com/archives/darwin-dev/2005/Feb/msg00072.html */ #include void JFile_flushLog(JFile *self) { //int fd = fileno(self->log); fflush(self->log); //fcntl(fd, F_FULLFSYNC, NULL); } void JFile_flushFile(JFile *self) { //int fd = fileno(self->file); fflush(self->file); //fcntl(fd, F_FULLFSYNC, NULL); }