/*
 * re_plugin_unsaved.c
 *
 * A programmers editor
 *
 * re_data plugin to support the unsaved changes file.
 * (for recovery when the program closes unexpectedly)
 *
 * Author: Dario Rodriguez antartica@whereismybit.com
 * 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 <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>


#include "re_plugin_unsaved.h"

#define UNSAVEDPREFIX "."
#define UNSAVEDPOSTFIX ".reu"
#define UNSAVEDHEADER "reunsaved00,"
#define UNSAVEDGROWSIZE (256*1024)


int redata_unsaved_add(redata_t *redata, redata_plugin_t *slot, undo_t *undo);
int redata_unsaved_unadd(redata_t *redata, redata_plugin_t *slot, undo_t *undo);
static int redata_unsaved_loadquestion(redata_t *redata, redata_plugin_t *slot,char *filename);
static int redata_unsaved_postload(redata_t *redata, redata_plugin_t *slot,char *filename);
static int redata_unsaved_check_gen(redata_t *redata, redata_plugin_t *slot, char *filename);
static char *unsaved_genname(char *filename, 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);

int
redata_unsaved_register(redata_t *redata, redata_plugin_t *slot)
{
        unsaved_t *unsaved;
        if(redata==NULL || slot==NULL)
                return(-1);
        if((unsaved=malloc(sizeof(unsaved_t)))==NULL)
                return(-1);
        memset(unsaved,0,sizeof(unsaved_t));
        unsaved->unsavedfd=-1;
        unsaved->sizebuf=unsaved->usedbuf=0;
        strncpy(slot->name,"unsaved",sizeof(slot->name));
        slot->name[sizeof(slot->name)-1]='\0';
        slot->unregister=redata_unsaved_unregister;
        slot->loadquestion=redata_unsaved_loadquestion;
        slot->wipe=redata_unsaved_wipe;
        slot->postload=redata_unsaved_postload;
        slot->postsave=redata_unsaved_trunc;
        slot->add_or_unadd=redata_unsaved_add_or_unadd;
        slot->commit=redata_unsaved_commit;
        slot->userptr=unsaved;
        return(0);
}

int
redata_unsaved_unregister(redata_t *redata, redata_plugin_t *slot, char *filename)
{
        char unsname[PATH_MAX];
        unsaved_t *unsaved=(unsaved_t *) ((slot!=NULL)?(slot->userptr):NULL);
        if(redata==NULL || slot==NULL || unsaved==NULL)
                return(-1);
        if(unsaved->buf!=NULL)
                free(unsaved->buf),unsaved->buf=NULL;
        unsaved->sizebuf=unsaved->usedbuf=0;
        if(unsaved->unsavedfd!=-1) {
                close(unsaved->unsavedfd),unsaved->unsavedfd=-1;
                if(filename!=NULL && unsaved_genname(filename,unsname,sizeof(unsname))!=NULL)
                        unlink(unsname);
        }
        if(slot->userptr!=NULL)
                free(slot->userptr),slot->userptr=NULL;
        return(0);
}


int
redata_unsaved_exists(redata_t *redata, redata_plugin_t *slot, char *filename)
{
        char unsname[PATH_MAX+1];
        int fd;
        unsaved_t *unsaved=(unsaved_t *) ((slot!=NULL)?(slot->userptr):NULL);
        if(redata==NULL || slot==NULL || unsaved==NULL || filename==NULL || filename[0]=='\0')
                return(-1); /* sanity check failed */
        if((unsaved_genname(filename,unsname,sizeof(unsname)))==NULL)
                return(-1); /* malformed filename */
        if((fd=open(unsname,O_RDONLY))==-1)
                return(-1);
        close(fd),fd=-1;
        return(0);
}

int
redata_unsaved_wipe(redata_t *redata, redata_plugin_t *slot, char *filename)
{
        char unsname[PATH_MAX];
        unsaved_t *unsaved=(unsaved_t *) ((slot!=NULL)?(slot->userptr):NULL);
        if(redata==NULL || slot==NULL || unsaved==NULL || filename==NULL || filename[0]=='\0')
                return(-1);
        if(unsaved->unsavedfd!=-1) {
                close(unsaved->unsavedfd),unsaved->unsavedfd=-1;
                if(unsaved_genname(filename,unsname,sizeof(unsname))!=NULL)
                        unlink(unsname);
        }
        unsaved->usedbuf=0;
        return(0);
}

