ftp.nice.ch/pub/next/unix/network/news/nn.6.4.16.s.tar.gz#/nn/nn.c

This is nn.c in view mode; [Download] [Up]

/*
 *	(c) Copyright 1990, Kim Fabricius Storm.  All rights reserved.
 *
 *	The nn user interface main program
 */

#include "config.h"
#include "menu.h"
#include "term.h"
#include "keymap.h"
#include "options.h"
#include "proto.h"
#include "articles.h"
#ifdef USE_MALLOC_H
#include <malloc.h>
#endif

import char *bin_directory;

import int
    seq_cross_filtering,					/* articles */
    dont_sort_folders,						/* folder.c */
    dont_split_digests, dont_sort_articles, also_unsub_groups,	/* group.c */
    also_cross_postings,
    case_fold_search,						/* match.c */
    preview_window, fmt_linenum, fmt_rptsubj,			/* menu.c */
    show_article_date, first_page_lines,			/* more.c */
    keep_rc_backup, no_update,					/* newsrc.c */
    hex_group_args,						/* sequence */
    show_current_time, conf_dont_sleep;				/* term.c */

export int
    article_limit = -1,
    also_read_articles = 0,
    also_full_digest = 0,
    batch_mode = 0,
    conf_auto_quit = 0,
    do_kill_handling = 1,
    group_name_args = 0,
    merged_menu = 0,
    prev_also_read = 1,
    repeat_group_query = 0,
    report_cost_on_exit = 1,
    show_motd_on_entry = 1,
    silent = 0,
    verbose = 0,
    Debug = 0;

export int check_db_update = 12 /* HOURS */;

static int
    nngrab_mode = 0,
    prompt_for_group = 0;

static char
    *match_subject = NULL,
    *match_sender = NULL,
    *init_file = NULL;

Option_Description(nn_options) {
    'a', Int_Option(article_limit),
    'B', Bool_Option(keep_rc_backup),
    'd', Bool_Option(dont_split_digests),
    'f', Bool_Option(dont_sort_folders),
    'g', Bool_Option(prompt_for_group),
    'G', Bool_Option(nngrab_mode),
    'i', Bool_Option(case_fold_search),
    'I', String_Option_Optional(init_file, NULL),
    'k', Bool_Option(do_kill_handling),
    'l', Int_Option(first_page_lines),
    'L', Int_Option_Optional(fmt_linenum, 3),
    'm', Bool_Option(merged_menu),
    'n', String_Option(match_sender),
    'N', Bool_Option(no_update),
    'q', Bool_Option(dont_sort_articles),
    'Q', Bool_Option(silent),
    'r', Bool_Option(repeat_group_query),
    's', String_Option(match_subject),
    'S', Bool_Option(fmt_rptsubj),
    'T', Bool_Option(show_current_time),
    'w', Int_Option_Optional(preview_window, 5),
    'W', Bool_Option(conf_dont_sleep),
    'x', Int_Option_Optional(also_read_articles, -1),
    'X', Bool_Option(also_unsub_groups),
    'Z', Int_Option(Debug),
    '\0',
};


static int
    report_number_of_articles = 0;
static char
    *check_message_format = NULL;
import int
    quick_unread_count;

Option_Description(check_options) {
    'Q', Bool_Option(silent),
    'r', Bool_Option(report_number_of_articles),
    'f', String_Option(check_message_format),
    't', Bool_Option(verbose),
    'v', Bool_Option(verbose),
    'c', Bool_Option(quick_unread_count),
    '\0',
};

/* program name == argv[0] without path */

char *pname;

static int must_unlock = 0;

