"Fossies" - the Fresh Open Source Software Archive

Member "s-nail-14.9.7/maildir.c" (16 Feb 2018, 30861 Bytes) of package /linux/misc/s-nail-14.9.7.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "maildir.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 14.9.6_vs_14.9.7.

    1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
    2  *@ Maildir folder support. FIXME rewrite - why do we chdir(2)??
    3  *@ FIXME Simply truncating paths isn't really it.
    4  *
    5  * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
    6  * Copyright (c) 2012 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
    7  */
    8 /*
    9  * Copyright (c) 2004
   10  * Gunnar Ritter.  All rights reserved.
   11  *
   12  * Redistribution and use in source and binary forms, with or without
   13  * modification, are permitted provided that the following conditions
   14  * are met:
   15  * 1. Redistributions of source code must retain the above copyright
   16  *    notice, this list of conditions and the following disclaimer.
   17  * 2. Redistributions in binary form must reproduce the above copyright
   18  *    notice, this list of conditions and the following disclaimer in the
   19  *    documentation and/or other materials provided with the distribution.
   20  * 3. All advertising materials mentioning features or use of this software
   21  *    must display the following acknowledgement:
   22  *    This product includes software developed by Gunnar Ritter
   23  *    and his contributors.
   24  * 4. Neither the name of Gunnar Ritter nor the names of his contributors
   25  *    may be used to endorse or promote products derived from this software
   26  *    without specific prior written permission.
   27  *
   28  * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
   29  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   30  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   31  * ARE DISCLAIMED.  IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
   32  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   33  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
   34  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
   35  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   36  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   37  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   38  * SUCH DAMAGE.
   39  */
   40 #undef n_FILE
   41 #define n_FILE maildir
   42 
   43 #ifndef HAVE_AMALGAMATION
   44 # include "nail.h"
   45 #endif
   46 
   47 #include <dirent.h>
   48 
   49 /* a_maildir_tbl should be a hash-indexed array of trees! */
   50 static struct message **a_maildir_tbl, **a_maildir_tbl_top;
   51 static ui32_t a_maildir_tbl_prime, a_maildir_tbl_maxdist;
   52 static sigjmp_buf    _maildir_jmp;
   53 
   54 static void             __maildircatch(int s);
   55 static void             __maildircatch_hold(int s);
   56 
   57 /* Do some cleanup in the tmp/ subdir */
   58 static void             _cleantmp(void);
   59 
   60 static int              _maildir_setfile1(char const *name, enum fedit_mode fm,
   61                            int omsgCount);
   62 
   63 static int a_maildir_cmp(void const *a, void const *b);
   64 
   65 static int              _maildir_subdir(char const *name, char const *sub,
   66                            enum fedit_mode fm);
   67 
   68 static void             _maildir_append(char const *name, char const *sub,
   69                            char const *fn);
   70 
   71 static void             readin(char const *name, struct message *m);
   72 
   73 static void             maildir_update(void);
   74 
   75 static void             _maildir_move(struct n_timespec const *tsp,
   76                            struct message *m);
   77 
   78 static char *           mkname(struct n_timespec const *tsp, enum mflag f,
   79                            char const *pref);
   80 
   81 static enum okay        maildir_append1(struct n_timespec const *tsp,
   82                            char const *name, FILE *fp, off_t off1,
   83                            long size, enum mflag flag);
   84 
   85 static enum okay        trycreate(char const *name);
   86 
   87 static enum okay        mkmaildir(char const *name);
   88 
   89 static struct message * mdlook(char const *name, struct message *data);
   90 
   91 static void             mktable(void);
   92 
   93 static enum okay        subdir_remove(char const *name, char const *sub);
   94 
   95 static void
   96 __maildircatch(int s)
   97 {
   98    NYD_X; /* Signal handler */
   99    siglongjmp(_maildir_jmp, s);
  100 }
  101 
  102 static void
  103 __maildircatch_hold(int s)
  104 {
  105    NYD_X; /* Signal handler */
  106    n_UNUSED(s);
  107    /* TODO no STDIO in signal handler, no _() tr's -- pre-translate interrupt
  108     * TODO globally; */
  109    n_err_sighdl(_("\nImportant operation in progress: "
  110       "interrupt again to forcefully abort\n"));
  111    safe_signal(SIGINT, &__maildircatch);
  112 }
  113 
  114 static void
  115 _cleantmp(void)
  116 {
  117    struct stat st;
  118    struct n_string s, *sp;
  119    si64_t now;
  120    DIR *dirp;
  121    struct dirent *dp;
  122    NYD_ENTER;
  123 
  124    if ((dirp = opendir("tmp")) == NULL)
  125       goto jleave;
  126 
  127    now = n_time_now(FAL0)->ts_sec;
  128    sp = n_string_creat_auto(&s);
  129 
  130    while ((dp = readdir(dirp)) != NULL) {
  131       if (dp->d_name[0] == '.')
  132          continue;
  133 
  134       sp = n_string_trunc(sp, 0);
  135       sp = n_string_push_buf(sp, "tmp/", sizeof("tmp/") -1);
  136       sp = n_string_push_cp(sp, dp->d_name);
  137       if (stat(n_string_cp(sp), &st) == -1)
  138          continue;
  139       if (st.st_atime + 36*3600 < now)
  140          unlink(sp->s_dat);
  141    }
  142    closedir(dirp);
  143 jleave:
  144    NYD_LEAVE;
  145 }
  146 
  147 static int
  148 _maildir_setfile1(char const *name, enum fedit_mode fm, int omsgCount)
  149 {
  150    int i;
  151    NYD_ENTER;
  152 
  153    if (!(fm & FEDIT_NEWMAIL))
  154       _cleantmp();
  155 
  156    mb.mb_perm = ((n_poption & n_PO_R_FLAG) || (fm & FEDIT_RDONLY))
  157          ? 0 : MB_DELE;
  158    if ((i = _maildir_subdir(name, "cur", fm)) != 0)
  159       goto jleave;
  160    if ((i = _maildir_subdir(name, "new", fm)) != 0)
  161       goto jleave;
  162    _maildir_append(name, NULL, NULL);
  163 
  164    n_autorec_relax_create();
  165    for (i = ((fm & FEDIT_NEWMAIL) ? omsgCount : 0); i < msgCount; ++i) {
  166       readin(name, message + i);
  167       n_autorec_relax_unroll();
  168    }
  169    n_autorec_relax_gut();
  170 
  171    if (fm & FEDIT_NEWMAIL) {
  172       if (msgCount > omsgCount)
  173          qsort(&message[omsgCount], msgCount - omsgCount, sizeof *message,
  174             &a_maildir_cmp);
  175    } else if (msgCount)
  176       qsort(message, msgCount, sizeof *message, &a_maildir_cmp);
  177    i = msgCount;
  178 jleave:
  179    NYD_LEAVE;
  180    return i;
  181 }
  182 
  183 static int
  184 a_maildir_cmp(void const *xa, void const *xb){
  185    char const *cpa, *cpa_pid, *cpb, *cpb_pid;
  186    union {struct message const *mp; char const *cp;} a, b;
  187    si64_t at, bt;
  188    int rv;
  189    NYD2_ENTER;
  190 
  191    a.mp = xa;
  192    b.mp = xb;
  193 
  194    /* We could have parsed the time somewhen in the past, do a quick shot */
  195    at = (si64_t)a.mp->m_time;
  196    bt = (si64_t)b.mp->m_time;
  197    if(at != 0 && bt != 0 && (at -= bt) != 0)
  198       goto jret;
  199 
  200    /* Otherwise we need to parse the name */
  201    a.cp = &a.mp->m_maildir_file[4];
  202    b.cp = &b.mp->m_maildir_file[4];
  203 
  204    /* Interpret time stored in name, and use it for comparison */
  205    if(((n_idec_si64_cp(&at, a.cp, 10, &cpa)
  206             ) & n_IDEC_STATE_EMASK) != n_IDEC_STATE_EBASE || *cpa != '.' ||
  207          a.cp == cpa)
  208       goto jm1; /* Fishy */
  209    if(((n_idec_si64_cp(&bt, b.cp, 10, &cpb)
  210             ) & n_IDEC_STATE_EMASK) != n_IDEC_STATE_EBASE || *cpb != '.' ||
  211          b.cp == cpb)
  212       goto j1; /* Fishy */
  213 
  214    if((at -= bt) != 0)
  215       goto jret;
  216 
  217    /* If the seconds part does not work, go deeper.
  218     * We use de-facto standard "maildir — E-mail directory" from the Courier
  219     * mail server, also used by, e.g., Dovecot: sec.MusecPpid.hostname:2,flags.
  220     * However, a different name convention exists which uses
  221     * sec.pid_counter.hostname:2,flags.
  222     * First go for usec/counter, then pid */
  223 
  224    /* A: exact "standard"? */
  225    cpa_pid = NULL;
  226    a.cp = ++cpa;
  227    if((rv = *a.cp) == 'M')
  228       ;
  229    /* Known compat? */
  230    else if(digitchar(rv)){
  231       cpa_pid = a.cp++;
  232       while((rv = *a.cp) != '\0' && rv != '_')
  233          ++a.cp;
  234       if(rv == '\0')
  235          goto jm1; /* Fishy */
  236    }
  237    /* This is compatible to what dovecot does, it surely does not do so
  238     * for nothing, but i have no idea, but am too stupid to ask */
  239    else for(;; rv = *++a.cp){
  240       if(rv == 'M')
  241          break;
  242       if(rv == '\0' || rv == '.' || rv == n_MAILDIR_SEPARATOR)
  243          goto jm1; /* Fishy */
  244    }
  245    ++a.cp;
  246    if(((n_idec_si64_cp(&at, a.cp, 10, &cpa)
  247             ) & n_IDEC_STATE_EMASK) != n_IDEC_STATE_EBASE)
  248       goto jm1; /* Fishy */
  249 
  250    /* B: as above */
  251    cpb_pid = NULL;
  252    b.cp = ++cpb;
  253    if((rv = *b.cp) == 'M')
  254       ;
  255    else if(digitchar(rv)){
  256       cpb_pid = b.cp++;
  257       while((rv = *b.cp) != '\0' && rv != '_')
  258          ++b.cp;
  259       if(rv == '\0')
  260          goto j1;
  261    }else for(;; rv = *++b.cp){
  262       if(rv == 'M')
  263          break;
  264       if(rv == '\0' || rv == '.' || rv == n_MAILDIR_SEPARATOR)
  265          goto jm1;
  266    }
  267    ++b.cp;
  268    if(((n_idec_si64_cp(&bt, b.cp, 10, &cpb)
  269             ) & n_IDEC_STATE_EMASK) != n_IDEC_STATE_EBASE)
  270       goto j1;
  271 
  272    if((at -= bt) != 0)
  273       goto jret;
  274 
  275    /* So this gets hairy: sort by PID, then hostname */
  276    if(cpa_pid != NULL){
  277       a.cp = cpa_pid;
  278       xa = cpa;
  279    }else{
  280       a.cp = cpa;
  281       if(*a.cp++ != 'P')
  282          goto jm1; /* Fishy */
  283    }
  284    if(((n_idec_si64_cp(&at, a.cp, 10, &cpa)
  285             ) & n_IDEC_STATE_EMASK) != n_IDEC_STATE_EBASE)
  286       goto jm1; /* Fishy */
  287 
  288    if(cpb_pid != NULL){
  289       b.cp = cpb_pid;
  290       xb = cpb;
  291    }else{
  292       b.cp = cpb;
  293       if(*b.cp++ != 'P')
  294          goto j1; /* Fishy */
  295    }
  296    if(((n_idec_si64_cp(&bt, b.cp, 10, &cpb)
  297             ) & n_IDEC_STATE_EMASK) != n_IDEC_STATE_EBASE)
  298       goto jm1; /* Fishy */
  299 
  300    if((at -= bt) != 0)
  301       goto jret;
  302 
  303    /* Hostname */
  304    a.cp = (cpa_pid != NULL) ? xa : cpa;
  305    b.cp = (cpb_pid != NULL) ? xb : cpb;
  306    for(;; ++a.cp, ++b.cp){
  307       char ac, bc;
  308 
  309       ac = *a.cp;
  310       at = (ac != '\0' && ac != n_MAILDIR_SEPARATOR);
  311       bc = *b.cp;
  312       bt = (bc != '\0' && bc != n_MAILDIR_SEPARATOR);
  313       if((at -= bt) != 0)
  314          break;
  315       at = ac;
  316       if((at -= bc) != 0)
  317          break;
  318       if(ac == '\0')
  319          break;
  320    }
  321 
  322 jret:
  323    rv = (at == 0 ? 0 : (at < 0 ? -1 : 1));
  324 jleave:
  325    NYD2_LEAVE;
  326    return rv;
  327 jm1:
  328    rv = -1;
  329    goto jleave;
  330 j1:
  331    rv = 1;
  332    goto jleave;
  333 }
  334 
  335 static int
  336 _maildir_subdir(char const *name, char const *sub, enum fedit_mode fm)
  337 {
  338    DIR *dirp;
  339    struct dirent *dp;
  340    int rv;
  341    NYD_ENTER;
  342 
  343    if ((dirp = opendir(sub)) == NULL) {
  344       n_err(_("Cannot open directory %s\n"),
  345          n_shexp_quote_cp(savecatsep(name, '/', sub), FAL0));
  346       rv = -1;
  347       goto jleave;
  348    }
  349    if (access(sub, W_OK) == -1)
  350       mb.mb_perm = 0;
  351    while ((dp = readdir(dirp)) != NULL) {
  352       if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' ||
  353             (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
  354          continue;
  355       if (dp->d_name[0] == '.')
  356          continue;
  357       if (!(fm & FEDIT_NEWMAIL) || mdlook(dp->d_name, NULL) == NULL)
  358          _maildir_append(name, sub, dp->d_name);
  359    }
  360    closedir(dirp);
  361    rv = 0;
  362 jleave:
  363    NYD_LEAVE;
  364    return rv;
  365 }
  366 
  367 static void
  368 _maildir_append(char const *name, char const *sub, char const *fn)
  369 {
  370    struct message *m;
  371    time_t t = 0;
  372    enum mflag f = MUSED | MNOFROM | MNEWEST;
  373    char const *cp, *xp;
  374    NYD_ENTER;
  375    n_UNUSED(name);
  376 
  377    if (fn != NULL && sub != NULL) {
  378       if (!strcmp(sub, "new"))
  379          f |= MNEW;
  380 
  381       /* C99 */{
  382          si64_t tib;
  383 
  384          (void)/*TODO*/n_idec_si64_cp(&tib, fn, 10, &xp);
  385          t = (time_t)tib;
  386       }
  387 
  388       if ((cp = strrchr(xp, ',')) != NULL && PTRCMP(cp, >, xp + 2) &&
  389             cp[-1] == '2' && cp[-2] == n_MAILDIR_SEPARATOR) {
  390          while (*++cp != '\0') {
  391             switch (*cp) {
  392             case 'F':
  393                f |= MFLAGGED;
  394                break;
  395             case 'R':
  396                f |= MANSWERED;
  397                break;
  398             case 'S':
  399                f |= MREAD;
  400                break;
  401             case 'T':
  402                f |= MDELETED;
  403                break;
  404             case 'D':
  405                f |= MDRAFT;
  406                break;
  407             }
  408          }
  409       }
  410    }
  411 
  412    /* Ensure room (and a NULLified last entry) */
  413    ++msgCount;
  414    message_append(NULL);
  415    --msgCount;
  416 
  417    if (fn == NULL || sub == NULL)
  418       goto jleave;
  419 
  420    m = &message[msgCount++];
  421    /* C99 */{
  422       char *tmp;
  423       size_t sz, i;
  424 
  425       i = strlen(fn) +1;
  426       sz = strlen(sub);
  427       m->m_maildir_file = tmp = n_alloc(sz + 1 + i);
  428       memcpy(tmp, sub, sz);
  429       tmp[sz++] = '/';
  430       memcpy(&tmp[sz], fn, i);
  431    }
  432    m->m_time = t;
  433    m->m_flag = f;
  434    m->m_maildir_hash = n_torek_hash(fn);
  435 jleave:
  436    NYD_LEAVE;
  437    return;
  438 }
  439 
  440 static void
  441 readin(char const *name, struct message *m)
  442 {
  443    char *buf;
  444    size_t bufsize, buflen, cnt;
  445    long size = 0, lines = 0;
  446    off_t offset;
  447    FILE *fp;
  448    int emptyline = 0;
  449    NYD_ENTER;
  450 
  451    if ((fp = Fopen(m->m_maildir_file, "r")) == NULL) {
  452       n_err(_("Cannot read %s for message %lu\n"),
  453          n_shexp_quote_cp(savecatsep(name, '/', m->m_maildir_file), FAL0),
  454          (ul_i)PTR2SIZE(m - message + 1));
  455       m->m_flag |= MHIDDEN;
  456       goto jleave;
  457    }
  458 
  459    cnt = fsize(fp);
  460    fseek(mb.mb_otf, 0L, SEEK_END);
  461    offset = ftell(mb.mb_otf);
  462    buf = smalloc(bufsize = LINESIZE);
  463    buflen = 0;
  464    while (fgetline(&buf, &bufsize, &cnt, &buflen, fp, 1) != NULL) {
  465       /* Since we simply copy over data without doing any transfer
  466        * encoding reclassification/adjustment we *have* to perform
  467        * RFC 4155 compliant From_ quoting here */
  468       if (emptyline && is_head(buf, buflen, FAL0)) {
  469          putc('>', mb.mb_otf);
  470          ++size;
  471       }
  472       size += fwrite(buf, 1, buflen, mb.mb_otf);/*XXX err hdling*/
  473       emptyline = (*buf == '\n');
  474       ++lines;
  475    }
  476    free(buf);
  477    if (!emptyline) {
  478       putc('\n', mb.mb_otf);
  479       ++lines;
  480       ++size;
  481    }
  482 
  483    Fclose(fp);
  484    fflush(mb.mb_otf);
  485    m->m_size = m->m_xsize = size;
  486    m->m_lines = m->m_xlines = lines;
  487    m->m_block = mailx_blockof(offset);
  488    m->m_offset = mailx_offsetof(offset);
  489    substdate(m);
  490 jleave:
  491    NYD_LEAVE;
  492 }
  493 
  494 static void
  495 maildir_update(void)
  496 {
  497    struct message *m;
  498    struct n_timespec const *tsp;
  499    int dodel, c, gotcha = 0, held = 0, modflags = 0;
  500    NYD_ENTER;
  501 
  502    if (mb.mb_perm == 0)
  503       goto jfree;
  504 
  505    if (!(n_pstate & n_PS_EDIT)) {
  506       holdbits();
  507       for (m = message, c = 0; PTRCMP(m, <, message + msgCount); ++m) {
  508          if (m->m_flag & MBOX)
  509             c++;
  510       }
  511       if (c > 0)
  512          if (makembox() == STOP)
  513             goto jbypass;
  514    }
  515 
  516    tsp = n_time_now(TRU1); /* TODO FAL0, eventloop update! */
  517 
  518    n_autorec_relax_create();
  519    for (m = message, gotcha = 0, held = 0; PTRCMP(m, <, message + msgCount);
  520          ++m) {
  521       if (n_pstate & n_PS_EDIT)
  522          dodel = m->m_flag & MDELETED;
  523       else
  524          dodel = !((m->m_flag & MPRESERVE) || !(m->m_flag & MTOUCH));
  525       if (dodel) {
  526          if (unlink(m->m_maildir_file) < 0)
  527             n_err(_("Cannot delete file %s for message %lu\n"),
  528                n_shexp_quote_cp(savecatsep(mailname, '/', m->m_maildir_file),
  529                   FAL0), (ul_i)PTR2SIZE(m - message + 1));
  530          else
  531             ++gotcha;
  532       } else {
  533          if ((m->m_flag & (MREAD | MSTATUS)) == (MREAD | MSTATUS) ||
  534                (m->m_flag & (MNEW | MBOXED | MSAVED | MSTATUS | MFLAG |
  535                MUNFLAG | MANSWER | MUNANSWER | MDRAFT | MUNDRAFT))) {
  536             _maildir_move(tsp, m);
  537             n_autorec_relax_unroll();
  538             ++modflags;
  539          }
  540          ++held;
  541       }
  542    }
  543    n_autorec_relax_gut();
  544 
  545 jbypass:
  546    if ((gotcha || modflags) && (n_pstate & n_PS_EDIT)) {
  547       fprintf(n_stdout, "%s %s\n",
  548          n_shexp_quote_cp(displayname, FAL0),
  549          ((ok_blook(bsdcompat) || ok_blook(bsdmsgs))
  550           ? _("complete") : _("updated.")));
  551    } else if (held && !(n_pstate & n_PS_EDIT) && mb.mb_perm != 0) {
  552       if (held == 1)
  553          fprintf(n_stdout, _("Held 1 message in %s\n"), displayname);
  554       else
  555          fprintf(n_stdout, _("Held %d messages in %s\n"), held, displayname);
  556    }
  557    fflush(n_stdout);
  558 jfree:
  559    for (m = message; PTRCMP(m, <, message + msgCount); ++m)
  560       free(n_UNCONST(m->m_maildir_file));
  561    NYD_LEAVE;
  562 }
  563 
  564 static void
  565 _maildir_move(struct n_timespec const *tsp, struct message *m)
  566 {
  567    char *fn, *newfn;
  568    NYD_ENTER;
  569 
  570    fn = mkname(tsp, m->m_flag, m->m_maildir_file + 4);
  571    newfn = savecat("cur/", fn);
  572    if (!strcmp(m->m_maildir_file, newfn))
  573       goto jleave;
  574    if (link(m->m_maildir_file, newfn) == -1) {
  575       n_err(_("Cannot link %s to %s: message %lu not touched\n"),
  576          n_shexp_quote_cp(savecatsep(mailname, '/', m->m_maildir_file), FAL0),
  577          n_shexp_quote_cp(savecatsep(mailname, '/', newfn), FAL0),
  578          (ul_i)PTR2SIZE(m - message + 1));
  579       goto jleave;
  580    }
  581    if (unlink(m->m_maildir_file) == -1)
  582       n_err(_("Cannot unlink %s\n"),
  583          n_shexp_quote_cp(savecatsep(mailname, '/', m->m_maildir_file), FAL0));
  584 jleave:
  585    NYD_LEAVE;
  586 }
  587 
  588 static char *
  589 mkname(struct n_timespec const *tsp, enum mflag f, char const *pref)
  590 {
  591    static char *node;
  592    static struct n_timespec ts;
  593 
  594    char *cp;
  595    int size, n, i;
  596    NYD_ENTER;
  597 
  598    if (pref == NULL) {
  599       si64_t s;
  600 
  601       if(n_pid == 0)
  602          n_pid = getpid();
  603 
  604       if (node == NULL) {
  605          cp = n_nodename(FAL0);
  606          n = size = 0;
  607          do {
  608             if (UICMP(32, n, <, size + 8))
  609                node = srealloc(node, size += 20);
  610             switch (*cp) {
  611             case '/':
  612                node[n++] = '\\', node[n++] = '0',
  613                node[n++] = '5', node[n++] = '7';
  614                break;
  615             case ':':
  616                node[n++] = '\\', node[n++] = '0',
  617                node[n++] = '7', node[n++] = '2';
  618                break;
  619             default:
  620                node[n++] = *cp;
  621             }
  622          } while (*cp++ != '\0');
  623       }
  624 
  625       /* Problem: Courier spec uses microseconds, not nanoseconds */
  626       if((s = tsp->ts_sec) > ts.ts_sec){
  627          ts.ts_sec = s;
  628          ts.ts_nsec = tsp->ts_nsec / (n_DATE_NANOSSEC / n_DATE_MICROSSEC);
  629       }else{
  630          s = tsp->ts_nsec / (n_DATE_NANOSSEC / n_DATE_MICROSSEC);
  631          if(s <= ts.ts_nsec)
  632             s = ts.ts_nsec + 1;
  633          if(s < n_DATE_MICROSSEC)
  634             ts.ts_nsec = s;
  635          else{
  636             ++ts.ts_sec;
  637             ts.ts_nsec = 0;
  638          }
  639       }
  640 
  641       /* Create a name according to Courier spec */
  642       size = 60 + strlen(node);
  643       cp = n_autorec_alloc(size);
  644       n = snprintf(cp, size, "%" PRId64 ".M%" PRIdZ "P%ld.%s:2,",
  645             ts.ts_sec, ts.ts_nsec, (long)n_pid, node);
  646    } else {
  647       size = (n = strlen(pref)) + 13;
  648       cp = salloc(size);
  649       memcpy(cp, pref, n +1);
  650       for (i = n; i > 3; --i)
  651          if (cp[i - 1] == ',' && cp[i - 2] == '2' &&
  652                cp[i - 3] == n_MAILDIR_SEPARATOR) {
  653             n = i;
  654             break;
  655          }
  656       if (i <= 3) {
  657          memcpy(cp + n, ":2,", 3 +1);
  658          n += 3;
  659       }
  660    }
  661    if (n < size - 7) {
  662       if (f & MDRAFTED)
  663          cp[n++] = 'D';
  664       if (f & MFLAGGED)
  665          cp[n++] = 'F';
  666       if (f & MANSWERED)
  667          cp[n++] = 'R';
  668       if (f & MREAD)
  669          cp[n++] = 'S';
  670       if (f & MDELETED)
  671          cp[n++] = 'T';
  672       cp[n] = '\0';
  673    }
  674    NYD_LEAVE;
  675    return cp;
  676 }
  677 
  678 static enum okay
  679 maildir_append1(struct n_timespec const *tsp, char const *name, FILE *fp,
  680    off_t off1, long size, enum mflag flag)
  681 {
  682    char buf[4096], *fn, *tfn, *nfn;
  683    struct stat st;
  684    FILE *op;
  685    size_t nlen, flen, n;
  686    enum okay rv = STOP;
  687    NYD_ENTER;
  688 
  689    nlen = strlen(name);
  690 
  691    /* Create a unique temporary file */
  692    for (nfn = (char*)0xA /* XXX no magic */;; n_msleep(500, FAL0)) {
  693       flen = strlen(fn = mkname(tsp, flag, NULL));
  694       tfn = n_autorec_alloc(n = nlen + flen + 6);
  695       snprintf(tfn, n, "%s/tmp/%s", name, fn);
  696 
  697       /* Use "wx" for O_EXCL XXX stat(2) rather redundant; coverity:TOCTOU */
  698       if ((!stat(tfn, &st) || n_err_no == n_ERR_NOENT) &&
  699             (op = Fopen(tfn, "wx")) != NULL)
  700          break;
  701 
  702       nfn = (char*)(PTR2SIZE(nfn) - 1);
  703       if (nfn == NULL) {
  704          n_err(_("Can't create an unique file name in %s\n"),
  705             n_shexp_quote_cp(savecat(name, "/tmp"), FAL0));
  706          goto jleave;
  707       }
  708    }
  709 
  710    if (fseek(fp, off1, SEEK_SET) == -1)
  711       goto jtmperr;
  712    while (size > 0) {
  713       size_t z = UICMP(z, size, >, sizeof buf) ? sizeof buf : (size_t)size;
  714 
  715       if (z != (n = fread(buf, 1, z, fp)) || n != fwrite(buf, 1, n, op)) {
  716 jtmperr:
  717          n_err(_("Error writing to %s\n"), n_shexp_quote_cp(tfn, FAL0));
  718          Fclose(op);
  719          goto jerr;
  720       }
  721       size -= n;
  722    }
  723    Fclose(op);
  724 
  725    nfn = n_autorec_alloc(n = nlen + flen + 6);
  726    snprintf(nfn, n, "%s/new/%s", name, fn);
  727    if (link(tfn, nfn) == -1) {
  728       n_err(_("Cannot link %s to %s\n"), n_shexp_quote_cp(tfn, FAL0),
  729          n_shexp_quote_cp(nfn, FAL0));
  730       goto jerr;
  731    }
  732    rv = OKAY;
  733 jerr:
  734    if (unlink(tfn) == -1)
  735       n_err(_("Cannot unlink %s\n"), n_shexp_quote_cp(tfn, FAL0));
  736 jleave:
  737    NYD_LEAVE;
  738    return rv;
  739 }
  740 
  741 static enum okay
  742 trycreate(char const *name)
  743 {
  744    struct stat st;
  745    enum okay rv = STOP;
  746    NYD_ENTER;
  747 
  748    if (!stat(name, &st)) {
  749       if (!S_ISDIR(st.st_mode)) {
  750          n_err(_("%s is not a directory\n"), n_shexp_quote_cp(name, FAL0));
  751          goto jleave;
  752       }
  753    } else if (!n_path_mkdir(name)) {
  754       n_err(_("Cannot create directory %s\n"), n_shexp_quote_cp(name, FAL0));
  755       goto jleave;
  756    }
  757    rv = OKAY;
  758 jleave:
  759    NYD_LEAVE;
  760    return rv;
  761 }
  762 
  763 static enum okay
  764 mkmaildir(char const *name) /* TODO proper cleanup on error; use path[] loop */
  765 {
  766    char *np;
  767    size_t sz;
  768    enum okay rv = STOP;
  769    NYD_ENTER;
  770 
  771    if (trycreate(name) == OKAY) {
  772       np = ac_alloc((sz = strlen(name)) + 4 +1);
  773       memcpy(np, name, sz);
  774       memcpy(np + sz, "/tmp", 4 +1);
  775       if (trycreate(np) == OKAY) {
  776          memcpy(np + sz, "/new", 4 +1);
  777          if (trycreate(np) == OKAY) {
  778             memcpy(np + sz, "/cur", 4 +1);
  779             rv = trycreate(np);
  780          }
  781       }
  782       ac_free(np);
  783    }
  784    NYD_LEAVE;
  785    return rv;
  786 }
  787 
  788 static struct message *
  789 mdlook(char const *name, struct message *data)
  790 {
  791    struct message **mpp, *mp;
  792    ui32_t h, i;
  793    NYD_ENTER;
  794 
  795    if(data != NULL)
  796       i = data->m_maildir_hash;
  797    else
  798       i = n_torek_hash(name);
  799    h = i;
  800    mpp = &a_maildir_tbl[i %= a_maildir_tbl_prime];
  801 
  802    for(i = 0;;){
  803       if((mp = *mpp) == NULL){
  804          if(n_UNLIKELY(data != NULL)){
  805             *mpp = mp = data;
  806             if(i > a_maildir_tbl_maxdist)
  807                a_maildir_tbl_maxdist = i;
  808          }
  809          break;
  810       }else if(mp->m_maildir_hash == h && !strcmp(&mp->m_maildir_file[4], name))
  811          break;
  812 
  813       if(n_UNLIKELY(mpp++ == a_maildir_tbl_top))
  814          mpp = a_maildir_tbl;
  815       if(++i > a_maildir_tbl_maxdist && n_UNLIKELY(data == NULL)){
  816          mp = NULL;
  817          break;
  818       }
  819    }
  820    NYD_LEAVE;
  821    return mp;
  822 }
  823 
  824 static void
  825 mktable(void)
  826 {
  827    struct message *mp;
  828    size_t i;
  829    NYD_ENTER;
  830 
  831    i = a_maildir_tbl_prime = msgCount;
  832    i <<= 1;
  833    do
  834       a_maildir_tbl_prime = n_prime_next(a_maildir_tbl_prime);
  835    while(a_maildir_tbl_prime < i);
  836    a_maildir_tbl = n_calloc(a_maildir_tbl_prime, sizeof *a_maildir_tbl);
  837    a_maildir_tbl_top = &a_maildir_tbl[a_maildir_tbl_prime - 1];
  838    a_maildir_tbl_maxdist = 0;
  839    for(mp = message, i = msgCount; i-- != 0; ++mp)
  840       mdlook(&mp->m_maildir_file[4], mp);
  841    NYD_LEAVE;
  842 }
  843 
  844 static enum okay
  845 subdir_remove(char const *name, char const *sub)
  846 {
  847    char *path;
  848    int pathsize, pathend, namelen, sublen, n;
  849    DIR *dirp;
  850    struct dirent *dp;
  851    enum okay rv = STOP;
  852    NYD_ENTER;
  853 
  854    namelen = strlen(name);
  855    sublen = strlen(sub);
  856    path = smalloc(pathsize = namelen + sublen + 30 +1);
  857    memcpy(path, name, namelen);
  858    path[namelen] = '/';
  859    memcpy(path + namelen + 1, sub, sublen);
  860    path[namelen + sublen + 1] = '/';
  861    path[pathend = namelen + sublen + 2] = '\0';
  862 
  863    if ((dirp = opendir(path)) == NULL) {
  864       n_perr(path, 0);
  865       goto jleave;
  866    }
  867    while ((dp = readdir(dirp)) != NULL) {
  868       if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' ||
  869             (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
  870          continue;
  871       if (dp->d_name[0] == '.')
  872          continue;
  873       n = strlen(dp->d_name);
  874       if (UICMP(32, pathend + n +1, >, pathsize))
  875          path = srealloc(path, pathsize = pathend + n + 30);
  876       memcpy(path + pathend, dp->d_name, n +1);
  877       if (unlink(path) == -1) {
  878          n_perr(path, 0);
  879          closedir(dirp);
  880          goto jleave;
  881       }
  882    }
  883    closedir(dirp);
  884 
  885    path[pathend] = '\0';
  886    if (rmdir(path) == -1) {
  887       n_perr(path, 0);
  888       goto jleave;
  889    }
  890    rv = OKAY;
  891 jleave:
  892    free(path);
  893    NYD_LEAVE;
  894    return rv;
  895 }
  896 
  897 FL int
  898 maildir_setfile(char const * volatile name, enum fedit_mode fm)
  899 {
  900    sighandler_type volatile saveint;
  901    struct cw cw;
  902    char const *emsg;
  903    int omsgCount;
  904    int volatile i = -1;
  905    NYD_ENTER;
  906 
  907    omsgCount = msgCount;
  908    if (cwget(&cw) == STOP) {
  909       n_alert(_("Cannot open current directory"));
  910       goto jleave;
  911    }
  912 
  913    if (!(fm & FEDIT_NEWMAIL) && !quit(FAL0))
  914       goto jleave;
  915 
  916    saveint = safe_signal(SIGINT, SIG_IGN);
  917 
  918    if (!(fm & FEDIT_NEWMAIL)) {
  919       if (fm & FEDIT_SYSBOX)
  920          n_pstate &= ~n_PS_EDIT;
  921       else
  922          n_pstate |= n_PS_EDIT;
  923       if (mb.mb_itf) {
  924          fclose(mb.mb_itf);
  925          mb.mb_itf = NULL;
  926       }
  927       if (mb.mb_otf) {
  928          fclose(mb.mb_otf);
  929          mb.mb_otf = NULL;
  930       }
  931       initbox(name);
  932       mb.mb_type = MB_MAILDIR;
  933    }
  934 
  935    if(!n_is_dir(name, FAL0)){
  936       emsg = N_("Not a maildir: %s\n");
  937       goto jerr;
  938    }else if(chdir(name) < 0){
  939       emsg = N_("Cannot enter maildir://%s\n");
  940 jerr:
  941       n_err(V_(emsg), n_shexp_quote_cp(name, FAL0));
  942       mb.mb_type = MB_VOID;
  943       *mailname = '\0';
  944       msgCount = 0;
  945       cwrelse(&cw);
  946       safe_signal(SIGINT, saveint);
  947       goto jleave;
  948    }
  949 
  950    a_maildir_tbl = NULL;
  951    if (sigsetjmp(_maildir_jmp, 1) == 0) {
  952       if (fm & FEDIT_NEWMAIL)
  953          mktable();
  954       if (saveint != SIG_IGN)
  955          safe_signal(SIGINT, &__maildircatch);
  956       i = _maildir_setfile1(name, fm, omsgCount);
  957    }
  958    if ((fm & FEDIT_NEWMAIL) && a_maildir_tbl != NULL)
  959       n_free(a_maildir_tbl);
  960 
  961    safe_signal(SIGINT, saveint);
  962 
  963    if (i < 0) {
  964       mb.mb_type = MB_VOID;
  965       *mailname = '\0';
  966       msgCount = 0;
  967    }
  968 
  969    if (cwret(&cw) == STOP)
  970       n_panic(_("Cannot change back to current directory"));
  971    cwrelse(&cw);
  972 
  973    setmsize(msgCount);
  974    if ((fm & FEDIT_NEWMAIL) && mb.mb_sorted && msgCount > omsgCount) {
  975       mb.mb_threaded = 0;
  976       c_sort((void*)-1);
  977    }
  978 
  979    if (!(fm & FEDIT_NEWMAIL)) {
  980       n_pstate &= ~n_PS_SAW_COMMAND;
  981       n_pstate |= n_PS_SETFILE_OPENED;
  982    }
  983 
  984    if ((n_poption & n_PO_EXISTONLY) && !(n_poption & n_PO_HEADERLIST)) {
  985       i = (msgCount == 0);
  986       goto jleave;
  987    }
  988 
  989    if (!(fm & FEDIT_NEWMAIL) && (fm & FEDIT_SYSBOX) && msgCount == 0) {
  990       if (mb.mb_type == MB_MAILDIR /* XXX ?? */ && !ok_blook(emptystart))
  991          n_err(_("No mail at %s\n"), n_shexp_quote_cp(name, FAL0));
  992       i = 1;
  993       goto jleave;
  994    }
  995 
  996    if ((fm & FEDIT_NEWMAIL) && msgCount > omsgCount)
  997       newmailinfo(omsgCount);
  998    i = 0;
  999 jleave:
 1000    NYD_LEAVE;
 1001    return i;
 1002 }
 1003 
 1004 FL bool_t
 1005 maildir_quit(bool_t hold_sigs_on)
 1006 {
 1007    sighandler_type saveint;
 1008    struct cw cw;
 1009    bool_t rv;
 1010    NYD_ENTER;
 1011 
 1012    if(hold_sigs_on)
 1013       rele_sigs();
 1014 
 1015    rv = FAL0;
 1016 
 1017    if (cwget(&cw) == STOP) {
 1018       n_alert(_("Cannot open current directory"));
 1019       goto jleave;
 1020    }
 1021 
 1022    saveint = safe_signal(SIGINT, SIG_IGN);
 1023 
 1024    if (chdir(mailname) == -1) {
 1025       n_err(_("Cannot change directory to %s\n"),
 1026          n_shexp_quote_cp(mailname, FAL0));
 1027       cwrelse(&cw);
 1028       safe_signal(SIGINT, saveint);
 1029       goto jleave;
 1030    }
 1031 
 1032    if (sigsetjmp(_maildir_jmp, 1) == 0) {
 1033       if (saveint != SIG_IGN)
 1034          safe_signal(SIGINT, &__maildircatch_hold);
 1035       maildir_update();
 1036    }
 1037 
 1038    safe_signal(SIGINT, saveint);
 1039 
 1040    if (cwret(&cw) == STOP)
 1041       n_panic(_("Cannot change back to current directory"));
 1042    cwrelse(&cw);
 1043    rv = TRU1;
 1044 jleave:
 1045    if(hold_sigs_on)
 1046       hold_sigs();
 1047    NYD_LEAVE;
 1048    return rv;
 1049 }
 1050 
 1051 FL enum okay
 1052 maildir_append(char const *name, FILE *fp, long offset)
 1053 {
 1054    struct n_timespec const *tsp;
 1055    char *buf, *bp, *lp;
 1056    size_t bufsize, buflen, cnt;
 1057    off_t off1 = -1, offs;
 1058    long size;
 1059    int flag;
 1060    enum {_NONE = 0, _INHEAD = 1<<0, _NLSEP = 1<<1} state;
 1061    enum okay rv;
 1062    NYD_ENTER;
 1063 
 1064    if ((rv = mkmaildir(name)) != OKAY)
 1065       goto jleave;
 1066 
 1067    buf = smalloc(bufsize = LINESIZE); /* TODO line pool; signals */
 1068    buflen = 0;
 1069    cnt = fsize(fp);
 1070    offs = offset /* BSD will move due to O_APPEND! ftell(fp) */;
 1071    size = 0;
 1072    tsp = n_time_now(TRU1); /* TODO -> eventloop */
 1073 
 1074    n_autorec_relax_create();
 1075    for (flag = MNEW, state = _NLSEP;;) {
 1076       bp = fgetline(&buf, &bufsize, &cnt, &buflen, fp, 1);
 1077 
 1078       if (bp == NULL ||
 1079             ((state & (_INHEAD | _NLSEP)) == _NLSEP &&
 1080              is_head(buf, buflen, FAL0))) {
 1081          if (off1 != (off_t)-1) {
 1082             if ((rv = maildir_append1(tsp, name, fp, off1, size, flag)) == STOP)
 1083                goto jfree;
 1084             n_autorec_relax_unroll();
 1085             if (fseek(fp, offs + buflen, SEEK_SET) == -1) {
 1086                rv = STOP;
 1087                goto jfree;
 1088             }
 1089          }
 1090          off1 = offs + buflen;
 1091          size = 0;
 1092          state = _INHEAD;
 1093          flag = MNEW;
 1094 
 1095          if (bp == NULL)
 1096             break;
 1097       } else
 1098          size += buflen;
 1099       offs += buflen;
 1100 
 1101       state &= ~_NLSEP;
 1102       if (buf[0] == '\n') {
 1103          state &= ~_INHEAD;
 1104          state |= _NLSEP;
 1105       } else if (state & _INHEAD) {
 1106          if (!ascncasecmp(buf, "status", 6)) {
 1107             lp = buf + 6;
 1108             while (whitechar(*lp))
 1109                ++lp;
 1110             if (*lp == ':')
 1111                while (*++lp != '\0')
 1112                   switch (*lp) {
 1113                   case 'R':
 1114                      flag |= MREAD;
 1115                      break;
 1116                   case 'O':
 1117                      flag &= ~MNEW;
 1118                      break;
 1119                   }
 1120          } else if (!ascncasecmp(buf, "x-status", 8)) {
 1121             lp = buf + 8;
 1122             while (whitechar(*lp))
 1123                ++lp;
 1124             if (*lp == ':') {
 1125                while (*++lp != '\0')
 1126                   switch (*lp) {
 1127                   case 'F':
 1128                      flag |= MFLAGGED;
 1129                      break;
 1130                   case 'A':
 1131                      flag |= MANSWERED;
 1132                      break;
 1133                   case 'T':
 1134                      flag |= MDRAFTED;
 1135                      break;
 1136                   }
 1137             }
 1138          }
 1139       }
 1140    }
 1141    assert(rv == OKAY);
 1142 jfree:
 1143    n_autorec_relax_gut();
 1144    free(buf);
 1145 jleave:
 1146    NYD_LEAVE;
 1147    return rv;
 1148 }
 1149 
 1150 FL enum okay
 1151 maildir_remove(char const *name)
 1152 {
 1153    enum okay rv = STOP;
 1154    NYD_ENTER;
 1155 
 1156    if (subdir_remove(name, "tmp") == STOP ||
 1157          subdir_remove(name, "new") == STOP ||
 1158          subdir_remove(name, "cur") == STOP)
 1159       goto jleave;
 1160    if (rmdir(name) == -1) {
 1161       n_perr(name, 0);
 1162       goto jleave;
 1163    }
 1164    rv = OKAY;
 1165 jleave:
 1166    NYD_LEAVE;
 1167    return rv;
 1168 }
 1169 
 1170 /* s-it-mode */