/* * 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 <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <limits.h> #include <inttypes.h> #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 "reunsaved00," static int redata_hash_gen(redata_t *redata, char *filename, char *buf, long buflen, char *resbuf129bytes); static int redata_unsaved_check_gen(redata_t *redata, char *filename); 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); static char *ptr_getlong(char *ptr,char *endptr,long *data); static char *ptr_getchar(char *ptr,char *endptr,char *data); static char *ptr_searchendchar(char *ptr, char *endptr, char endchar, char **endcharpos); static char sep_select(char *buf, int bufsize, char **pos); static void *mymemrchr(const void *s, int c, size_t n); static void meminvert(void *start, void *end); 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->undostack.sizeundo=0; redata->undostack.usedundo=0; redata->undostack.undo=NULL; redata->undostack.buf=NULL; /* unsaved */ redata->filename[0]='\0'; redata->unsavedfd=-1; redata->unsaved.sizebuf=redata->unsaved.usedbuf=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;i<redata->sizechunks;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; if(redata->tmpchunk!=NULL) free(redata->tmpchunk),redata->tmpchunk=NULL; /* undo */ if(redata->undostack.undo!=NULL) free(redata->undostack.undo),redata->undostack.undo=NULL; if(redata->undostack.buf!=NULL) free(redata->undostack.buf),redata->undostack.buf=NULL; /* redo */ if(redata->redostack.undo!=NULL) free(redata->redostack.undo),redata->redostack.undo=NULL; if(redata->redostack.buf!=NULL) free(redata->redostack.buf),redata->redostack.buf=NULL; /* unsaved */ if(redata->unsavedfd!=-1) { close(redata->unsavedfd),redata->unsavedfd=-1; if(unsaved_genname(redata->filename,unsname,sizeof(unsname))!=NULL) unlink(unsname); } if(redata->unsaved.buf!=NULL) { free(redata->unsaved.buf),redata->unsaved.buf=NULL; redata->unsaved.sizebuf=redata->unsaved.usedbuf=0; } /* free main struct */ free(redata),redata=NULL; return; } int redata_config_chunkdatasize(redata_t *redata, int chunkdatasize) { /* NOTE: this can only be configured immediately after doing redata_init() */ if(redata==NULL || chunkdatasize<=0 || redata->sizechunks!=0) return(-1); /* sanity check failed */ redata->chunkdatasize=chunkdatasize; return(0); } long redata_getsize(redata_t *redata) { if(redata==NULL) return(0); /* sanity check failed */ return(redata->chunkdatasize*redata->sizechunks); } long redata_getavailable(redata_t *redata) { if(redata==NULL) return(0); /* sanity check failed */ return(redata->available); } long redata_getused(redata_t *redata) { long used; if(redata==NULL) return(0); /* sanity check failed */ used=redata->chunkdatasize*redata->sizechunks-redata->available; return(used); } int redata_getposptr(redata_t *redata, long pos, int *numchunk, long *offset) { long used; int i; long 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;i<redata->sizechunks;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;i<redata->sizechunks;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->filename[0]='\0'; redata->unsaved.usedbuf=0; /* all done */ return(0); } int redata_chunk_insertnew(redata_t *redata, int afterthischunk) { rechunk_t *newchunk; int i; if(redata==NULL || afterthischunk<(-1) || afterthischunk>=redata->sizechunks) return(-1); if(redata->chunks[redata->sizechunks-1]->useddata!=0) { if(redata_preallocate(redata,redata_getsize(redata)+redata->chunkdatasize)==-1 || redata->chunks[redata->sizechunks-1]->useddata!=0) return(-1); } newchunk=redata->chunks[redata->sizechunks-1]; /* move the emtpy chunk to after chunkno */ for(i=redata->sizechunks-1;i>(afterthischunk+1);i--) redata->chunks[i]=redata->chunks[i-1]; redata->chunks[afterthischunk+1]=newchunk; return(0); } int redata_chunk_deletechunk(redata_t *redata, int chunkno) { rechunk_t *chunk; int i; if(redata==NULL || chunkno<0 || chunkno>=redata->sizechunks) return(-1); chunk=redata->chunks[chunkno]; if(chunk->useddata>0) { redata->available+=chunk->useddata; chunk->useddata=0; chunk->whatin_fresh=0; } for(i=chunkno;(i+1)<redata->sizechunks;i++) redata->chunks[i]=redata->chunks[i+1]; redata->chunks[redata->sizechunks-1]=chunk; return(0); } int redata_preallocate(redata_t *redata, long newsize) { long cursize; int nchunks; int i; long rechunksize; rechunk_t **newchunks,*chunk; int oldsizechunks; if(redata==NULL || redata->chunkdatasize==0 || newsize<0) return(-1); /* sanity check failed */ rechunksize=sizeof(rechunk_t)-1+redata->chunkdatasize; /* before doing any chunk alloc, we setup tmpchunk (used for moves) */ if(redata->tmpchunk==NULL) { if((redata->tmpchunk=malloc(rechunksize))==NULL) return(-1); /* insuf. mem. */ memset(redata->tmpchunk,0,rechunksize); } /* new chunk processing */ if((cursize=redata_getsize(redata))>=newsize) return(0); /* all done */ nchunks=(newsize-cursize+redata->chunkdatasize-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;i<nchunks;i++) { if((chunk=malloc(rechunksize))==NULL) return(-1); /* insuf. mem. */ memset(chunk,0,rechunksize); chunk->useddata=0; redata->chunks[oldsizechunks+i]=chunk; redata->sizechunks++; redata->available+=redata->chunkdatasize; } return(0); } int redata_chunk_movedata(redata_t *redata, int chunkfrom, long posfrom, int chunkto, long posto, long size) { if(redata==NULL || size<0 || chunkfrom<0 || chunkfrom>=redata->sizechunks || posfrom<0 || (posfrom+size)>redata->chunks[chunkfrom]->useddata || chunkto<0 || chunkto>=redata->sizechunks || posto<0 || (posto)>redata->chunks[chunkto]->useddata || (chunkfrom!=chunkto && (redata->chunkdatasize-redata->chunks[chunkto]->useddata)<size) || (chunkfrom==chunkto && posfrom<posto && posfrom+size>posto) ) return(-1); /* sanity check failed */ if(size==0 || (chunkfrom==chunkto && posfrom==posto) || (chunkfrom==chunkto && (posfrom+size)==posto) ) return(0); /* all done */ if(chunkfrom!=chunkto) { rechunk_t *from,*to; from=redata->chunks[chunkfrom]; to=redata->chunks[chunkto]; memmove(to->data+posto+size,to->data+posto,to->useddata-posto); memcpy(to->data+posto,from->data+posfrom,size); memmove(from->data+posfrom,from->data+posfrom+size,from->useddata-posfrom-size); from->useddata-=size; to->useddata+=size; from->whatin_fresh=0; to->whatin_fresh=0; } else { /* from==to */ rechunk_t *chunk=redata->chunks[chunkfrom]; memcpy(redata->tmpchunk->data,chunk->data+posfrom,size); if(posfrom>posto) { memmove(chunk->data+posto+size,chunk->data+posto,posfrom-posto); memcpy(chunk->data+posto,redata->tmpchunk->data,size); } else { memmove(chunk->data+posfrom,chunk->data+posfrom+size,posto-posfrom-size); memcpy(chunk->data+posto-size,redata->tmpchunk->data,size); } chunk->whatin_fresh=0; } return(0); } int redata_chunk_insertdata(redata_t *redata, int chunkto, long posto, char *buf, long buflen) { rechunk_t *chunk; if(redata==NULL || buflen<0 || buf==NULL || chunkto<0 || chunkto>=redata->sizechunks || posto<0 || posto>redata->chunks[chunkto]->useddata || (redata->chunkdatasize-redata->chunks[chunkto]->useddata)<buflen) return(-1); /* sanity check failed */ chunk=redata->chunks[chunkto]; memmove(chunk->data+posto+buflen,chunk->data+posto,chunk->useddata-posto); memcpy(chunk->data+posto,buf,buflen); chunk->useddata+=buflen; chunk->whatin_fresh=0; redata->available-=buflen; return(0); } int redata_chunk_deletedata(redata_t *redata, int chunkno, long pos, long n) { rechunk_t *chunk; if(redata==NULL || n<0 || chunkno<0 || chunkno>=redata->sizechunks || pos<0 || pos>redata->chunks[chunkno]->useddata || (redata->chunks[chunkno]->useddata-pos)<n) return(-1); /* sanity check failed */ if(n==0) return(0); /* all done */ chunk=redata->chunks[chunkno]; memmove(chunk->data+pos,chunk->data+pos+n,chunk->useddata-pos-n); chunk->useddata-=n; chunk->whatin_fresh=0; redata->available+=n; return(0); } int redata_chunk_splithere(redata_t *redata, int chunkno, int pos) { rechunk_t *chunk; int size; if(redata==NULL || chunkno<0 || chunkno>=redata->sizechunks || pos<0 || pos>redata->chunks[chunkno]->useddata) return(-1); /* sanity check failed */ chunk=redata->chunks[chunkno]; if(chunk->useddata==pos) return(0); /* all done: already splitted */ size=redata->chunks[chunkno]->useddata-pos; if((chunkno+1)<redata->sizechunks && (redata->chunkdatasize-redata->chunks[chunkno+1]->useddata)>=size) { redata_chunk_movedata(redata,chunkno,pos,chunkno+1,0,size); return(0); /* all done: moved data cleanly to next chunk */ } if(redata_chunk_insertnew(redata,chunkno)!=0) return(-1); /* insuf. mem. */ redata_chunk_movedata(redata,chunkno,pos,chunkno+1,0,size); return(0); } int redata_chunk_fillfromnext(redata_t *redata, int chunkno, int n) { if(redata==NULL || chunkno<0 || (chunkno+1)>=redata->sizechunks || (redata->chunkdatasize-redata->chunks[chunkno]->useddata)<n || (redata->chunks[chunkno+1]->useddata)<n) return(-1); /* sanity check failed */ return(redata_chunk_movedata(redata,chunkno+1,0,chunkno,redata->chunks[chunkno]->useddata,n)); } int redata_whatin_refresh(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]; if(chunk->whatin_fresh) return(0); memset(&(chunk->whatin),0,sizeof(whatin_t)); nlcount=0; for(i=0,lim=chunk->useddata;i<lim;i++) { nlcount+=(chunk->data[i]=='\n')?1:0; } chunk->whatin.nlcount=nlcount; chunk->whatin_fresh=1; return(0); } int redata_fix_nl(redata_t *redata, int chunkno) { /* make sure a line of len<chunksize is entirely inside one chunk (to simplify the syntax highlighting code) */ if(redata==NULL || chunkno<0 || chunkno>=redata->sizechunks || redata->chunks[chunkno]==NULL) return(-1); /* sanity check failed */ if(chunkno==(redata->sizechunks-1) || redata->chunks[chunkno]->useddata==0 || redata->chunks[chunkno]->data[redata->chunks[chunkno]->useddata-1]=='\n') return(0); /* Nothing to do (last chunk, chunk empty or already fixed) */ rechunk_t *chunk,*nextchunk; int linesize, nextlinesize, avail, nextavail; unsigned char *ptr,*nextptr; chunk=redata->chunks[chunkno]; nextchunk=redata->chunks[chunkno+1]; ptr=mymemrchr(chunk->data,'\n',chunk->useddata); nextptr=memchr(nextchunk->data,'\n',nextchunk->useddata); linesize=(ptr==NULL)?chunk->useddata:(chunk->useddata-(ptr-chunk->data)-1); nextlinesize=(nextptr==NULL)?nextchunk->useddata:((nextptr-nextchunk->data)+1); avail=redata->chunkdatasize-chunk->useddata; nextavail=redata->chunkdatasize-nextchunk->useddata; if(avail>=nextlinesize) { /* move remaining bytes of line to current chunk */ redata_chunk_movedata(redata, chunkno+1, 0, chunkno, chunk->useddata, nextlinesize); } else if(nextavail>=linesize) { /* move incomplete line to next chunk */ redata_chunk_movedata(redata, chunkno, chunk->useddata-linesize, chunkno+1, 0, linesize); } else if(ptr!=NULL) { /* only if we have more data in chunkno before this long line */ int newavail; /* create a new empty chunk in-between and put the line there */ if(redata_chunk_insertnew(redata,chunkno)==-1) return(-1); /* move the data; if it doesn't fit, move from the beginning until it is full */ redata_chunk_movedata(redata, chunkno, chunk->useddata-linesize, chunkno+1, 0, linesize); newavail=redata->sizechunks-linesize; redata_chunk_movedata(redata, chunkno+2, 0, chunkno+1, linesize, (newavail<nextlinesize)?newavail:nextlinesize); } 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); } static int redata_unsaved_check_gen(redata_t *redata, char *filename) { char unsname[PATH_MAX+1]; int fd,nread; static char header[]={UNSAVEDHEADER}; char filehash[129],undohash[129],buf[16]; char fileheader[]={UNSAVEDHEADER}; 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); memset(fileheader,0,sizeof(fileheader)); if((nread=read(fd,fileheader,sizeof(fileheader)-1))==-1 || nread!=(sizeof(fileheader)-1) || memcmp(fileheader,header,sizeof(fileheader))!=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,1))==-1 || nread!=1 || *buf!=',') { close(fd),fd=-1; return(-1); /* wrong hash separator */ } return(fd); } int redata_unsaved_check(redata_t *redata, char *filename) { int fd; if((fd=redata_unsaved_check_gen(redata,filename))==-1) return(-1); /* check failed */ 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[]={UNSAVEDHEADER}; 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!=-1) close(redata->unsavedfd),redata->unsavedfd=-1; 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,sizeof(header)-1))==-1 || n!=(sizeof(header)-1) || (n=write(redata->unsavedfd,redata->initialhash,128))==-1 || n!=128 || (n=write(redata->unsavedfd,",",1))==-1 || n!=1) { close(redata->unsavedfd),redata->unsavedfd=-1; unlink(unsname); return(-1); /* couldn't write header/hash */ } return(0); } int redata_unsaved_loadappend(redata_t *redata) { int fd; struct stat statbuf; static char header[]={UNSAVEDHEADER}; long headerhashsize; char *newptr; long nread,lim; char *ptr,*endptr,*aux,*bufptr; char actioncode; long pos,pos2; char endcode; int flag_multipart; if((fd=redata_unsaved_check_gen(redata,redata->filename))==-1) return(-1); /* check failed */ if(fstat(fd,&statbuf)!=0 || !S_ISREG(statbuf.st_mode)) { close(fd),fd=-1; return(-1); /* couldn't query size or not regular file */ } headerhashsize=(sizeof(header)-1)+1+128+1; /* load unsaved to memory */ if(redata->unsaved.sizebuf<(statbuf.st_size-headerhashsize)) { if((newptr=realloc(redata->unsaved.buf,(statbuf.st_size-headerhashsize)))==NULL) { close(fd),fd=-1; return(-1); /* insuf. mem. */ } redata->unsaved.buf=newptr; redata->unsaved.sizebuf=(statbuf.st_size-headerhashsize); } redata->unsaved.usedbuf=0; lim=(statbuf.st_size-headerhashsize); for(nread=0;redata->unsaved.usedbuf<lim;redata->unsaved.usedbuf+=nread,nread=0) { if((nread=read(fd,redata->unsaved.buf+redata->unsaved.usedbuf,lim-redata->unsaved.usedbuf))<=0) { redata->unsaved.usedbuf=0; close(fd),fd=-1; return(-1); /* short read */ } } close(fd),fd=-1; /* process unsaved data */ endptr=redata->unsaved.buf+redata->unsaved.usedbuf; for(ptr=redata->unsaved.buf;ptr<endptr;) { if((ptr=ptr_getchar(ptr,endptr,&actioncode))==NULL) return(-1); /* no space for action char */ /* multipart example: A10+$aj$%$% >> insert "aj$" into pos 10 */ if(actioncode=='A') { ptr=ptr_getlong(ptr,endptr,&pos); do { flag_multipart=0; ptr=ptr_getchar(ptr,endptr,&endcode); if(ptr!=NULL && endcode=='+') { flag_multipart=1; ptr=ptr_getchar(ptr,endptr,&endcode); } bufptr=ptr; ptr=ptr_searchendchar(ptr,endptr,endcode,&aux); if(ptr==NULL || pos<0 || pos>redata_getused(redata)) return(-1); /* malformed register */ redata_op_add(redata,pos,bufptr,aux-bufptr,NULL); pos+=aux-bufptr; } while(flag_multipart); } else if(actioncode=='D') { ptr=ptr_getlong(ptr,endptr,&pos); do { flag_multipart=0; ptr=ptr_getchar(ptr,endptr,&endcode); if(ptr!=NULL && endcode=='+') { flag_multipart=1; ptr=ptr_getchar(ptr,endptr,&endcode); } bufptr=ptr; ptr=ptr_searchendchar(ptr,endptr,endcode,&aux); if(ptr==NULL || pos<0 || (pos+(aux-bufptr))>redata_getused(redata)) return(-1); /* malformed register */ if(redata_data_compare(redata,pos,bufptr,aux-bufptr)!=0) return(-1); /* corrupted data */ redata_op_del(redata,pos,aux-bufptr,NULL); } while(flag_multipart); } else if(actioncode=='M') { ptr=ptr_getlong(ptr,endptr,&pos); ptr+=(ptr!=NULL && ptr<endptr)?1:0; ptr=ptr_getlong(ptr,endptr,&pos2); do { flag_multipart=0; ptr=ptr_getchar(ptr,endptr,&endcode); if(ptr!=NULL && endcode=='+') { flag_multipart=1; ptr=ptr_getchar(ptr,endptr,&endcode); } bufptr=ptr; ptr=ptr_searchendchar(ptr,endptr,endcode,&aux); if(ptr==NULL || pos<0 || (pos+(aux-bufptr))>redata_getused(redata) || pos2<0 || pos2>redata_getused(redata) || ((aux-bufptr)>0 && pos2>=pos && pos2<(pos+(aux-bufptr))) ) { return(-1); /* malformed register */ } if(redata_data_compare(redata,pos,bufptr,aux-bufptr)!=0) return(-1); /* corrupted data */ redata_op_move(redata,pos,(aux-bufptr),pos2,NULL); pos=(pos<pos2)?pos:(pos+(aux-bufptr)); pos2=pos2+(aux-bufptr); } while(flag_multipart); } else { return(-1); /* corrupted undobuf */ } } return(-1); } int redata_unsaved_add(redata_t *redata, undostack_t *stack, int undono) { char sep; undo_t *undo; char *sepend,*ptr,*endptr; char *buf; int k; int maxsize,newsize; char posbuf[128]; if(redata==NULL || (stack!=&(redata->undostack) && stack!=&(redata->redostack)) || undono<0 || undono>=stack->usedundo) return(-1); /* sanity check failed */ /* syntax (see loadappend): A<pos><+?><sep><text><sep>[<+?><sep><text><sep>[...]] */ undo=stack->undo+undono; if(undo->type!='A' && undo->type!='D' && undo->type!='M') return(-1); /* unrecognized undo type */ for(k=0,maxsize=0,buf=NULL;k<2;k++,maxsize=0) { ptr=stack->buf+undo->off; endptr=ptr+undo->len; if(k!=0) buf[maxsize]=undo->type; maxsize++; snprintf(posbuf,sizeof(posbuf),"%li",undo->posorig); posbuf[sizeof(posbuf)-1]='\0'; if(k!=0) strcpy(buf+maxsize,posbuf); maxsize+=strlen(posbuf); if(undo->type=='M') { snprintf(posbuf,sizeof(posbuf),",%li",undo->posdest); posbuf[sizeof(posbuf)-1]='\0'; if(k!=0) strcpy(buf+maxsize,posbuf); maxsize+=strlen(posbuf); } while(ptr<endptr) { sep=sep_select(ptr,endptr-ptr,&sepend); if(sepend!=endptr) { if(k!=0) buf[maxsize]='+'; maxsize++; } if(k!=0) buf[maxsize]=sep; maxsize++; if(k!=0) memcpy(buf+maxsize,ptr,sepend-ptr); maxsize+=sepend-ptr; if(k!=0) buf[maxsize]=sep; maxsize++; ptr=sepend; } if(k==0) { /* get mem */ if((redata->unsaved.sizebuf-redata->unsaved.sizebuf)<maxsize) { newsize=(redata->unsaved.sizebuf+maxsize+UNDOGROWSIZE-1)/UNDOGROWSIZE; newsize*=UNDOGROWSIZE; if((buf=realloc(redata->unsaved.buf,newsize))==NULL) return(-1); /* insuf. mem. */ redata->unsaved.buf=buf; redata->unsaved.sizebuf=newsize; } buf=redata->unsaved.buf+redata->unsaved.usedbuf; } } return(0); } int redata_unsaved_unadd(redata_t *redata, undostack_t *stack, int undono) { /* adds to unsaved the inverse operation to the one specified in the undo */ char sep; undo_t *undo; char *sepend,*ptr,*endptr; char *buf; int k; int maxsize,newsize; char posbuf[128]; if(redata==NULL || (stack!=&(redata->undostack) && stack!=&(redata->redostack)) || undono<0 || undono>=stack->usedundo) return(-1); /* sanity check failed */ /* syntax (see loadappend): A<pos><+?><sep><text><sep>[<+?><sep><text><sep>[...]] */ undo=stack->undo+undono; if(undo->type!='A' && undo->type!='D' && undo->type!='M') return(-1); /* unrecognized undo type */ for(k=0,maxsize=0,buf=NULL;k<2;k++,maxsize=0) { ptr=stack->buf+undo->off; endptr=ptr+undo->len; if(k!=0) buf[maxsize]=(undo->type=='A')?'D':(undo->type=='D')?'A':undo->type; maxsize++; if(undo->type=='A' || undo->type=='D') snprintf(posbuf,sizeof(posbuf),"%li",undo->posorig); else snprintf(posbuf,sizeof(posbuf),"%li",(undo->posorig<undo->posdest)?undo->posdest-undo->len:undo->posdest); posbuf[sizeof(posbuf)-1]='\0'; if(k!=0) strcpy(buf+maxsize,posbuf); maxsize+=strlen(posbuf); if(undo->type=='M') { snprintf(posbuf,sizeof(posbuf),",%li",(undo->posorig<undo->posdest)?undo->posorig:undo->posorig+undo->len); posbuf[sizeof(posbuf)-1]='\0'; if(k!=0) strcpy(buf+maxsize,posbuf); maxsize+=strlen(posbuf); } while(ptr<endptr) { sep=sep_select(ptr,endptr-ptr,&sepend); if(sepend!=endptr) { if(k!=0) buf[maxsize]='+'; maxsize++; } if(k!=0) buf[maxsize]=sep; maxsize++; if(k!=0) memcpy(buf+maxsize,ptr,sepend-ptr); maxsize+=sepend-ptr; if(k!=0) buf[maxsize]=sep; maxsize++; ptr=sepend; } if(k==0) { /* get mem */ if((redata->unsaved.sizebuf-redata->unsaved.sizebuf)<maxsize) { newsize=(redata->unsaved.sizebuf+maxsize+UNDOGROWSIZE-1)/UNDOGROWSIZE; newsize*=UNDOGROWSIZE; if((buf=realloc(redata->unsaved.buf,newsize))==NULL) return(-1); /* insuf. mem. */ redata->unsaved.buf=buf; redata->unsaved.sizebuf=newsize; } buf=redata->unsaved.buf+redata->unsaved.usedbuf; } } return(0); } int redata_unsaved_commit(redata_t *redata) { int n,nwritten; char unsname[PATH_MAX+1]; if(redata==NULL || redata->unsavedfd==-1) return(-1); for(nwritten=0;nwritten<redata->unsaved.usedbuf;nwritten+=n) { if((n=write(redata->unsavedfd,redata->unsaved.buf+nwritten,redata->unsaved.usedbuf-nwritten))<0) { close(redata->unsavedfd),redata->unsavedfd=-1; if((unsaved_genname(redata->filename,unsname,sizeof(unsname)))!=NULL) unlink(unsname); /* a corrupted unsaved is of no use, delete it */ return(-1); /* error writing */ } } redata->unsaved.usedbuf=0; return(0); } int redata_load(redata_t *redata, char *filename, int use_unsaved) { int fd,nread,totalread; int chunkno, avail; struct stat statbuf; rechunk_t *chunk; if(redata==NULL || filename==NULL) return(-1); /* sanity check failed */ redata_wipe(redata); strncpy(redata->filename,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<statbuf.st_size;totalread+=nread,nread=0) { if(chunkno>=redata->sizechunks || redata->chunks[chunkno]==NULL) { /* alloc another chunk */ if(redata_preallocate(redata,redata_getsize(redata)+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; } close(fd),fd=-1; for(chunkno=0;chunkno<redata->sizechunks;chunkno++) redata_fix_nl(redata,chunkno); for(chunkno=0;chunkno<redata->sizechunks;chunkno++) redata_whatin_refresh(redata,chunkno); /* 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;i<redata->sizechunks;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_hash(redata,redata->initialhash); redata->unsaved.usedbuf=0; return(0); } int redata_undobuf_reserve(redata_t *redata, undostack_t *stack, int minavail) { int unused; int newsize; char *newptr; if(redata==NULL || minavail<0 || (stack!=&(redata->undostack) && stack!=&(redata->redostack))) return(-1); /* sanity check failed */ unused=stack->sizebuf-stack->usedbuf; if(unused<minavail) { newsize=(stack->sizebuf+minavail+UNDOGROWSIZE-1)/UNDOGROWSIZE; newsize*=UNDOGROWSIZE; if((newptr=realloc(stack->buf,newsize))==NULL) return(-1); /* insuf. mem. */ stack->buf=newptr; stack->sizebuf=newsize; } return(0); } undo_t * redata_undo_new(redata_t *redata, undostack_t *stack, char type) { int newsize; undo_t *newptr,*undo; if(redata==NULL || (stack!=&(redata->undostack) && stack!=&(redata->redostack))) return(NULL); /* sanity check failed */ if(stack->sizeundo==stack->usedundo) { newsize=(stack->sizeundo+1+UNDOBLOCK-1)/UNDOBLOCK; newsize*=UNDOBLOCK; if((newptr=realloc(stack->undo,sizeof(undo_t)*newsize))==NULL) return(NULL); stack->undo=newptr; memset(stack->undo+stack->sizeundo,0,sizeof(undo_t)*(newsize-stack->sizeundo)); stack->sizeundo=newsize; } undo=stack->undo+stack->usedundo; stack->usedundo++; undo->type=type; undo->off=stack->usedbuf; undo->len=0; return(undo); } undo_t * redata_undo_newfromchunks(redata_t *redata, undostack_t *stack, char type, int pos1, int len) { int startpos,endpos; long startoff,endoff; undo_t *undo; int k; long used; long copyfrom,copysize; if(redata==NULL || len<=0 || (stack!=&(redata->undostack) && stack!=&(redata->redostack))) 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,stack,len)!=0) return(NULL); /* insuf. mem. */ if((undo=redata_undo_new(redata,stack,type))==NULL) return(NULL); /* insuf. mem. */ undo->off=stack->usedbuf; /* 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(stack->buf+stack->usedbuf,redata->chunks[k]->data+copyfrom,copysize); stack->usedbuf+=copysize; used+=copysize; } undo->len=used; return(undo); } undo_t * redata_undo_newfrombuf(redata_t *redata, undostack_t *stack, char type, char *buf, int buflen) { undo_t *undo; if(redata==NULL || buflen<=0 || (stack!=&(redata->undostack) && stack!=&(redata->redostack))) return(NULL); /* sanity check failed */ if(redata_undobuf_reserve(redata,stack, buflen)!=0) return(NULL); /* insuf. mem. */ if((undo=redata_undo_new(redata,stack, type))==NULL) return(NULL); /* insuf. mem. */ undo->off=stack->usedbuf; memcpy(stack->buf+stack->usedbuf,buf,buflen); stack->usedbuf+=buflen; undo->len=buflen; return(undo); } int redata_undo_movelast(redata_t *redata, undostack_t *from, undostack_t *to) { undo_t *undofrom; undo_t *undoto; if(redata==NULL || from==to || (from!=&(redata->undostack) && from!=&(redata->redostack)) || (to!=&(redata->undostack) && to!=&(redata->redostack)) || from->usedundo<1) return(-1); /* sanity check failed */ undofrom=from->undo+from->usedundo-1; if((undoto=redata_undo_newfrombuf(redata,to,undofrom->type,from->buf+undofrom->off,undofrom->len))==NULL) return(-1); /* insuf. mem. */ undoto->posorig=undofrom->posorig; undoto->posdest=undofrom->posdest; memcpy(undoto->prehash,undofrom->prehash,sizeof(undoto->prehash)); memcpy(undoto->posthash,undofrom->posthash,sizeof(undoto->posthash)); redata_undo_removelast(redata, from); return(0); } int redata_undo_removelast(redata_t *redata, undostack_t *stack) { undo_t *undo; if(redata==NULL || (stack!=&(redata->undostack) && stack!=&(redata->redostack)) || stack->usedundo<1) return(-1); /* sanity check failed */ undo=stack->undo+stack->usedundo-1; stack->usedbuf-=undo->len; memset(undo,0,sizeof(undo_t)); stack->usedundo--; return(0); } int redata_undo_wipe(redata_t *redata, undostack_t *stack) { if(redata==NULL || (stack!=&(redata->undostack) && stack!=&(redata->redostack))) return(-1); /* sanity check failed */ memset(stack->undo,0,sizeof(undo_t)*stack->usedundo); stack->usedundo=0; memset(stack->buf,0,stack->usedbuf); stack->usedbuf=0; return(0); } int redata_undo_inactivatelast(redata_t *redata, undostack_t *stack) { if(redata==NULL || (stack!=&(redata->undostack) && stack!=&(redata->redostack))) return(-1); /* sanity check failed */ if(stack->usedundo==0) return(-1); stack->usedundo--; stack->usedbuf-=stack->undo[stack->usedundo].len; return(0); } int redata_undo_reactivatelast(redata_t *redata, undostack_t *stack) { if(redata==NULL || (stack!=&(redata->undostack) && stack!=&(redata->redostack))) return(-1); /* sanity check failed */ if(stack->usedundo==stack->sizeundo) return(-1); stack->usedbuf+=stack->undo[stack->usedundo].len; stack->usedundo++; return(0); } int redata_op_add(redata_t *redata, long insertpos, char *buf, long buflen, undostack_t *fromhere) { int chunkno; long pos; undo_t *undo; rechunk_t *chunk,*nextchunk; long avail,nextavail; int i; int needed; char *ptr; if(redata==NULL || buf==NULL || buflen<0 || insertpos<0 || insertpos>redata_getused(redata) || (fromhere!=NULL && (fromhere!=&(redata->undostack) && fromhere!=&(redata->redostack)))) return(-1); /* sanity check failed */ if(redata_getposptr(redata,insertpos,&chunkno,&pos)==-1) return(-1); /* invalid pos */ if(fromhere!=&(redata->undostack)) { if((undo=redata_undo_newfrombuf(redata,&(redata->undostack),'A',buf,buflen))==NULL) return(-1); /* couldn't create undo struct */ redata_hash(redata,undo->prehash); } else { undo=NULL; } chunk=redata->chunks[chunkno]; avail=redata->chunkdatasize-chunk->useddata; nextchunk=((chunkno+1)<redata->sizechunks)?redata->chunks[chunkno+1]:NULL; nextavail=(nextchunk==NULL)?0:redata->chunkdatasize-nextchunk->useddata; if(avail>=buflen) { /* fits in current chunk */ redata_chunk_insertdata(redata,chunkno,pos,buf,buflen); redata_fix_nl(redata,chunkno); nextchunk=chunk; } else if((avail+nextavail)>=buflen) { /* part fits in current chunk, part in next chunk */ int bothering; bothering=chunk->useddata-pos; bothering=(bothering>nextavail)?nextavail:bothering; redata_chunk_movedata(redata,chunkno,chunk->useddata-bothering,chunkno+1,0,bothering); avail=redata->chunkdatasize-chunk->useddata; avail=(avail>buflen)?buflen:avail; redata_chunk_insertdata(redata,chunkno,pos,buf,avail); redata_chunk_insertdata(redata,chunkno+1,0,buf+avail,buflen-avail); } else { /* will need to add more chunks */ needed=(buflen+redata->chunkdatasize-1)/redata->chunkdatasize; needed*=redata->chunkdatasize; needed+=(chunk->useddata-pos); if(redata_preallocate(redata,redata_getsize(redata)+needed)!=0) { if(undo!=NULL) { redata->undostack.usedundo--; redata->undostack.usedbuf-=buflen; memset(undo,0,sizeof(undo_t)); } return(-1); /* insuf. mem. */ } redata_chunk_insertnew(redata,chunkno); redata_chunk_movedata(redata,chunkno,pos,chunkno+1,0,chunk->useddata-pos); nextchunk=redata->chunks[chunkno+1]; avail=redata->chunkdatasize-chunk->useddata; avail=(avail>buflen)?buflen:avail; redata_chunk_insertdata(redata,chunkno,chunk->useddata,buf,avail); for(ptr=buf+avail,i=chunkno;(ptr-buf)<buflen;i++,ptr+=avail) { redata_chunk_insertnew(redata,i); avail=redata->chunkdatasize-redata->chunks[i+1]->useddata; avail=(avail>(buflen-(ptr-buf)))?(buflen-(ptr-buf)):avail; redata_chunk_insertdata(redata,i+1,0,ptr,avail); } } /* fix nl until nextchunk (...fix_nl can insert chunks) */ for(i=chunkno;i<redata->sizechunks;i++) { redata_fix_nl(redata,i); if(redata->chunks[i]==nextchunk) break; } /* activate undo */ if(undo!=NULL) { /* new or from redo stack */ undo->posorig=undo->posdest=insertpos; redata_hash(redata,undo->posthash); if(fromhere==&(redata->redostack)) redata_undo_removelast(redata,&(redata->redostack)); else redata_undo_wipe(redata,&(redata->redostack)); } else { /* from undo stack */ redata_undo_movelast(redata,&(redata->undostack),&(redata->redostack)); } /* add to unsaved */ redata_unsaved_add(redata,&(redata->undostack),redata->undostack.usedundo-1); /* compact if needed */ if(redata_getsize(redata)>(redata->chunkdatasize) && redata_getavailable(redata)>(redata_getsize(redata)/2)) redata_compact(redata); return(0); } int redata_op_del(redata_t *redata, long delpos, long size, undostack_t *fromhere) { int chunkno,curchunk; long pos; undo_t *undo; rechunk_t *chunk; long ndel; long curpos,curdel; if(redata==NULL || size<0 || delpos<0 || (delpos+size)>redata_getused(redata) || (fromhere!=NULL && (fromhere!=&(redata->undostack) && fromhere!=&(redata->redostack)))) return(-1); /* sanity check failed */ if(redata_getposptr(redata,delpos,&chunkno,&pos)==-1) return(-1); /* invalid pos */ if(fromhere!=&(redata->undostack)) { if((undo=redata_undo_newfromchunks(redata,&(redata->undostack),'D',delpos,size))==NULL) return(-1); /* couldn't create undo struct */ redata_hash(redata,undo->prehash); } else { undo=NULL; } for(curchunk=chunkno,ndel=0;ndel<size;curchunk++) { curpos=(curchunk==chunkno)?pos:0; curdel=redata->chunks[curchunk]->useddata-curpos; curdel=(curdel>(size-ndel))?(size-ndel):curdel; redata_chunk_deletedata(redata,curchunk,curpos,curdel); ndel+=curdel; } /* fix nl and delete unused chunks */ for(curchunk--;curchunk>=chunkno;curchunk--) { chunk=redata->chunks[curchunk]; if(chunk->useddata==0) { /* move this chunk to the end */ redata_chunk_deletechunk(redata,curchunk); } else redata_fix_nl(redata,curchunk); } /* activate undo */ if(undo!=NULL) { /* new or from redo stack */ undo->posorig=undo->posdest=delpos; redata_hash(redata,undo->posthash); if(fromhere==&(redata->redostack)) redata_undo_removelast(redata,&(redata->redostack)); else redata_undo_wipe(redata,&(redata->redostack)); } else { /* from undo stack */ redata_undo_movelast(redata,&(redata->undostack),&(redata->redostack)); } /* add to unsaved */ redata_unsaved_add(redata,&(redata->undostack),redata->undostack.usedundo-1); /* compact if needed */ if(redata_getsize(redata)>(redata->chunkdatasize) && redata_getavailable(redata)>(redata_getsize(redata)/2)) redata_compact(redata); return(0); } int redata_op_move(redata_t *redata, long posorig, long size, long posdest, undostack_t *fromhere) { int chunkno,dchunkno,curchunk; long pos,dpos; undo_t *undo; rechunk_t *chunk; if(redata==NULL || size<0 || posorig<0 || (posorig+size)>redata_getused(redata) || posdest<0 || (posdest>=posorig && posdest<(posorig+size)) || (fromhere!=NULL && (fromhere!=&(redata->undostack) && fromhere!=&(redata->redostack)))) return(-1); /* sanity check failed */ if(redata_getposptr(redata,posorig,&chunkno,&pos)==-1) return(-1); /* invalid pos */ if(redata_getposptr(redata,posdest,&dchunkno,&dpos)==-1) return(-1); /* invalid pos */ if(fromhere!=&(redata->undostack)) { if((undo=redata_undo_newfromchunks(redata,&(redata->undostack),'M',posorig,size))==NULL) return(-1); /* couldn't create undo struct */ /* inactivate the undo so we are able to use return(-1) without removing it */ redata_undo_inactivatelast(redata,&(redata->undostack)); redata_hash(redata,undo->prehash); } else { undo=NULL; } if((pos+size)<=redata->chunks[chunkno]->useddata && (chunkno==dchunkno || (redata->chunkdatasize-redata->chunks[dchunkno]->useddata)>=size)) { /* trivial case: (all the data is in the same chunk) AND (it is intra-chunk move or destination chunk has enough avail. space) */ redata_chunk_movedata(redata, chunkno, pos, dchunkno, dpos, size); } else { /* data spans several chunks, no space on dest, etc: do it the hard way */ /* separate the selected data into its own chunk(s) */ /* lower positions have to make the boundary first (or risk undoing it with next create boundary) */ if(posdest<posorig) { /* make a chunk boundary in posdest */ if(redata_getposptr(redata,posdest,&chunkno,&pos)==-1 || redata_chunk_splithere(redata,chunkno,pos)!=0) return(-1); /* invalid pos or insuf. mem */ } /* make a chunk boundary in posorig and in posorig+size */ if(redata_getposptr(redata,posorig,&chunkno,&pos)==-1 || redata_chunk_splithere(redata,chunkno,pos)!=0) return(-1); /* invalid pos or insuf. mem */ if(redata_getposptr(redata,posorig+size,&chunkno,&pos)==-1 || redata_chunk_splithere(redata,chunkno,pos)!=0) return(-1); /* invalid pos or insuf. mem */ if(posdest>posorig) { /* make a chunk boundary in posdest */ if(redata_getposptr(redata,posdest,&chunkno,&pos)==-1 || redata_chunk_splithere(redata,chunkno,pos)!=0) return(-1); /* invalid pos or insuf. mem */ } /* reorder the chunks */ { int schunkno; long spos; if(redata_getposptr(redata,posorig,&chunkno,&pos)==-1) return(-1); /* invalid pos or insuf. mem */ if(redata_getposptr(redata,posorig+size,&schunkno,&spos)==-1) return(-1); /* invalid pos or insuf. mem */ if(redata_getposptr(redata,posdest,&dchunkno,&dpos)==-1) return(-1); /* invalid pos or insuf. mem */ if(pos!=0) chunkno++; if(spos!=0) schunkno++; if(dpos!=0) dchunkno++; if(chunkno<0 || schunkno<0 || dchunkno<0 || chunkno>=redata->sizechunks || schunkno>=redata->sizechunks || dchunkno>=redata->sizechunks) return(-1); /* ERROR: INTERNAL ERROR */ /* reorder inplace inverting the bytes (as in flipping a image) */ if(chunkno<dchunkno) { meminvert(redata->chunks+chunkno,redata->chunks+dchunkno); meminvert(redata->chunks+dchunkno-(schunkno-chunkno),redata->chunks+dchunkno); meminvert(redata->chunks+chunkno,redata->chunks+dchunkno-(schunkno-chunkno)); } else { meminvert(redata->chunks+dchunkno,redata->chunks+schunkno); meminvert(redata->chunks+dchunkno,redata->chunks+dchunkno+(schunkno-chunkno)); meminvert(redata->chunks+dchunkno+(schunkno-chunkno),redata->chunks+schunkno); } } } /* fix nl and delete unused chunks */ if(posorig<posdest) { if(redata_getposptr(redata,posorig,&chunkno,&pos)==-1) return(-1); /* invalid pos */ if(redata_getposptr(redata,posdest,&dchunkno,&dpos)==-1) return(-1); /* invalid pos */ } else { /* posorig>posdest */ if(redata_getposptr(redata,posdest,&chunkno,&pos)==-1) return(-1); /* invalid pos */ if(redata_getposptr(redata,posorig+size,&dchunkno,&dpos)==-1) return(-1); /* invalid pos */ } for(curchunk=dchunkno;curchunk>=chunkno;curchunk--) { chunk=redata->chunks[curchunk]; if(chunk->useddata==0) { /* move this chunk to the end */ redata_chunk_deletechunk(redata,curchunk); } else redata_fix_nl(redata,curchunk); } /* activate undo */ if(fromhere!=&(redata->undostack)) { /* reactivate the undo, now it is fine */ redata_undo_reactivatelast(redata,&(redata->undostack)); } if(undo!=NULL) { /* new or from redo stack */ undo->posorig=posorig; undo->posdest=posdest; redata_hash(redata,undo->posthash); if(fromhere==&(redata->redostack)) redata_undo_removelast(redata,&(redata->redostack)); else redata_undo_wipe(redata,&(redata->redostack)); } else { /* from undo stack */ redata_undo_movelast(redata,&(redata->undostack),&(redata->redostack)); } /* add to unsaved */ redata_unsaved_add(redata,&(redata->undostack),redata->undostack.usedundo-1); /* compact if needed */ if(redata_getsize(redata)>(redata->chunkdatasize) && redata_getavailable(redata)>(redata_getsize(redata)/2)) redata_compact(redata); return(0); } int redata_op_undo(redata_t *redata) { undo_t *undo; if(redata==NULL || redata->undostack.usedundo<1) return(-1); /* sanity check failed */ undo=redata->undostack.undo+redata->undostack.usedundo-1; if(undo->type=='A') { /* ADD */ redata_op_del(redata,undo->posorig,undo->len,&(redata->undostack)); } else if(undo->type=='D') { /* DEL */ redata_op_add(redata,undo->posorig,redata->undostack.buf+undo->off,undo->len,&(redata->undostack)); } else if(undo->type=='M') { /* MOVE */ if(undo->posorig<undo->posdest) redata_op_move(redata,undo->posdest-undo->len, undo->len, undo->posorig,&(redata->undostack)); else redata_op_move(redata,undo->posdest, undo->len, undo->posorig+undo->len,&(redata->undostack)); } else return(-1); /* unknown operation */ return(-1); } int redata_op_redo(redata_t *redata) { undo_t *undo; if(redata==NULL || redata->redostack.usedundo<1) return(-1); /* sanity check failed */ undo=redata->redostack.undo+redata->redostack.usedundo-1; if(undo->type=='A') { /* ADD */ redata_op_add(redata,undo->posorig,redata->redostack.buf+undo->off,undo->len,&(redata->redostack)); } else if(undo->type=='D') { /* DEL */ redata_op_del(redata,undo->posorig,undo->len,&(redata->redostack)); } else if(undo->type=='M') { /* MOVE */ redata_op_move(redata,undo->posorig, undo->len, undo->posdest,&(redata->redostack)); } else return(-1); /* unknown operation */ return(-1); } int redata_data_compare(redata_t *redata, long cmppos, char *buf, long buflen) { int chunkno; long pos; long compared; long n; int res; if(redata==NULL || cmppos<0 || buf==NULL || buflen<0 || cmppos>redata_getused(redata) || (cmppos+buflen)>redata_getused(redata)) return(-1); /* sanity check failed */ if(redata_getposptr(redata,cmppos,&chunkno,&pos)==-1) return(-1); /* invalid pos */ for(compared=0,n=0;compared<buflen && chunkno<redata->sizechunks;chunkno++,pos=0,compared+=n) { n=redata->chunks[chunkno]->useddata-pos; n=(n<0)?0:((compared+n)>buflen)?(buflen-compared):n; if((res=memcmp(redata->chunks[chunkno]->data+pos,buf+compared,n))!=0) return(res); } if(compared<buflen) return(1); return(0); } int redata_compact(redata_t *redata) { /* compact and free surplus chunks */ /* criterion: */ /* 1. if two neighbouring chunks could join with 10% free in result chunk, do it */ /* 2. if there are more than 2 unused chunks free at the end, free all unused chunks except two */ #warning TODO return(-1); } int redata_hash(redata_t *redata, char *resbuf129bytes) { return(redata_hash_gen(redata,NULL,NULL,0,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(NULL,filename,NULL,0,resbuf129bytes)); } int redata_memhash(redata_t *redata, char *buf, long buflen, char *resbuf129bytes) { if(resbuf129bytes!=NULL) *resbuf129bytes='\0'; if(buf==NULL || buflen<0) return(-1); return(redata_hash_gen(NULL,NULL,buf,buflen,resbuf129bytes)); } static int redata_hash_gen(redata_t *redata, char *filename, char *buf, long buflen, char *resbuf129bytes) { static char conv[]={"0123456789ABCDEF"}; sha3_context sha3; unsigned char *hash; int i,c; int fd=-1; struct stat statbuf; if(resbuf129bytes==NULL) return(-1); /* sanity check failed */ if(resbuf129bytes!=NULL) *resbuf129bytes='\0'; if((redata==NULL && filename==NULL && buf==NULL) || (redata!=NULL && (filename!=NULL || buf!=NULL)) || (filename!=NULL && (redata!=NULL || buf!=NULL)) || (buf!=NULL && (redata!=NULL || filename!=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(redata!=NULL) { for(i=0;i<redata->sizechunks;i++) sha3_Update(&sha3,(void *) redata->chunks[i]->data,redata->chunks[i]->useddata); } else if(filename!=NULL) { char buf[16384]; long totalread,nread; for(totalread=0,nread=0;totalread<statbuf.st_size;totalread+=nread,nread=0) { if((nread=read(fd,buf,sizeof(buf)))<=0) { close(fd),fd=-1; return(-1); /* short read */ } sha3_Update(&sha3,(void *) buf,nread); } close(fd),fd=-1; } else if(buf!=NULL) { sha3_Update(&sha3,(void *) buf,buflen); } hash=(unsigned char *)sha3_Finalize(&sha3); for(i=0;i<64;i++) { c=hash[i]; resbuf129bytes[i<<1]=conv[((c>>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(bufsize<filenamelen) return(NULL); name=buf; } for(ptr=filename+strlen(filename);ptr>filename && 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); } static char * ptr_getlong(char *ptr,char *endptr,long *data) { long l,s; if(ptr==NULL || endptr==NULL || ptr>endptr) return(NULL); s=1; if(ptr<endptr && *ptr=='-') { s=-1; ptr++; } for(l=0;ptr<endptr && *ptr>='0' && *ptr<='9';ptr++) { l*=10; l+=(*ptr-'0'); } l*=s; if(data!=NULL) *data=l; return(ptr); } static char * ptr_getchar(char *ptr,char *endptr,char *data) { if(ptr==NULL || endptr==NULL || ptr>endptr) return(NULL); if(data!=NULL) *data=*ptr; ptr++; return(ptr); } static char * ptr_searchendchar(char *ptr, char *endptr, char endchar, char **endcharpos) { char *aux; if(ptr==NULL || endptr==NULL || ptr>endptr) return(NULL); if((aux=memchr(ptr,endchar,endptr-ptr))==NULL) return(NULL); if(endcharpos!=NULL) *endcharpos=aux; return(aux+1); } static char sep_select(char *buf, int bufsize, char **pos) { static char seps[]={"$%@!|&/='\"^*;:,-_"}; char *ptr,*bestptr; int i,besti; bestptr=buf; if(pos!=NULL) *pos=NULL; for(i=0,besti=0;i<sizeof(seps);i++) { if((ptr=memchr(buf,seps[i],bufsize))==NULL) return(seps[i]); if(ptr>bestptr) { besti=0; bestptr=ptr; } } if(pos!=NULL) *pos=bestptr; return(seps[besti]); } static void * mymemrchr(const void *s, int c, size_t n) { long i; void *res=NULL; unsigned char b; b=(*((unsigned int *)(&c)))&0xff; for(i=0;i<n;i++) { if(((unsigned char *)s)[i]==b) res=(((unsigned char *)s)+i); } return(res); } static void meminvert(void *start, void *end) { unsigned char *a=(unsigned char *)start; unsigned char *b=(unsigned char *)end; unsigned char t; for(b=b-1;a<b;a++,b--) { t=*a; *a=*b; *b=t; } }