#include #include #include #include #include #include #include #include #include extern const char *__progname; #include "folder.h" #include "seq.h" #include "system.h" #include "deconst.h" #include "message.h" typedef struct resv RESV; struct resv { RESV *link; const char *who; } ; struct mm_folder { FOLDER *link; const char *name; const char *pathname; int lockfd; int locked; int exlock; RESV *staylocked; int nmsgs; int *msgs; struct timespec mtime; } ; static FOLDER *folders_head; static __inline__ int ok_folder_name_char(int) __attribute__ ((__const__)); static __inline__ int ok_folder_name_char(int ch) { if (isalnum(ch)) return(1); switch (ch) { case '+': case ',': case '-': case '.': case '/': case '=': case '_': return(1); break; } return(0); } static void make_dirs(const char *path) { char *buf; char *sp; int n; int mode; const char *modestr; buf = strdup(path); mode = 0700; modestr = profile_lookup(system_get_profile(),"foldermode"); if (modestr) { mode = strtol(modestr,&sp,8); if ((modestr == sp) || *sp) mode = 0700; } n = 0; while (1) { if (mkdir(buf,mode) >= 0) { sp = buf; for (;n>0;n--) { sp += strlen(sp); *sp++ = '/'; if (mkdir(buf,mode) < 0) break; } free(buf); return; } sp = rindex(buf,'/'); if (sp == 0) { free(buf); return; } *sp = '\0'; n ++; } } static void folder_free(FOLDER *f) { free(deconst(f->name)); free(deconst(f->pathname)); if (f->lockfd >= 0) close(f->lockfd); free(f); } static int compare_msg(const void *ivp1, const void *ivp2) { register int i1; register int i2; i1 = *(const int *)ivp1; i2 = *(const int *)ivp2; if (i1 > i2) return(1); if (i1 < i2) return(-1); return(0); } static int update_msgno_cache(FOLDER *f) { struct stat stb; int n; folder_must_be_locked(f,"update_msgno_cache"); n = 0; while ( (f->nmsgs < 0) || (stat(f->pathname,&stb) < 0) || (stb.st_mtimespec.tv_sec != f->mtime.tv_sec) || (stb.st_mtimespec.tv_nsec != f->mtime.tv_nsec) ) { int msghave; DIR *d; struct dirent *e; int maxno; int i; n ++; if (f->nmsgs >= 0) free(f->msgs); f->nmsgs = 0; f->msgs = 0; msghave = 0; d = opendir(f->pathname); if (d == 0) return(1); maxno = 0; while ((e=readdir(d))) { if (e->d_ino == 0) continue; for (i=e->d_namlen-1;i>=0;i--) if (!isdigit(e->d_name[i])) break; if (i < 0) { if (f->nmsgs >= msghave) f->msgs = realloc(f->msgs,(msghave=f->nmsgs+8)*sizeof(int)); f->msgs[f->nmsgs++] = atoi(e->d_name); } } closedir(d); f->mtime = stb.st_mtimespec; heapsort(f->msgs,f->nmsgs,sizeof(int),compare_msg); } return(n); } static int new_message_no(FOLDER *f) { update_msgno_cache(f); return((f->nmsgs>0)?f->msgs[f->nmsgs-1]+1:1); } static int adjust_msg(FOLDER *f, int old, int can_up, int can_down) { int l; int h; int m; int n; int x; int i; n = folder_get_msgnos(f,0,-1,0); if (n < 1) return(-1); l = -1; h = n; while (h-l > 1) { m = (h + l) >> 1; if (folder_get_msgnos(f,1,m,&x) < 0) { fprintf(stderr,"%s: phase error adjusting message numbers in +%s, check it\n",__progname,folder_name(f)); return(-1); } if (x == old) return(old); if (x < old) l = m; else h = m; } if (can_up && (h < n)) { i = h; } else if (can_down && (l >= 0)) { i = l; } else { return(-1); } /* note that if we get here we must have gone through the while body above at least once, so m and x will have been set. */ if (i != m) { if (folder_get_msgnos(f,1,h,&x) < 0) { fprintf(stderr,"%s: phase error adjusting message numbers in +%s, check it\n",__progname,folder_name(f)); return(-1); } } return(x); } char *folder_skipname(const char *name) { while (*name && ok_folder_name_char(*name)) name ++; return(deconst(name)); } int folder_lookup(const char *name, FOLDER **fp, char **ep) { const char *np; FOLDER *f; char *t; const char *d; const char *lockfile; for (np=name;ok_folder_name_char(*np);np++) ; if (ep) *ep = deconst(np); if (np == name) return(0); t = malloc((np-name)+1); bcopy(name,t,np-name); t[np-name] = '\0'; for (f=folders_head;f;f=f->link) { if (!strcmp(f->name,t)) { *fp = f; free(t); return(1); } } f = malloc(sizeof(FOLDER)); f->name = 0; f->pathname = 0; f->lockfd = -1; f->nmsgs = -1; f->name = t; d = system_folder_dir(); t = malloc(strlen(d)+1+(np-name)+1); sprintf(t,"%s/%s",d,f->name); f->pathname = t; lockfile = profile_lookup(system_get_profile(),"folderlock"); if (! lockfile) lockfile = ".lock"; t = malloc(strlen(f->pathname)+1+strlen(lockfile)+1); sprintf(t,"%s/%s",f->pathname,lockfile); f->lockfd = open(t,O_RDWR|O_CREAT|O_TRUNC,0600); if (f->lockfd < 0) { if (errno == ENOENT) { make_dirs(f->pathname); f->lockfd = open(t,O_RDWR|O_CREAT|O_TRUNC,0600); if (f->lockfd < 0) { folder_free(f); return(0); } } else { folder_free(f); return(0); } } fcntl(f->lockfd,F_SETFD,1); f->locked = 0; f->staylocked = 0; *fp = f; f->link = folders_head; folders_head = f; return(1); } int folder_lock(FOLDER *f, unsigned int how) { int lockop; switch (how & (FOLDER_SHARED|FOLDER_EXCLUSIVE)) { case FOLDER_SHARED: lockop = LOCK_SH; break; case FOLDER_EXCLUSIVE: lockop = LOCK_EX; break; default: errno = EINVAL; return(-1); break; } switch (how & (FOLDER_WAIT|FOLDER_NOWAIT)) { case FOLDER_WAIT: break; case FOLDER_NOWAIT: lockop |= LOCK_NB; break; default: errno = EINVAL; return(-1); break; } if (f->locked) { if ((how & FOLDER_EXCLUSIVE) && !f->exlock) { fprintf(stderr,"%s: exclusive-locking folder already locked shared\n",__progname); abort(); } f->locked ++; return(0); } if (flock(f->lockfd,lockop) < 0) return(-1); f->locked = 1; f->exlock = ((how & FOLDER_EXCLUSIVE) != 0); return(0); } void folder_unlock(FOLDER *f) { if (f->locked < 1) { fprintf(stderr,"%s: unlocking un-locked folder\n",__progname); abort(); } if (f->locked > 1) { f->locked --; return; } if (f->staylocked) { RESV *r; fprintf(stderr,"%s: folder isn't staying locked\n",__progname); for (r=f->staylocked;r;r=r->link) { fprintf(stderr,"%s: reservation placed by %s\n",__progname,r->who); } abort(); } flock(f->lockfd,LOCK_UN); f->locked = 0; } MESSAGE *folder_new_link(FOLDER *f, const char *path) { int mno; MESSAGE *m; folder_must_be_locked(f,"folder_new_link"); mno = new_message_no(f); m = message_make(f,mno); if (link(path,message_path(m)) < 0) { int e; e = errno; message_free(m); errno = e; return(0); } return(m); } MESSAGE *folder_new_create(FOLDER *f) { int mno; MESSAGE *m; char *sp; int mode; int retries; const char *modestr; folder_must_be_locked(f,"folder_new_create"); mode = 0600; modestr = profile_lookup(system_get_profile(),"messagemode"); if (modestr) { mode = strtol(modestr,&sp,8); if ((modestr == sp) || *sp) mode = 0600; } retries = 0; retry:; if (retries > 10) { errno = EDEADLK; return(0); } mno = new_message_no(f); m = message_make(f,mno); if (message_open(m,O_RDWR|O_CREAT|O_EXCL|O_EXLOCK,mode) < 0) { int e; e = errno; if (errno == EEXIST) { message_free(m); goto retry; } } return(m); } const char *folder_name(FOLDER *f) { return(f->name); } const char *folder_path(FOLDER *f) { return(f->pathname); } void folder_must_be_locked(FOLDER *f, const char *who) { if (! f->locked) { fprintf(stderr,"%s: %s: folder not locked\n",__progname,who); abort(); } } void *folder_must_remain_locked(FOLDER *f, const char *who) { RESV *r; folder_must_be_locked(f,who); r = malloc(sizeof(RESV)); r->who = who; r->link = f->staylocked; f->staylocked = r; return(r); } void folder_may_be_unlocked(FOLDER *f, void *ticket) { RESV *rarg; RESV *r; RESV **rp; folder_must_be_locked(f,"folder_may_be_unlocked"); rarg = ticket; for (rp=&f->staylocked;(r=*rp);rp=&r->link) { if (r == rarg) { *rp = r->link; free(r); return; } } fprintf(stderr,"%s: folder_may_be_unlocked: reservation not found\n",__progname); abort(); } int folder_get_msgnos(FOLDER *f, int count, int off, int *vec) { int n; if ( ( update_msgno_cache(f) && (off >= 0) ) || (off > f->nmsgs) ) { return(-1); } if (off < 0) off = 0; n = f->nmsgs - off; if (n > count) n = count; if (n > 0) bcopy(f->msgs+off,vec,n*sizeof(int)); return(f->nmsgs); } int folder_msg(FOLDER *f, int which) { SEQS *ss; int mno; const char *seqname; switch (which) { case FM_PREV: seqname = "prev"; break; case FM_CUR: seqname = "cur"; break; case FM_NEXT: seqname = "next"; break; default: return(FOLDER_INVARG); break; } ss = seqs_open(f); if (ss) { SEQ *s; s = seq_lookup(ss,seqname); if (seq_get(s,0,0,0) < 1) { switch (which) { case FM_CUR: seqs_abort(ss); update_msgno_cache(f); if (f->nmsgs > 0) return(f->msgs[0]); break; case FM_NEXT: s = seq_lookup(ss,"cur"); if (seq_get(s,0,0,0) < 1) { seqs_abort(ss); update_msgno_cache(f); if (f->nmsgs > 0) return(f->msgs[0]); } else { seqs_abort(ss); } break; default: seqs_abort(ss); break; } return(FOLDER_NOSUCH); } seq_get(s,1,0,&mno); seqs_abort(ss); } else { return(FOLDER_SYSCALLERR); } return(mno); } void folder_deleted_messages(FOLDER *f, const int *v, int nv) { SEQS *ss; int i; int pm; int cm; int nm; int pmdel; int cmdel; int nmdel; pm = folder_msg(f,FM_PREV); cm = folder_msg(f,FM_CUR); nm = folder_msg(f,FM_NEXT); pmdel = 0; cmdel = 0; nmdel = 0; for (i=0;i 0) seq_add(s,pm); } if (cmdel) { SEQ *s; cm = adjust_msg(f,cm,1,1); s = seq_lookup(ss,"cur"); seq_clear(s); if (cm > 0) seq_add(s,cm); } if (nmdel) { SEQ *s; nm = adjust_msg(f,nm,1,0); s = seq_lookup(ss,"next"); seq_clear(s); if (nm > 0) seq_add(s,nm); } if (ss) { for (i=0;i= 0) seq_remove_all(ss,v[i]); seqs_close(ss); } else { fprintf(stderr,"%s: can't open sequence database in +%s: %s\n",__progname,folder_name(f),strerror(errno)); } }