static int nn_locked()
{
    if (no_update) return 0;

    switch (who_am_i) {
     case I_AM_ADMIN:
     case I_AM_CHECK:
     case I_AM_POST:
     case I_AM_GREP:
     case I_AM_SPEW:
	return 0;		/* will not update .newsrc */
    }

    if (proto_lock(I_AM_NN, PL_SET)) {
	extern char proto_host[];
	
	if (proto_host[0])
	    user_error("\nnn is running on host %s\nor .nn/LOCK should be removed.\n\n", proto_host);
	else
	user_error("\nAnother nn process is already running\n\n");
    }

    must_unlock = 1;
    return 0;
}

#define setup_access()	ACC_PARSE_VARIABLES

flag_type parse_access_flags()
{
    flag_type access_mode = 0;

    if (do_kill_handling)
	access_mode |= ACC_DO_KILL;
    if (also_cross_postings)
	access_mode |= ACC_ALSO_CROSS_POSTINGS;
    if (also_read_articles)
	access_mode |= ACC_ALSO_READ_ARTICLES;
    if (dont_split_digests)
	access_mode |= ACC_DONT_SPLIT_DIGESTS;
    if (also_full_digest)
	access_mode |= ACC_ALSO_FULL_DIGEST;
    if (dont_sort_articles)
	access_mode |= ACC_DONT_SORT_ARTICLES;

    return access_mode;
}

export group_header *jump_to_group = NULL;
export int enter_last_read_mode = 1;

static group_header *last_group_maint(last, upd)
group_header *last;
int upd;
{
    group_header *gh;
    FILE *f;
    char *lg_file;
    char buf[256], *cp;

    lg_file = relative(nn_directory, "NEXTG");
    if (upd) {
	if (last == NULL)
	    unlink(lg_file);
	else {
	    f = open_file(lg_file, OPEN_CREATE | MUST_EXIST);
	    fprintf(f, "%s\n", last->group_name);
	    fclose(f);
	}
	return NULL;
    }

    if (enter_last_read_mode == 0) goto none;

    f = open_file(lg_file, OPEN_READ);
    if (f == NULL) goto none;
    
    if (fgets(buf, 256, f)) {
	if (cp = strchr(buf, NL)) *cp = NUL;
	gh = lookup(buf);
    }
    fclose(f);

    if (gh == NULL || gh == last || !(gh->group_flag & G_SEQUENCE))
	goto none;
    
    switch (enter_last_read_mode) {
     case 1:		/* confirm if unread, skip if read */
	if (gh->unread_count == 0) goto none;
     case 2:		/* confirm any case */
	prompt_line = Lines-1;
	prompt("Enter %s (%ld unread)? ", gh->group_name, gh->unread_count);
	if (!yes(0)) goto none;
	break;
     case 3:		/* enter uncond if unread */
	if (gh->unread_count == 0) goto none;
     case 4:		/* enter uncond */
	break;
    }
    return gh;
 none:
    return last;
}

