/* * recenteditor_data.c * * A programmers editor * * Structures to hold the current file contents. * * Author: Dario Rodriguez dario@softhome.net * This program is licensed under the terms of GNU GPL v2.1+ */ #include #include #include #include #include #include #include #include #include #include "recenteditor_data.h" #include "sha3/sha3.h" #define CHUNKSIZE 32768 #define UNDOBLOCK 1024 #define UNDOGROWSIZE (256*1024) #define UNSAVEDPREFIX "." #define UNSAVEDPOSTFIX ".reu" #define SECURESAVEPREFIX "." #define SECURESAVEPOSTFIX ".saving" #define UNSAVEDHEADER "REUNSAV\n" #define UNSAVEDVERSION "\n000001\0" static int redata_hash_gen(redata_t *redata, char *filename, char *resbuf129bytes); static char *unsaved_genname(char *filename, char *buf, int bufsize); static char *securesave_genname(char *filename, char *buf, int bufsize); static char *genname(char *filename,char *prefix, char *postfix, char *buf, int bufsize); redata_t * redata_init(void) { redata_t *redata; if((redata=malloc(sizeof(redata_t)))==NULL) return(NULL); /* sanity check failed */ memset(redata,0,sizeof(redata_t)); /* data */ redata->sizechunks=0; redata->chunks=NULL; redata->chunkdatasize=CHUNKSIZE; redata->available=0; /* undo */ redata->sizeundo=0; redata->usedundo=0; redata->curundo=0; redata->undo=NULL; redata->undobuf=NULL; /* unsaved */ redata->filename[0]='\0'; redata->unsavedfd=-1; redata->flag_unsaveddata=0; /* all done */ return(redata); } void redata_free(redata_t *redata) { int i; char unsname[PATH_MAX]; if(redata==NULL) return; /* nothing to do */ /* data */ for(i=0;isizechunks;i++) { if(redata->chunks[i]!=NULL) free(redata->chunks[i]),redata->chunks[i]=NULL; } redata->sizechunks=0; if(redata->chunks!=NULL) free(redata->chunks),redata->chunks=NULL; /* undo */ if(redata->undo!=NULL) free(redata->undo),redata->undo=NULL; if(redata->undobuf!=NULL) free(redata->undobuf),redata->undobuf=NULL; /* unsaved */ if(redata->unsavedfd!=-1) { close(redata->unsavedfd),redata->unsavedfd=-1; if(unsaved_genname(redata->filename,unsname,sizeof(unsname))!=NULL) unlink(unsname); } redata->flag_unsaveddata=0; /* free main struct */ free(redata),redata=NULL; return; } int redata_getsize(redata_t *redata) { if(redata==NULL) return(0); /* sanity check failed */ return(redata->chunkdatasize*redata->sizechunks); } int redata_getavailable(redata_t *redata) { if(redata==NULL) return(0); /* sanity check failed */ return(redata->available); } int redata_getused(redata_t *redata) { int used; if(redata==NULL) return(0); /* sanity check failed */ used=redata->chunkdatasize*redata->sizechunks-redata->available; return(used); } int redata_getposptr(redata_t *redata, int pos, int *numchunk, int *offset) { int used; int i; int ipos; used=redata_getused(redata); if(redata==NULL || pos<0 || pos>used || numchunk==NULL || offset==NULL) return(-1); /* sanity check failed */ for(ipos=0,i=0;isizechunks;ipos+=redata->chunks[i]->useddata,i++) { if(pos>(ipos+redata->chunks[i]->useddata)) continue; /* found */ *numchunk=i; *offset=pos-ipos; return(0); } return(-1); } int redata_wipe(redata_t *redata) { int i; char unsname[PATH_MAX]; if(redata==NULL) return(-1); /* sanity check failed */ /* data */ for(i=0;isizechunks;i++) { redata->chunks[i]->useddata=0; memset(&(redata->chunks[i]->whatin),0,sizeof(whatin_t)); } redata->available=redata_getsize(redata); /* unsaved */ if(redata->unsavedfd!=-1) { close(redata->unsavedfd),redata->unsavedfd=-1; if(unsaved_genname(redata->filename,unsname,sizeof(unsname))!=NULL) unlink(unsname); } redata->flag_unsaveddata=0; redata->filename[0]='\0'; /* all done */ return(0); } int redata_preallocate(redata_t *redata, int newsize) { int cursize; int nchunks; int i; int rechunksize; rechunk_t **newchunks,*chunk; int oldsizechunks; if(redata==NULL || redata->chunkdatasize==0 || newsize<0) return(-1); /* sanity check failed */ if((cursize=redata_getsize(redata))>=newsize) return(0); /* all done */ nchunks=(newsize-cursize+redata->chunkdatasize-1)/redata->chunkdatasize; rechunksize=sizeof(rechunk_t)-1+redata->chunkdatasize; if((newchunks=realloc(redata->chunks,sizeof(rechunk_t *)*(redata->sizechunks+nchunks)))==NULL) return(-1); /* insuf. mem. */ redata->chunks=newchunks; memset(redata->chunks+redata->sizechunks,0,sizeof(rechunk_t *)*nchunks); oldsizechunks=redata->sizechunks; for(i=0;iuseddata=0; redata->chunks[oldsizechunks+i]=chunk; redata->sizechunks++; redata->available+=redata->chunkdatasize; } return(0); } int redata_fill_whatin(redata_t *redata, int chunkno) { rechunk_t *chunk; int nlcount; int i,lim; if(redata==NULL || chunkno<0 || chunkno>=redata->sizechunks || redata->chunks[chunkno]==NULL) return(-1); /* sanity check failed */ chunk=redata->chunks[chunkno]; memset(&(chunk->whatin),0,sizeof(whatin_t)); nlcount=0; for(i=0,lim=chunk->useddata;idata[i]=='\n')?1:0; } chunk->whatin.nlcount=nlcount; return(0); } int redata_unsaved_exists(redata_t *redata, char *filename) { char unsname[PATH_MAX+1]; int fd; if(redata==NULL || filename==NULL) return(-1); /* sanity check failed */ if((unsaved_genname(filename,unsname,sizeof(unsname)))==NULL) return(-1); /* malformed filename */ if((fd=open(unsname,O_RDONLY))==-1) return(-1); close(fd),fd=-1; return(0); } int redata_unsaved_check(redata_t *redata, char *filename) { char unsname[PATH_MAX+1]; int fd,nread; static char header[9]={UNSAVEDHEADER}; static char version[9]={UNSAVEDVERSION}; char filehash[129],undohash[129],buf[64]; if(redata==NULL || filename==NULL) return(-1); /* sanity check failed */ if((unsaved_genname(filename,unsname,sizeof(unsname)))==NULL) return(-1); /* malformed filename */ if(redata_filehash(redata,filename,filehash)!=0) return(-1); if((fd=open(unsname,O_RDONLY))==-1) return(-1); if((nread=read(fd,buf,8))==-1 || nread!=8 || memcmp(buf,header,8)!=0) { close(fd),fd=-1; return(-1); /* corrupted header */ } if((nread=read(fd,undohash,128))==-1 || nread!=128 || memcmp(undohash,filehash,128)!=0) { close(fd),fd=-1; return(-1); /* wrong hash */ } if((nread=read(fd,buf,8))==-1 || nread!=8 || memcmp(buf,version,8)!=0) { close(fd),fd=-1; return(-1); /* wrong version */ } close(fd),fd=-1; return(0); } int redata_unsaved_unlink(redata_t *redata) { char unsname[PATH_MAX+1]; if(redata==NULL || redata->filename[0]=='\0') return(-1); /* sanity check failed */ if(redata_unsaved_exists(redata,redata->filename)==-1) return(0); /* file not found, nothing to unlink */ if((unsaved_genname(redata->filename,unsname,sizeof(unsname)))==NULL) return(-1); /* malformed filename */ unlink(unsname); return(0); } int redata_unsaved_trunc(redata_t *redata) { char unsname[PATH_MAX+1]; static char header[9]={UNSAVEDHEADER}; static char version[9]={UNSAVEDVERSION}; int n; if(redata==NULL || redata->filename[0]=='\0') return(-1); /* sanity check failed */ redata_unsaved_unlink(redata); if((unsaved_genname(redata->filename,unsname,sizeof(unsname)))==NULL) return(-1); /* malformed filename */ if((redata->unsavedfd=open(unsname,O_WRONLY|O_TRUNC|O_CREAT,0644))==-1) return(-1); /* couldn't open file for writing */ if((n=write(redata->unsavedfd,header,8))==-1 || n!=8 || (n=write(redata->unsavedfd,redata->initialhash,128))==-1 || n!=128 || (n=write(redata->unsavedfd,version,8))==-1 || n!=8) { close(redata->unsavedfd),redata->unsavedfd=-1; unlink(unsname); return(-1); /* couldn't write header/hash/version */ } return(0); } int redata_unsaved_loadappend(redata_t *redata) { #warning TODO return(-1); } int redata_unsaved_add(redata_t *redata, undo_t *undo) { #warning TODO return(-1); } int redata_unsaved_unadd(redata_t *redata, undo_t *undo) { #warning TODO return(-1); } int redata_load(redata_t *redata, char *filename, int use_unsaved) { #warning TODO: make sure a line of lenfilename,filename,sizeof(redata->filename)); redata->filename[sizeof(redata->filename)-1]='\0'; if((fd=open(filename,O_RDONLY))==-1 || fstat(fd,&statbuf)!=0 || !S_ISREG(statbuf.st_mode)) { if(fd!=-1) close(fd),fd=-1; return(-1); /* file not found, couldn't query size or not regular file */ } /* preallocate 10% more than needed */ if(redata_preallocate(redata,statbuf.st_size+(statbuf.st_size/10)+1 )) { close(fd),fd=-1; return(-1); /* insuf. mem. */ } for(totalread=0,chunkno=0,nread=0;totalread=redata->sizechunks || redata->chunks[chunkno]==NULL) { /* alloc another chunk */ if(redata_preallocate(redata,(redata->sizechunks+1)*redata->chunkdatasize)==-1 || chunkno>=redata->sizechunks || redata->chunks[chunkno]==NULL) { close(fd),fd=-1; fprintf(stderr,"redata_load: INTERNAL ERROR\n"); return(-2); /* internal error */ } } chunk=redata->chunks[chunkno]; /* leave 10% free on each chunk */ avail=(redata->chunkdatasize-redata->chunkdatasize/10)-chunk->useddata; avail=(avail<0)?0:avail; avail=((totalread+avail)>statbuf.st_size)?statbuf.st_size-totalread:avail; if(avail==0) { chunkno++; /* full, try next */ continue; } if((nread=read(fd,chunk->data+chunk->useddata,avail))<=0) { close(fd),fd=-1; return(-1); /* short read */ } chunk->useddata+=nread; redata->available-=nread; redata_fill_whatin(redata,chunkno); } close(fd),fd=-1; /* unsaved */ redata_hash(redata,redata->initialhash); if(use_unsaved) { /* apply missing changes and append new changes to unsaved */ redata_unsaved_loadappend(redata); } else { /* nuke existing unsaved (if exists) and prepare new unsaved */ redata_unsaved_trunc(redata); } /* all done */ return(0); } int redata_save(redata_t *redata, char *filename) { int fd; int i,n; char tmpfile[PATH_MAX+1]; if(redata==NULL || filename==NULL) return(-1); /* sanity check failed */ if((securesave_genname(redata->filename,tmpfile,sizeof(tmpfile)))==NULL) return(-1); /* malformed filename */ if((fd=open(tmpfile,O_WRONLY|O_TRUNC|O_CREAT,0644))==-1) return(-1); /* couldn't open file for writing */ for(i=0;isizechunks;i++) { if(redata->chunks[i]->useddata==0) continue; if((n=write(fd,redata->chunks[i]->data,redata->chunks[i]->useddata))==-1 || n!=redata->chunks[i]->useddata) { close(fd),fd=-1; unlink(tmpfile); return(-1); /* short write */ } } close(fd),fd=-1; if(rename(tmpfile,filename)!=0) { unlink(tmpfile); return(-1); /* couldn't overwrite old file */ } redata_unsaved_unlink(redata); redata->flag_unsaveddata=0; redata_hash(redata,redata->initialhash); return(0); } int redata_undobuf_reserve(redata_t *redata, int minavail) { int unused; int newsize; char *newptr; if(redata==NULL || minavail<0) return(-1); /* sanity check failed */ unused=redata->sizeundobuf-redata->usedundobuf; if(unusedsizeundobuf+minavail+UNDOGROWSIZE-1)/UNDOGROWSIZE; newsize*=UNDOGROWSIZE; if((newptr=realloc(redata->undobuf,newsize))==NULL) return(-1); /* insuf. mem. */ redata->undobuf=newptr; redata->sizeundobuf=newsize; } return(0); } undo_t * redata_undo_new(redata_t *redata, char *type) { int newsize; undo_t *newptr,*undo; if(redata==NULL || type==NULL) return(NULL); /* sanity check failed */ if(redata->sizeundo==redata->usedundo) { newsize=(redata->sizeundo+1+UNDOBLOCK-1)/UNDOBLOCK; newsize*=UNDOBLOCK; if((newptr=realloc(redata->undo,sizeof(undo_t)*newsize))==NULL) return(NULL); redata->undo=newptr; memset(redata->undo+redata->sizeundo,0,sizeof(undo_t)*(newsize-redata->sizeundo)); redata->sizeundo=newsize; } undo=redata->undo+redata->usedundo; redata->usedundo++; strncpy(undo->type,type,sizeof(undo->type)); undo->type[sizeof(undo->type)-1]='\0'; undo->off=redata->usedundobuf; undo->len=0; return(undo); } undo_t * redata_undo_newfromchunks(redata_t *redata,char *type, int pos1, int len) { int startpos,startoff,endpos,endoff; undo_t *undo; int k; int used; int copyfrom,copysize; if(redata==NULL || type==NULL || len<=0) return(NULL); /* sanity check failed */ if(redata_getposptr(redata,pos1,&startpos,&startoff)==-1 || redata_getposptr(redata,pos1+len,&endpos,&endoff)==-1) { return(NULL); /* chunk data out of bounds */ } if(redata_undobuf_reserve(redata,len)!=0) return(NULL); /* insuf. mem. */ if((undo=redata_undo_new(redata,type))==NULL) return(NULL); /* insuf. mem. */ undo->off=redata->usedundobuf; /* copy contents */ for(k=startpos,used=0;k<=endpos;k++) { if(k==startpos && k==endpos) { copyfrom=startoff; copysize=endoff-startoff; } else if(k==startpos) { copyfrom=startoff; copysize=redata->chunks[k]->useddata-startoff; } else if(k==endpos) { copyfrom=0; copysize=endoff; } else { copyfrom=0; copysize=redata->chunks[k]->useddata; } memcpy(redata->undobuf+redata->usedundobuf,redata->chunks[k]->data+copyfrom,copysize); redata->usedundobuf+=copysize; used+=copysize; } undo->len=used; return(undo); } undo_t * redata_undobuf_newfrombuf(redata_t *redata, char *type, char *buf, int buflen) { undo_t *undo; if(redata==NULL || type==NULL || buflen<=0) return(NULL); /* sanity check failed */ if(redata_undobuf_reserve(redata,buflen)!=0) return(NULL); /* insuf. mem. */ if((undo=redata_undo_new(redata,type))==NULL) return(NULL); /* insuf. mem. */ undo->off=redata->usedundobuf; memcpy(redata->undobuf+redata->usedundobuf,buf,buflen); redata->usedundobuf+=buflen; undo->len=buflen; return(undo); } int redata_op_add(redata_t *redata, char *buf, int sizebuf, int pos) { #warning TODO return(-1); } int redata_op_del(redata_t *redata, int pos, int size) { #warning TODO return(-1); } int redata_op_move(redata_t *redata, int posorig, int size, int posdest) { #warning TODO return(-1); } int redata_op_undo(redata_t *redata) { #warning TODO return(-1); } int redata_op_redo(redata_t *redata) { #warning TODO return(-1); } int redata_hash(redata_t *redata, char *resbuf129bytes) { return(redata_hash_gen(redata,NULL,resbuf129bytes)); } int redata_filehash(redata_t *redata, char *filename, char *resbuf129bytes) { if(resbuf129bytes!=NULL) *resbuf129bytes='\0'; if(filename==NULL) return(-1); return(redata_hash_gen(redata,filename,resbuf129bytes)); } static int redata_hash_gen(redata_t *redata, char *filename, char *resbuf129bytes) { static char conv[]={"0123456789ABCDEF"}; sha3_context sha3; unsigned char *hash; int i,c; int fd=-1; struct stat statbuf; if(resbuf129bytes!=NULL) *resbuf129bytes='\0'; if(redata==NULL || resbuf129bytes==NULL) { return(-1); /* sanity check failed */ } if(filename!=NULL) { if((fd=open(filename,O_RDONLY))==-1 || fstat(fd,&statbuf)!=0 || !S_ISREG(statbuf.st_mode)) { if(fd!=-1) close(fd),fd=-1; return(-1); /* file not found, couldn't query size or not regular file */ } } sha3_Init512(&sha3); if(filename==NULL) { for(i=0;isizechunks;i++) sha3_Update(&sha3,(void *) redata->chunks[i]->data,redata->chunks[i]->useddata); } else { char buf[16384]; int totalread,nread; for(totalread=0,nread=0;totalread>4)&0xf)]; resbuf129bytes[(i<<1)+1]=conv[(c&0xf)]; } resbuf129bytes[128]='\0'; /* NOTE this is SHA3-512, not keccak (empty result is a69f..cd26) */ return(0); } static char * unsaved_genname(char *filename, char *buf, int bufsize) { static char pre[]={UNSAVEDPREFIX}; static char post[]={UNSAVEDPOSTFIX}; return(genname(filename,pre,post,buf,bufsize)); } static char * securesave_genname(char *filename, char *buf, int bufsize) { static char pre[]={SECURESAVEPREFIX}; static char post[]={SECURESAVEPOSTFIX}; return(genname(filename,pre,post,buf,bufsize)); } static char * genname(char *filename,char *prefix, char *postfix, char *buf, int bufsize) { char *name,*ptr; int filenamelen; int prelen,postlen,finallen,off; if(filename==NULL || prefix==NULL || postfix==NULL) return(NULL); filenamelen=strlen(filename); prelen=strlen(prefix); postlen=strlen(postfix); finallen=filenamelen+prelen+postlen+1; if(buf==NULL) { if((name=malloc(finallen))==NULL) return(NULL); } else { if(bufsizefilename && ptr[-1]!='/';ptr--) ; off=0; memcpy(name+off,filename,ptr-filename); off+=ptr-filename; memcpy(name+off,prefix,prelen); off+=prelen; memcpy(name+off,ptr,filenamelen-(ptr-filename)); off+=filenamelen-(ptr-filename); memcpy(name+off,postfix,postlen); off+=postlen; name[off]='\0'; return(name); }