static int
redata_unsaved_check_gen(redata_t *redata, redata_plugin_t *slot, char *filename)
{
        char unsname[PATH_MAX+1];
        int fd,nread;
        static char header[]={UNSAVEDHEADER};
        char filehash[129],undohash[129],buf[16];
        char fileheader[]={UNSAVEDHEADER};
        unsaved_t *unsaved=(unsaved_t *) ((slot!=NULL)?(slot->userptr):NULL);
        if(redata==NULL || slot==NULL || unsaved==NULL || filename==NULL || filename[0]=='\0')
                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, redata_plugin_t *slot, char *filename)
{
        int fd;
        if((fd=redata_unsaved_check_gen(redata,slot,filename))==-1)
                return(-1); /* check failed */
        close(fd),fd=-1;
        return(0);
}


int
redata_unsaved_unlink(redata_t *redata, redata_plugin_t *slot, char *filename)
{
        char unsname[PATH_MAX+1];
        unsaved_t *unsaved=(unsaved_t *) ((slot!=NULL)?(slot->userptr):NULL);
        if(redata==NULL || slot==NULL || unsaved==NULL || filename==NULL || filename[0]=='\0')
                return(-1); /* sanity check failed */
        if(redata_unsaved_exists(redata,slot,filename)==-1)
                return(0); /* file not found, nothing to unlink */
        if((unsaved_genname(filename,unsname,sizeof(unsname)))==NULL)
                return(-1); /* malformed filename */
        unlink(unsname);
        return(0);
}

int
redata_unsaved_trunc(redata_t *redata, redata_plugin_t *slot, char *oldfilename, char *newfilename)
{
        char unsname[PATH_MAX+1];
        static char header[]={UNSAVEDHEADER};
        int n;
        unsaved_t *unsaved=(unsaved_t *) ((slot!=NULL)?(slot->userptr):NULL);
        if(redata==NULL || slot==NULL || unsaved==NULL || newfilename==NULL || newfilename[0]=='\0')
                return(-1); /* sanity check failed */
        if(oldfilename!=NULL)
                redata_unsaved_unlink(redata,slot,oldfilename);
        if((unsaved_genname(newfilename,unsname,sizeof(unsname)))==NULL)
                return(-1); /* malformed filename */
        if(unsaved->unsavedfd!=-1)
                close(unsaved->unsavedfd),unsaved->unsavedfd=-1;
        redata_hash(redata,unsaved->initialhash);
        unsaved->usedbuf=0;
        if((unsaved->unsavedfd=open(unsname,O_WRONLY|O_TRUNC|O_CREAT,0644))==-1)
                return(-1); /* couldn't open file for writing */
        if((n=write(unsaved->unsavedfd,header,sizeof(header)-1))==-1
          || n!=(sizeof(header)-1)
          || (n=write(unsaved->unsavedfd,unsaved->initialhash,128))==-1 || n!=128
          || (n=write(unsaved->unsavedfd,",",1))==-1 || n!=1) {
                close(unsaved->unsavedfd),unsaved->unsavedfd=-1;
                unlink(unsname);
                return(-1); /* couldn't write header/hash */
        }
        return(0);
}

int
redata_unsaved_truncload(redata_t *redata, redata_plugin_t *slot, char *filename)
{
#if 0
fprintf(stderr,"UNSAVED: TRUNCLOAD (ignore)\n");
#endif
        return(redata_unsaved_trunc(redata, slot, NULL, filename));
}


