char * expand_strdup( const char *s ) { struct passwd *pw; const char *p, *q; char *r; if (*s == '~') { s++; if (!*s) { p = 0; q = Home; } else if (*s == '/') { p = s; q = Home; } else { if ((p = strchr( s, '/' ))) { r = my_strndup( s, (int)(p - s) ); pw = getpwnam( r ); free( r ); } else pw = getpwnam( s ); if (!pw) return 0; q = pw->pw_dir; } nfasprintf( &r, "%s%s", q, p ? p : "" ); return r; } else return nfstrdup( s ); }
static void box_selected( int sts, void *aux ) { DECL_SVARS; sync_rec_t *srec, *nsrec; char *s, *cmname, *csname; store_t *ctx[2]; channel_conf_t *chan; FILE *jfp; int opts[2], line, t1, t2, t3; struct stat st; struct flock lck; char fbuf[16]; /* enlarge when support for keywords is added */ char buf[128], buf1[64], buf2[64]; if (check_ret( sts, aux )) return; INIT_SVARS(aux); ctx[0] = svars->ctx[0]; ctx[1] = svars->ctx[1]; svars->state[t] |= ST_SELECTED; if (!(svars->state[1-t] & ST_SELECTED)) return; chan = svars->chan; if (!strcmp( chan->sync_state ? chan->sync_state : global_sync_state, "*" )) { if (!ctx[S]->path) { error( "Error: store '%s' does not support in-box sync state\n", chan->stores[S]->name ); sbail: svars->ret = SYNC_FAIL; sync_bail2( svars ); return; } nfasprintf( &svars->dname, "%s/." EXE "state", ctx[S]->path ); } else { csname = clean_strdup( ctx[S]->name ); if (chan->sync_state) nfasprintf( &svars->dname, "%s%s", chan->sync_state, csname ); else { cmname = clean_strdup( ctx[M]->name ); nfasprintf( &svars->dname, "%s:%s:%s_:%s:%s", global_sync_state, chan->stores[M]->name, cmname, chan->stores[S]->name, csname ); free( cmname ); } free( csname ); if (!(s = strrchr( svars->dname, '/' ))) { error( "Error: invalid SyncState location '%s'\n", svars->dname ); goto sbail; } *s = 0; if (mkdir( svars->dname, 0700 ) && errno != EEXIST) { sys_error( "Error: cannot create SyncState directory '%s'", svars->dname ); goto sbail; } *s = '/'; } nfasprintf( &svars->jname, "%s.journal", svars->dname ); nfasprintf( &svars->nname, "%s.new", svars->dname ); nfasprintf( &svars->lname, "%s.lock", svars->dname ); memset( &lck, 0, sizeof(lck) ); #if SEEK_SET != 0 lck.l_whence = SEEK_SET; #endif #if F_WRLCK != 0 lck.l_type = F_WRLCK; #endif if ((svars->lfd = open( svars->lname, O_WRONLY|O_CREAT, 0666 )) < 0) { sys_error( "Error: cannot create lock file %s", svars->lname ); svars->ret = SYNC_FAIL; sync_bail2( svars ); return; } if (fcntl( svars->lfd, F_SETLK, &lck )) { error( "Error: channel :%s:%s-:%s:%s is locked\n", chan->stores[M]->name, ctx[M]->orig_name, chan->stores[S]->name, ctx[S]->orig_name ); svars->ret = SYNC_FAIL; sync_bail1( svars ); return; } if ((jfp = fopen( svars->dname, "r" ))) { debug( "reading sync state %s ...\n", svars->dname ); if (!fgets( buf, sizeof(buf), jfp ) || !(t = strlen( buf )) || buf[t - 1] != '\n') { error( "Error: incomplete sync state header in %s\n", svars->dname ); jbail: fclose( jfp ); bail: svars->ret = SYNC_FAIL; sync_bail( svars ); return; } if (sscanf( buf, "%63s %63s", buf1, buf2 ) != 2 || sscanf( buf1, "%d:%d", &svars->uidval[M], &svars->maxuid[M] ) < 2 || sscanf( buf2, "%d:%d:%d", &svars->uidval[S], &svars->smaxxuid, &svars->maxuid[S] ) < 3) { error( "Error: invalid sync state header in %s\n", svars->dname ); goto jbail; } line = 1; while (fgets( buf, sizeof(buf), jfp )) { line++; if (!(t = strlen( buf )) || buf[t - 1] != '\n') { error( "Error: incomplete sync state entry at %s:%d\n", svars->dname, line ); goto jbail; } fbuf[0] = 0; if (sscanf( buf, "%d %d %15s", &t1, &t2, fbuf ) < 2) { error( "Error: invalid sync state entry at %s:%d\n", svars->dname, line ); goto jbail; } srec = nfmalloc( sizeof(*srec) ); srec->uid[M] = t1; srec->uid[S] = t2; s = fbuf; if (*s == 'X') { s++; srec->status = S_EXPIRE | S_EXPIRED; } else srec->status = 0; srec->flags = parse_flags( s ); debug( " entry (%d,%d,%u,%s)\n", srec->uid[M], srec->uid[S], srec->flags, srec->status & S_EXPIRED ? "X" : "" ); srec->msg[M] = srec->msg[S] = 0; srec->tuid[0] = 0; srec->next = 0; *svars->srecadd = srec; svars->srecadd = &srec->next; svars->nsrecs++; } fclose( jfp ); } else { if (errno != ENOENT) { error( "Error: cannot read sync state %s\n", svars->dname ); goto bail; } } line = 0; if ((jfp = fopen( svars->jname, "r" ))) { if (!stat( svars->nname, &st ) && fgets( buf, sizeof(buf), jfp )) { debug( "recovering journal ...\n" ); if (!(t = strlen( buf )) || buf[t - 1] != '\n') { error( "Error: incomplete journal header in %s\n", svars->jname ); goto jbail; } if (memcmp( buf, JOURNAL_VERSION "\n", strlen(JOURNAL_VERSION) + 1 )) { error( "Error: incompatible journal version " "(got %.*s, expected " JOURNAL_VERSION ")\n", t - 1, buf ); goto jbail; } srec = 0; line = 1; while (fgets( buf, sizeof(buf), jfp )) { line++; if (!(t = strlen( buf )) || buf[t - 1] != '\n') { error( "Error: incomplete journal entry at %s:%d\n", svars->jname, line ); goto jbail; } if (buf[0] == '#' ? (t3 = 0, (sscanf( buf + 2, "%d %d %n", &t1, &t2, &t3 ) < 2) || !t3 || (t - t3 != TUIDL + 3)) : buf[0] == '(' || buf[0] == ')' || buf[0] == '{' || buf[0] == '}' ? (sscanf( buf + 2, "%d", &t1 ) != 1) : buf[0] == '+' || buf[0] == '&' || buf[0] == '-' || buf[0] == '|' || buf[0] == '/' || buf[0] == '\\' ? (sscanf( buf + 2, "%d %d", &t1, &t2 ) != 2) : (sscanf( buf + 2, "%d %d %d", &t1, &t2, &t3 ) != 3)) { error( "Error: malformed journal entry at %s:%d\n", svars->jname, line ); goto jbail; } if (buf[0] == '(') svars->maxuid[M] = t1; else if (buf[0] == ')') svars->maxuid[S] = t1; else if (buf[0] == '{') svars->newuid[M] = t1; else if (buf[0] == '}') svars->newuid[S] = t1; else if (buf[0] == '|') { svars->uidval[M] = t1; svars->uidval[S] = t2; } else if (buf[0] == '+') { srec = nfmalloc( sizeof(*srec) ); srec->uid[M] = t1; srec->uid[S] = t2; debug( " new entry(%d,%d)\n", t1, t2 ); srec->msg[M] = srec->msg[S] = 0; srec->status = 0; srec->flags = 0; srec->tuid[0] = 0; srec->next = 0; *svars->srecadd = srec; svars->srecadd = &srec->next; svars->nsrecs++; } else { for (nsrec = srec; srec; srec = srec->next) if (srec->uid[M] == t1 && srec->uid[S] == t2) goto syncfnd; for (srec = svars->srecs; srec != nsrec; srec = srec->next) if (srec->uid[M] == t1 && srec->uid[S] == t2) goto syncfnd; error( "Error: journal entry at %s:%d refers to non-existing sync state entry\n", svars->jname, line ); goto jbail; syncfnd: debugn( " entry(%d,%d,%u) ", srec->uid[M], srec->uid[S], srec->flags ); switch (buf[0]) { case '-': debug( "killed\n" ); srec->status = S_DEAD; break; case '#': debug( "TUID now %." stringify(TUIDL) "s\n", buf + t3 + 2 ); memcpy( srec->tuid, buf + t3 + 2, TUIDL ); break; case '&': debug( "TUID %." stringify(TUIDL) "s lost\n", srec->tuid ); srec->flags = 0; srec->tuid[0] = 0; break; case '<': debug( "master now %d\n", t3 ); srec->uid[M] = t3; srec->tuid[0] = 0; break; case '>': debug( "slave now %d\n", t3 ); srec->uid[S] = t3; srec->tuid[0] = 0; break; case '*': debug( "flags now %d\n", t3 ); srec->flags = t3; break; case '~': debug( "expire now %d\n", t3 ); if (t3) srec->status |= S_EXPIRE; else srec->status &= ~S_EXPIRE; break; case '\\': t3 = (srec->status & S_EXPIRED); debug( "expire back to %d\n", t3 / S_EXPIRED ); if (t3) srec->status |= S_EXPIRE; else srec->status &= ~S_EXPIRE; break; case '/': t3 = (srec->status & S_EXPIRE); debug( "expired now %d\n", t3 / S_EXPIRE ); if (t3) { if (svars->smaxxuid < srec->uid[S]) svars->smaxxuid = srec->uid[S]; srec->status |= S_EXPIRED; } else srec->status &= ~S_EXPIRED; break; default: error( "Error: unrecognized journal entry at %s:%d\n", svars->jname, line ); goto jbail; } } } } fclose( jfp ); } else { if (errno != ENOENT) { error( "Error: cannot read journal %s\n", svars->jname ); goto bail; } } t1 = 0; for (t = 0; t < 2; t++) if (svars->uidval[t] >= 0 && svars->uidval[t] != ctx[t]->uidvalidity) { error( "Error: UIDVALIDITY of %s changed (got %d, expected %d)\n", str_ms[t], ctx[t]->uidvalidity, svars->uidval[t] ); t1++; } if (t1) goto bail; if (!(svars->nfp = fopen( svars->nname, "w" ))) { error( "Error: cannot write new sync state %s\n", svars->nname ); goto bail; } if (!(svars->jfp = fopen( svars->jname, "a" ))) { error( "Error: cannot write journal %s\n", svars->jname ); fclose( svars->nfp ); goto bail; } setlinebuf( svars->jfp ); if (!line) Fprintf( svars->jfp, JOURNAL_VERSION "\n" ); opts[M] = opts[S] = 0; for (t = 0; t < 2; t++) { if (chan->ops[t] & (OP_DELETE|OP_FLAGS)) { opts[t] |= OPEN_SETFLAGS; opts[1-t] |= OPEN_OLD; if (chan->ops[t] & OP_FLAGS) opts[1-t] |= OPEN_FLAGS; } if (chan->ops[t] & (OP_NEW|OP_RENEW)) { opts[t] |= OPEN_APPEND; if (chan->ops[t] & OP_RENEW) opts[1-t] |= OPEN_OLD; if (chan->ops[t] & OP_NEW) opts[1-t] |= OPEN_NEW; if (chan->ops[t] & OP_EXPUNGE) opts[1-t] |= OPEN_FLAGS; if (chan->stores[t]->max_size) opts[1-t] |= OPEN_SIZE; } if (chan->ops[t] & OP_EXPUNGE) { opts[t] |= OPEN_EXPUNGE; if (chan->stores[t]->trash) { if (!chan->stores[t]->trash_only_new) opts[t] |= OPEN_OLD; opts[t] |= OPEN_NEW|OPEN_FLAGS; } else if (chan->stores[1-t]->trash && chan->stores[1-t]->trash_remote_new) opts[t] |= OPEN_NEW|OPEN_FLAGS; } } if ((chan->ops[S] & (OP_NEW|OP_RENEW)) && chan->max_messages) opts[S] |= OPEN_OLD|OPEN_NEW|OPEN_FLAGS; if (line) for (srec = svars->srecs; srec; srec = srec->next) { if (srec->status & S_DEAD) continue; if ((mvBit(srec->status, S_EXPIRE, S_EXPIRED) ^ srec->status) & S_EXPIRED) opts[S] |= OPEN_OLD|OPEN_FLAGS; if (srec->tuid[0]) { if (srec->uid[M] == -2) opts[M] |= OPEN_NEW|OPEN_FIND, svars->state[M] |= S_FIND; else if (srec->uid[S] == -2) opts[S] |= OPEN_NEW|OPEN_FIND, svars->state[S] |= S_FIND; } } svars->drv[M]->prepare_opts( ctx[M], opts[M] ); svars->drv[S]->prepare_opts( ctx[S], opts[S] ); if (!svars->smaxxuid && load_box( svars, M, (ctx[M]->opts & OPEN_OLD) ? 1 : INT_MAX, 0, 0 )) return; load_box( svars, S, (ctx[S]->opts & OPEN_OLD) ? 1 : INT_MAX, 0, 0 ); }