/*
* re_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 <stdarg.h>
#include "re_data.h"
#include "sha3/sha3.h"
/* DEFAULT CHUNKSIZE: 1024 */
//#define CHUNKSIZE 65536
//#define CHUNKSIZE 32768
//#define CHUNKSIZE 4096
//#define CHUNKSIZE 1024
//#define CHUNKSIZE 160
#define CHUNKSIZE 16
#define UNDOBLOCK 1024
#define ADDNBLOCK 1024
#define UNDOGROWSIZE (256*1024)
#define SECURESAVEPREFIX "."
#define SECURESAVEPOSTFIX ".saving"
static int redata_hash_gen(redata_t *redata, char *filename, char *buf, long buflen, char *resbuf129bytes);
static char *securesave_genname(char *filename, char *buf, int bufsize);
static void *mymemrchr(const void *s, int c, size_t n);
static void meminvert(void *start, void *end);
static size_t memrchroffset(char *ptr, int c, size_t n);
#if 0
static void
redata_debug_chunkdump(redata_t *redata, char *title)
{
int m,k;
char c;
title=(title==NULL)?"":title;
fprintf(stderr,"%s:CHUNKDUMP (sizechunks:%i)\n",title,redata->sizechunks);
for(m=0;m<redata->sizechunks;m++) {
fprintf(stderr,"%s:chunk[%i] len:%-5i data:\"",title,m,redata->chunks[m]->useddata);
for(k=0;k<redata->chunks[m]->useddata;k++) {
c=redata->chunks[m]->data[k];
if(c=='\n' || c=='\0')
fprintf(stderr,"\\%c",(c=='\n')?'n':'0');
else if(c<' ' || c>'~' || c=='\\')
fprintf(stderr,"\\x%02X",((unsigned char *)redata->chunks[m]->data)[k]);
else
fprintf(stderr,"%c",c);
}
fprintf(stderr,"\"\n");
}
}
#define CHUNKDEBUG(a) redata_debug_chunkdump a
#else
#define CHUNKDEBUG(a)
#endif
redata_t *
redata_init(int (*pluginregisterfn)(redata_t *redata, redata_plugin_t *slot), ...)
{
redata_t *redata;
va_list args;
int nargs;
int res;
int (*fn)(redata_t *redata, redata_plugin_t *slot);
/* count number of plugins */
va_start(args,pluginregisterfn);
for(nargs=0,fn=pluginregisterfn;fn!=NULL;fn=va_arg(args,int (*)(redata_t *redata, redata_plugin_t *slot)))
nargs++;
va_end(args);
/* get memory */
if((redata=malloc(sizeof(redata_t)+sizeof(redata_plugin_t)*(nargs-1)))==NULL)
return(NULL); /* sanity check failed */
memset(redata,0,sizeof(redata_t)+sizeof(redata_plugin_t)*(nargs-1));
redata->sizeplugins=nargs;
/* 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;
/* plugins */
va_start(args,pluginregisterfn);
for(nargs=0,fn=pluginregisterfn;fn!=NULL;fn=va_arg(args,int (*)(redata_t *redata,redata_plugin_t *slot))) {
res=fn(redata,redata->plugins+nargs);
redata->plugins[nargs].active=(res==0)?1:0;
nargs++;
}
va_end(args);
/* filename */
redata->filename[0]='\0';
/* all done */
return(redata);
}
void
redata_free(redata_t *redata)
{
int i;
if(redata==NULL)
return; /* nothing to do */
/* plugins */
for(i=0;i<redata->sizeplugins;i++) {
if(redata->plugins[i].unregister!=NULL)
redata->plugins[i].unregister(redata,redata->plugins+i,redata->filename);
memset(redata->plugins+i,0,sizeof(redata_plugin_t));
}
/* 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;
/* addnbuf */
if(redata->addnbuf!=NULL)
free(redata->addnbuf),redata->addnbuf=NULL,redata->sizeaddnbuf=0;
/* free main struct */
free(redata),redata=NULL;
return;
}
void
redata_idleproc(redata_t *redata, char *filename)
{
int i;
if(redata==NULL)
return;
for(i=0;i<redata->sizeplugins;i++) {
if(redata->plugins[i].commit!=NULL)
redata->plugins[i].commit(redata,redata->plugins+i,filename);
}
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, int *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;
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);
/* plugins */
for(i=0;i<redata->sizeplugins;i++) {
if(redata->plugins[i].wipe!=NULL)
redata->plugins[i].wipe(redata,redata->plugins+i,redata->filename);
}
redata->filename[0]='\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;
redata_chunk_unfreshnext(redata, chunkno);
}
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;
redata_chunk_unfreshrange(redata,chunkfrom,chunkto);
redata_chunk_unfreshnext(redata,chunkfrom);
redata_chunk_unfreshnext(redata,chunkto);
} 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;
redata_chunk_unfreshnext(redata,chunkfrom);
}
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;
redata->available-=buflen;
chunk->whatin_fresh=0;
redata_chunk_unfreshnext(redata,chunkto);
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;
redata->available+=n;
chunk->whatin_fresh=0;
redata_chunk_unfreshnext(redata,chunkno);
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_chunk_unfreshnext(redata_t *redata, int chunkno)
{
int next;
if(redata==NULL || chunkno<0 || chunkno>=redata->sizechunks)
return(-1); /* sanity check failed */
/* invalidate whatin for next chunks until one not empty */
for(next=chunkno+1;next<redata->sizechunks;next++) {
redata->chunks[next]->whatin_fresh=0;
if(redata->chunks[next]->useddata!=0)
break;
}
return(0);
}
int
redata_chunk_unfreshrange(redata_t *redata, int fromchunkno, int tochunkno)
{
int chunkno;
if(redata==NULL || fromchunkno<0 || fromchunkno>=redata->sizechunks || tochunkno<0 || tochunkno>=redata->sizechunks)
return(-1); /* sanity check failed */
if(fromchunkno>tochunkno) {
chunkno=fromchunkno;
fromchunkno=tochunkno;
tochunkno=chunkno;
}
/* invalidate whatin in range */
for(chunkno=fromchunkno;chunkno<=tochunkno;chunkno++)
redata->chunks[chunkno]->whatin_fresh=0;
return(0);
}
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;
if(chunkno>0) {
int prev;
for(prev=chunkno-1;prev>0 && redata->chunks[prev]->useddata==0;prev--)
;
if(redata->chunks[prev]->useddata>0 && redata->chunks[prev]->data[redata->chunks[prev]->useddata-1]!='\n')
chunk->whatin.iscontinuation=1;
}
chunk->whatin_fresh=1;
return(0);
}
int
redata_fix_nl(redata_t *redata, int chunkno)
{
rechunk_t *chunk,*nextchunk;
int linesize, nextlinesize, avail, nextavail;
unsigned char *ptr,*nextptr;
/* 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) */
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_loadquestions_setup(redata_t *redata, char *filename)
{
int i;
if(redata==NULL || filename==NULL)
return(-1); /* sanity check failed */
redata_loadquestions_wipe(redata);
/* unsaved and other plugins: loadquestion (pre-to-load) */
for(i=0;i<redata->sizeplugins;i++) {
if(!redata->plugins[i].active)
continue;
if(redata->plugins[i].loadquestion!=NULL)
redata->plugins[i].loadquestion(redata,redata->plugins+i,filename);
}
return(0);
}
question_t *
redata_loadquestions_getnext(redata_t *redata)
{
int i;
if(redata==NULL)
return(NULL); /* sanity check failed */
for(i=0;i<redata->sizeplugins;i++) {
if(!redata->plugins[i].active || redata->plugins[i].question.active==0)
continue;
if(redata->plugins[i].question.selectedoption==-1)
return(&(redata->plugins[i].question));
}
return(NULL); /* no more questions */
}
int
redata_loadquestions_reply(redata_t *redata, question_t *question, int selectedoption)
{
if(redata==NULL || question==NULL || selectedoption<-1 || selectedoption>=question->nopts)
return(-1);
question->selectedoption=(selectedoption==-1)?question->defaultoption:selectedoption;
return(0);
}
int
redata_loadquestions_wipe(redata_t *redata)
{
int i;
if(redata==NULL)
return(-1);
for(i=0;i<redata->sizeplugins;i++) {
if(!redata->plugins[i].active)
continue;
memset(redata->plugins[i].questionfilename,0,sizeof(redata->plugins[i].questionfilename));
memset(&(redata->plugins[i].question),0,sizeof(redata->plugins[i].question));
redata->plugins[i].question.active=0;
redata->plugins[i].question.selectedoption=-1;
}
return(0);
}
int
redata_load(redata_t *redata, char *filename, char **errordesc)
{
#warning TODO: what to do when there plugins with questions AFTER loading instead of before loading...
int fd,nread,totalread;
int chunkno, avail;
struct stat statbuf;
rechunk_t *chunk;
int i;
int reply;
if(redata==NULL || filename==NULL) {
if(errordesc!=NULL)
*errordesc="Internal error";
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;
if(errordesc!=NULL)
*errordesc="File not found, couldn't query size or not a regular file";
return(-1); /* file not found, couldn't query size or not a regular file */
}
/* preallocate 10% more than needed */
if(redata_preallocate(redata,statbuf.st_size+(statbuf.st_size/10)+1 )) {
close(fd),fd=-1;
if(errordesc!=NULL)
*errordesc="Insufficient memory";
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;
if(errordesc!=NULL)
*errordesc="Short read loading file";
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 and other plugins: postload */
for(i=0;i<redata->sizeplugins;i++) {
if(!redata->plugins[i].active)
continue;
if(redata->plugins[i].postload!=NULL)
redata->plugins[i].postload(redata,redata->plugins+i,filename);
}
redata_loadquestions_wipe(redata);
#warning TODO: IMPLEMENT POSTLOADQUESTIONS (call now loadquestion_postload).
/* all done */
return(0);
}
int
redata_save(redata_t *redata, char *filename, char **errordesc)
{
int fd;
int i,n;
char tmpfile[PATH_MAX+1];
if(redata==NULL || filename==NULL) {
if(errordesc!=NULL)
*errordesc="Internal error";
return(-1); /* sanity check failed */
}
if((securesave_genname(redata->filename,tmpfile,sizeof(tmpfile)))==NULL) {
if(errordesc!=NULL)
*errordesc="Malformed filename";
return(-1); /* malformed filename */
}
if((fd=open(tmpfile,O_WRONLY|O_TRUNC|O_CREAT,0644))==-1) {
if(errordesc!=NULL)
*errordesc="Couldn't open file for writing";
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);
if(errordesc!=NULL)
*errordesc="Short write saving file";
return(-1); /* short write */
}
}
close(fd),fd=-1;
if(rename(tmpfile,filename)!=0) {
unlink(tmpfile);
if(errordesc!=NULL)
*errordesc="Couldn't overwrite old file";
return(-1); /* couldn't overwrite old file */
}
for(i=0;i<redata->sizeplugins;i++) {
if(redata->plugins[i].postsave!=NULL)
redata->plugins[i].postsave(redata,redata->plugins+i,redata->filename,filename);
}
strncpy(redata->filename,filename,sizeof(redata->filename));
redata->filename[sizeof(redata->filename)-1]='\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 startchunkno,endchunkno;
int 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,&startchunkno,&startoff)==-1 ||
redata_getposptr(redata,pos1+len,&endchunkno,&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=startchunkno,used=0;k<=endchunkno;k++) {
if(k==startchunkno && k==endchunkno) {
copyfrom=startoff;
copysize=endoff-startoff;
} else if(k==startchunkno) {
copyfrom=startoff;
copysize=redata->chunks[k]->useddata-startoff;
} else if(k==endchunkno) {
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;
#ifdef REDATA_HASHUNDO
memcpy(undoto->prehash,undofrom->prehash,sizeof(undoto->prehash));
memcpy(undoto->posthash,undofrom->posthash,sizeof(undoto->posthash));
#endif
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_undo_groupinit(redata_t *redata, undostack_t *stack)
{
/* stores the current position in (undostack).groupinit */
#warning TODO
return(-1);
}
int redata_undo_groupcommit(redata_t *redata, undostack_t *stack)
{
/* marks as hint_groupedwithnext from stored groupinit position in undostack to last-1 position (if groupinit pos is the last pos, do nothing)*/
#warning TODO
return(-1);
}
int
redata_op_add(redata_t *redata, long insertpos, char *buf, long buflen, undostack_t *fromhere)
{
int chunkno;
int offset;
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(insertpos==0 && redata->sizechunks==0) {
/* add the first chunk */
redata_preallocate(redata,buflen);
}
if(redata_getposptr(redata,insertpos,&chunkno,&offset)==-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 */
#ifdef REDATA_HASHUNDO
redata_hash(redata,undo->prehash);
#endif
} 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,offset,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-offset;
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,offset,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-offset);
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,offset,chunkno+1,0,chunk->useddata-offset);
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;
#ifdef REDATA_HASHUNDO
redata_hash(redata,undo->posthash);
#endif
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 plugins (unsaved,...) */
for(i=0;i<redata->sizeplugins;i++) {
if(redata->plugins[i].add!=NULL)
redata->plugins[i].add(redata,redata->plugins+i,redata->undostack.undo+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_addn(redata_t *redata, long pos, char character, long n, undostack_t *fromhere)
{
if(redata==NULL || n<0)
return(-1); /* sanity check failed */
if(n==0)
return(0); /* nothing to do */
if(redata->sizeaddnbuf<n) {
char *newptr;
long newsize;
newsize=(n+1+ADDNBLOCK)/ADDNBLOCK;
newsize*=ADDNBLOCK;
if((newptr=realloc(redata->addnbuf,newsize))==NULL)
return(-1); /* insuf. mem. */
redata->addnbuf=newptr;
redata->sizeaddnbuf=newsize;
}
memset(redata->addnbuf,character,n);
redata->addnbuf[n]='\0';
return(redata_op_add(redata, pos, redata->addnbuf, n, fromhere));
}
int
redata_op_del(redata_t *redata, long delpos, long size, undostack_t *fromhere)
{
int chunkno,curchunk;
int offset;
undo_t *undo;
rechunk_t *chunk;
long ndel;
long curpos,curdel;
int i;
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,&offset)==-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 */
#ifdef REDATA_HASHUNDO
redata_hash(redata,undo->prehash);
#endif
} else {
undo=NULL;
}
for(curchunk=chunkno,ndel=0;ndel<size;curchunk++) {
curpos=(curchunk==chunkno)?offset: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;
#ifdef REDATA_HASHUNDO
redata_hash(redata,undo->posthash);
#endif
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 plugins (unsaved,...) */
for(i=0;i<redata->sizeplugins;i++) {
if(redata->plugins[i].add!=NULL)
redata->plugins[i].add(redata,redata->plugins+i,redata->undostack.undo+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;
int offset,doffset;
undo_t *undo;
rechunk_t *chunk;
int i;
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,&offset)==-1)
return(-1); /* invalid pos */
if(redata_getposptr(redata,posdest,&dchunkno,&doffset)==-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));
#ifdef REDATA_HASHUNDO
redata_hash(redata,undo->prehash);
#endif
} else {
undo=NULL;
}
if((offset+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, offset, dchunkno, doffset, 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,&offset)==-1
|| redata_chunk_splithere(redata,chunkno,offset)!=0)
return(-1); /* invalid pos or insuf. mem */
}
/* make a chunk boundary in posorig and in posorig+size */
if(redata_getposptr(redata,posorig,&chunkno,&offset)==-1
|| redata_chunk_splithere(redata,chunkno,offset)!=0)
return(-1); /* invalid pos or insuf. mem */
if(redata_getposptr(redata,posorig+size,&chunkno,&offset)==-1
|| redata_chunk_splithere(redata,chunkno,offset)!=0)
return(-1); /* invalid pos or insuf. mem */
if(posdest>posorig) {
/* make a chunk boundary in posdest */
if(redata_getposptr(redata,posdest,&chunkno,&offset)==-1
|| redata_chunk_splithere(redata,chunkno,offset)!=0)
return(-1); /* invalid pos or insuf. mem */
}
/* reorder the chunks */
{
int schunkno;
int soffset;
if(redata_getposptr(redata,posorig,&chunkno,&offset)==-1)
return(-1); /* invalid pos or insuf. mem */
if(redata_getposptr(redata,posorig+size,&schunkno,&soffset)==-1)
return(-1); /* invalid pos or insuf. mem */
if(redata_getposptr(redata,posdest,&dchunkno,&doffset)==-1)
return(-1); /* invalid pos or insuf. mem */
if(offset!=0)
chunkno++;
if(soffset!=0)
schunkno++;
if(doffset!=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,&offset)==-1)
return(-1); /* invalid pos */
if(redata_getposptr(redata,posdest,&dchunkno,&doffset)==-1)
return(-1); /* invalid pos */
} else { /* posorig>posdest */
if(redata_getposptr(redata,posdest,&chunkno,&offset)==-1)
return(-1); /* invalid pos */
if(redata_getposptr(redata,posorig+size,&dchunkno,&doffset)==-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;
#ifdef REDATA_HASHUNDO
redata_hash(redata,undo->posthash);
#endif
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 plugins (unsaved,...) */
for(i=0;i<redata->sizeplugins;i++) {
if(redata->plugins[i].add!=NULL)
redata->plugins[i].add(redata,redata->plugins+i,redata->undostack.undo+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, long *newcursorpos)
{
undo_t *undo;
long newpos=0;
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 */
newpos=undo->posorig;
redata_op_del(redata,undo->posorig,undo->len,&(redata->undostack));
} else if(undo->type=='D') { /* DEL */
newpos=undo->posorig+undo->len;
redata_op_add(redata,undo->posorig,redata->undostack.buf+undo->off,undo->len,&(redata->undostack));
} else if(undo->type=='M') { /* MOVE */
newpos=undo->posorig+undo->len;
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 */
if(newcursorpos!=NULL)
*newcursorpos=newpos;
#warning TODO: Is it neccessary to do an unadd to the plugins?
return(0);
}
int
redata_op_redo(redata_t *redata, long *newcursorpos)
{
undo_t *undo;
long newpos;
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 */
newpos=undo->posorig+undo->len;
redata_op_add(redata,undo->posorig,redata->redostack.buf+undo->off,undo->len,&(redata->redostack));
} else if(undo->type=='D') { /* DEL */
newpos=undo->posorig;
redata_op_del(redata,undo->posorig,undo->len,&(redata->redostack));
} else if(undo->type=='M') { /* MOVE */
newpos=undo->posorig+undo->len;
redata_op_move(redata,undo->posorig, undo->len, undo->posdest,&(redata->redostack));
} else
return(-1); /* unknown operation */
#warning TODO: Is it neccessary to do an unadd to the plugins?
if(newcursorpos!=NULL)
*newcursorpos=newpos;
return(0);
}
int
redata_data_compare(redata_t *redata, long cmppos, char *buf, long buflen)
{
int chunkno;
int offset;
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,&offset)==-1)
return(-1); /* invalid pos */
for(compared=0,n=0;compared<buflen && chunkno<redata->sizechunks;chunkno++,offset=0,compared+=n) {
n=redata->chunks[chunkno]->useddata-offset;
n=(n<0)?0:((compared+n)>buflen)?(buflen-compared):n;
if((res=memcmp(redata->chunks[chunkno]->data+offset,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 */
int i,l;
rechunk_t *chunk,**newchunks;
if(redata==NULL)
return(-1); /* sanity check failed */
/* skip free chunks at end */
for(i=redata->sizechunks-1;i>=0;i--) {
if(redata->chunks[i]->useddata!=0)
break;
}
/* join neighbouring chunks where appropiate */
l=redata->chunkdatasize-(redata->chunkdatasize)/10;
for(;i>0;i--) {
if((redata->chunks[i]->useddata+redata->chunks[i-1]->useddata)>=l)
continue;
/* move data to prev. chunk, move chunk to end */
redata_chunk_fillfromnext(redata,i-1,redata->chunks[i]->useddata);
if(redata->chunks[i]->useddata>0)
continue; /* couldn't move data */
chunk=redata->chunks[i];
memmove(redata->chunks+i,redata->chunks+i+1,sizeof(rechunk_t *)*(redata->sizechunks-i-1));
redata->chunks[redata->sizechunks-1]=chunk;
}
/* free unused chunks at end (leave two empty chunks, free the rest) */
for(i=redata->sizechunks-1;i>=0;i--) {
if(redata->chunks[i]->useddata!=0)
break;
}
l=i+3;
if(l<redata->sizechunks) {
for(i=l;i<redata->sizechunks;i++) {
free(redata->chunks[i]),redata->chunks[i]=NULL;
redata->available-=redata->chunkdatasize;
}
if((newchunks=realloc(redata->chunks,l*sizeof(rechunk_t *)))!=NULL)
redata->chunks=newchunks;
redata->sizechunks=l;
}
return(0);
}
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));
}
undostack_t *
redata_getstack(redata_t *redata, undo_t *undo)
{
if(redata==NULL || undo==NULL)
return(NULL); /* sanity check failed */
if(undo>=redata->undostack.undo && undo<(redata->undostack.undo+redata->undostack.sizeundo))
return(&(redata->undostack));
if(undo>=redata->redostack.undo && undo<(redata->redostack.undo+redata->redostack.sizeundo))
return(&(redata->redostack));
return(NULL); /* unknown stack */
}
char *
redata_generic_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);
}
long
redata_searchforward(redata_t *redata, long posini, char *str, int len)
{
int numchunk;
int offset;
int avail;
long chunkstartpos;
rechunk_t *chunk;
char *ptr;
if(redata==NULL || posini<0 || (posini+len)>redata_getsize(redata) || len<0 || (str==NULL && len>0))
return(-1); /* sanity check failed */
if(len==0)
return(posini); /* nothing to do, empty string is always equal */
if(redata_getposptr(redata,posini,&numchunk,&offset)!=0)
return(-1); /* couldn't get pos */
chunkstartpos=posini-offset;
for(;numchunk<redata->sizechunks
;chunkstartpos+=chunk->useddata,numchunk++,offset=0) {
chunk=redata->chunks[numchunk];
avail=chunk->useddata-offset;
while(avail>0 && (ptr=memchr(chunk->data+offset,str[0],avail))!=NULL) {
offset=ptr-((char *)(chunk->data));
if(redata_memcmp(redata,chunkstartpos+offset,str,len)==0)
return(chunkstartpos+offset);
offset++;
avail=chunk->useddata-offset;
}
}
return(-1); /* not found */
}
long
redata_searchbackwards(redata_t *redata, long posini, char *str, int len)
{
int numchunk;
int offset;
long chunkstartpos;
rechunk_t *chunk;
char *ptr;
if(redata==NULL || posini<0 || (posini+len)>redata_getsize(redata) || len<0 || (str==NULL && len>0))
return(-1); /* sanity check failed */
if(len==0)
return(posini); /* nothing to do, empty string is always equal */
if(redata_getposptr(redata,posini,&numchunk,&offset)!=0)
return(-1); /* couldn't get pos */
chunkstartpos=posini-offset;
for(offset++;numchunk>=0
;numchunk--
,offset=(numchunk>=0)?redata->chunks[numchunk]->useddata:0
,chunkstartpos-=(numchunk>=0)?redata->chunks[numchunk]->useddata:0) {
chunk=redata->chunks[numchunk];
while(offset>0 && (ptr=mymemrchr(chunk->data,str[0],offset))!=NULL) {
offset=ptr-((char *)(chunk->data));
if(redata_memcmp(redata,chunkstartpos+offset,str,len)==0)
return(chunkstartpos+offset);
}
}
return(-1); /* not found */
}
int
redata_memcmp(redata_t *redata, long pos, char *str, int len)
{
int numchunk;
int offset;
int avail;
int ncompared;
rechunk_t *chunk;
int res;
if(redata==NULL || pos<0 || (pos+len)>redata_getsize(redata) || len<0 || (str==NULL && len>0))
return(-1); /* sanity check failed, return "this is smaller" */
if(len==0)
return(0); /* nothing to do, empty string is always equal */
if(redata_getposptr(redata,pos,&numchunk,&offset)!=0)
return(-1); /* couldn't get pos */
for(ncompared=0;numchunk<redata->sizechunks;numchunk++,offset=0) {
chunk=redata->chunks[numchunk];
avail=chunk->useddata-offset;
avail=(avail>(len-ncompared))?(len-ncompared):avail;
if((res=memcmp(chunk->data+offset,str+ncompared,avail))!=0 || (ncompared+avail)==len)
return(res); /* comparison finished */
ncompared+=avail;
}
return(-1); /* not enough data in chunks */
}
int
redata_generic_utf8len(char *ptr, int size)
{
int len,i;
/* calculate the number of utf8-charaters in buffer */
if(size<0 || (ptr==NULL && size!=0))
return(-1);
/* for now we only count the number of code points */
/* in UTF8: 0x00-0x7f single byte chars
* 0xc0-0xff leading bytes
* 0x80-0xbf continuation bytes (ignore these for len)*/
/*#warning TODO: XXX support combining code points (at least U+0300 - U+036F ( https://en.wikipedia.org/wiki/Combining_character ) */
for(len=0,i=0;i<size;i++)
len+=((ptr[i]&0xc0)!=0x80)?1:0;
return(len);
/*#warning TODO: XXX Also consider tabs*/
}
char *
redata_generic_utf8col(char *ptr, int size, int col)
{
int len,i;
/* return a pointer to the "n"th ("col"th) utf8-character in buffer */
if(size<0 || (ptr==NULL && size!=0))
return(NULL); /* sanity check failed */
/* see reui_utf8len() for explanation of algorithm */
/*#warning TODO: support combining code points (at least U+0300 - U+036F ( https://en.wikipedia.org/wiki/Combining_character ) */
if(col>=size)
return(NULL);/* col greater than maximum possible char. count */
/* skip "col" amount of single byte chars and leading bytes */
for(len=0,i=0;len<col && i<size;i++)
len+=((ptr[i]&0xc0)!=0x80)?1:0;
/* if we landed in a continuation byte, advance until next single byte chars or leading byte */
while(i<size && (ptr[i]&0xc0)==0x80)
i++;
if(i>=size)
return(NULL); /* col is beyond end of string */
return(ptr+i);
/*#warning TODO: XXX Also consider tabs*/
}
int
redata_generic_utf8charlen(char *ptr, int maxsize)
{
int i;
/* returns the len in bytes of the character starting at ptr (zero on error)*/
if(ptr==NULL || maxsize<1)
return(0); /* sanity check failed */
/*#warning TODO: support combining code points (at least U+0300 - U+036F ( https://en.wikipedia.org/wiki/Combining_character ) */
if(((unsigned char *)ptr)[0]<0x80)
return(1); /* single byte char */
if((ptr[0]&0xc0)==0x80)
return(0); /* error: this is continuation, not leading byte */
for(i=1;i<maxsize && (ptr[i]&0xc0)==0x80;i++)
;
return(i);
}
inline int
redata_generic_utf8isstartbyte(int candidate)
{
if((candidate&0xc0)!=0x80)
return(1);
return(0);
}
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);
}
int
redata_line_rawinfo(redata_t *redata, long pos, long *startpos, char **startptr, int *len, int *is_continuation)
{
long chunkpos,newpos,endpos;
int nchunk;
rechunk_t *chunk;
if(redata==NULL || pos<0 || pos>=redata_getused(redata))
return(-1);
for(nchunk=0,chunkpos=0
;nchunk<redata->sizechunks
;chunkpos+=(chunk!=NULL)?chunk->useddata:0,nchunk++) {
if((chunk=redata->chunks[nchunk])==NULL)
continue;
if(pos>=chunkpos && pos<(chunkpos+chunk->useddata))
break;
}
if(nchunk>=redata->sizechunks)
return(-1); /* pos not found */
for(newpos=pos;newpos>chunkpos && chunk->data[newpos-chunkpos-1]!='\n';newpos--)
;
for(endpos=pos;endpos<(chunkpos+chunk->useddata) && chunk->data[endpos-chunkpos]!='\n';endpos++)
;
if(endpos==(chunkpos+chunk->useddata))
endpos--;
if(startpos!=NULL)
*startpos=newpos;
if(startptr!=NULL)
*startptr=(char *) (chunk->data+(newpos-chunkpos));
if(len!=NULL)
*len=endpos-newpos+1;
if(is_continuation!=NULL) {
if(!(chunk->whatin_fresh))
redata_whatin_refresh(redata,nchunk);
*is_continuation=(newpos==chunkpos && chunk->whatin.iscontinuation)?1:0;
}
return(0);
}
int
redata_line_realstart(redata_t *redata, long pos, long *startpos)
{
int is_continuation;
long nextpos,newpos;
if(redata==NULL || pos<0)
return(-1); /* sanity check failed */
nextpos=pos;
do {
if(redata_line_rawinfo(redata,nextpos,&newpos,NULL,NULL,&is_continuation)==-1)
return(-1);
nextpos=newpos-1;
} while(is_continuation && newpos>0);
if(startpos!=NULL)
*startpos=newpos;
return(0);
}
int
redata_line_realend(redata_t *redata, long pos, long *endpos)
{
long nextpos,newpos;
long datasize;
char *ptr;
int len;
int has_nl;
if(redata==NULL || pos<0)
return(-1); /* sanity check failed */
nextpos=pos;
if((datasize=redata_getused(redata))<=0)
return(-1); /* couldn't get last pos or there is no data */
do {
if(redata_line_rawinfo(redata,nextpos,&newpos,&ptr,&len,NULL)==-1 || len==0)
return(-1);
nextpos=newpos+len;
has_nl=(len>0 && ptr[len-1]=='\n')?1:0;
} while(!has_nl && nextpos<datasize);
if(endpos!=NULL)
*endpos=newpos+len-1;
return(0);
}
int
redata_line_prevrealstart(redata_t *redata, long pos, long *startpos)
{
long newpos,prevpos;
if(redata==NULL || pos<0)
return(-1);
if(redata_line_realstart(redata,pos,&newpos)==-1)
return(-1); /* couldn't get start of line */
if(redata_line_realstart(redata,newpos-1,&prevpos)==-1)
return(-1); /* couldn't get start of line */
if(startpos!=NULL)
*startpos=prevpos;
return(0);
}
int
redata_line_nextrealstart(redata_t *redata, long pos, long *startpos)
{
long newpos,nextpos;
if(redata==NULL || pos<0)
return(-1);
if(redata_line_realend(redata,pos,&newpos)==-1)
return(-1); /* couldn't get end of line */
if(redata_line_realstart(redata,newpos+1,&nextpos)==-1)
return(-1); /* couldn't get start of line */
if(startpos!=NULL)
*startpos=nextpos;
return(0);
}
int
redata_line_inccol(redata_t *redata, long pos, int ncolrequest, long *newpos, int *ncoldone)
{
long curpos,startpos;
char *ptr;
int len;
int n,i;
int done;
if(redata==NULL || pos<0 || ncolrequest<0)
return(-1); /* sanity check failed */
if(ncolrequest==0) {
if(newpos!=NULL)
*newpos=pos;
if(ncoldone!=NULL)
*ncoldone=0;
return(0); /* nothing to do */
}
for(n=0,curpos=pos;n<=ncolrequest;) {
if(redata_line_rawinfo(redata,curpos,&startpos,&ptr,&len,NULL)==-1 || len==0)
return(-1); /* couldn't get current line data */
done=0;
/* advance until we are positioned on the next char to the requested one */
for(i=startpos-curpos;i<len;i++) {
if(redata_generic_utf8isstartbyte(ptr[i]))
n++;
if(ptr[i]=='\n' || n>ncolrequest) {
done=1;
break;
}
}
curpos=startpos+i;
if(done)
break;
}
if(newpos!=NULL)
*newpos=curpos;
if(ncoldone!=NULL)
*ncoldone=n-1;
return(0);
}
int
redata_pos2linecol(redata_t *redata, long pos, int *resline, int *rescol)
{
if(redata==NULL || pos<0 || pos>=redata_getused(redata))
return(-1);
/* calculate line */
if(resline!=NULL) {
long chunkpos,newpos;
int startline;
int nchunk;
rechunk_t *chunk;
int i;
for(nchunk=0,chunkpos=0,startline=0
;nchunk<redata->sizechunks
;chunkpos+=(chunk!=NULL)?chunk->useddata:0
,startline+=(chunk!=NULL)?chunk->whatin.nlcount:0,nchunk++) {
if((chunk=redata->chunks[nchunk])==NULL)
continue;
if(!(chunk->whatin_fresh))
redata_whatin_refresh(redata,nchunk);
if(pos>=chunkpos && pos<(chunkpos+chunk->useddata))
break;
}
if(nchunk>=redata->sizechunks)
return(-1); /* pos not found */
for(newpos=chunkpos,i=0;newpos<pos && i<chunk->useddata;newpos++,i++) {
if(chunk->data[i]=='\n')
startline++;
}
*resline=startline;
}
/* calculate col */
if(rescol!=NULL) {
long realstart,curpos,startpos;
char *ptr;
int len;
int n,i;
int done;
if(redata_line_realstart(redata,pos,&realstart)==-1)
return(-1); /* startpos for pos not found */
for(n=0,curpos=realstart;curpos<pos;) {
if(redata_line_rawinfo(redata,curpos,&startpos,&ptr,&len,NULL)==-1 || len==0)
return(-1); /* couldn't get current line data */
done=0;
for(i=0;i<len && (startpos+i)<pos;i++) {
if(redata_generic_utf8isstartbyte(ptr[i]))
n++;
if(ptr[i]=='\n') {
done=1;
break;
}
}
curpos=startpos+i;
if(done)
break;
}
*rescol=n;
}
return(0);
}
int
redata_linecol2pos(redata_t *redata, int line, int colrequest, long *pos, int *coldone)
{
long chunkpos,realstart,curpos,startpos;
int startline;
int nchunk;
rechunk_t *chunk;
int i,n;
char *ptr;
int len;
int done;
if(redata==NULL || line<0 || colrequest<0)
return(-1);
/* find line */
for(nchunk=0,chunkpos=0,startline=0
;nchunk<redata->sizechunks
;chunkpos+=(chunk!=NULL)?chunk->useddata:0
,startline+=(chunk!=NULL)?chunk->whatin.nlcount:0,nchunk++) {
if((chunk=redata->chunks[nchunk])==NULL)
continue;
if(!(chunk->whatin_fresh))
redata_whatin_refresh(redata,nchunk);
if(line>=startline && line<=(startline+chunk->whatin.nlcount))
break;
}
if(nchunk>=redata->sizechunks)
return(-1); /* line not found */
for(i=0;line!=startline && i<chunk->useddata;i++) {
if(chunk->data[i]=='\n')
startline++;
}
if(i>chunk->useddata)
return(-1); /* line not found */
realstart=chunkpos+i;
/* find col */
for(n=0,curpos=realstart;n<colrequest;) {
if(redata_line_rawinfo(redata,curpos,&startpos,&ptr,&len,NULL)==-1 || len==0)
return(-1); /* couldn't get current line data */
done=0;
for(i=0;colrequest>0 && i<len && n<=colrequest;i++) {
if(redata_generic_utf8isstartbyte(ptr[i])) {
if(n==colrequest) {
done=1;
break;
}
n++;
}
if(ptr[i]=='\n') {
done=1;
break;
}
}
curpos=startpos+i;
if(done)
break;
}
if(coldone!=NULL)
*coldone=n;
if(pos!=NULL)
*pos=curpos;
return(0);
}
static char *
securesave_genname(char *filename, char *buf, int bufsize)
{
static char pre[]={SECURESAVEPREFIX};
static char post[]={SECURESAVEPOSTFIX};
return(redata_generic_genname(filename,pre,post,buf,bufsize));
}
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;
}
}
static size_t
memrchroffset(char *ptr, int c, size_t n)
{
size_t i;
for(i=n-1;i>=0;i--) {
if(((unsigned char *)ptr)[i]==c)
return(i);
}
return(-1);
}