int
redata_unsaved_loadappend(redata_t *redata, redata_plugin_t *slot, char *filename)
{
        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;
        unsaved_t *unsaved=(unsaved_t *) ((slot!=NULL)?(slot->userptr):NULL);
#if 0
fprintf(stderr,"UNSAVED: LOADAPPEND (recover)\n");
#endif
        if(redata==NULL || slot==NULL || unsaved==NULL || filename==NULL)
                return(-1);
#if 0
fprintf(stderr,"UNSAVED: LOADAPPEND pre-check\n");
#endif
        if((fd=redata_unsaved_check_gen(redata,slot,filename))==-1)
                return(-1); /* check failed */
#if 0
fprintf(stderr,"UNSAVED: LOADAPPEND post-check\n");
#endif
        if(fstat(fd,&statbuf)!=0 || !S_ISREG(statbuf.st_mode)) {
                close(fd),fd=-1;
                return(-1); /* couldn't query size or not regular file */
        }
        redata_hash(redata,unsaved->initialhash);
        headerhashsize=(sizeof(header)-1)+128+1;
#if 0
fprintf(stderr,"UNSAVED: LOADAPPEND headerhashsize: %li\n",headerhashsize);
#endif
        /* load unsaved to memory */
        if(unsaved->sizebuf<(statbuf.st_size-headerhashsize)) {
                if((newptr=realloc(unsaved->buf,(statbuf.st_size-headerhashsize)))==NULL) {
                        close(fd),fd=-1;
                        return(-1); /* insuf. mem. */
                }
                unsaved->buf=newptr;
                unsaved->sizebuf=(statbuf.st_size-headerhashsize);
        }
        unsaved->usedbuf=0;
        lim=(statbuf.st_size-headerhashsize);
        for(nread=0;unsaved->usedbuf<lim;unsaved->usedbuf+=nread,nread=0) {
                if((nread=read(fd,unsaved->buf+unsaved->usedbuf,lim-unsaved->usedbuf))<=0) {
                        unsaved->usedbuf=0;
                        close(fd),fd=-1;
                        return(-1); /* short read */
                }
        }
        close(fd),fd=-1;
        /* process unsaved data */
        slot->active=0;
        endptr=unsaved->buf+unsaved->usedbuf;
        for(ptr=unsaved->buf;ptr<endptr;) {
#ifdef DEBUG_PLUGIN_UNSAVED
{
int m;
fprintf(stderr,"%05X: ",ptr-unsaved->buf);
for(m=0;m<16 && (ptr+m)<endptr;m++)
        fprintf(stderr,"%02X%s ",((unsigned char *)ptr)[m],(m==7)?" ":"");
fprintf(stderr," | ");
for(m=0;m<16 && (ptr+m)<endptr;m++)
        fprintf(stderr,"%c%s",((((unsigned char *)ptr)[m])<20)?'.':((((unsigned char *)ptr)[m])>126)?'.':((unsigned char *)ptr)[m],(m==7)?" ":"");
fprintf(stderr,"\n");
}
#endif
                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 */
                }
        }
        slot->active=1;
        return(0);
}

int
redata_unsaved_add_or_unadd(redata_t *redata, redata_plugin_t *slot, undo_t *undo, int is_unadd)
{
        return((is_unadd==0)?
          redata_unsaved_add(redata, slot, undo):
          redata_unsaved_unadd(redata, slot, undo));
}


int
redata_unsaved_add(redata_t *redata, redata_plugin_t *slot, undo_t *undo)
{
        char sep;
        char *sepend,*ptr,*endptr;
        char *buf;
        int k;
        int maxsize,newsize;
        char posbuf[128];
        undostack_t *stack;
        unsaved_t *unsaved=(unsaved_t *) ((slot!=NULL)?(slot->userptr):NULL);
        stack=redata_getstack(redata,undo);
        if(redata==NULL || slot==NULL || unsaved==NULL || undo==NULL || stack==NULL || slot->active==0)
                return(-1); /* sanity check failed */
        /* syntax (see loadappend): A<pos><+?><sep><text><sep>[<+?><sep><text><sep>[...]] */
        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((unsaved->sizebuf-unsaved->usedbuf)<maxsize) {
                                newsize=(unsaved->sizebuf+maxsize+UNSAVEDGROWSIZE-1)/UNSAVEDGROWSIZE;
                                newsize*=UNSAVEDGROWSIZE;
                                if((buf=realloc(unsaved->buf,newsize))==NULL)
                                        return(-1); /* insuf. mem. */
                                unsaved->buf=buf;
                                unsaved->sizebuf=newsize;
#if 0
fprintf(stderr,"UNSAVED: ADD realloc: %li\n",(long)newsize);
#endif
                        }
                        buf=unsaved->buf+unsaved->usedbuf;
                } else
                        unsaved->usedbuf+=maxsize;
        }
        return(0);
}

int
redata_unsaved_unadd(redata_t *redata, redata_plugin_t *slot, undo_t *undo)
{
        /* adds to unsaved the inverse operation to the one specified in the undo */
        char sep;
        char *sepend,*ptr,*endptr;
        char *buf;
        int k;
        int maxsize,newsize;
        char posbuf[128];
        undostack_t *stack;
        unsaved_t *unsaved=(unsaved_t *) ((slot!=NULL)?(slot->userptr):NULL);
        stack=redata_getstack(redata,undo);
        if(redata==NULL || slot==NULL || unsaved==NULL || undo==NULL || stack==NULL || slot->active==0)
                return(-1); /* sanity check failed */
        /* syntax (see loadappend): A<pos><+?><sep><text><sep>[<+?><sep><text><sep>[...]] */
        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((unsaved->sizebuf-unsaved->usedbuf)<maxsize) {
                                newsize=(unsaved->sizebuf+maxsize+UNSAVEDGROWSIZE-1)/UNSAVEDGROWSIZE;
                                newsize*=UNSAVEDGROWSIZE;
                                if((buf=realloc(unsaved->buf,newsize))==NULL)
                                        return(-1); /* insuf. mem. */
                                unsaved->buf=buf;
                                unsaved->sizebuf=newsize;
#if 0
fprintf(stderr,"UNSAVED: UNADD realloc: %li\n",(long)newsize);
#endif
                        }
                        buf=unsaved->buf+unsaved->usedbuf;
                }
        }
        return(0);
}

