"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.6.2/src/active.c" (23 Dec 2022, 36419 Bytes) of package /linux/misc/tin-2.6.2.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 "active.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.6.1_vs_2.6.2.

    1 /*
    2  *  Project   : tin - a Usenet reader
    3  *  Module    : active.c
    4  *  Author    : I. Lea
    5  *  Created   : 1992-02-16
    6  *  Updated   : 2022-12-23
    7  *  Notes     :
    8  *
    9  * Copyright (c) 1992-2023 Iain Lea <iain@bricbrac.de>
   10  * 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  *
   16  * 1. Redistributions of source code must retain the above copyright notice,
   17  *    this list of conditions and the following disclaimer.
   18  *
   19  * 2. Redistributions in binary form must reproduce the above copyright
   20  *    notice, this list of conditions and the following disclaimer in the
   21  *    documentation and/or other materials provided with the distribution.
   22  *
   23  * 3. Neither the name of the copyright holder nor the names of its
   24  *    contributors may be used to endorse or promote products derived from
   25  *    this software without specific prior written permission.
   26  *
   27  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   28  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   29  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   30  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
   31  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   32  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   33  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   34  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
   35  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   36  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   37  * POSSIBILITY OF SUCH DAMAGE.
   38  */
   39 
   40 
   41 #ifndef TIN_H
   42 #   include "tin.h"
   43 #endif /* !TIN_H */
   44 #ifndef TCURSES_H
   45 #   include "tcurses.h"
   46 #endif /* !TCURSES_H */
   47 
   48 /*
   49  * List of allowed separator chars in active file
   50  * unused in parse_active_line()
   51  */
   52 #define ACTIVE_SEP  " \n"
   53 
   54 #ifdef NNTP_ABLE
   55 #   ifdef DISABLE_PIPELINING
   56 #       define NUM_SIMULTANEOUS_GROUP_COMMAND 1
   57 #   else
   58 #       define NUM_SIMULTANEOUS_GROUP_COMMAND 50
   59 #   endif /* DISABLE_PIPELINING */
   60 #endif /* NNTP_ABLE */
   61 
   62 t_bool force_reread_active_file = FALSE;
   63 static time_t active_timestamp; /* time active file read (local) */
   64 
   65 
   66 /*
   67  * Local prototypes
   68  */
   69 static FILE *open_newgroups_fp(int idx);
   70 static FILE *open_news_active_fp(void);
   71 static int check_for_any_new_groups(void);
   72 static void active_add(struct t_group *ptr, t_artnum count, t_artnum max, t_artnum min, const char *moderated);
   73 static void append_group_line(char *active_file, char *group_path, t_artnum art_max, t_artnum art_min, char *base_dir);
   74 static void make_group_list(char *active_file, char *base_dir, char *fixed_base, char *group_path);
   75 static void read_active_file(void);
   76 static void read_newsrc_active_file(void);
   77 static void subscribe_new_group(char *group, char *autosubscribe, char *autounsubscribe);
   78 #ifdef NNTP_ABLE
   79     static t_bool do_read_newsrc_active_file(FILE *fp);
   80     static t_bool parse_count_line(char *line, t_artnum *max, t_artnum *min, t_artnum *count, char *moderated);
   81     static void read_active_counts(void);
   82 #else
   83     static void do_read_newsrc_active_file(FILE *fp);
   84 #endif /* NNTP_ABLE */
   85 
   86 
   87 t_bool
   88 need_reread_active_file(
   89     void)
   90 {
   91     return (force_reread_active_file || (tinrc.reread_active_file_secs != 0 &&
   92         (int) (time(NULL) - active_timestamp) >= tinrc.reread_active_file_secs));
   93 }
   94 
   95 
   96 /*
   97  * Resync active file when reread_active_file_secs have passed or
   98  * force_reread_active_file is set.
   99  * Return TRUE if a reread was performed
  100  */
  101 t_bool
  102 resync_active_file(
  103     void)
  104 {
  105     char *old_group = NULL;
  106     t_bool command_line = FALSE;
  107 
  108     if (!need_reread_active_file())
  109         return FALSE;
  110 
  111     reread_active_for_posted_arts = FALSE;
  112 
  113     if (selmenu.curr >= 0 && selmenu.max)
  114         old_group = my_strdup(CURR_GROUP.name);
  115 
  116     write_newsrc();
  117     read_news_active_file();
  118 
  119 #ifdef HAVE_MH_MAIL_HANDLING
  120     read_mail_active_file();
  121 #endif /* HAVE_MH_MAIL_HANDLING */
  122 
  123     if (read_cmd_line_groups())
  124         command_line = TRUE;
  125 
  126     read_newsrc(newsrc, bool_not(command_line));
  127 
  128     if (command_line)       /* Can't show only unread groups with cmd line groups */
  129         tinrc.show_only_unread_groups = FALSE;
  130     else
  131         toggle_my_groups(old_group);
  132 
  133     FreeIfNeeded(old_group);
  134     show_selection_page();
  135 
  136     return TRUE;
  137 }
  138 
  139 
  140 /*
  141  * Populate a slot in the active[] array
  142  * TODO: 1) Have a preinitialised default slot and block assign it for speed
  143  * TODO: 2) Lump count/max/min/moderat into a t_active, big patch but much cleaner throughout tin
  144  */
  145 static void
  146 active_add(
  147     struct t_group *ptr,
  148     t_artnum count,
  149     t_artnum max,
  150     t_artnum min,
  151     const char *moderated)
  152 {
  153     /* name - pre-initialised when group is made */
  154     ptr->aliasedto = ((moderated[0] == '=') ? my_strdup(moderated + 1) : NULL);
  155     ptr->description = NULL;
  156     /* spool - see below */
  157     ptr->moderated = moderated[0];
  158     ptr->count = count;
  159     ptr->xmax = max;
  160     ptr->xmin = min;
  161     /* type - see below */
  162     ptr->inrange = FALSE;
  163     ptr->read_during_session = FALSE;
  164     ptr->art_was_posted = FALSE;
  165     ptr->subscribed = FALSE;            /* not in my_group[] yet */
  166     ptr->newgroup = FALSE;
  167     ptr->bogus = FALSE;
  168     ptr->next = -1;                     /* hash chain */
  169     ptr->newsrc.xbitmap = (t_bitmap *) 0;
  170     ptr->attribute = (struct t_attribute *) 0;
  171     ptr->glob_filter = &glob_filter;
  172     set_default_bitmap(ptr);
  173 
  174     if (moderated[0] == '/') {
  175         ptr->type = GROUP_TYPE_SAVE;
  176         ptr->spooldir = my_strdup(moderated); /* TODO: Unix'ism, other OSs need transformation */
  177     } else {
  178         ptr->type = GROUP_TYPE_NEWS;
  179         ptr->spooldir = spooldir;       /* another global - sigh */
  180     }
  181 }
  182 
  183 
  184 /*
  185  * Decide how to handle a bogus groupname.
  186  * If we process them interactively, create an empty active[] for this
  187  * group and mark it bogus for display in the group selection page
  188  * Otherwise, bogus groups are not displayed and are dealt with when newsrc
  189  * is written.
  190  */
  191 t_bool
  192 process_bogus(
  193     char *name) /* return value is always ignored */
  194 {
  195     struct t_group *ptr;
  196 
  197     if (read_saved_news || tinrc.strip_bogus != BOGUS_SHOW)
  198         return FALSE;
  199 
  200     if ((ptr = group_add(name)) == NULL)
  201         return FALSE;
  202 
  203     active_add(ptr, T_ARTNUM_CONST(0), T_ARTNUM_CONST(1), T_ARTNUM_CONST(0), "n");
  204     ptr->bogus = TRUE;      /* Mark it bogus */
  205     /*
  206      * Set pointer to default attributes
  207      */
  208     if (ptr->attribute && !ptr->attribute->global)
  209         free(ptr->attribute);
  210     ptr->attribute = scopes[0].attribute;
  211 
  212     if (my_group_add(name, FALSE) < 0)
  213         return TRUE;
  214 
  215     return FALSE;       /* Nothing was printed yet */
  216 }
  217 
  218 
  219 /*
  220  * Parse line from news or mail active files
  221  */
  222 t_bool
  223 parse_active_line(
  224     char *line,
  225     t_artnum *max,
  226     t_artnum *min,
  227     char *moderated)
  228 {
  229     char *p = NULL, *q = NULL, *r = NULL;
  230 #ifdef DEBUG
  231     char *l;
  232 #endif /* DEBUG */
  233     t_bool lineok = FALSE;
  234 
  235     if (line[0] == '#' || line[0] == '\0')
  236         return lineok;
  237 
  238 #ifdef DEBUG
  239     l = my_strdup(line);
  240 #endif /* DEBUG */
  241 
  242     if (strtok(line, ACTIVE_SEP)) {     /* skip group name */
  243         if ((p = strtok(NULL, ACTIVE_SEP))) {   /* group max count */
  244             if ((q = strtok(NULL, ACTIVE_SEP))) {   /* group min count */
  245                 if ((r = strtok(NULL, ACTIVE_SEP))) /* mod status or path to mailgroup */
  246                     lineok = TRUE;
  247             }
  248         }
  249     }
  250 
  251     if (!lineok) {
  252 #ifdef DEBUG
  253         /* TODO: This also logs broken non NNTP active lines (i.e. mail.active) to NNTP */
  254         if ((debug & DEBUG_NNTP) && verbose > 1)
  255             debug_print_file("NNTP", "Active file corrupt - %s", l);
  256 
  257         free(l);
  258 #endif /* DEBUG */
  259         if (!p || !q) {
  260             return lineok;
  261         }
  262     }
  263 
  264     *max = atoartnum(p);
  265     *min = atoartnum(q);
  266 
  267     if (!lineok) { /* missing moderation flag - seen on usenet.farm */
  268         strcpy(moderated, "y"); /* guess posting is fine */
  269         lineok = TRUE;
  270     } else {
  271 #ifdef DEBUG
  272         free(l);
  273 #endif /* DEBUG */
  274         strcpy(moderated, r);
  275     }
  276 
  277     return lineok;
  278 }
  279 
  280 
  281 #ifdef NNTP_ABLE
  282 /*
  283  * Parse line from "LIST COUNTS" (RFC 6048 2.2.)
  284  * group high low count status
  285  */
  286 static t_bool
  287 parse_count_line(
  288     char *line,
  289     t_artnum *max,
  290     t_artnum *min,
  291     t_artnum *count,
  292     char *moderated)
  293 {
  294     char *p = NULL, *q = NULL, *r = NULL, *s = NULL;
  295     t_bool lineok = FALSE;
  296 
  297     if (line[0] == '#' || line[0] == '\0')
  298         return FALSE;
  299 
  300     if (strtok(line, ACTIVE_SEP)) {     /* skip group name */
  301         if ((p = strtok(NULL, ACTIVE_SEP))) {   /* group max */
  302             if ((q = strtok(NULL, ACTIVE_SEP))) {   /* group min */
  303                 if ((r = strtok(NULL, ACTIVE_SEP))) { /* group count */
  304                     s = strtok(NULL, ACTIVE_SEP);   /* mod status or path to mailgroup */
  305                     lineok = TRUE;
  306                 }
  307             }
  308         }
  309     }
  310 
  311     if (!p || !q || !r || !s || !lineok) {
  312 #   ifdef DEBUG
  313         if ((debug & DEBUG_NNTP) && verbose > 1)
  314             debug_print_file("NNTP", "unparsable \"LIST COUNTS\" line: \"%s\"", line);
  315 #   endif /* DEBUG */
  316         return FALSE;
  317     }
  318 
  319     *max = atoartnum(p);
  320     *min = atoartnum(q);
  321     *count = atoartnum(r);
  322     strcpy(moderated, s);
  323 
  324     return TRUE;
  325 }
  326 #endif /* NNTP_ABLE */
  327 
  328 
  329 /*
  330  * Load the active information into active[] by counting the min/max/count
  331  * for each news group.
  332  * Parse a line from the .newsrc file
  333  * Send GROUP command to NNTP server directly to keep window.
  334  * We can't know the 'moderator' status and always return 'y'
  335  * But we don't change if the 'moderator' status is already checked by
  336  * read_active_file()
  337  * Returns TRUE if NNTP is enabled and authentication is needed
  338  */
  339 #ifdef NNTP_ABLE
  340 static t_bool
  341 #else
  342 static void
  343 #endif /* NNTP_ABLE */
  344 do_read_newsrc_active_file(
  345     FILE *fp)
  346 {
  347     char *ptr;
  348     char *p;
  349     char moderated[PATH_LEN];
  350     int window = 0;
  351     long processed = 0L;
  352     t_artnum count = T_ARTNUM_CONST(-1), min = T_ARTNUM_CONST(1), max = T_ARTNUM_CONST(0);
  353     static char ngname[NNTP_GRPLEN + 1]; /* RFC 3977 3.1 limits group names to 497 octets */
  354     struct t_group *grpptr;
  355 #ifdef NNTP_ABLE
  356     t_bool need_auth = FALSE;
  357     char *ngnames[NUM_SIMULTANEOUS_GROUP_COMMAND];
  358     int index_i = 0;
  359     int index_o = 0;
  360 #endif /* NNTP_ABLE */
  361 
  362     rewind(fp);
  363 
  364     if (!batch_mode || verbose)
  365         wait_message(0, _(txt_reading_news_newsrc_file));
  366 
  367     while ((ptr = tin_fgets(fp, FALSE)) != NULL || window != 0) {
  368         if (ptr) {
  369             p = strpbrk(ptr, ":!");
  370 
  371             if (!p || *p != SUBSCRIBED) /* Invalid line or unsubscribed */
  372                 continue;
  373             *p = '\0';          /* Now ptr is the group name */
  374 
  375             /*
  376              * 128 should be enough for a groupname, >256 and we overflow buffers
  377              * later on
  378              * TODO: check RFCs for possible max. size
  379              */
  380             my_strncpy(ngname, ptr, 128);
  381             ptr = ngname;
  382         }
  383 
  384         if (read_news_via_nntp && !read_saved_news) {
  385 #ifdef NNTP_ABLE
  386             char buf[NNTP_STRLEN];
  387             char line[NNTP_STRLEN];
  388 
  389             if (window < NUM_SIMULTANEOUS_GROUP_COMMAND && ptr && (!list_active || (newsrc_active && list_active && group_find(ptr, FALSE)))) {
  390                 ngnames[index_i] = my_strdup(ptr);
  391                 snprintf(buf, sizeof(buf), "GROUP %s", ngnames[index_i]);
  392 #   ifdef DEBUG
  393                 if ((debug & DEBUG_NNTP) && verbose > 1)
  394                     debug_print_file("NNTP", "read_newsrc_active_file() %s", buf);
  395 #   endif /* DEBUG */
  396                 put_server(buf);
  397                 index_i = (index_i + 1) % NUM_SIMULTANEOUS_GROUP_COMMAND;
  398                 window++;
  399             }
  400             if (window == NUM_SIMULTANEOUS_GROUP_COMMAND || ptr == NULL) {
  401                 int respcode = get_only_respcode(line, sizeof(line));
  402 
  403                 if (reconnected_in_last_get_server) {
  404                     /*
  405                      * If tin reconnected, last output is resended to server.
  406                      * So received data is for ngnames[last window_i].
  407                      * We resend all buffered command except for last window_i.
  408                      * And rotate buffer to use data received.
  409                      */
  410                     int i;
  411                     int j = index_o;
  412                     for (i = 0; i < window - 1; i++) {
  413                         snprintf(buf, sizeof(buf), "GROUP %s", ngnames[j]);
  414 #   ifdef DEBUG
  415                         if ((debug & DEBUG_NNTP) && verbose > 1)
  416                             debug_print_file("NNTP", "read_newsrc_active_file() %s", buf);
  417 #   endif /* DEBUG */
  418                         put_server(buf);
  419                         j = (j + 1) % NUM_SIMULTANEOUS_GROUP_COMMAND;
  420                     }
  421                     if (--index_o < 0)
  422                         index_o = NUM_SIMULTANEOUS_GROUP_COMMAND - 1;
  423                     if (--index_i < 0)
  424                         index_i = NUM_SIMULTANEOUS_GROUP_COMMAND - 1;
  425                     if (index_i != index_o)
  426                         ngnames[index_o] = ngnames[index_i];
  427                 }
  428 
  429                 switch (respcode) {
  430 
  431                     case OK_GROUP:
  432                         {
  433                             char fmt[25];
  434 
  435                             snprintf(fmt, sizeof(fmt), "%%"T_ARTNUM_SFMT" %%"T_ARTNUM_SFMT" %%"T_ARTNUM_SFMT" %%%ds", NNTP_GRPLEN);
  436                             if (sscanf(line, fmt, &count, &min, &max, ngname) != 4) {
  437 #   ifdef DEBUG
  438                                 if ((debug & DEBUG_NNTP) && verbose > 1)
  439                                     debug_print_file("NNTP", "Invalid response to \"GROUP %s\": \"%s\"", ngnames[index_o], line);
  440 #   endif /* DEBUG */
  441                             }
  442                             if (strcmp(ngname, ngnames[index_o]) != 0) {
  443 #   ifdef DEBUG
  444                                 if ((debug & DEBUG_NNTP) && verbose > 1)
  445                                     debug_print_file("NNTP", "Groupname mismatch in response to \"GROUP %s\": \"%s\"", ngnames[index_o], line);
  446 #   endif /* DEBUG */
  447                             }
  448                             ptr = ngname;
  449                             free(ngnames[index_o]);
  450                             index_o = (index_o + 1) % NUM_SIMULTANEOUS_GROUP_COMMAND;
  451                             window--;
  452                             break;
  453                         }
  454 
  455                     case ERR_NOAUTH:
  456                     case NEED_AUTHINFO:
  457                         need_auth = TRUE; /* delay auth till end of loop */
  458                         /* keep lint quiet: */
  459                         /* FALLTHROUGH */
  460 
  461                     case ERR_NOGROUP:
  462                         free(ngnames[index_o]);
  463                         index_o = (index_o + 1) % NUM_SIMULTANEOUS_GROUP_COMMAND;
  464                         window--;
  465                         continue;
  466 
  467                     case ERR_ACCESS:
  468                         tin_done(NNTP_ERROR_EXIT, "%s", line);
  469                         /* keep lint quiet: */
  470                         /* FALLTHROUGH */
  471 
  472                     default:
  473 #   ifdef DEBUG
  474                         if ((debug & DEBUG_NNTP) && verbose > 1)
  475                             debug_print_file("NNTP", "NOT_OK %s", line);
  476 #   endif /* DEBUG */
  477                         free(ngnames[index_o]);
  478                         index_o = (index_o + 1) % NUM_SIMULTANEOUS_GROUP_COMMAND;
  479                         window--;
  480                         continue;
  481                 }
  482             } else
  483                 continue;
  484 #endif /* NNTP_ABLE */
  485         } else {
  486             if (group_get_art_info(spooldir, ptr, GROUP_TYPE_NEWS, &count, &max, &min))
  487                 continue;
  488         }
  489 
  490         strcpy(moderated, "y");
  491 
  492         if (++processed % 5 == 0)
  493             spin_cursor();
  494 
  495         /*
  496          * Load group into group hash table
  497          * NULL means group already present, so we just fixup the counters
  498          * This call may implicitly ++num_active
  499          */
  500         if ((grpptr = group_add(ptr)) == NULL) {
  501             t_bool changed = FALSE;
  502 
  503             if ((grpptr = group_find(ptr, FALSE)) == NULL)
  504                 continue;
  505 
  506             if (max > grpptr->xmax) {
  507                 grpptr->xmax = max;
  508                 changed = TRUE;
  509             }
  510             if (min > grpptr->xmin) {
  511                 grpptr->xmin = min;
  512                 changed = TRUE;
  513             }
  514             if (changed) {
  515                 grpptr->count = count;
  516                 expand_bitmap(grpptr, 0); /* TODO: expand_bitmap(grpptr,grpptr->xmin) should be enough */
  517             }
  518             continue;
  519         }
  520 
  521         /*
  522          * Load the new group in active[]
  523          */
  524         active_add(grpptr, count, max, min, moderated);
  525     }
  526 #ifdef NNTP_ABLE
  527     return need_auth;
  528 #endif /* NNTP_ABLE */
  529 }
  530 
  531 
  532 /*
  533  * Wrapper for do_read_newsrc_active_file() to handle
  534  * missing authentication
  535  */
  536 static void
  537 read_newsrc_active_file(
  538     void)
  539 {
  540     FILE *fp;
  541 #ifdef NNTP_ABLE
  542     t_bool need_auth;
  543 #endif /* NNTP_ABLE */
  544 
  545     /*
  546      * return immediately if no .newsrc can be found or .newsrc is empty
  547      * when function asked to use .newsrc
  548      */
  549     if ((fp = fopen(newsrc, "r")) == NULL)
  550         return;
  551 
  552     if (file_size(newsrc) <= 0L) {
  553         fclose(fp);
  554         return;
  555     }
  556 
  557 #ifdef NNTP_ABLE
  558     need_auth = do_read_newsrc_active_file(fp);
  559 #else
  560     do_read_newsrc_active_file(fp);
  561 #endif /* NNTP_ABLE */
  562 
  563 #ifdef NNTP_ABLE
  564     if (need_auth) { /* delayed auth */
  565         if (!authenticate(nntp_server, userid, FALSE))
  566             tin_done(EXIT_FAILURE, _(txt_auth_failed), ERR_ACCESS);
  567 #   if defined(MAXARTNUM) && defined(USE_LONG_ARTICLE_NUMBERS)
  568         set_maxartnum(FALSE);
  569 #   endif /* MAXARTNUM && USE_LONG_ARTICLE_NUMBERS */
  570         if (do_read_newsrc_active_file(fp))
  571             tin_done(EXIT_FAILURE, _(txt_auth_failed), ERR_ACCESS);
  572     }
  573 #endif /* NNTP_ABLE */
  574 
  575     fclose(fp);
  576 
  577     /*
  578      * Exit if active file wasn't read correctly or is empty
  579      */
  580     if (tin_errno || !num_active) {
  581         if (newsrc_active && !num_active)
  582             tin_done(EXIT_FAILURE, _(txt_error_server_has_no_listed_groups), newsrc);
  583         else
  584             tin_done(EXIT_FAILURE, _(txt_active_file_is_empty), (read_news_via_nntp ? (read_saved_news ? news_active_file : _(txt_servers_active)) : news_active_file));
  585     }
  586 
  587     if (!batch_mode || verbose)
  588         my_fputc('\n', stdout);
  589 }
  590 
  591 
  592 /*
  593  * Open the news active file locally or send the LIST command
  594  */
  595 static FILE *
  596 open_news_active_fp(
  597     void)
  598 {
  599 #ifdef NNTP_ABLE
  600     if (read_news_via_nntp && !read_saved_news)
  601         return (nntp_command("LIST", OK_GROUPS, NULL, 0));
  602 #endif /* NNTP_ABLE */
  603     return (fopen(news_active_file, "r"));
  604 }
  605 
  606 
  607 /*
  608  * Load the active file into active[]
  609  */
  610 static void
  611 read_active_file(
  612     void)
  613 {
  614     FILE *fp;
  615     char *ptr;
  616     char moderated[PATH_LEN];
  617     long processed = 0L;
  618     struct t_group *grpptr;
  619     t_artnum count = T_ARTNUM_CONST(-1), min = T_ARTNUM_CONST(1), max = T_ARTNUM_CONST(0);
  620 
  621     if (!batch_mode || verbose)
  622         wait_message(0, _(txt_reading_news_active_file));
  623 
  624     if ((fp = open_news_active_fp()) == NULL) {
  625         if (cmd_line && !batch_mode)
  626             my_fputc('\n', stderr);
  627 
  628 #ifdef NNTP_ABLE
  629         if (read_news_via_nntp)
  630             tin_done(EXIT_FAILURE, _(txt_cannot_retrieve), ACTIVE_FILE);
  631 #   ifndef NNTP_ONLY
  632         else
  633             tin_done(EXIT_FAILURE, _(txt_cannot_open_active_file), news_active_file, tin_progname);
  634 #   endif /* !NNTP_ONLY */
  635 #else
  636         tin_done(EXIT_FAILURE, _(txt_cannot_open), news_active_file);
  637 #endif /* NNTP_ABLE */
  638     }
  639 
  640     while ((ptr = tin_fgets(fp, FALSE)) != NULL) {
  641 #if defined(DEBUG) && defined(NNTP_ABLE)
  642         if ((debug & DEBUG_NNTP) && verbose) /* long multiline response */
  643             debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
  644 #endif /* DEBUG && NNTP_ABLE */
  645 
  646         if (++processed % MODULO_COUNT_NUM == 0)
  647             spin_cursor();
  648 
  649         if (!parse_active_line(ptr, &max, &min, moderated))
  650             continue;
  651 
  652         /*
  653          * Load group into group hash table
  654          * NULL means group already present, so we just fixup the counters
  655          * This call may implicitly ++num_active
  656          */
  657         if ((grpptr = group_add(ptr)) == NULL) {
  658             if ((grpptr = group_find(ptr, FALSE)) == NULL)
  659                 continue;
  660 
  661             if (max > grpptr->xmax) {
  662                 grpptr->xmax = max;
  663                 grpptr->count = count;
  664             }
  665 
  666             if (min > grpptr->xmin) {
  667                 grpptr->xmin = min;
  668                 grpptr->count = count;
  669             }
  670 
  671             continue;
  672         }
  673 
  674         /*
  675          * Load the new group in active[]
  676          */
  677         active_add(grpptr, count, max, min, moderated);
  678     }
  679 
  680     TIN_FCLOSE(fp);
  681 
  682     /*
  683      * Exit if active file wasn't read correctly or is empty
  684      */
  685     if (tin_errno || !num_active)
  686         tin_done(EXIT_FAILURE, _(txt_active_file_is_empty), (read_news_via_nntp ? (read_saved_news ? news_active_file : _(txt_servers_active)) : news_active_file));
  687 
  688     if (!batch_mode || verbose)
  689         my_fputc('\n', stdout);
  690 }
  691 
  692 
  693 #ifdef NNTP_ABLE
  694 /*
  695  * Load the active file into active[] via LIST COUNTS
  696  */
  697 static void
  698 read_active_counts(
  699     void)
  700 {
  701     FILE *fp;
  702     char *ptr;
  703     char moderated[PATH_LEN];
  704     long processed = 0L;
  705     struct t_group *grpptr;
  706     t_artnum count = T_ARTNUM_CONST(-1), min = T_ARTNUM_CONST(1), max = T_ARTNUM_CONST(0);
  707 
  708     if (!batch_mode || verbose)
  709         wait_message(0, _(txt_reading_news_active_file));
  710 
  711     if ((fp = nntp_command("LIST COUNTS", OK_GROUPS, NULL, 0)) == NULL) {
  712         if (cmd_line && !batch_mode)
  713             my_fputc('\n', stderr);
  714 
  715         tin_done(EXIT_FAILURE, _(txt_cannot_retrieve), ACTIVE_FILE);
  716     }
  717 
  718     while ((ptr = tin_fgets(fp, FALSE)) != NULL) {
  719 #   ifdef DEBUG
  720         if ((debug & DEBUG_NNTP) && verbose) /* long multiline response */
  721             debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
  722 #   endif /* DEBUG */
  723 
  724         if (++processed % MODULO_COUNT_NUM == 0)
  725             spin_cursor();
  726 
  727         if (!parse_count_line(ptr, &max, &min, &count, moderated))
  728             continue;
  729 
  730         /*
  731          * Load group into group hash table
  732          * NULL means group already present, so we just fixup the counters
  733          * This call may implicitly ++num_active
  734          */
  735         if ((grpptr = group_add(ptr)) == NULL) {
  736             if ((grpptr = group_find(ptr, FALSE)) == NULL)
  737                 continue;
  738 
  739             if (max > grpptr->xmax) {
  740                 grpptr->xmax = max;
  741                 grpptr->count = count;
  742             }
  743 
  744             if (min > grpptr->xmin) {
  745                 grpptr->xmin = min;
  746                 grpptr->count = count;
  747             }
  748 
  749             continue;
  750         }
  751 
  752         /*
  753          * Load the new group in active[]
  754          */
  755         active_add(grpptr, count, max, min, moderated);
  756     }
  757 
  758     /*
  759      * Exit if active file wasn't read correctly or is empty
  760      */
  761     if (tin_errno || !num_active)
  762         tin_done(EXIT_FAILURE, _(txt_active_file_is_empty), _(txt_servers_active));
  763 
  764     if (!batch_mode || verbose)
  765         my_fputc('\n', stdout);
  766 }
  767 #endif /* NNTP_ABLE */
  768 
  769 
  770 /*
  771  * Load the active file into active[]
  772  * Check and preload any new newgroups into my_group[]
  773  */
  774 int
  775 read_news_active_file(
  776     void)
  777 {
  778     FILE *fp;
  779     int newgrps = 0;
  780     t_bool do_group_cmds = !nntp_caps.list_counts;
  781 #if defined(NNTP_ABLE) && !defined(DISABLE_PIPELINING)
  782     t_bool did_list_cmd = FALSE;
  783 #endif /* NNTP_ABLE && !DISABLE_PIPELINING */
  784 
  785     /*
  786      * Ignore -n if no .newsrc can be found or .newsrc is empty
  787      */
  788     if (newsrc_active) {
  789         if ((fp = fopen(newsrc, "r")) == NULL) {
  790             list_active = TRUE;
  791             newsrc_active = FALSE;
  792         } else {
  793             fclose(fp);
  794             if (file_size(newsrc) <= 0L) {
  795                 list_active = TRUE;
  796                 newsrc_active = FALSE;
  797             }
  798         }
  799     }
  800 
  801     /* Read an active file if it is allowed */
  802     if (list_active) {
  803 #ifdef NNTP_ABLE
  804 #   ifndef DISABLE_PIPELINING
  805         did_list_cmd = TRUE;
  806 #   endif /* !DISABLE_PIPELINING */
  807         if (read_news_via_nntp && nntp_caps.list_counts)
  808             read_active_counts();
  809         else
  810 #endif /* NNTP_ABLE */
  811             read_active_file();
  812     }
  813 
  814     /* Read .newsrc and check each group */
  815     if (newsrc_active) {
  816 #ifdef NNTP_ABLE
  817 #   ifndef DISABLE_PIPELINING
  818         /*
  819          * prefer LIST COUNTS, otherwise use LIST ACTIVE (-l) or GROUP (-n)
  820          * or both (-ln); LIST COUNTS/ACTIVE grplist is used up to
  821          * PIPELINE_LIMIT groups in newsrc
  822          */
  823         if (read_news_via_nntp && (list_active || nntp_caps.list_counts) && !did_list_cmd) {
  824             char buff[NNTP_STRLEN];
  825             char *ptr, *q;
  826             char moderated[PATH_LEN];
  827             int r = 0, j = 0;
  828             int i;
  829             struct t_group *grpptr;
  830             t_artnum count = T_ARTNUM_CONST(-1), min = T_ARTNUM_CONST(1), max = T_ARTNUM_CONST(0);
  831             t_bool need_auth = FALSE;
  832 
  833             *buff = '\0';
  834             /* we can't use for_each_group(i) yet, so we have to parse the newsrc */
  835             if ((fp = fopen(newsrc, "r")) != NULL) {
  836                 while (tin_fgets(fp, FALSE) != NULL)
  837                     j++;
  838                 rewind(fp);
  839                 if (j < PIPELINE_LIMIT) {
  840                     while ((ptr = tin_fgets(fp, FALSE)) != NULL) {
  841                         if (!(q = strpbrk(ptr, ":!")))
  842                             continue;
  843                         *q = '\0';
  844                         if (nntp_caps.type == CAPABILITIES && (nntp_caps.list_active || nntp_caps.list_counts)) {
  845                             /* LIST ACTIVE or LIST COUNTS takes wildmats */
  846                             if (*buff && ((strlen(buff) + strlen(ptr)) < (NNTP_GRPLEN - 1))) { /* append group name */
  847                                 snprintf(buff + strlen(buff), sizeof(buff) - strlen(buff), ",%s", ptr);
  848                             } else {
  849                                 if (*buff) {
  850                                     put_server(buff);
  851                                     r++;
  852                                 }
  853                                 snprintf(buff, sizeof(buff), "LIST %s %s", nntp_caps.list_counts ? "COUNTS" : "ACTIVE", ptr);
  854                             }
  855                             continue;
  856                         } else
  857                             snprintf(buff, sizeof(buff), "LIST ACTIVE %s", ptr);
  858                         put_server(buff);
  859                         r++;
  860                         *buff = '\0';
  861                     }
  862                     if (*buff) {
  863                         put_server(buff);
  864                         r++;
  865                     }
  866                 } else
  867                     do_group_cmds = TRUE;
  868 
  869                 fclose(fp);
  870 
  871                 if (j < PIPELINE_LIMIT) {
  872                     for (i = 0; i < r && !did_reconnect; i++) {
  873                         if ((j = get_only_respcode(buff, sizeof(buff))) != OK_GROUPS) {
  874                             /* TODO: add 483 (RFC 3977) code */
  875                             if (j == ERR_NOAUTH || j == NEED_AUTHINFO)
  876                                 need_auth = TRUE;
  877 #       if 0 /* do we need something like this? */
  878                             if (j == ERR_CMDSYN)
  879                                 list_active = TRUE;
  880 #       endif /* 0 */
  881                             continue;
  882                         } else {
  883                             while ((ptr = tin_fgets(FAKE_NNTP_FP, FALSE)) != NULL) {
  884 #       ifdef DEBUG
  885                                 if ((debug & DEBUG_NNTP) && verbose) /* long multiline response */
  886                                     debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
  887 #       endif /* DEBUG */
  888                                 if (nntp_caps.type == CAPABILITIES && nntp_caps.list_counts) {
  889                                     if (!parse_count_line(ptr, &max, &min, &count, moderated))
  890                                         continue;
  891                                 } else {
  892                                     if (!parse_active_line(ptr, &max, &min, moderated))
  893                                         continue;
  894                                 }
  895 
  896                                 if ((grpptr = group_add(ptr)) == NULL) {
  897                                     if ((grpptr = group_find(ptr, FALSE)) == NULL)
  898                                         continue;
  899 
  900                                     if (max > grpptr->xmax) {
  901                                         grpptr->xmax = max;
  902                                         grpptr->count = count;
  903                                     }
  904                                     if (min > grpptr->xmin) {
  905                                         grpptr->xmin = min;
  906                                         grpptr->count = count;
  907                                     }
  908                                     continue;
  909                                 }
  910                                 active_add(grpptr, count, max, min, moderated);
  911                             }
  912                         }
  913                     }
  914                     if (need_auth) { /* retry after auth is overkill here, so just auth */
  915                         if (!authenticate(nntp_server, userid, FALSE))
  916                             tin_done(EXIT_FAILURE, _(txt_auth_failed), nntp_caps.type == CAPABILITIES ? ERR_AUTHFAIL : ERR_ACCESS);
  917                         /* no set_maxartnum() here after auth as we're pipelining ... */
  918                     }
  919                 }
  920                 did_reconnect = FALSE;
  921             }
  922         }
  923 #   endif /* !DISABLE_PIPELINING */
  924 #endif /* NNTP_ABLE */
  925         if (!nntp_caps.list_counts || do_group_cmds)
  926             read_newsrc_active_file();
  927     }
  928 
  929     (void) time(&active_timestamp);
  930     force_reread_active_file = FALSE;
  931 
  932     /*
  933      * check_for_any_new_groups() also does $AUTOSUBSCRIBE
  934      */
  935     if (check_for_new_newsgroups)
  936         newgrps = check_for_any_new_groups();
  937 
  938     /*
  939      * finally we have a list of all groups and can set the attributes
  940      */
  941     assign_attributes_to_groups();
  942 
  943     return newgrps;
  944 }
  945 
  946 
  947 /*
  948  * Open the active.times file locally or send the NEWGROUPS command
  949  * "NEWGROUPS yymmdd hhmmss"
  950  */
  951 static FILE *
  952 open_newgroups_fp(
  953     int idx)
  954 {
  955 #ifdef NNTP_ABLE
  956     char line[NNTP_STRLEN];
  957     struct tm *ngtm;
  958 
  959     if (read_news_via_nntp && !read_saved_news) {
  960         /*
  961          * not checking for caps_type == CAPABILITIES && reader as some
  962          * servers do not support it even if advertising READER so we must
  963          * handle errors anyway and just issue the cmd.
  964          */
  965         if (idx == -1 || ((ngtm = localtime(&newnews[idx].time)) == NULL))
  966             return (FILE *) 0;
  967 
  968         /*
  969          * RFC 3977 states that we SHOULD use 4 digit year but some servers
  970          * still do not support it.
  971          */
  972         snprintf(line, sizeof(line), "NEWGROUPS %02d%02d%02d %02d%02d%02d",
  973             ngtm->tm_year % 100, ngtm->tm_mon + 1, ngtm->tm_mday,
  974             ngtm->tm_hour, ngtm->tm_min, ngtm->tm_sec);
  975 
  976         return (nntp_command(line, OK_NEWGROUPS, NULL, 0));
  977     }
  978 #endif /* NNTP_ABLE */
  979     return (fopen(active_times_file, "r"));
  980 }
  981 
  982 
  983 /*
  984  * Check for any newly created newsgroups.
  985  *
  986  * If reading news locally check the NEWSLIBDIR/active.times file.
  987  * Format:   Groupname Seconds Creator
  988  *
  989  * If reading news via NNTP issue a NEWGROUPS command.
  990  * Format:   (as active file) Groupname Maxart Minart moderated
  991  */
  992 static int
  993 check_for_any_new_groups(
  994     void)
  995 {
  996     FILE *fp;
  997     char *autosubscribe, *autounsubscribe;
  998     char *ptr, *line, buf[NNTP_STRLEN];
  999     char old_newnews_host[PATH_LEN];
 1000     int newnews_index;
 1001     int newgrps = 0;
 1002     time_t old_newnews_time;
 1003     time_t new_newnews_time;
 1004 
 1005     if (!batch_mode /* || verbose */)
 1006         wait_message(0, _(txt_checking_new_groups));
 1007 
 1008     (void) time(&new_newnews_time);
 1009 
 1010     /*
 1011      * find out if we have read news from here before otherwise -1
 1012      */
 1013     if ((newnews_index = find_newnews_index(nntp_server)) >= 0) {
 1014         STRCPY(old_newnews_host, newnews[newnews_index].host);
 1015         old_newnews_time = newnews[newnews_index].time;
 1016     } else {
 1017         STRCPY(old_newnews_host, "UNKNOWN");
 1018         old_newnews_time = (time_t) 0;
 1019     }
 1020 
 1021 #ifdef DEBUG
 1022     if ((debug & DEBUG_NNTP) && verbose > 1)
 1023         debug_print_file("NNTP", "Newnews old=[%lu]  new=[%lu]", (unsigned long int) old_newnews_time, (unsigned long int) new_newnews_time);
 1024 #endif /* DEBUG */
 1025 
 1026     if ((fp = open_newgroups_fp(newnews_index)) != NULL) {
 1027         /*
 1028          * Need these later. They list user-defined groups to be
 1029          * automatically subscribed or unsubscribed.
 1030          */
 1031         autosubscribe = getenv("AUTOSUBSCRIBE");
 1032         autounsubscribe = getenv("AUTOUNSUBSCRIBE");
 1033 
 1034         while ((line = tin_fgets(fp, FALSE)) != NULL) {
 1035             /*
 1036              * Split the group name off and subscribe. If we're reading local,
 1037              * we must check the creation date manually
 1038              */
 1039             if ((ptr = strchr(line, ' ')) != NULL) {
 1040                 if (!read_news_via_nntp && ((time_t) atol(ptr) < old_newnews_time || old_newnews_time == (time_t) 0))
 1041                     continue;
 1042 
 1043                 *ptr = '\0';
 1044             }
 1045             subscribe_new_group(line, autosubscribe, autounsubscribe);
 1046             newgrps++;
 1047         }
 1048         TIN_FCLOSE(fp);
 1049 
 1050         if (tin_errno)
 1051             return 0;               /* Don't update the time if we quit */
 1052     }
 1053 
 1054     /*
 1055      * Update (if already existing) or create (if new) the in-memory
 1056      * 'last time newgroups checked' slot for this server. It will be written
 1057      * out as part of tinrc.
 1058      */
 1059     if (newnews_index >= 0)
 1060         newnews[newnews_index].time = new_newnews_time;
 1061     else {
 1062         snprintf(buf, sizeof(buf), "%s %lu", nntp_server, (unsigned long int) new_newnews_time);
 1063         load_newnews_info(buf);
 1064     }
 1065 
 1066     if (!batch_mode)
 1067         my_fputc('\n', stdout);
 1068 
 1069     return newgrps;
 1070 }
 1071 
 1072 
 1073 /*
 1074  * Subscribe to a new news group:
 1075  * Handle the AUTOSUBSCRIBE/AUTOUNSUBSCRIBE env vars
 1076  * They hold a wildcard list of groups that should be automatically
 1077  * (un)subscribed when a new group is found
 1078  * If a group is autounsubscribed, completely ignore it
 1079  * If a group is autosubscribed, subscribe to it
 1080  * Otherwise, mark it as New for inclusion in selection screen
 1081  */
 1082 static void
 1083 subscribe_new_group(
 1084     char *group,
 1085     char *autosubscribe,
 1086     char *autounsubscribe)
 1087 {
 1088     int idx;
 1089     struct t_group *ptr;
 1090 
 1091     /*
 1092      * If we explicitly don't auto subscribe to this group, then don't bother going on
 1093      */
 1094     if ((autounsubscribe != NULL) && match_group_list(group, autounsubscribe))
 1095         return;
 1096 
 1097     /*
 1098      * Try to add the group to our selection list. If this fails, we're
 1099      * probably using -n, so we fake an entry with no counts. The count will
 1100      * be properly updated when we enter the group. Otherwise there is some
 1101      * mismatch in the active.times data and we ignore the newgroup.
 1102      */
 1103     if ((idx = my_group_add(group, FALSE)) < 0) {
 1104         if (list_active) {
 1105 /*          my_fprintf(stderr, "subscribe_new_group: %s not in active[] && list_active\n", group); */
 1106             return;
 1107         }
 1108 
 1109         if ((ptr = group_add(group)) != NULL)
 1110             active_add(ptr, T_ARTNUM_CONST(0), T_ARTNUM_CONST(1), T_ARTNUM_CONST(0), "y");
 1111 
 1112         if ((idx = my_group_add(group, FALSE)) < 0)
 1113             return;
 1114     }
 1115 
 1116     if (!no_write && (autosubscribe != NULL) && match_group_list(group, autosubscribe)) {
 1117         if (!batch_mode || verbose)
 1118             my_printf(_(txt_autosubscribed), group);
 1119 
 1120         /*
 1121          * as subscribe_new_group() is called from check_for_any_new_groups()
 1122          * which has pending data on the socket if reading via NNTP we are not
 1123          * allowed to issue any NNTP commands yet
 1124          */
 1125         subscribe(&active[my_group[idx]], SUBSCRIBED, bool_not(read_news_via_nntp));
 1126         /*
 1127          * Bad kluge to stop group later appearing in New newsgroups. This
 1128          * effectively loses the group, and it has now been subscribed to and
 1129          * so will be reread later by read_newsrc()
 1130          */
 1131         selmenu.max--;
 1132     } else
 1133         active[my_group[idx]].newgroup = TRUE;
 1134 }
 1135 
 1136 
 1137 /*
 1138  * See if group is a member of group_list, returning a boolean.
 1139  * group_list is a comma separated list of newsgroups, ! implies NOT
 1140  * The same degree of wildcarding as used elsewhere in tin is allowed
 1141  */
 1142 t_bool
 1143 match_group_list(
 1144     const char *group,
 1145     const char *group_list)
 1146 {
 1147     char *separator;
 1148     char pattern[HEADER_LEN];
 1149     size_t group_len, list_len;
 1150     t_bool negate, accept = FALSE;
 1151 
 1152     list_len = strlen(group_list);
 1153     /*
 1154      * walk through comma-separated entries in list
 1155      */
 1156     while (list_len != 0) {
 1157         /*
 1158          * find end/length of this entry
 1159          */
 1160         separator = strchr(group_list, ',');
 1161         group_len = MIN(((separator == NULL) ? list_len : (size_t) (separator - group_list)), sizeof(pattern) - 1);
 1162 
 1163         if ((negate = (*group_list == '!'))) {
 1164             /*
 1165              * a '!' before the pattern inverts sense of match
 1166              */
 1167             group_list++;
 1168             group_len--;
 1169             list_len--;
 1170         }
 1171         /*
 1172          * copy out the entry and terminate it properly
 1173          */
 1174         strncpy(pattern, group_list, group_len);
 1175         pattern[group_len] = '\0';
 1176         /*
 1177          * case-insensitive wildcard match
 1178          */
 1179         if (GROUP_MATCH(group, pattern, TRUE))
 1180             accept = bool_not(negate);  /* matched! */
 1181 
 1182         /*
 1183          * now examine next entry if any
 1184          */
 1185         if (group_list[group_len] != '\0')
 1186             group_len++;    /* skip the separator */
 1187 
 1188         group_list += group_len;
 1189         list_len -= group_len;
 1190     }
 1191     return accept;
 1192 }
 1193 
 1194 
 1195 /*
 1196  * Add or update an entry to the in-memory newnews[] array (The times newgroups
 1197  * were last checked for a particular news server)
 1198  * If this is first time we've been called, zero out the array.
 1199  *
 1200  * Side effects:
 1201  *   'info' is modified. Caller should not depend on it.
 1202  */
 1203 void
 1204 load_newnews_info(
 1205     char *info)
 1206 {
 1207     char *ptr;
 1208     int i;
 1209     time_t new_time;
 1210 
 1211     /*
 1212      * initialize newnews[] if no entries
 1213      */
 1214     if (!num_newnews) {
 1215         for (i = 0; i < max_newnews; i++) {
 1216             newnews[i].host = NULL;
 1217             newnews[i].time = (time_t) 0;
 1218         }
 1219     }
 1220 
 1221     /*
 1222      * Split 'info' into hostname and time
 1223      */
 1224     if ((ptr = strchr(info, ' ')) == NULL)
 1225         return;
 1226 
 1227     *ptr++ = '\0';
 1228     new_time = (time_t) atol(ptr);
 1229 
 1230     /*
 1231      * If this is a new host entry, set it up
 1232      */
 1233     if ((i = find_newnews_index(info)) == -1) {
 1234         i = num_newnews++;
 1235 
 1236         if (i >= max_newnews)
 1237             expand_newnews();
 1238         newnews[i].host = my_strdup(info);
 1239     }
 1240 
 1241     newnews[i].time = new_time;
 1242 
 1243 #ifdef DEBUG
 1244     if ((debug & DEBUG_NNTP) && verbose > 1)
 1245         debug_print_file("NNTP", "ACTIVE host=[%s] time=[%lu]", newnews[i].host, (unsigned long int) newnews[i].time);
 1246 #endif /* DEBUG */
 1247 }
 1248 
 1249 
 1250 /*
 1251  * Return the index of cur_newnews_host in newnews[] or -1 if not found
 1252  */
 1253 int
 1254 find_newnews_index(
 1255     const char *cur_newnews_host)
 1256 {
 1257     int i;
 1258 
 1259     for (i = 0; i < num_newnews; i++) {
 1260         if (STRCMPEQ(cur_newnews_host, newnews[i].host))
 1261             return i;
 1262     }
 1263 
 1264     return -1;
 1265 }
 1266 
 1267 
 1268 /*
 1269  * Get a single status char from the moderated field. Used on selection screen
 1270  * and in header of group screen
 1271  */
 1272 char
 1273 group_flag(
 1274     char ch)
 1275 {
 1276     switch (ch) {
 1277         case 'm':
 1278             return 'M';
 1279 
 1280         case 'x':
 1281         case 'n':
 1282         case 'j':
 1283             return 'X';
 1284 
 1285         case '=':
 1286             return '=';
 1287 
 1288         default:
 1289             return ' ';
 1290     }
 1291 }
 1292 
 1293 
 1294 void
 1295 create_save_active_file(
 1296     void)
 1297 {
 1298     char *fb;
 1299     char group_path[PATH_LEN];
 1300     char local_save_active_file[PATH_LEN];
 1301 
 1302     joinpath(local_save_active_file, sizeof(local_save_active_file), rcdir, ACTIVE_SAVE_FILE);
 1303 
 1304     if (no_write && file_size(local_save_active_file) != -1L)
 1305         return;
 1306 
 1307     if (strfpath((cmdline.args & CMDLINE_SAVEDIR) ? cmdline.savedir : tinrc.savedir, group_path, sizeof(group_path), NULL, FALSE)) {
 1308         wait_message(0, _(txt_creating_active));
 1309         print_active_head(local_save_active_file);
 1310 
 1311         while (strlen(group_path) && group_path[strlen(group_path) - 1] == '/')
 1312             group_path[strlen(group_path) - 1] = '\0';
 1313 
 1314         fb = my_strdup(group_path);
 1315         make_group_list(local_save_active_file, (cmdline.args & CMDLINE_SAVEDIR) ? cmdline.savedir : tinrc.savedir, fb, group_path);
 1316         free(fb);
 1317     }
 1318 }
 1319 
 1320 
 1321 static void
 1322 make_group_list(
 1323     char *active_file,
 1324     char *base_dir,
 1325     char *fixed_base,
 1326     char *group_path)
 1327 {
 1328     DIR *dir;
 1329     DIR_BUF *direntry;
 1330     char *ptr;
 1331     char filename[PATH_LEN];
 1332     char path[PATH_LEN];
 1333     t_artnum art_max;
 1334     t_artnum art_min;
 1335     struct stat stat_info;
 1336     t_bool is_dir;
 1337 
 1338     if ((dir = opendir(group_path)) != NULL) {
 1339         is_dir = FALSE;
 1340         while ((direntry = readdir(dir)) != NULL) {
 1341             STRCPY(filename, direntry->d_name);
 1342             joinpath(path, sizeof(path), group_path, filename);
 1343             if (!(filename[0] == '.' && filename[1] == '\0') &&
 1344                 !(filename[0] == '.' && filename[1] == '.' && filename[2] == '\0')) {
 1345                 if (stat(path, &stat_info) != -1) {
 1346                     if (S_ISDIR(stat_info.st_mode))
 1347                         is_dir = TRUE;
 1348                 }
 1349             }
 1350             if (is_dir) {
 1351                 is_dir = FALSE;
 1352                 strcpy(group_path, path);
 1353 
 1354                 make_group_list(active_file, base_dir, fixed_base, group_path);
 1355                 find_art_max_min(group_path, &art_max, &art_min);
 1356                 append_group_line(active_file, group_path + strlen(fixed_base) + 1, art_max, art_min, fixed_base);
 1357                 if ((ptr = strrchr(group_path, '/')) != NULL)
 1358                     *ptr = '\0';
 1359             }
 1360         }
 1361         CLOSEDIR(dir);
 1362     }
 1363 }
 1364 
 1365 
 1366 static void
 1367 append_group_line(
 1368     char *active_file,
 1369     char *group_path,
 1370     t_artnum art_max,
 1371     t_artnum art_min,
 1372     char *base_dir)
 1373 {
 1374     FILE *fp;
 1375     char *file_tmp;
 1376 
 1377     if (art_max == 0 && art_min == 1)
 1378         return;
 1379 
 1380     file_tmp = get_tmpfilename(active_file);
 1381 
 1382     if (!backup_file(active_file, file_tmp)) {
 1383         free(file_tmp);
 1384         return;
 1385     }
 1386 
 1387     if ((fp = fopen(active_file, "a+")) != NULL) {
 1388         char *ptr;
 1389         char *group_name;
 1390         int err;
 1391 
 1392         ptr = group_name = my_strdup(group_path);
 1393         ptr++;
 1394         while ((ptr = strchr(ptr, '/')) != NULL)
 1395             *ptr = '.';
 1396 
 1397         wait_message(0, "Appending=[%s %"T_ARTNUM_PFMT" %"T_ARTNUM_PFMT" %s]\n", group_name, art_max, art_min, base_dir);
 1398         print_group_line(fp, group_name, art_max, art_min, base_dir);
 1399         if ((err = ferror(fp)) || fclose(fp)) { /* TODO: issue warning? */
 1400             if (err) {
 1401                 clearerr(fp);
 1402                 fclose(fp);
 1403             }
 1404             err = rename(file_tmp, active_file);
 1405 #ifdef DEBUG
 1406             if ((debug & DEBUG_MISC) && err) /* TODO: is this the right debug-level? */
 1407                 perror_message(_(txt_rename_error), file_tmp, active_file);
 1408 #endif /* DEBUG */
 1409         }
 1410         free(group_name);
 1411     }
 1412     unlink(file_tmp);
 1413     free(file_tmp);
 1414 }