/* * 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 #include #include #include #include #include #include #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->usedbufusedbuf+=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;ptrbuf); for(m=0;m<16 && (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 && ptrredata_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=(posactive=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<+?>[<+?>[...]] */ 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(ptrsizebuf-unsaved->usedbuf)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<+?>[<+?>[...]] */ 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->posorigposdest)?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->posorigposdest)?undo->posorig:undo->posorig+undo->len); posbuf[sizeof(posbuf)-1]='\0'; if(k!=0) strcpy(buf+maxsize,posbuf); maxsize+=strlen(posbuf); } while(ptrsizebuf-unsaved->usedbuf)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;nwrittenusedbuf;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='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;ibestptr) { besti=0; bestptr=ptr; } } if(pos!=NULL) *pos=bestptr; return(seps[besti]); }