int
redata_unsaved_commit(redata_t *redata, redata_plugin_t *slot,char *filename)
{
        int n,nwritten;
        char unsname[PATH_MAX+1];
        unsaved_t *unsaved=(unsaved_t *) ((slot!=NULL)?(slot->userptr):NULL);
        if(redata==NULL || slot==NULL || unsaved==NULL || unsaved->unsavedfd==-1)
                return(-1);
#if 0
if(unsaved->usedbuf>0)
        fprintf(stderr,"UNSAVED_COMMITTED\n");
#endif
        for(nwritten=0;nwritten<unsaved->usedbuf;nwritten+=n) {
                if((n=write(unsaved->unsavedfd,unsaved->buf+nwritten,unsaved->usedbuf-nwritten))<0) {
                        close(unsaved->unsavedfd),unsaved->unsavedfd=-1;
                        if((unsaved_genname(filename,unsname,sizeof(unsname)))!=NULL)
                                unlink(unsname); /* a corrupted unsaved is of no use, delete it */
                        slot->active=0;
                        return(-1); /* error writing */
                }
        }
        unsaved->usedbuf=0;
        return(0);
}

static int
redata_unsaved_loadquestion(redata_t *redata, redata_plugin_t *slot,char *filename)
{
        static char mytitle[]={"Unsaved data from a previous session detected"};
        static char mybody[]={"The file you're trying to load has some unsaved data from a previous session. I can recover the data. What do you want to do?"};
        static char *myopts[]={"Recover unsaved data (safest option)","Don't use old unsaved data, delete old unsaved data (if you're sure what you're doing)"};
        static char mytitleshort[]={"Unsaved data found"};
        static char mybodyshort[]={"Try to recover the data?"};
        static char *myoptsshort[]={"Recover data","Ignore data","Quit"};
        question_t *q;
        if(redata==NULL || slot==NULL || filename==NULL)
                return(-1); /* sanity check failed */
        q=&(slot->question);
        q->active=0;
        if(redata_unsaved_check(redata,slot,filename)==-1)
                return(0); /* no unsaved data on disk */
        q->active=1;
        q->title=mytitle;
        q->titleshort=mytitleshort;
        q->body=mybody;
        q->bodyshort=mybodyshort;
        q->opts=myopts;
        q->optsshort=myoptsshort;
        q->nopts=3;
        q->defaultoption=1;
        q->selectedoption=-1;
        return(0);
}

static int
redata_unsaved_postload(redata_t *redata, redata_plugin_t *slot,char *filename)
{
#warning XXX TODO: add errordesc parameter, so that the user can see what happened.
        int selectedoption;
        if(redata==NULL || slot==NULL || filename==NULL)
                return(-1); /* sanity check failed */
        /* questionreply is from loadquestion, specifically the selected element in the array "opts" */
        selectedoption=slot->question.selectedoption;
        if(slot->question.active==0 || selectedoption==1 || selectedoption==-1) /* delete unsaved data */
                return(redata_unsaved_truncload(redata, slot, filename));
        else if(selectedoption==0) /* recover */
                return(redata_unsaved_loadappend(redata, slot, filename));
#if 0
#error TODO: implement be able to force-quit from plugin */
        else if(selectedoption==2) /* quit */
                return(-1);
#endif
        return(-1);
}


static char *
unsaved_genname(char *filename, char *buf, int bufsize)
{
        static char pre[]={UNSAVEDPREFIX};
        static char post[]={UNSAVEDPOSTFIX};
        return(redata_generic_genname(filename,pre,post,buf,bufsize));
}

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) {
                        if(pos!=NULL)
                                *pos=buf+bufsize;
                        return(seps[i]);
                }
                if(ptr>bestptr) {
                        besti=0;
                        bestptr=ptr;
                }
        }
        if(pos!=NULL)
                *pos=bestptr;
        return(seps[besti]);
}