static read_news(access_mode, mask)
flag_type access_mode;
char *mask;
{
    register group_header *gh, *prev, *tmp, *after_loop;
    flag_type group_mode;
    int menu_cmd;
    int must_clear = 0, did_jump = 0;
    group_header *last_group_read = NULL;
    extern int menu();

    prev = group_sequence;
    gh = group_sequence;
    after_loop = NULL;

    if (access_mode == 0 && !also_read_articles) {
	gh = last_group_maint(gh, 0);
	did_jump = gh != group_sequence;
	m_invoke(-2);
    }

    for (;;) {
	group_mode = access_mode;
	
	if (s_hangup) break;

	if (gh == NULL) {
	    if (after_loop != NULL) {
		gh = after_loop;
		after_loop = NULL;
		if (gh->unread_count <= 0)
		    group_mode |= ACC_ORIG_NEWSRC;
		goto enter_direct;
	    }

	    if (did_jump) {
		did_jump = 0;
		gh = group_sequence;
		continue;
	    }

	    if (must_clear && conf_auto_quit) {
		prompt("\1LAST GROUP READ.  QUIT NOW?\1");
		switch (yes(1)) {
		 case 1:
		    break;
		 case 0:
		    gh = group_sequence;
		 default:
		    after_loop = prev;
		    continue;
		}
	    }
	    last_group_read = NULL;
	    break;
	}

	if (gh->group_flag & G_UNSUBSCRIBED) {
	    if (!also_unsub_groups) {
		gh = gh->next_group;
		continue;
	    }
	} else
	    if (!also_read_articles && gh->unread_count <= 0) {
		gh = gh->next_group;
		continue;
	    }

     enter_direct:

	free_memory();

	if (gh->group_flag & G_FOLDER) {
	    menu_cmd = folder_menu(gh->group_name, 0);
	    if (menu_cmd == ME_NO_REDRAW) {
		menu_cmd = ME_NO_ARTICLES;
		gh->last_db_article = 0;
	    }
	} else {
	    group_mode |= setup_access();
	    if (group_mode & ACC_ORIG_NEWSRC)
		gh->last_article = gh->first_article;
	    
	    menu_cmd = group_menu(gh, (article_number)(-1), group_mode, mask, menu);
	    group_mode = access_mode;
	}

	if (menu_cmd != ME_NO_ARTICLES) {
	    last_group_read = gh;
	    after_loop = NULL;
	    must_clear++;
	}

	switch (menu_cmd) {

	 case ME_QUIT:	/* or jump */
	    if (!jump_to_group) goto out;

	    prev = jump_to_group;
	    jump_to_group = NULL;
	    did_jump = 1;
	    /* fall thru */

	 case ME_PREV:
	    tmp = gh;
	    gh = prev;
	    prev = tmp;
	    if (gh->unread_count <= 0)
		group_mode |= ACC_ORIG_NEWSRC;
	    else if (prev_also_read)
		group_mode |= ACC_MERGED_NEWSRC;
	    goto enter_direct;

	 case ME_NEXT:
	    prev = gh;
	    /* fall thru */

	 case ME_NO_ARTICLES:
	    if (gh->group_flag & G_MERGE_HEAD) {
		do gh = gh->next_group;
	        while (gh && (gh->group_flag & G_MERGE_SUB));
	    } else
		gh = gh->next_group;
	    continue;
	}
    }

 out:
    if (access_mode == 0 && !also_read_articles)
	last_group_maint(last_group_read, 1);

    return must_clear;
}


static catch_up()
{
    register group_header *gh;
    char answer[50];
    int mode, must_help;
    import int newsrc_update_freq, novice;
    import int menu();
    flag_type access_mode;

    access_mode = setup_access();
    
    prt_unread("\nCatch-up on %u ? (auto)matically (i)nteractive ");
    fl;
    mode = 0;

    if (gets(answer)) {
	if (strncmp(answer, "auto", 4) == 0) {
	    printf("\nUPDATING .newsrc FILE....");
	    fl;
	    mode = -1;
	} else
	if (*answer == 'i')
	    mode = 1;
    }

    if (mode == 0) {
	printf("\nNO UPDATE\n");
	return;
    }

    newsrc_update_freq = 9999;
    must_help = novice;

    Loop_Groups_Sequence(gh) {

	if ((gh->group_flag & G_COUNTED) == 0) continue;

	if (mode < 0) {
	    update_rc_all(gh, 0);
	    continue;
	}

     again:
	if (must_help) {
	    printf("\n  y - mark all articles as read in current group\n");
	    printf("  n - do not update group\n");
	    printf("  r - read the group now\n");
	    printf("  U - unsubscribe to current group\n");
	    printf("  ? - this message\n");
	    printf("  q - quit\n\n");

	    must_help = 0;
	}

	printf("Update %s (%ld)? (ynrU?q) n\b", gh->group_name,
	       (long)(gh->last_db_article - gh->last_article));
	fl;

	if (gets(answer) == NULL || s_keyboard)
	    *answer = 'q';

	switch (*answer) {

	 case 'q':
	    putchar(NL);
	    printf("Update rest? (yn) n\b");
	    fl;
	    if (gets(answer) == NULL || *answer != 'y')
		return;

	    mode = -1;
	    break;

	 case NUL:
	 case 'n':
	    continue;

	 case 'r':
	    raw();
	    group_menu(gh, (article_number)(-1), access_mode, (char *)NULL, menu);
	    unset_raw();
	    clrdisp();
	    if (gh->unread_count > 0) goto again;
	    continue;

	 case 'U':
	    update_rc_all(gh, 1);
	    continue;

	 case 'y':
	    break;

	 default:
	    must_help = 1;
	    goto again;
	}

	update_rc_all(gh, 0);
    }

    printf("DONE\n");

    flush_newsrc();
}

