[comp.sources.unix] v19i065: NN, a Usenet news reader, Part04/15

rsalz@uunet.uu.net (Rich Salz) (06/23/89)

Submitted-by: mcvax!tidk!storm@uunet.UU.NET (Kim F. Storm)
Posting-number: Volume 19, Issue 65
Archive-name: nn/part04

#!/bin/sh
# this is part 4 of a multipart archive
# do not concatenate these parts, unpack them in order with /bin/sh
# file folder.c continued
#
CurArch=4
if test ! -r s2_seq_.tmp
then echo "Please unpack part 1 first!"
     exit 1; fi
( read Scheck
  if test "$Scheck" != $CurArch
  then echo "Please unpack part $Scheck next!"
       exit 1;
  else exit 0; fi
) < s2_seq_.tmp || exit 1
echo "x - Continuing file folder.c"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' >> folder.c
X	
X	if (*path == '|') return -1;	/* no completion for pipes */
X	
X	if (*path == '+' || *path == '~') {
X	    if (!expand_file_name(nbuf, path))
X		return 0;	/* no completions */
X	} else
X	    strcpy(nbuf, path);
X	
X	if (base = strrchr(nbuf, '/')) {
X	    if (base == nbuf) {
X		dir = "/";
X		base++;
X	    } else {
X		*base++ = NUL;
X		dir = nbuf;
X	    }
X	} else {
X	    base = nbuf;
X	    dir = ".";
X	}
X	
X	tail_offset = strlen(base);
X
X	dir_in_use = list_directory(dir, base);
X
X	return dir_in_use;
X    }
X
X    if (index)
X	return help_directory();
X    
X    if (!next_directory(buffer)) return 0;
X    
X    strcpy(tail, buffer+tail_offset);
X    
X    return 1;
X}
X
X
X/*
X * 	read file names in directory 'dir' starting with 'prefix'
X *
X *	this could be speeded up by an order of magnitude by
X *	reading the directory directly into an array and sort
X *	it internally.
X *	
X */
X
Xstatic char dir_path[FILENAME], *dir_tail;
X
X#ifndef HAVE_DIRECTORY
X
Xstatic FILE *dirf;
Xstatic int prefix_lgt;
X
Xstatic list_directory(dir, prefix)
Xchar *dir, *prefix;
X{
X    sprintf(dir_path, "cd %s && echo %s* 2>/dev/null", dir, prefix);
X    prefix_lgt = strlen(prefix);
X    
X    if ((dirf = popen(dir_path, "r")) == NULL) return 0;
X
X    dir_tail = dir_path; 
X    while (*dir_tail++ = *dir++); 
X    dir_tail[-1] = '/';
X
X    return 1;
X}
X
Xstatic next_directory(buffer)
Xchar *buffer;
X{
X    register char *cp;
X    register int c;
X    
X    cp = buffer;
X    while ((c = getc(dirf)) != EOF && (c != SP) && (c != NL))
X	*cp++ = c;
X    
X    if (cp != buffer) {
X	*cp = NUL;
X	if (strcmp(buffer + prefix_lgt, "*")) {
X
X	    strcpy(dir_tail, buffer);
X    	    if (file_exist(dir_path, "d")) {
X		*cp++ = '/';
X		*cp = NUL;
X	    }
X
X	    return 1;
X	}
X	
X    }
X    
X    close_directory();
X    return 0;
X}
X
Xhelp_directory()
X{
X    return 0;
X}
X
Xstatic close_directory()
X{
X    if (dirf) {
X	pclose(dirf);
X	dirf = NULL;
X    }
X}    
X#else /* HAVE_DIRECTORY */
X
Xstatic string_marker str_mark;
Xstatic char **completions = NULL;
Xstatic char **comp_iterator;
Xstatic char **comp_help;
X
X/* 
X * list_directory scans the directory twice; first time to find out how
X * many matches there are, and second time to save the names, after
X * sufficient memory have been allocated to store it all.
X */
X
Xstatic sort_directory(f1, f2)		/* Used by qsort */
X    register char **f1;
X    register char **f2;
X{
X    return strcmp(*f1, *f2);
X}
X
Xstatic list_directory(dir, prefix)
Xchar *dir, *prefix;
X{
X    DIR *dirp;
X    register Direntry *dp;
X    register char *cp;
X    register char **comp;
X    int pflen = strlen(prefix);
X    unsigned nmatch = 1;	/* No. of completions plus one */
X
X    if ((dirp = opendir(dir)) == NULL)
X	return 0;			/* tough luck */
X
X    while ((dp = readdir(dirp)) != NULL) {
X	cp = dp->d_name;
X	if (*cp == '.' && (cp[1] == '\0' || (cp[1] == '.' && cp[2] == '\0')))
X	    continue;
X	if (strncmp(prefix, cp, pflen) == 0)
X	    nmatch++;
X    }
X    if (nmatch == 1
X	|| (completions = (char **)calloc(nmatch, sizeof(char *))) == NULL) {
X	closedir(dirp);
X	return 0;
X    }
X    mark_str(&str_mark);
X
X    rewinddir(dirp);
X    for (comp = completions; (dp = readdir(dirp)) != NULL; ) {
X	cp = dp->d_name;
X	if (*cp == '.' && (cp[1] == '\0' || (cp[1] == '.' && cp[2] == '\0')))
X	    continue;
X	if (strncmp(prefix, cp, pflen) == 0)
X	    strcpy(*comp++ = alloc_str(strlen(cp)), cp);
X    }
X    closedir(dirp);
X    *comp = (char *)0;
X    qsort((char *)completions, comp - completions, sizeof(char *), sort_directory);
X    comp_iterator = completions;
X    comp_help = completions;
X    
X    dir_tail = dir_path; 
X    while (*dir_tail++ = *dir++); 
X    dir_tail[-1] = '/';
X    
X    return 1;
X}
X
Xstatic next_directory(buffer)
X    register char *buffer;
X{
X    if (*comp_iterator != NULL) {
X	strcpy(dir_tail, *comp_iterator);
X	strcpy(buffer, *comp_iterator++);
X	
X	if ( file_exist(dir_path, "d") ) 
X	    strcat(buffer, "/");
X	return 1;
X    }
X    close_directory();
X    return 0;
X}
X
Xhelp_directory()
X{
X    list_completion((char *)NULL);
X
X    if (*comp_help == NULL) comp_help = completions;
X    while (*comp_help && list_completion(*comp_help))
X	    comp_help++;
X    
X    fl;
X    return 1;
X}
X
Xstatic close_directory()
X{
X    if (completions) {
X	release_str(&str_mark);
X	free((char *)completions);
X	completions = NULL;
X    }
X}    
X#endif /* HAVE_DIRECTORY */
X
Xstatic int cancel_count;
X
Xfcancel(ah)
Xarticle_header *ah;
X{
X    if (ah->flag & A_CANCEL) {
X	cancel_count--;
X	ah->flag &= ~A_CANCEL;
X    } else {
X	cancel_count++;
X	ah->flag |= A_CANCEL;
X    } 
X}
X
Xstatic folder_header()
X{
X    so_printxy(0, 0, "Folder: %s", current_group->group_name);
X
X    return 1;	/* number of header lines */
X}    
X
Xfolder_menu(path)
Xchar *path;
X{
X    FILE 			*folder;
X    register article_header	*ap;
X    news_header_buffer 		dgbuf;
X    char 			buffer[256];
X    int				more, length, re, menu_cmd, was_raw;
X    memory_marker		mem_marker;
X    group_header 		fake_group;
X    int				cc_save;
X    
X    fake_group.group_name = path;
X    fake_group.group_flag = G_RC_UPDATED | G_FOLDER | G_READ;
X    init_group(&fake_group);
X    
X    folder = open_file(group_path_name, OPEN_READ);
X    if (folder == NULL) {
X	msg("%s not found", path);
X	return ME_NO_REDRAW;
X    }
X
X    was_raw = no_raw();
X    s_keyboard = 0;
X    
X    printf("\rReading: %-.65s", path);
X    clrline();
X    
X    current_group = &fake_group;
X
X    mark_memory(&mem_marker);
X    
X    ap = alloc_art();
X    
X    more = 1;
X    while (more && (more = get_digest_article(folder, dgbuf)) >= 0) {
X	if (s_keyboard) break;
X	
X	ap->a_number = 0;
X	ap->flag = A_FOLDER;
X
X	ap->lines = digest.dg_lines;
X
X	ap->hpos = digest.dg_hpos;
X	ap->fpos = digest.dg_fpos;
X	ap->lpos = digest.dg_lpos;
X
X	if (digest.dg_from) {
X	    length = pack_name(buffer, digest.dg_from, NAME_LENGTH);
X	    ap->sender = alloc_str(length);
X	    strcpy(ap->sender, buffer);
X	} else
X	    ap->sender = "";
X
X	if (digest.dg_subj) {
X	    length = pack_subject(buffer, digest.dg_subj, &re, 255);
X	    ap->replies = re;
X	    ap->subject = alloc_str(length);
X	    strcpy(ap->subject, buffer);
X	} else {
X	    ap->replies = 0;
X	    ap->subject = "";
X	}
X	
X	add_article(ap);
X	ap = alloc_art();
X    }
X    
X    fclose(folder);
X
X    if (was_raw) raw();
X    
X    if (s_keyboard) {
X	menu_cmd = ME_NO_REDRAW;
X    } else
X    if (n_articles == 0) {
X	msg("Not a folder (no article header)");
X	menu_cmd = ME_NO_REDRAW;
X    } else {
X	strcpy(buffer, path);
X	fake_group.group_name = buffer;	/* save for later use */
X    
X	if (n_articles > 1) {
X	    clrdisp();
X	    prompt_line = 2;
X	    if (!dont_sort_folders) sort_articles();
X	}
X    
X	cc_save = cancel_count;
X	cancel_count = 0;
X
X     reenter_menu:
X	menu_cmd = menu(folder_header);
X
X	if (cancel_count) {
X	    clrdisp();
X	    printf("Folder: %s\nFile:   %s\n\n", buffer, group_path_name);
X	    printf("Remove %d article%s from folder? ", 
X		   cancel_count, cancel_count == 1 ? "" : "s");
X	    fl;
X	    
X	    switch (yes(1)) {
X	     case 1:
X		printf("\n\n");
X		rewrite_folder();
X		break;
X	     case 0:
X		break;
X	     default:
X		goto reenter_menu;
X	    }
X	}
X	cancel_count = cc_save;
X    }
X    
X    release_memory(&mem_marker);
X
X    return menu_cmd;
X}
X
X
Xrewrite_folder()
X{
X    register FILE *src, *dst;
X    char oldfile[FILENAME], *sp;
X    register int c;
X    register long cnt;
X    register article_header *ah, **ahp;
X    register int n;
X    
X    if ((src = fopen(group_path_name, "r")) == NULL) {
X	msg("Cannot open %s", group_path_name);
X	return;
X    }
X    
X    strcpy(oldfile, group_path_name);
X    sp = strrchr(oldfile, '/');
X    if (!sp) goto move_error;
X    strcpy(sp+1, "~OLD~FOLDER~");
X
X    unlink(oldfile);
X    if (link(group_path_name, oldfile) < 0) goto move_error;
X    if (unlink(group_path_name) < 0) {
X	if (unlink(oldfile) == 0) goto move_error;
X	printf("\n\n%s was linked to %s --- cannot proceed\n",
X	       group_path_name, oldfile);
X	sleep(5);
X	return;
X    }
X    
X    if ((dst = fopen(group_path_name, "w")) == NULL) {
X	fclose(src);
X	goto move_back;
X    }
X
X    unsort_articles(1);
X
X    printf("Compressing folder..."); fl;
X    
X    for (ahp = articles, n = n_articles; --n >= 0; ahp++) {
X	ah = *ahp;
X	if (ah->flag & A_CANCEL) continue;
X	fseek(src, ah->hpos, 0);
X	cnt = ah->lpos - ah->hpos;
X	while (--cnt >= 0) {
X	    if ((c = getc(src)) == EOF) break;
X	    putc(c, dst);
X	}
X	putc(NL, dst);
X    }
X    fclose(src);
X    if (ferror(dst)) {
X	fclose(dst);
X	goto move_back;
X    }
X    return;
X
Xmove_back:
X    if (link(oldfile, group_path_name) == 0) {
X	unlink(oldfile);
X	printf("Cannot create new file -- Folder restored\n");
X	sleep(2);
X    } else {
X	printf("Cannot create new file\n\nFolder saved in %s\n",
X	       oldfile);
X	sleep(10);
X    }
X    return;
X    
Xmove_error:
X    fclose(src);
X    printf("\n\nCannot move folder %s to %s\n",
X	   group_path_name, oldfile);
X    sleep(3);
X    return;
X}
NO_NEWS_IS_GOOD_NEWS
echo "File folder.c is complete"
chmod 0644 folder.c || echo "restore of folder.c fails"
set `wc -c folder.c`;Sum=$1
if test "$Sum" != "11228"
then echo original size 11228, current size $Sum;fi
echo "x - extracting global.c (Text)"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' > global.c &&
X#include <signal.h>
X#include <errno.h>
X#include <pwd.h>
X#include "config.h"
X
Xexport char *home_directory;
Xexport char *nn_directory;
Xexport char news_directory[] = NEWS_DIRECTORY;	/* /usr/spool/news */
Xexport char lib_directory[]  = LIB_DIRECTORY;	/* /usr/local/lib/nn 	*/
Xexport char db_directory[]   = DB_DIRECTORY;	/* /usr/spool/nn	*/
X
Xexport char *temp_file;
X
Xexport char *pager = PAGER;		/* pg or more		*/
X
Xexport int is_master;
X
X/* signal handler interface */
X
Xexport int s_hangup		= 0;	/* hangup signal */
Xexport int s_keyboard		= 0;	/* keyboard interrupt */
Xexport int s_pipe		= 0;	/* broken pipe */
Xexport int s_redraw		= 0;	/* redraw signal (if job control) */
X
Xstatic sig_type catch_hangup(n)
X{
X    signal(n, SIG_IGN);
X    
X    s_hangup++;
X}
X
Xstatic sig_type catch_keyboard(n)
X{
X    s_keyboard++;
X    
X#ifdef RESET_SIGNAL_WHEN_CAUGHT
X    signal(n, catch_keyboard);
X#endif    
X}
X
Xstatic sig_type catch_pipe(n)
X{
X    s_pipe++;
X    
X#ifdef RESET_SIGNAL_WHEN_CAUGHT
X    signal(n, catch_pipe);
X#endif    
X}
X
X#ifdef HAVE_JOBCONTROL
Xstatic sig_type catch_redraw(n)
X{
X    s_redraw++;
X
X#ifdef RESET_SIGNAL_WHEN_CAUGHT
X    signal(n, catch_redraw);
X#endif
X}
X#endif
X
X
Xinit_global(who)
Xint who;
X{
X    char *env;
X    unsigned short getuid(), getgid();
X    int getpid();
X
X    is_master = (who == 1);
X    
X    signal(SIGTERM, catch_hangup);
X    signal(SIGHUP,  catch_hangup);
X    signal(SIGPIPE, catch_pipe);
X    signal(SIGALRM, SIG_IGN);
X    
X#ifdef SIGPWR
X    signal(SIGPWR, catch_hangup);
X#endif
X
X    user_id = getuid();
X    group_id = getgid();
X    process_id = getpid();
X    
X    if (is_master) {
X	signal(SIGINT,  catch_hangup);
X	signal(SIGQUIT, catch_hangup);
X	return;
X    }
X    
X    signal(SIGINT,  catch_keyboard);
X    signal(SIGQUIT, catch_keyboard);
X#ifdef HAVE_JOBCONTROL
X    signal(SIGCONT, catch_redraw);
X#endif	
X
X    if ((home_directory = getenv("HOME")) == NULL) 
X	user_error("No HOME environment variable");
X
X    nn_directory = mk_file_name(home_directory, ".nn");
X    
X    if (!file_exist(nn_directory, "drwx"))
X	mkdir(nn_directory, 0755);	/* should check here */
X
X    if ((env = getenv("TMPDIR")) == NULL) env = TMP_DIRECTORY;
X    temp_file = mk_file_name(env, "nn.XXXXXX");	/* dies in ANSI C! */
X    mktemp(temp_file);
X}
X
X/*
X * this is for admin K & W commands and for master -w
X */
X
Xkill_master(sig)
Xint sig;
X{
X    FILE *m_pid;
X    int  pid, ok;
X    char buf[10];
X    
X    m_pid = open_file(relative(lib_directory, "MPID"), OPEN_READ);
X    if (m_pid == NULL) {
X	errno = ESRCH;
X	return 0;
X    }
X    
X    ok = 0;	/* not yet */
X
X    if (fgets(buf, 10, m_pid) == NULL)
X	printf("MPID file is empty\n");
X    else {
X	pid = atoi(buf);
X	if (pid <= 2)
X	    printf("MPID file contains illegal process id: %d\n", pid);
X	else
X	    if (kill(pid, sig) != -1) ok++;
X    }
X
X    fclose(m_pid);
X
X    return ok;
X}	
X
X    
Xmem_check(addr, size, msg)
Xchar *addr, *msg;
Xint size;
X{
X    if (addr == NULL) {
X	if (is_master)
X	    sys_error("Cannot allocate %d %s", size, msg);
X	else    
X	    user_error("Cannot allocate %d %s", size, msg);
X    }
X}
X
XFILE *open_file(name, mode)
Xchar *name;
Xint mode;
X{
X    FILE *f;
X    int fd;
X    
X    if ((mode & DONT_CREATE) && !file_exist(name, (char *)NULL))
X	return NULL;
X
X    switch (mode & 0x0f) {
X
X     case OPEN_READ:
X	
X	f = fopen(name, "r");
X	break;
X	
X     case OPEN_UPDATE:
X	
X/*	f = fopen(name, "r+"); 	-- not reliable on many systems (sigh) */
X
X	if ((fd = open(name, O_WRONLY)) >= 0) {
X	    if ((f = fdopen(fd, "w")) != NULL) return f;
X	    close(fd);
X	}
X
X	/* fall thru */
X	
X     case OPEN_CREATE:
X	
X	f = fopen(name, "w");
X	break;
X	
X     case OPEN_APPEND:
X	
X	f = fopen(name, "a");
X	break;
X
X     default:
X    
X	sys_error("Illegal mode: open_file(%s, %d)", name, mode);
X    }
X
X    if (f) {
X	if (mode & OPEN_UNLINK) unlink(name);
X	return f;
X    }
X    
X    if ((mode & MUST_EXIST) == 0) return NULL;
X    
X    if (is_master)
X	sys_error("Cannot open file %s, mode: %d", name, mode);
X    else {
X	log_entry('R', "Client cannot open file %s, mode: %d", name, mode);
X	user_error("Cannot open file %s", name);
X    }
X    
X    return NULL;
X}
X
X
X
X
X/*
X * 	relative -- concat directory name and file name
X */
X
Xchar *relative(dir, name)
Xchar *dir, *name;
X{
X    static char concat_path[FILENAME];
X
X    sprintf(concat_path, "%s/%s", dir, name);
X    return concat_path;
X}
X
X
Xchar *mk_file_name(dir, name)
Xchar *dir, *name;
X{
X    char *buf;
X    
X    buf = malloc((unsigned)(strlen(dir) + strlen(name) + 2));
X    mem_check(buf, 1, "file name");
X    sprintf(buf, "%s/%s", dir, name);
X
X    return buf;
X}
X
X
Xchar *home_relative(dir)
Xchar *dir;
X{
X    if (dir) {
X	if (*dir == '/') 
X	    return copy_str(dir);
X	else {
X	    if (*dir == '~' && *++dir == '/') dir++;
X	    return mk_file_name(home_directory, dir);
X	}
X    }
X    return NULL;
X}
X
X    
Xchar *copy_str(str)
Xchar *str;
X{
X    char *new;
X    
X    new = malloc((unsigned)(strlen(str) + 1));
X    mem_check(new, 1, "string");
X    if (new) strcpy(new, str);
X
X    return new;
X}
X
Xtime_t m_time(f)
XFILE *f;
X{
X    struct stat st;
X    
X    if (fstat(fileno(f), &st) < 0) return 0;
X    return st.st_mtime;
X}
X
X
Xtime_t file_exist(name, mode)
Xchar *name;
Xchar *mode;
X{
X    struct stat statb;
X    extern int errno;
X    
X    if (stat(name, &statb)) return 0;
X
X    if (mode == NULL) return statb.st_mtime;
X    
X    while (*mode) {
X	switch (*mode++) {
X	case 'd':
X	    if ((statb.st_mode & S_IFMT) == S_IFDIR) continue;
X	    errno = ENOTDIR;
X	    return 0;
X	case 'f':
X	    if ((statb.st_mode & S_IFMT) == S_IFREG) continue;
X	    if ((statb.st_mode & S_IFMT) == 0000000) continue;
X	    if ((statb.st_mode & S_IFMT) == S_IFDIR) {
X		errno = EISDIR;
X		return 0;
X	    }
X	    break;
X	case 'r':
X	    if ((statb.st_mode & 0400) && statb.st_uid == user_id) continue;
X	    if ((statb.st_mode & 0040) && statb.st_gid == group_id) continue;
X    	    if ((statb.st_mode & 0004)) continue;
X	    break;
X	case 'w':
X	    if ((statb.st_mode & 0200) && statb.st_uid == user_id) continue;
X	    if ((statb.st_mode & 0020) && statb.st_gid == group_id) continue;
X    	    if ((statb.st_mode & 0002)) continue;
X	    break;
X	case 'x':
X	    if ((statb.st_mode & 0100) && statb.st_uid == user_id) continue;
X	    if ((statb.st_mode & 0010) && statb.st_gid == group_id) continue;
X    	    if ((statb.st_mode & 0001)) continue;
X	    break;
X	}	    
X	errno = EACCES;
X	return 0;
X    }
X
X    /* all modes are ok */
X    return statb.st_mtime;
X}
X
X
X
Xprint_version(fmt)
Xchar *fmt;
X{
X    extern int Update_Level, Patch_Level;
X    int param;
X    
X    for (; *fmt; fmt++) {
X
X	if (*fmt == '%') {
X	    switch (*++fmt) {
X	     case 'R':
X		param = RELEASE;
X		break;
X	     case 'V':
X		param = VERSION;
X		break;
X	     case 'P':
X		param = Patch_Level;
X		break;
X	     case 'U':
X		param = Update_Level;
X		break;
X	     default:
X		continue;
X	    }
X	    printf("%d", param);
X	    continue;
X	} 
X	putchar(*fmt);
X    }
X
X    fl;
X}
X
X/*VARARGS*/
Xlog_entry(va_alist)
Xva_dcl
X{
X    int type;
X    va_list ap;
X
X    va_start(ap);
X    type = va_arg1(int);
X    enter_log(type, va_args2toN);
X    va_end(ap);
X}
X
X#ifdef HAVE_SYSLOG
X#include <syslog.h>
X#endif /* HAVE_SYSLOG */
X
X/*VARARGS*/
Xsys_error(va_alist)
Xva_dcl
X{
X    va_list ap;
X
X    va_start(ap);
X    enter_log('E', va_args1toN);
X    va_end(ap);
X
X    if (is_master) {
X#ifndef HAVE_SYSLOG
X	FILE *f;
X	
X	f = open_file("/dev/console", OPEN_CREATE);
X	if (f == NULL) nn_exit(8);
X	fprintf(f, "\n\rNNMASTER FATAL ERROR\n\r");
X	fclose(f);
X#else /* HAVE_SYSLOG */
X	char buf[512];
X	char *fmt;
X
X	va_start(ap);
X	fmt = va_arg1(char *);
X	vsprintf(buf, fmt, va_args2toN);
X	va_end(ap);
X
X	openlog("nnmaster", LOG_CONS, LOG_DAEMON);
X	syslog(LOG_ALERT, "%s", buf);
X	closelog();
X#endif /* HAVE_SYSLOG */
X    }    
X    nn_exit(7);
X}
X
X
Xstatic enter_log(type, va_tail)
Xchar type;
Xva_tdcl
X{
X    FILE *log;
X    char *msg, buf[512], logname[512];
X
X    msg  = va_arg1(char *);
X    vsprintf(buf, msg, va_args2toN);
X
X    /* cannot use relative: one of the args may be generated by it */
X    sprintf(logname, "%s/Log", lib_directory);
X
X    log = open_file(logname, OPEN_APPEND);
X    if (log == NULL) return 0;
X
X    fprintf(log, "%c: %s (%s): %s\n", type,
X	    date_time((time_t)0), user_name(), buf);
X    
X    fclose(log);
X    return 1;
X}
X
X
Xchar *user_name()
X{
X    static char *user = NULL;
X    struct passwd *pw, *getpwuid();
X
X    if (is_master) return "M";
X    
X    if (user == NULL) {
X	pw = getpwuid((int)user_id);
X	if (pw == NULL) user = "?";
X	user = copy_str(pw->pw_name);
X    }
X    
X    return user;
X}
X
X
Xchar *date_time(t)
Xtime_t t;
X{
X    char *str;
X    
X    if (t == (time_t)0) time(&t);
X    str = ctime(&t);
X    
X    str[16] = 0;
X    return str+4;
X}
X
X
X#ifndef HAVE_MKDIR
X
Xmkdir(path, mode)
Xchar *path;
Xint mode;
X{
X    char command[FILENAME*2 + 20];
X    
X    sprintf(command, "{ mkdir %s && chmod %o %s ; } > /dev/null 2>&1",
X	    path, mode, path);
X    return system(command);
X}
X
X#endif
NO_NEWS_IS_GOOD_NEWS
chmod 0644 global.c || echo "restore of global.c fails"
set `wc -c global.c`;Sum=$1
if test "$Sum" != "8789"
then echo original size 8789, current size $Sum;fi
echo "x - extracting global.h (Text)"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' > global.h &&
X/*
X *	Marks for global/external variables
X */
X
X#define	export			/* export variable from module */
X#define import	extern		/* import variable into module */
X
X/*
X *	Various constants and types
X */
X
Xtypedef int32	article_number;
Xtypedef int16	group_number;
Xtypedef uint32	time_stamp;
X
X/* frequently used characters */
X
X#define NUL	'\0'
X#define TAB	'\t'
X#define NL	'\n'
X#define CR	'\r'
X#define BS	'\b'
X#define SP	' '
X
X/* misc macros */
X
X#define fl fflush(stdout)
X
X#ifdef CTRL
X#undef CTRL
X#endif
X#define CTRL(c)	(c&037)
X
X#ifndef HAVE_STRCHR
X#define	strrchr		rindex
X#define strchr		index
X#endif
X
X#ifdef SIGNAL_HANDLERS_ARE_VOID
Xtypedef void sig_type;
X#else
Xtypedef int sig_type;
X#endif
X
X/*
X *	Some systems don't define these in <sys/stat.h>
X */
X
X#ifndef S_IFMT
X#define	S_IFMT	0170000			/* type of file */
X#define S_IFDIR	0040000			/* directory */
X#define S_IFREG	0100000			/* regular */
X#endif
X
X#ifndef O_RDONLY
X#define	O_RDONLY	0
X#define	O_WRONLY	1
X#define	O_RDWR		2
X#endif
X
X/* define types of library functions */
X
Xchar 	*malloc(), *calloc();
Xchar 	*getenv(), *ctime();
Xchar 	*strchr(), *strrchr();
Xoff_t 	lseek(), ftell(), tell();
Xtime_t 	time();
Xint	atoi();
Xlong	atol();
X
X
X/* define types of own functions */
X
Xchar *mk_file_name(), *home_relative();
Xchar *date_time(), *user_name();
Xchar *copy_str();
X
Xtime_t	file_exist(), m_time();
X
Xextern FILE *open_file();
Xchar *relative();
X
X#define	OPEN_READ	0	/* open for reading */
X#define	OPEN_UPDATE	1	/* open/create for update */
X#define	OPEN_CREATE	2	/* create/truncate for write */
X#define	OPEN_APPEND	3	/* open for append */
X
X#define	DONT_CREATE	0x40	/* return if file does not exist */
X#define	MUST_EXIST	0x80	/* fatal error if cannot open */
X#define	OPEN_UNLINK	0x100	/* unlink after open (not OPEN_UPDATE) */
X
X
X/*
X *	Other external definitions
X *
X *	NOTICE: the distinction between pointers and arrays is important
X *		here (they are global variables - not function arguments)
X */
X
Xextern char
X
X    *home_directory,
X    *nn_directory,
X
X    news_directory[],
X    lib_directory[],
X    db_directory[],
X
X    *pager;
X
X
Xextern int
X
X    s_hangup,	/* hangup signal */
X    s_keyboard,	/* keyboard signal */
X    s_pipe,	/* broken pipe */
X    s_redraw,	/* continue signal after stop */
X
X#ifdef NNTP
X    use_nntp,   /* 1 iff we are using nntp */
X#endif
X
X    is_master;
X
X
Xunsigned short			/* as they are on most systems... */
X
X    user_id,
X    group_id;
X
Xint
X
X    process_id;
X
Xextern int errno;
X
X#include "vararg.h"
X#include "data.h"
X
X/*
X *	db external data
X */
X
Xextern master_header master;
X
X/* group headers */
X
Xextern group_header *active_groups, **sorted_groups;
X
X/* current group information */
X
Xextern char 	group_path_name[];
Xextern char	*group_file_name;
X
Xextern group_header *current_group, *group_sequence;
X
Xextern group_header *lookup();
X
X
X#define	Loop_Groups_Number(num) \
X    for (num = master.number_of_groups; --num >= 0; )
X
X#define Loop_Groups_Header(gh) \
X    for (gh=active_groups+master.number_of_groups; --gh >= active_groups;)
X
Xint l_g_index;
X
X#define Loop_Groups_Sorted(gh) \
X    for (l_g_index = 0; \
X	 (l_g_index < master.number_of_groups) && \
X	 (gh = sorted_groups[l_g_index]) ;\
X	 l_g_index++)
X
X#define	Loop_Groups_Sequence(gh) \
X    for (gh = group_sequence; gh; gh = gh->next_group)
NO_NEWS_IS_GOOD_NEWS
chmod 0644 global.h || echo "restore of global.h fails"
set `wc -c global.h`;Sum=$1
if test "$Sum" != "3245"
then echo original size 3245, current size $Sum;fi
echo "x - extracting group.c (Text)"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' > group.c &&
X/*
X *	group menu
X */
X
X#include "config.h"
X#include "articles.h"
X#include "db.h"
X#include "term.h"
X#include "menu.h"
X#include "keymap.h"
X#include "regexp.h"
X
X
Xexport int  dont_split_digests = 0;
Xexport int  dont_sort_articles = 0;
X
Ximport int  article_limit, also_read_articles;
Ximport int  no_update;
Ximport int  merged_menu;
X
Xchar *quick_match();
X
X/*
X * completion of group name
X */
X
Xgroup_completion(hbuf, ix)
Xchar *hbuf;
Xint ix;
X{
X    static group_number next_group, n1, n2;
X    static char *head, *tail, *last;
X    static int  tail_offset, prev_lgt, l1, l2;
X    static group_header *prev_group, *p1, *p2;
X    register group_header *gh;
X    register char *t1, *t2;
X    int order;
X
X    if (ix < 0) return 0;
X
X    if (hbuf) {
X	n2 = next_group = 0;
X	p2 = prev_group = NULL;
X	l2 = 0;
X	
X	if (head = strrchr(hbuf, ',')) 
X	    head++;
X	else
X	    head = hbuf;
X	tail = hbuf + ix;
X	tail_offset = ix - (head - hbuf);
X	if (last = strrchr(head, '.')) last++; else last = head;
X	return 1;
X    }
X
X    if (ix) {
X	n1 = next_group, p1 = prev_group, l1 = prev_lgt;
X	next_group = n2, prev_group = p2, prev_lgt = l2;
X	list_completion((char *)NULL);
X    }
X    
X    *tail = NUL;
X    
X    while (next_group < master.number_of_groups) {
X	gh = sorted_groups[next_group++]; 
X	if (gh->group_name_length <= tail_offset) continue;
X
X	if (prev_group &&
X	    strncmp(prev_group->group_name, gh->group_name, prev_lgt) == 0)
X	    continue;
X	
X	order = strncmp(gh->group_name, head, tail_offset);
X	if (order < 0) continue;
X	if (order > 0) break;
X	
X	t1 = gh->group_name + tail_offset;
X	if (t2 = strchr(t1, '.')) {
X	    strncpy(tail, t1, t2 - t1 + 1);
X	    tail[t2 - t1 + 1] = NUL;
X	} else
X	    strcpy(tail, t1);
X
X	prev_group = gh;
X	prev_lgt = tail_offset + strlen(tail);
X	if (ix) {
X	    if (list_completion(last) == 0) break;
X	} else
X	    return 1;
X    }
X
X    if (ix) {
X	n2 = next_group, p2 = prev_group, l2 = prev_lgt;
X	if (n2 > master.number_of_groups) 
X	    n2 = 0, p2 = NULL, l2 = 0;
X	next_group = n1, prev_group = p1, prev_lgt = l1;
X	return 1;
X    }
X
X    next_group = 0;
X    prev_group = NULL;
X    return 0;
X}
X
X
X/* flags to access_group */
X
X#define	ALSO_CROSS_POSTINGS	0x01
X#define	DONT_SORT_ARTICLES	0x02
X#define	DONT_SPLIT_DIGESTS	0x04	/* only full digest */
X#define	ALSO_FULL_DIGEST	0x08	/* also full digest */
X
Xstatic access_group(gh, first_article, last_article, flags, submask, do_kill)
Xregister group_header	*gh;
Xarticle_number	first_article, last_article;
Xint		flags;
Xchar 		*submask;
Xint		do_kill;
X{
X    FILE			*data;
X    data_header			hdr;
X    off_t	  		data_offset;
X    register article_header	*ah;
X    cross_post_number		cross_post;
X    int				skip_digest;
X    int				n;
X    string_marker		str_marker;
X    memory_marker		mem_marker;
X    article_number		art_num;
X    static regexp		*subpattern = NULL;
X    static char			subptext[80];
X    
X    if (init_group(gh) <= 0) return -2;
X
X    if (first_article < gh->first_l_article) 
X	first_article = gh->first_l_article;
X
X    if (last_article > gh->last_l_article) 
X	last_article = gh->last_l_article;
X
X    if (last_article == 0 || first_article > last_article) return 0;
X
X    data = open_data_file(gh, 'd', OPEN_READ);
X    if (data == NULL) return -1;
X
X    if ((data_offset = get_data_offset(gh, first_article)) == (off_t)(-1))
X	return -1;
X
X    if (submask && *submask == '/') {
X	submask++;
X	if (subpattern != NULL) {
X	    if (strncmp(submask, subptext, 80) != 0) {
X		free(subpattern);
X		subpattern = NULL;
X	    }
X	}
X	if (subpattern == NULL) {
X	    strncpy(subptext, submask, 80);
X	    subpattern = regcomp(submask);
X	    if (subpattern == NULL) return -1;
X	}
X	submask = NULL;
X    } else
X	if (subpattern != NULL) {
X	    free(subpattern);
X	    subpattern = NULL;
X	}
X    
X    mark_memory(&mem_marker);
X    
X    ah = alloc_art();
X
X    skip_digest = 0;
X
Xskip_to_next:
X    fseek(data, data_offset, 0);
X    
Xread_next:
X    if (data_offset >= gh->data_write_offset) goto out;
X    
X    if (!db_read_art(data, &hdr, &data_offset)) goto out;
X
X    if (hdr.dh_lpos == (off_t)0)	/* article not accessible */
X	goto read_next;
X
X    if (art_num = hdr.dh_number) {
X	if (art_num < 0) art_num = -art_num;
X	if (art_num > gh->last_l_article ||
X	    art_num < gh->first_l_article)
X	    goto data_error;
X    }
X    
X    data_offset += hdr.dh_cross_postings * sizeof(cross_post_number)
X		 + hdr.dh_sender_length 
X		 + hdr.dh_subject_length;
X
X    if (skip_digest && IS_SUB_DIGEST(hdr)) goto skip_to_next;
X	
X    skip_digest = 0;
X	
X    if (hdr.dh_cross_postings) {
X	if ((flags & ALSO_CROSS_POSTINGS) == 0) {
X	    n = hdr.dh_cross_postings;
X	    do {
X		if (fread((char *)&cross_post, sizeof(cross_post_number), 1, data) != 1)
X		    goto data_error;
X#ifdef NETWORK_DATABASE
X#ifndef NETWORK_BYTE_ORDER
X		cross_post = ntohl(cross_post);
X#endif
X#endif		
X		if (active_groups[cross_post].group_flag & G_SUBSCRIPTION)
X		    break;
X	    } while (--n > 0);
X	    if (n > 0) {
X		if (IS_DIGEST_HEADER(hdr)) skip_digest++;
X		goto skip_to_next;
X	    } 
X	} else
X	    fseek(data, (off_t)(sizeof(cross_post_number)*hdr.dh_cross_postings), 1);
X    }	
X
X    ah->flag = 0;
X
X    if (IS_DIGEST_HEADER(hdr)) {
X	if ((flags & (DONT_SPLIT_DIGESTS | ALSO_FULL_DIGEST)) == 0) 
X	    goto skip_to_next;	/* don't want the full digest when split */
X	skip_digest++;
X	ah->flag |= A_FULL_DIGEST;
X    } else
X	if (IS_SUB_DIGEST(hdr))
X	    ah->flag |= A_DIGEST;
X    
X    ah->a_number = ARTICLE_NUMBER(hdr);
X    if (ah->a_number > last_article) goto out;
X    
X    mark_str(&str_marker);
X    
X    if (hdr.dh_sender_length) {
X	ah->sender = alloc_str((int)hdr.dh_sender_length);
X	if (fread(ah->sender, sizeof(char), (int)hdr.dh_sender_length, data)
X	    != hdr.dh_sender_length) goto data_error;
X    } else
X	ah->sender = "";
X    
X    if (hdr.dh_subject_length) {
X	ah->subject = alloc_str((int)hdr.dh_subject_length);
X	if (fread(ah->subject, sizeof(char), (int)hdr.dh_subject_length, data)
X	    !=  hdr.dh_subject_length) goto data_error;
X    } else
X	ah->subject = "";
X
X    ah->hpos	= hdr.dh_hpos;
X    ah->fpos	= ah->hpos + (off_t)(hdr.dh_fpos);
X    ah->lpos	= hdr.dh_lpos;
X    
X    ah->replies	= hdr.dh_replies;
X    ah->lines	= hdr.dh_lines;
X
X    ah->t_stamp = hdr.dh_date;
X    
X    if ((subpattern && !regexec(subpattern, ah->subject)) ||
X	(submask && quick_match(ah->subject, submask) == NULL) ||
X	(do_kill && kill_article(ah))) {
X	killed_articles++;
X	release_str(&str_marker);
X	goto read_next;
X    }
X
X    ah->a_group = merged_menu ? current_group : NULL;
X    
X    add_article(ah);
X    ah = alloc_art();
X
X    goto read_next;
X
X    
Xdata_error:
X    log_entry('E', "%s: data inconsistency", gh->group_name);
X    fclose(data);
X    release_memory(&mem_marker);
X    return -1;
X    
Xout:
X    fclose(data);
X    
X    if ((flags & DONT_SORT_ARTICLES) == 0)
X	sort_articles();
X    
X    return n_articles;
X}
X
Xstatic article_number current_first_article;
X
X
Xstatic print_header()
X{
X    extern long unread_articles;
X    extern int unread_groups;
X    
X    so_printxy(0, 0, "Newsgroup: %s", current_group->group_name);
X    clrline();
X    
X    so_gotoxy(-1, 0, 0);
X    
X    so_printf("Articles: %d", n_articles);
X
X    if (no_update) {
X	so_printf(" NO UPDATE");
X    } else {
X	if (current_first_article > current_group->first_article + 1)
X	    so_printf((killed_articles > 0) ? "(L-%ld,K-%d)" : "(L-%ld)", 
X		      current_first_article - current_group->first_article - 1,
X		      killed_articles);
X	else
X	    if (killed_articles > 0)
X		so_printf(" (K-%d)", killed_articles);
X	
X	if (unread_articles > 0) {
X	    so_printf(" of %ld", unread_articles);
X	    if (unread_groups > 0) 
X		so_printf("/%d", unread_groups);
X	}
X	
X	if ((current_group->group_flag & G_SUBSCRIPTION) == 0)
X	    so_printf(" UNSUB");
X	else if (current_group->group_flag & G_NEW) 
X	    so_printf(" NEW"); 
X	
X	if (current_group->group_flag & G_READ) 
X	    so_printf(" READ");
X    }
X    
X    so_end();
X
X    return 1;	/* number of lines in header */
X}
X
X
X
Xgroup_menu(gh, first_art, submask, do_kill, menu)
Xregister group_header *gh;
Xarticle_number first_art;
Xchar *submask;
Xint do_kill;
Xint (*menu)();
X{
X    int status, was_unread;
X    int menu_cmd, access_mode, did_selection, o_killed;
X    article_number o_first_article, prev_last, last_article;
X    memory_marker sel_marker;	/* for marking old selection */
X    
X#define	menu_return(cmd) { menu_cmd = (cmd); goto menu_exit; }
X    
X    o_first_article = current_first_article;
X    o_killed = killed_articles;
X    mark_memory(&sel_marker);
X    
Xafter_selection:
X
X    did_selection = 0;
X    killed_articles = 0;
X
X    prev_last = gh->last_l_article;
X
X    was_unread = add_unread(gh, -1);
X    
X    if (update_group(gh) < 0) {
X	status = -2;
X	goto access_exception;
X    }
X    
X    if (gh->last_l_article < prev_last) {
X	/* expire + renumbering */
X	int32 updflag;
X
X	if ((gh->last_article = gh->first_l_article - 1) < 0)
X	    gh->last_article = 0;
X	gh->first_article = gh->last_article;
X	updflag = gh->group_flag & (G_RC_UPDATED|G_READ);
X	gh->group_flag &= ~(G_RC_UPDATED|G_READ);
X	update_rc(gh);
X	gh->group_flag &= ~(G_RC_UPDATED|G_READ);
X	gh->group_flag |= updflag;
X    }
X
X    if (was_unread)
X	add_unread(gh, 1);
X
X    gotoxy(0, 0);
X    fl;
X
X    access_mode = 0;
X    if (also_read_articles || submask)
X	access_mode |= ALSO_CROSS_POSTINGS;
X    if (dont_split_digests)
X	access_mode |= DONT_SPLIT_DIGESTS;
X    if (dont_sort_articles)
X	access_mode |= DONT_SORT_ARTICLES;
X    
X    if (first_art == -1) {
X	if (submask == NULL && !also_read_articles) {
X	    if (has_selection(gh, &current_first_article, &last_article)) {
X		status = access_group(gh, current_first_article, last_article,
X				      DONT_SORT_ARTICLES, (char *)NULL, do_kill);
X		do_selections(status >= 0 && n_articles);
X		if (status < 0) goto access_exception;
X		if (n_articles) {
X		    did_selection = 1;
X		    if (!dont_sort_articles) sort_articles();
X		    goto read_the_articles;
X		}
X	    }
X	}
X	
X	if (also_read_articles || submask)
X	    current_first_article = gh->first_l_article;
X	else
X	    current_first_article = gh->last_article + 1;
X	
X	if (article_limit > 0) {
X	    article_number temp;
X	    
X	    temp = gh->last_l_article - article_limit + 1;
X	    if (current_first_article < temp) current_first_article = temp;
X	}
X    } else
X	current_first_article = first_art;
X    
X    status = access_group(gh, current_first_article, gh->last_l_article,
X			  access_mode, submask, do_kill);
X
X access_exception:
X
X    if (status < 0) {
X	if (status == -1)
X	    msg("DATABASE CORRUPTED FOR GROUP %s", gh->group_name);
X/*	else
X	    msg("Group %s is blocked - try again later", gh->group_name);
X*/
X	menu_return( ME_NEXT );
X    }
X    
X    if (n_articles == 0)
X	menu_return( ME_NO_ARTICLES );
X
X read_the_articles:
X
X    menu_cmd = (*menu)(print_header);
X    
X menu_exit:
X
X    if (menu_cmd == ME_QUIT || menu_cmd == ME_NEXT || menu_cmd == ME_PREV)
X	if (submask == NULL && !no_update) 
X	    save_selection(gh, current_first_article, gh->last_l_article);
X
X    if (menu_cmd == ME_READ || menu_cmd == ME_NO_ARTICLES) {
X	if (did_selection) {
X	    int was_read = gh->group_flag & (G_READ|G_RC_UPDATED);
X
X	    prev_last = gh->last_l_article;
X	    gh->last_l_article = last_article;
X	    update_rc(gh);
X	    gh->last_l_article = prev_last;
X	    
X	    if (last_article < gh->last_l_article) {
X		gh->group_flag &= ~ (G_READ|G_RC_UPDATED);
X		gh->group_flag |= was_read;
X		release_memory(&sel_marker);
X		goto after_selection;
X	    }
X	} else
X	    if (submask == NULL && !also_read_articles &&
X		(menu_cmd != ME_NO_ARTICLES || 
X		 (gh->group_flag & G_NEW) == 0) &&
X		(first_art == -1 || 
X		 current_first_article == gh->first_article + 1))
X		update_rc(gh);
X    }
X    
X    current_first_article = o_first_article;
X    killed_articles = o_killed;
X    
X    return menu_cmd;
X}
X
X
Xgoto_group(command, ah)
Xint command;
Xarticle_header *ah;
X{
X    register group_header *gh;
X    char ans1, *answer, *submask, buffer[FILENAME];
X    article_number first, o_current_first;
X    memory_marker mem_marker;
X    group_header *orig_group;
X    int menu_cmd;
X    extern int menu(), file_completion();
X    extern article_header *get_menu_article();
X    extern int get_from_macro;
X    extern group_header *jump_to_group;
X
X#define goto_return( cmd ) \
X    { menu_cmd = cmd; goto goto_exit; }
X
X    m_startinput();
X    
X    gh = orig_group = current_group;
X    
X    o_current_first = current_first_article;
X    
X    submask = NULL;
X
X    if (command == K_GOTO_GROUP) 
X	goto get_group_name;
X    
X    for (;;) {
X	if (command == K_ADVANCE_GROUP)
X	    gh = gh->next_group;
X	else
X	    gh = gh->prev_group;
X	
X	if (gh == NULL)
X	    goto_return(ME_NO_REDRAW);
X	
X	if (gh->first_l_article >= gh->last_l_article) continue;
X	
X	prompt("\1Enter\1 %s%s%s ?", gh->group_name,
X	       gh->group_flag & G_SUBSCRIPTION ? "" : " (UNSUB)",
X	       gh->group_flag & G_READ ? " (READ)" : "" );
X	
X	command = get_c();
X	if (command & GETC_COMMAND) goto_return(ME_REDRAW);
X	if (command == 'y' || command == 'Y') break;
X	if (command == 'n' || command == 'N') goto_return(ME_NO_REDRAW);
X	
X	command = menu_key_map[command];
X	if (command == K_CONTINUE) break;
X	if (command == K_ADVANCE_GROUP) continue;
X	if (command == K_BACK_GROUP) continue;
X	if (command == K_GOTO_GROUP) goto get_group_name;
X	goto_return(ME_NO_REDRAW);
X    }
X    
X    if (gh == orig_group) goto_return(ME_NO_REDRAW);
X    
X    goto get_first;
X    
X get_group_name:
X
X    if (current_group == NULL) {
X	prompt("\1Enter Group or Folder\1 (+./~) ");
X	answer = get_s(NONE, NONE, "+./~", group_completion);
X    } else {
X	prompt("\1Group or Folder\1 (+./~ %=N) ");
X	answer = get_s(NONE, NONE, "+./0123456789~=%", group_completion);
X    }
X    
X    if (answer == NULL) goto_return(ME_NO_REDRAW);
X
X    if ((ans1 = *answer) == NUL) {
X	if (current_group == NULL) goto_return(ME_NO_REDRAW);
X	goto get_first;
X    }
X    
X    sprintf(buffer, "%c", ans1);
X    
X    switch (ans1) {
X
X     case '%':
X	if (current_group == NULL) goto_return(ME_NO_REDRAW);
X	if ((current_group->group_flag & G_FOLDER) == 0) {
X	    if (!ah) {
X		prompt("\1READ\1");
X		if ((ah = get_menu_article()) == NULL) 
X		    goto_return(ME_NO_REDRAW);
X	    }
X	    if ((ah->flag & A_DIGEST) == 0) {
X		*group_file_name = NUL;
X		sprintf(buffer, "%s%ld", group_path_name, ah->a_number);
X		answer = buffer;
X		goto get_folder;
X	    }
X	}
X	
X	msg("cannot split articles inside a folder or digest");
X	goto_return(ME_NO_REDRAW);
X
X     case '.':
X     case '~':
X	strcat(buffer, "/");
X     case '+':
X     case '/':
X	if (!get_from_macro) {
X	    prompt("\1Folder\1 ");
X	    answer = get_s(NONE, buffer, NONE, file_completion);
X	    if (answer == NULL || answer[0] == NUL) goto_return(ME_NO_REDRAW);
X	}
X	
X     get_folder:
X	m_endinput();
X	if (!expand_file_name(buffer, answer)) goto_return (ME_NO_REDRAW);
X	menu_cmd = folder_menu(buffer);
X	init_group(orig_group);
X	goto goto_exit;
X
X	
X     case 'a':
X	if (answer[1] == NUL || strcmp(answer, "all") == 0) {
X	    if (current_group == NULL) goto_return(ME_NO_REDRAW);
X	    first = 0;
X	    goto more_articles;
X	}
X	break;
X	
X     case 'u':
X	if (answer[1] == NUL || strcmp(answer, "unread") == 0) {
X	    if (current_group == NULL) goto_return(ME_NO_REDRAW);
X	    first = gh->first_article + 1;
X	    goto more_articles;
X	}
X	break;
X
X     case 's':
X	if (answer[1] == NUL) {
X	    if (current_group == NULL) goto_return(ME_NO_REDRAW);
X	    goto get_subject;
X	}
X	
X	break;
X	
X     case '=':
X	if (current_group == NULL) goto_return(ME_NO_REDRAW);
X	goto get_subject;
X	
X     case '0':
X     case '1':
X     case '2':
X     case '3':
X     case '4':
X     case '5':
X     case '6':
X     case '7':
X     case '8':
X     case '9':
X	if (current_group == NULL) goto_return(ME_NO_REDRAW);
X	if (current_first_article <= gh->first_l_article) {
X	    msg("No extra articles");
X	    flush_input();
X	    goto_return(ME_NO_REDRAW);
X	}
X	
X	if (!get_from_macro) {
X	    prompt("\1Number of extra articles\1 max %ld: ",
X		   current_first_article - gh->first_l_article);
X	    sprintf(buffer, "%c", ans1);
X	    answer = get_s(NONE, buffer, NONE, NO_COMPLETION);
X	    if (answer == NULL || *answer ==  NUL) goto_return(ME_NO_REDRAW);
X	}
X	
X	first = current_first_article - atol(answer);
X	goto more_articles;
X	
X     default:
X	break;
X    }
X
X    if ((gh = lookup(answer)) == NULL) {
X	msg("No group named %s", answer);
X	goto_return(ME_NO_REDRAW);
X    }
X
X
X get_first:
X
X    m_advinput();
X
X    if (gh->last_l_article == 0 ||
X	gh->last_l_article < gh->first_l_article) {
X	msg("Group %s is empty", answer);
X	goto_return(ME_NO_REDRAW);
X    }
X
X    if (gh == orig_group
X	&& current_first_article <= gh->first_article) {
X
X	if (current_first_article <= gh->first_l_article) {
X	    /* no more articles to read */
X	    *answer = '=';
X	    get_from_macro = 0;
X	    goto get_subject;
X	}
X	    
X	prompt("\1Number of extra articles\1 all %ld, =subject: ",
X	       (long)(current_first_article - gh->first_l_article));
X
X	answer = "a=s";
X    } else {
X	if (gh != orig_group)
X	    current_first_article = gh->last_l_article + 1;
X	
X        prompt("\1Number of%s articles\1 u)nread %ld,%s a)ll %ld, s)ubject: ",
X	       gh == orig_group ? " extra" : "",
X	       (long)(current_first_article - gh->first_article - 1),
X	       (gh->group_flag & G_UNREAD_COUNT) ? " j)ump," : "",
X	       (long)(current_first_article - gh->first_l_article));
X
X	answer = (gh->group_flag & G_UNREAD_COUNT) ? "uja=s" : "ua=s";
X    }
X    
X    answer = get_s(NONE, NONE, answer, NO_COMPLETION);
X    if (answer == NULL) goto_return(ME_NO_REDRAW);
X
Xget_subject:	/* when *answer == '=' */
X    
X    switch (*answer) {
X	
X     case NUL:
X	if (gh != orig_group || current_first_article <= gh->first_l_article) {
X	    first = 0;
X	    break;
X	}
X	/* else fall thru */
X	
X     case 'u':
X	first = gh->first_article + 1;
X	break;
X
X     case 'j':
X	jump_to_group = gh;
X	goto_return(ME_QUIT);
X	
X     case 'a':
X	first = 0;
X	break;
X	
X     case 's':
X     case '=':
X	first = 0;
X	if (get_from_macro) {
X	    submask = answer + 1;
X	} else {
X	    prompt("=");
X	    submask = get_s(ah ? ah->subject : NONE, NONE, ah ? NONE : "%=",
X			    NO_COMPLETION);
X	    if (submask == NULL) goto_return(ME_NO_REDRAW);
X	    if (*submask == '%' || *submask == '=') {
X		if ((ah = get_menu_article()) == 0) goto_return(ME_NO_REDRAW);
X		*submask = NUL;
X	    }
X	}
X	
X	if (*submask == NUL)
X	    if (ah) {
X		strncpy(submask, ah->subject, GET_S_BUFFER);
X		submask[GET_S_BUFFER-1] = NUL;
X	    } else
X		goto_return(ME_NO_REDRAW);
X	
X	if (*submask) {
X	    if (*submask != '/') init_quick_match(submask);
X	} else
X	    submask = NULL;
X	break;
X	
X     case '0':
X     case '1':
X     case '2':
X     case '3':
X     case '4':
X     case '5':
X     case '6':
X     case '7':
X     case '8':
X     case '9':
X	first = current_first_article - atol(answer);
X	break;
X
X     default:
X	ding();
X	goto_return(ME_NO_REDRAW);
X    }
X    
X    if (first > gh->last_l_article) goto_return(ME_NO_REDRAW);
X
Xmore_articles:
X    if (gh == orig_group && submask == NULL && 
X	first < current_first_article) {
X	if (current_first_article <= gh->first_l_article) {
X	    msg("No extra articles");
X	    goto_return(ME_NO_REDRAW);
X	}
X	
X	if (access_group(gh, first, current_first_article - 1,
X			      ALSO_CROSS_POSTINGS, (char *)NULL, 0) < 0) {
X	    msg("Cannot read extra articles (now)");
X	    goto_return(ME_NO_REDRAW);
X	}
X
X	current_first_article = first;
X	
X	goto_return(ME_REDRAW);
X    }
X
X    mark_memory(&mem_marker);
X    m_endinput();
X    menu_cmd = group_menu(gh, first, submask, 0, menu);
X    release_memory(&mem_marker);
X
X    if (gh != orig_group) {
X	current_first_article = o_current_first;
X	if (orig_group) init_group(orig_group);
X    }
X    
Xgoto_exit:
X    m_endinput();
X    return menu_cmd;
X}
X
Xstatic merged_header()
X{
X    so_printxy(0, 0, "MERGED NEWS GROUPS:  %d ARTICLES", n_articles);
X    clrline();
X
X    return 1;
X}
X
Xmerge_and_read(submask, do_kill)
Xchar *submask;
Xint do_kill;
X{
X    register group_header *cur;
X    group_header dummy_group;
X    int access_mode;
X    
X    free_memory();
X
X    access_mode = DONT_SORT_ARTICLES;
X    if (also_read_articles || submask)
X	access_mode |= ALSO_CROSS_POSTINGS;
X    if (dont_split_digests)
X	access_mode |= DONT_SPLIT_DIGESTS;
X	
X    for (cur = group_sequence; cur != NULL; cur = cur->next_group) {
X	if (s_hangup || s_keyboard) break;
X
X	if (cur->group_flag & G_FOLDER) {
X	    printf("\n\rIgnoring folder: %s\n\r", cur->group_name);
X	    continue;
X	}
X
X	if (!also_read_articles)
X	    if (cur->last_article >= cur->last_l_article)
X		continue;
X	
X	printf("\r%s", cur->group_name); clrline();
X	
X	access_group(cur, -1, cur->last_l_article, access_mode, submask, do_kill);
X    }
X    merge_memory();
X    if (n_articles == 0) return;
X    if (!dont_sort_articles) sort_articles();
X
X    dummy_group.group_flag = 0;
X    dummy_group.save_file = NULL;
X    dummy_group.group_name = "dummy";
X    dummy_group.kill_list = NULL;
X
X    current_group = &dummy_group;
X    
X    menu(merged_header);
X    
X    free_memory();
X}
X
Xunsubscribe(gh)
Xgroup_header *gh;
X{
X    if (no_update) {
X	msg("nn started in \"no update\" mode");
X	return 0;
X    }
X    
X    if (gh->group_flag & G_FOLDER) {
X	msg("cannot unsubscribe to a folder");
X	return 0;
X    }
X
X    if (gh->group_flag & G_SUBSCRIPTION) {
X	prompt("\1Unsubscribe to\1 %s ? ", gh->group_name);
X	if (yes(0) <= 0) return 0;
X
X	add_unread(gh, -1);
X	gh->group_flag &= ~G_SUBSCRIPTION;
X	write_rc_entry(gh, 0);
X	return 1;
X    }
X
X    prompt("Already unsubscribed.  \1Resubscribe to\1 %s ? ",
X	   gh->group_name);
X    if (yes(1) <= 0) return 0;
X	
X    gh->group_flag |= G_SUBSCRIPTION;
X    write_rc_entry(gh, 0);
X
X    add_unread(gh, 1);
X
X    return 1;
X}
X
X
Xgroup_overview(amount)
Xint amount;	/* 0=>unread,subscribed, 1=>unread,all, 2=>all,all 3=>unsub*/
X{
X    register group_header *gh;
X    
X    clrdisp();
X
X    pg_init(0, 2);
X
X    Loop_Groups_Sorted(gh) {
X
X	if (gh->group_flag & G_NO_DIRECTORY) continue;
X
X	if (amount <= 1 && gh->last_article >= gh->last_l_article)
X		continue;
X
X	if (amount == 0 && (gh->group_flag & G_SUBSCRIPTION) == 0)
X		continue;
X
X	if (amount == 3 && (gh->group_flag & G_SUBSCRIPTION))
X		continue;
X
X	if (pg_next() < 0) break;
X
X	printf("%6ld %s%s",
X	       (long)(gh->last_l_article - gh->last_article),
X	       gh->group_name,
X	       gh->group_flag & G_SUBSCRIPTION ? "" : " (!)");
X    }
X
X    pg_end();
X}
NO_NEWS_IS_GOOD_NEWS
chmod 0644 group.c || echo "restore of group.c fails"
set `wc -c group.c`;Sum=$1
if test "$Sum" != "22057"
then echo original size 22057, current size $Sum;fi
echo "x - extracting help.commands (Text)"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' > help.commands &&
X;:ACOMMAND NAMES;:A						;:AMAP COMMAND;:A
X
X;:BNAME		MENU	MORE	FUNCTION
Xadvance-group	A 		advance one group in sequence
Xback-group	B 		go back one group in sequence
Xcancel		C	C	cancel an article
Xcommand		:	:	extenced command prefix
Xcompress		c	compress text (eliminate extra spaces)
Xcontinue	SPACE	SPACE	the "space bar" command
Xfind			/	regular expression search
Xfind-next		.	repeat regular expression search
Xfollow		F 	f F	follow up
Xfull-digest		H	show complete digest
Xgoto-group	G	G	goto group or open folder
Xgoto-menu		=	go back to menu
Xhelp		?	?	online help
Xkill-select	K	K	kill/select handling
Xlayout		L		change menu layout
Xline+1		down	CR	next menu line/scroll one line 
Xline-1		up		previous menu line
Xline=@			g	goto specific line
Xmail		M	m M	mail or forward
Xmessage		^P	^P	repeat last prompt line message
Xnext-article		n	skip to next article
Xnext-group	N		goto to next group without reading current
Xnext-subject		k	skip to next article with different subject
Xnil				unbound key
Xoverview	Y	Y	show groups with unread news
Xpage+1		>		goto next page if any
Xpage+1/2		d ^D	scroll half page forward
Xpage-1		<	DEL	goto one page back
Xpage-1/2		u ^U	scroll half page backwards
Xpage=$		$	$	goto end of menu/article
Xpage=0			h	goto header of article	
Xpage=1		^	^	goto first menu/article page
Xpage=@				goto specific page of article (not implemented)
Xpost				post new article
Xpreview		%		preview article
Xprevious	P	P	goto previous group/article
Xprint			p	print article
Xquit		Q	Q	quit nn
Xread-return	Z		read selected articles and return to menu
Xread-skip	X		read selected article, skip unseen menu pages
Xredraw		^L ^R	^L ^R	redraw screen
Xreply		R	r R	reply
Xrot13			D	decrypt rot13 article
Xsave-body	W	w W	save article without header
Xsave-full	S	s S	save article with full header
Xsave-short	O	o O	save article with short header
Xselect          .		select (or deselect) current menu entry
Xselect-auto     +		select "auto-selected" articles
Xselect-invert 	@		invert all selections on current menu page
Xselect-range    -		select range of articles
Xselect-subject	*		select all articles with current subject
Xshell         	!	!	shell command prefix
Xunselect-all  	~		unselect all articles
Xunshar				unshar article(s)
Xunsub         	U	U	unsubscribe (or subscribe) to current group
Xversion       	V	V	print release information
NO_NEWS_IS_GOOD_NEWS
chmod 0644 help.commands || echo "restore of help.commands fails"
set `wc -c help.commands`;Sum=$1
if test "$Sum" != "2312"
then echo original size 2312, current size $Sum;fi
echo "x - extracting help.extended (Text)"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' > help.extended &&
X;:AEXTENDED COMMANDS;:A
X
X:help COMMAND		give help on specific command
X
X:q!			quit nn without update (only with -B option)
X:x			quit nn, mark current group as read
X
X:mkdir [DIR]		create directory DIR (will prompt for DIR if omitted)
X:cd [DIR]		change working directory to DIR
X
X:admin			enter administration mode
X
X:set OPTION [VALUE]	set or unset option (use 'help set' for more info)
X:map MODE KEY COMMAND	remap key or command
X:show TABLE		show contents of various tables
X:sort MODE		sort menu according to subject, age, or arrival
X
X:unread (N)		mark current group as unread (last N articles)
X:coredump		abort with a core dump
NO_NEWS_IS_GOOD_NEWS
chmod 0644 help.extended || echo "restore of help.extended fails"
set `wc -c help.extended`;Sum=$1
if test "$Sum" != "626"
then echo original size 626, current size $Sum;fi
echo "x - extracting help.help (Text)"
sed 's/^X//' << 'NO_NEWS_IS_GOOD_NEWS' > help.help &&
X;:AHELP COMMAND;:A
X
XSynopsis
X
X	:help subject
X
XHelp is available on the the following subjects:
X
Xcommands	commands that can be bound to keys
Xextended	extended commands (:command)
Xmap		key mapping
Xset		variable setting
NO_NEWS_IS_GOOD_NEWS
echo "End of part 4"
echo "File help.help is continued in part 5"
echo "5" > s2_seq_.tmp
exit 0
---
Kim F. Storm        storm@texas.dk        Tel +45 429 174 00
Texas Instruments, Marielundvej 46E, DK-2730 Herlev, Denmark
	  No news is good news, but nn is better!

-- 
Please send comp.sources.unix-related mail to rsalz@uunet.uu.net.
Use a domain-based address or give alternate paths, or you may lose out.