export char *mail_box = NULL;

unread_mail(t)
time_t t;
{
    struct stat st;
    static time_t next = 0;
    static int any = 0;

    if (next > t) return any;

    next = t + 60;
    any = 0;

    if (mail_box == NULL ||
	stat(mail_box, &st) != 0 ||
	st.st_size == 0 ||
	st.st_mtime < st.st_atime) return 0;

    any = 1;

    return 1;
}

#ifdef ACCOUNTING
#ifndef STATISTICS
#define STATISTICS 1
#endif
#define NEED_ACCOUNT
#else
#ifdef AUTHORIZE
#define NEED_ACCOUNT
#endif
#endif

#ifdef STATISTICS
static time_t usage_time = 0;
static time_t last_tick = 0;

stop_usage()
{
    last_tick = 0;
}

time_t tick_usage()
{
    register time_t now;

    now = cur_time();

    /*
     * We ignore ticks > 2 minutes because the user has probably
     * just left the terminal inside nn and done something else
     */
    if (last_tick > (now - 120))
	usage_time += now - last_tick;

    last_tick = now;
    return now;
}

log_usage()
{
#ifdef ACCOUNTING
    account('U', report_cost_on_exit);
#else
    if (usage_time < (STATISTICS * 60)) return; /* don't log short sessions */

    usage_time /= 60;
    log_entry('U', "USAGE %d.%02d", usage_time/60, usage_time%60);
#endif
}

#else
stop_usage()
{
}

time_t tick_usage()
{
    return cur_time();
}

log_usage()
{
}
#endif

#ifdef NEED_ACCOUNT
account(option, report)
char option;
int report;
{
    char *args[10], **ap;
    char buf1[16], buf2[16];
    int ok;

    if (who_am_i != I_AM_NN && who_am_i != I_AM_POST) return 0;

    if (report) {
	putchar(CR); clrline(); fl;
    }

    ap = args;
    *ap++ = "nnacct";
    if (report) *ap++ = "-r";
#ifdef STATISTICS
    sprintf(buf1, "-%c%ld", option, (long)usage_time/60);
#else
    sprintf(buf1, "-%c0", option);
#endif
    *ap++ = buf1;
    sprintf(buf2, "-W%d", who_am_i);
    *ap++ = buf2;
    *ap++ = NULL;
    ok = execute(relative(bin_directory, "nnacct"), args, 0);
    if (option == 'U' && report) putchar(NL);
    return ok;
}
#endif

/* this will go into emacs_mode.c when it is completed someday */

emacs_mode()
{
    printf("EMACS MODE IS NOT SUPPORTED YET.\n");
}


static do_nnspew()
{
    register group_header *gh;
    import int seq_cross_filtering;
    int ix;

    ix = 0;
    Loop_Groups_Header(gh) {
	if (gh->master_flag & M_IGNORE_GROUP) continue;
	gh->preseq_index = ++ix;
    }

    /* Only output each article once */
    /* Must also use this mode when using nngrab! */

    seq_cross_filtering = 1;

    Loop_Groups_Header(gh) {
	if (s_hangup) return 1;
	if (gh->master_flag & M_IGNORE_GROUP) continue;
	access_group(gh, gh->first_db_article, gh->last_db_article,
		     ACC_DONT_SORT_ARTICLES | ACC_ALSO_FULL_DIGEST |
		     ACC_SPEW_MODE, (char *)NULL);
    }
    return 0;
}

static prt_version()
{
    printf("Release %s,  Kim F. Storm, 1991\n\n", version_id);
}

display_motd(check)
int check;
{
    time_t last_motd, cur_motd;
    char *dot_motd, motd[FILENAME];

    strcpy(motd, relative(lib_directory, "motd"));
    cur_motd = file_exist(motd, (char *)NULL);
    if (cur_motd == 0) return 0;

    if (!check) {
	display_file(motd, CLEAR_DISPLAY | CONFIRMATION);
	return 1;
    }

    dot_motd = relative(nn_directory, ".motd");
    last_motd = file_exist(dot_motd, (char *)NULL);

    if (last_motd >= cur_motd) return 0;

    unlink(dot_motd);
    fclose(open_file(dot_motd, OPEN_CREATE|MUST_EXIST));

    clrdisp();

    so_printxy(0, 0, "Message of the day");
    gotoxy(0, 2); prt_version();

    display_file(motd, CONFIRMATION);
    return 1;
}

static do_db_check()
{
    import char *news_active;
    time_t last_upd;

    last_upd = (cur_time() - master.last_scan) / (60 * 60);
    if (last_upd < check_db_update) return;
    /* too old - but is nnmaster the culprit? */
    if (master.last_scan == file_exist(news_active, (char *)NULL))
	printf("Notice: no news has arrived for the last %ld hours\n", last_upd);
    else
	printf("Notice: nnmaster has not updated database in %ld hours\n", last_upd);
    sleep(3);
}

clean_group(gh)
group_header *gh;
{
}

main(argc, argv)
int argc;
char *argv[];
{
    extern long unread_articles;
    extern char *program_name();
    int say_welcome = 0, cmd;
    flag_type access_mode;
    char *mask;
    extern long initial_memory_break;
    extern char *sbrk();

    initial_memory_break = (long)sbrk(0);

#ifdef USE_MALLOC_H
#ifdef MALLOC_MAXFAST
    mallopt(M_MXFAST, MALLOC_MAXFAST);
#endif
#ifdef MALLOC_FASTBLOCKS
    mallopt(M_NLBLKS, MALLOC_FASTBLOCKS);
#endif
#ifdef MALLOC_GRAIN
    mallopt(M_GRAIN, MALLOC_GRAIN);
#endif
#endif

    pname = program_name(argv);

    if (strcmp(pname, "nnadmin") == 0) {
	who_am_i = I_AM_ADMIN;
    } else
    if (strcmp(pname, "nnemacs") == 0) {
	who_am_i = I_AM_EMACS;
    } else
    if (strcmp(pname, "nncheck") == 0) {
	who_am_i = I_AM_CHECK;
    } else
    if (strcmp(pname, "nntidy") == 0) {
	who_am_i = I_AM_TIDY;
    } else
    if (strcmp(pname, "nngoback") == 0) {
	who_am_i = I_AM_GOBACK;
    } else
    if (strcmp(pname, "nngrep") == 0) {
	who_am_i = I_AM_GREP;
    } else
    if (strcmp(pname, "nnpost") == 0) {
	who_am_i = I_AM_POST;
    } else
    if (strcmp(pname, "nnbatch") == 0) {
	who_am_i = I_AM_NN;
	batch_mode = 1;
    } else {
	who_am_i = I_AM_NN;
    }

    if (who_am_i == I_AM_NN || (who_am_i == I_AM_ADMIN && argc == 1)) {
	if (!batch_mode && !isatty(0)) {
	    if (who_am_i == I_AM_NN && argc == 2) {
		if (strcmp(argv[1], "-SPEW") == 0) {
		    who_am_i = I_AM_SPEW;
		    init_global();
		    goto nnspew;
		}
	    }

	    fprintf(stderr, "Input is not a tty\n");
	    exit(1);
	}
    }

#ifdef AUTHORIZE
    init_execute();
    if (cmd = account('P', 0)) {
	switch (cmd) {
	 case 1:
	    if (who_am_i == I_AM_POST) {
		printf("You are not authorized to post news\n");
		exit(41);
	    }
	    /* ok_to_post = 0; */
	    cmd = 0;
	    break;
	 case 2:
	    printf("You are not authorized to read news\n");
	    break;
	 case 3:
	    printf("You cannot read news at this time\n");
	    break;
	 case 4:
	    printf("Your news reading quota is exceeded\n");
	    break;
	 default:
	    printf("Bad authorization -- reason %d\n", cmd);
	    break;
	}
	if (cmd) exit(40 + cmd);
    }
#endif

    if ((say_welcome = init_global()) < 0) {
	printf("%s: nn has not been invoked to initialize user files\n", pname);
	nn_exit(1);
    }

#ifdef NNTP
    nntp_check();
#endif

    init_key_map();
#ifndef AUTHORIZE
    init_execute();
#endif
    init_macro();

 nnspew:

    open_master(OPEN_READ);

    switch (who_am_i) {

     case I_AM_NN:
	init_term(1);

	if (say_welcome) {
	    extern int first_time_user;
	    
	    first_time_user = 1;
	    display_file("adm.welcome", CLEAR_DISPLAY | CONFIRMATION);
	    clrdisp();
	}

	visit_init_file(0, argv[1]);

	if (show_motd_on_entry)
	    if (display_motd(1))
		silent = 1;
	
	init_answer();

	group_name_args =
	    parse_options(argc, argv, (char *)NULL, nn_options,
			  " [group | [+]folder]...");

	if (also_read_articles) {
	    if (article_limit < 0)
		article_limit = also_read_articles;
	    do_kill_handling = 0;
	    no_update++;
	}

	if (match_subject) {
	    mask = match_subject;
	    access_mode = ACC_ON_SUBJECT;
	} else if (match_sender) {
	    mask = match_sender;
	    access_mode = ACC_ON_SENDER;
	} else {
	    mask = NULL;
	    access_mode = 0;
	}

	if (mask) {
	    if (case_fold_search) fold_string(mask);
	    no_update++;
	    do_kill_handling = 0;
	}

	if (nngrab_mode) {
	    seq_cross_filtering = 1;
	    hex_group_args = 1;
	    no_update = 1;
	}

	if (merged_menu) {
	    no_update++;
	}

	if (!no_update && group_name_args > 0)
	    no_update = only_folder_args(argv + 1);

	break;

     case I_AM_ADMIN:
	if (argc == 1) {
	    init_term(1);
	    visit_init_file(0, (char *)NULL);
	}
	admin_mode(argv[1]);
	nn_exit(0);

     case I_AM_CHECK:
	init_term(0);
	visit_init_file(0, (char *)NULL);
	silent = 0;		/* override setting in init file */
	group_name_args =
	    parse_options(argc, argv, (char *)NULL, check_options,
			  " [group]...");
	no_update = 1;		/* don't update .newsrc and LAST */
	break;

     case I_AM_EMACS:
	silent = 1;
	break;

     case I_AM_TIDY:
	init_term(0);
	visit_init_file(0, (char *)NULL);
	group_name_args = opt_nntidy(argc, argv);
	break;

     case I_AM_GOBACK:
	init_term(0);
	visit_init_file(0, (char *)NULL);
	group_name_args = opt_nngoback(argc, &argv);
	break;

     case I_AM_GREP:
	init_term(0);
	visit_init_file(0, (char *)NULL);
	opt_nngrep(argc, argv);
	silent = 1;
	no_update = 1;
	break;

     case I_AM_POST:
	do_nnpost(argc, argv);
	nn_exit(0);

     case I_AM_SPEW:
	if (proto_lock(I_AM_SPEW, PL_SET) == 0) {
	    cmd = do_nnspew();
	    proto_lock(I_AM_SPEW, PL_CLEAR);
	} else
	    cmd = 1;	/* don't interfere with other nnspew */
	nn_exit(cmd);
    }

    if (!silent && who_am_i != I_AM_CHECK)
	prt_version();

    if (nn_locked()) nn_exit(2);

    visit_rc_file();

    if (group_name_args > 0)
	named_group_sequence(argv + 1);
    else
	normal_group_sequence();

    if (who_am_i != I_AM_TIDY && who_am_i != I_AM_GOBACK)
	count_unread_articles();

    switch (who_am_i) {

     case I_AM_EMACS:
	prt_unread("%U %G\n");
	emacs_mode();
	break;

     case I_AM_CHECK:
	if (report_number_of_articles) {
	    prt_unread("%U");
	    break;
	}

	if (check_message_format) {
	    if (unread_articles || !silent)
		prt_unread(check_message_format);
	} else
	if (!silent) {
	    if (unread_articles || report_number_of_articles)
		prt_unread("There %i %u in %g\n");
	    else
		prt_unread((char *)NULL);
	}

	nn_exit( unread_articles ? 0 : 99 );
	/*NOTREACHED*/

     case I_AM_TIDY:
	do_tidy_newsrc();
	break;

     case I_AM_GOBACK:
	do_goback();
	break;

     case I_AM_NN:
	if (check_db_update)
	    do_db_check();

	if (unread_articles == 0 &&
	    group_name_args == 0 &&
	    !also_read_articles &&
	    !prompt_for_group) {
	    if (!silent) prt_unread((char *)NULL);
	    break;
	}

	if (do_kill_handling)
	    do_kill_handling = init_kill();

	if (prompt_for_group) {
	    import int also_cross_postings;

	    if (mask != NULL)
		user_error("Cannot use -s/-n with -g\n\r");
		
	    also_cross_postings = 1;
	    do {
		raw();
		clrdisp();
		current_group = NULL;
		prompt_line = 2;
		cmd = goto_group(K_GOTO_GROUP, (article_header *)NULL, setup_access());
		
		if (cmd == ME_NO_REDRAW) sleep(2);
	    } while (repeat_group_query && cmd != ME_QUIT && cmd != ME_NO_REDRAW);
	    clrdisp();
	    unset_raw();
	    break;
	}

	if (!no_update && article_limit == 0) {
	    catch_up();
	    break;
	}

	if (merged_menu) {
	    merge_and_read(access_mode | setup_access(), mask);
	    clrdisp();
	    break;
	}

	if (read_news(access_mode, mask)) {
	    clrdisp();

	    if (master.db_lock[0])
		printf("Database has been locked:\n%s\n", master.db_lock);

	    if (!also_read_articles &&
		unread_articles > 0 &&
		!silent && group_name_args == 0)
		prt_unread("There %i still %u in %g\n\n\r");
	    break;
	}
	gotoxy(0,Lines-1);
	if (group_name_args == 0)
	    printf("No News\n");
	else
	    printf("\r\n");
	break;

     case I_AM_GREP:
	do_grep(argv+1);
	break;
    }

    nn_exit(0);
    /*NOTREACHED*/
}

/*
 * nn_exit() --- called whenever a program exits.
 */

nn_exit(n)
{
    static int loop = 0;

    if (loop) exit(n);
    loop++;

    visual_off();

#ifdef NNTP
    nntp_cleanup();
#endif /* NNTP */
    close_master();
    flush_newsrc();

    if (who_am_i == I_AM_NN)
	log_usage();

    if (must_unlock)
	proto_lock(I_AM_NN, PL_CLEAR);

    exit(n);
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.