Subject:  v22i042:  NN Newsreader, release 6.4, Part07/21
Newsgroups: comp.sources.unix
Approved: rsalz@uunet.UU.NET
X-Checksum-Snefru: 39dcb800 39e05c76 44c3ba86 18e5775a

Submitted-by: "Kim F. Storm" <storm@texas.dk>
Posting-number: Volume 22, Issue 42
Archive-name: nn6.4/part07

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then feed it
# into a shell via "sh file" or similar.  To overwrite existing files,
# type "sh file -c".
# The tool that generated this appeared in the comp.sources.unix newsgroup;
# send mail to comp-sources-unix@uunet.uu.net if you want that tool.
# Contents:  man/nn.1.B master.c
# Wrapped by storm@texas.dk on Sun May  6 18:19:31 1990
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
echo If this archive is complete, you will see the following message:
echo '          "shar: End of archive 7 (of 22)."'
if test -f 'man/nn.1.B' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'man/nn.1.B'\"
else
  echo shar: Extracting \"'man/nn.1.B'\" \(32572 characters\)
  sed "s/^X//" >'man/nn.1.B' <<'END_OF_FILE'
X.\" BEGINPART B
X.SH FILE NAME EXPANSION
XWhen the save commands prompts for a file name, the following file
Xname expansions are performed on the file name you enter:
X.TP
X\fB+\fP\fIfolder\fP
XThe
X.B +
Xis replaced by the contents of the
X.B folder
Xvariable (default value "~/News/") resulting in the name of a file in the
X.I folder
X.IR directory .
XExamples:
X.br
X	+emacs, +nn, +sources/shar/nn
X.TP
X\fB+\fP
XA single plus is replaced by the expansion of the file name contained in the
X.B default-save-file
Xvariable.
X.TP
X\fB~/\fP\fIfile\fP
XThe
X.B ~
Xis replaced by the contents of the environment variable HOME, i.e. the
Xpath name of your home directory.
XExamples:
X.br
X	~/News/emacs, ~/News/nn, ~/src/shar/nn
X.TP
X\fB|\fP\fIcommand-line\fP
XInstead of writing to a file, the articles are piped to the given
Xshell (/bin/sh) command-line.  Each save or write command will create a
Xseparate pipe, but all articles saved or written in one command (in
Xselection mode) are given
Xas input to the same shell command.  Example:
X.br
X	| pr | lp
X.br
XThis will print the articles on the printer after they have been piped
Xthrough pr.
X    It is possible to create separate pipes for each saved article by
Xusing a double pipe symbol in the beginning of the command, e.g.
X.br
X	|| cd ~/src/nn ; patch
X.br
X.LP
XThe following symbols are expanded in a file name or command:
X.TP
X.B $F
Xwill be expanded to the name of the current group with the periods
Xreplaced by slashes, e.g. rec/music/synth.
X.TP
X.B $G
Xwill be expanded to the name of the current group.
X.TP
X.B $L
Xwill be expanded to the \fIlast component\fP of the name of the
Xcurrent group.  You may use this to create default save file names
Xlike +src/$L in the comp.sources groups.
X.TP
X.B $N
Xwill be expanded to the (local) article number, e.g. 1099.  In
Xselection mode it is only allowed at the end of the file name!
X.TP
X.B $(VAR)
Xis replaced by the string value of the environment variable \fIVAR\fP.
X.LP
XUsing these symbols, a simple naming scheme for `default folder name' is
X.B +$G
Xwhich will use the group name as folder name.  Another possibility is
X.BR +$F/$N .
X.LP
XAs mentioned above, you can also instruct \fInn\fP to save a series of
Xfiles in separate, unique files.  All that is required is that the
Xfile name contains an asterisk, e.g.
X.br
X	+src/hype/part*.shar
X.br
XThis will cause each of the articles to be saved in separate, unique
Xfiles named part1.shar, part2.shar, and so on, always choosing a part
Xnumber that results in a unique file name (i.e. if part1.shar did
Xalready exist, the first article would be saved in part2.shar, the
Xnext in part3.shar, and so on).
X.LP
X\fBRelated variables\fP:
Xdefault-save-file, folder, save-counter, save-counter-offset.
X.SH FILE AND GROUP NAME COMPLETION
XWhen entering a file name or a news group name, a simple
X.B completion
Xfeature is available using the \fBspace\fP, \fBtab\fP, and \fB?\fP keys.
X.LP
XHitting \fBspace\fP anywhere during input will complete the
X.I current
X.I component
Xof the file name or group name with the
X.I first
Xavailable possibility.
X.LP
XIf this possibility is not the one you want, keep on hitting
X.B space
Xuntil it appears.
X.LP
XWhen the right completion has appeared, you can just continue typing
Xthe file or group name, or you can hit
X.B tab
Xto fix the current component, and get the
X.I first
Xpossibility for the next component, and then use
X.B space
Xto go through the other possible completions.
X.LP
XThe
X.B ?
Xkey will produce a list of the possible
X.I completions
Xof the current component.  If the list is too long for the available
Xspace on screen, the key can be repeated to get the next part of the
Xlist.
X.LP
XThe current completion can be deleted with the
X.B erase
Xkey.
X.LP
XThe default value for a file name is the last file name you have
Xentered, so if you enter a
X.B space
Xas the first character after the prompt, the last file name will be
Xrepeated (and you can edit it if you like).  In some cases, a string
Xwill already be written for you in the prompt line, and to get the
Xdefault value in these cases, use the \fBkill\fP key.  This also means
Xthat if you neither want the initial value, nor the default value, you
Xwill have to hit the \fBkill\fP twice to get a clean prompt line.
X.LP
X\fBRelated variables\fP:
Xcomp1-key, comp2-key, help-key, suggest-default-save.
X.SH POSTING AND RESPONDING TO ARTICLES
XIn both selection mode and reading mode you can post new articles,
Xpost follow-ups to articles, send replies to the author of an article,
Xand you can send mail to another user with the option of including an
Xarticle in the letter.  In reading mode, a response is made to the
Xcurrent article, while in selection mode you will be prompted for an
Xarticle to respond to.
X.LP
XThe following commands are available (the lower-case equivalents are
Xalso available in reading mode):
X.TP
X\&\fBR\fP	{\fBreply\fP}
XReply through mail to the author of the article.  This is the prefered
Xway to respond to an article unless you think your reply is of general
Xinterest.
X.TP
X\&\fBF\fP	{\fBfollow\fP}
XFollow-up with an article in the same newsgroup (unless an alternative
Xgroup is specified in the article header).
X.TP
X\&\fBM\fP	{\fBmail\fP}
XMail a letter or
X.I forward
Xan article to a single recipient.
XIn selection mode, you will be prompted for an article to include
Xin your letter, and in reading mode you will be asked if the current
Xarticle should be included in the letter.
XYou will then be prompted for the recipient of the letter (default
Xrecipient is yourself)
Xand the subject of the letter (if an article is included, you may hit
X.B space
Xto get the default subject which is the subject of the included article).
X  The header of the article is only included in the posted letter if
Xit is forwarded (i.e. not edited), or if the variable
X\fBinclude-full-header\fP is set.
X.TP
X\&\fB:post\fP	{\fBpost\fP}
XPost a new article to any newsgroup.  This command will prompt you for
Xa
X.I comma-separated
Xlist of newsgroups to post to (you cannot enter a space because
X.B space
Xis used for group name completion as described below).
X.LP
XGenerally, \fInn\fP will construct a file with a suitable header, optionally
Xinclude a copy of the article in the file with each non-empty line
Xprefixed by a `>' character (except in mail mode), and invoke an
Xeditor of your choice (using the EDITOR environment variable) on this
Xfile, positioning you on the first line of the body of the article (if
Xit knows the editor).
X.PP
XWhen you have completed editing the message, it will compare it to the
Xunedited file, and if they are identical (i.e. you did not make any
Xchanges to the file), or it is empty, the operation is cancelled.
XOtherwise you will be prompted for an action to take on the
Xconstructed article (enter first letter followed by \fBreturn\fP, or
Xjust \fBreturn\fP to take the default action):
X.br
X.sp 0.5v
X	Action: a)bort e)dit i)spell r)eedit s)end v)iew w)rite  (send)
X.sp 0.5v
X.br
XYou now have the opportunity to perform one of the following actions:
X.LP
X.in +2m
X.ta 5m
X.\"ta 4 9
X\fBa\fP	throw the response away (will ask for confirmation),
X.br
X\fBe\fP	edit the file again,
X.br
X\fBi\fP	run an (interactive) \fBspell-checker\fP on the text,
X.br
X\fBr\fP	throw away the edited text and edit the original text,
X.br
X\fBs\fP	send the article or letter,
X.br
X\fBv\fP	view the article (through the \fBpager\fP), or
X.br
X\fBw\fP	append it to a file (before you send it).
X.in -2m
X.DT
X.LP
X\fBRelated variables\fP:
Xappend-signature-mail, append-signature-post, default-distribution,
Xedit-response-check, editor, include-art-id, include-full-header,
Xincluded-mark, mail-header, mail-record, mail-script, mailer,
Xmailer-pipe-input, news-header, news-record, news-script,
Xorig-to-include-mask, pager, query-signature,
Xrecord, response-check-pause, response-default-answer,
Xsave-counter, save-counter-offset, save-report, spell-checker.
X.SH JUMPING TO OTHER GROUPS
XBy default \fInn\fP will present the news groups in a predefined
Xsequence (see the section on Presentation Sequence later on).
XTo override this sequence and have a look at any other group the
X.B G
X{\fBgoto-group\fP} command available in both selection and reading
Xmode enables you to move freely between all the newsgroups.
X.LP
XFurthermore, the
X.B G
Xcommand enables you to open folders and other files, to read old
Xarticles you have read before, and to grep for a specific subject in a
Xgroup.
X.PP
XIt is important to notice that normally the goto command is recursive,
Xi.e. a new \fImenu level\fP is created when the specified group or
Xfolder is presented, and when it has been read, \fInn\fP will continue
Xthe activity in the group that was presented before the goto command
Xwas executed.  However, if there are unread articles in the target
Xgroup you can avoid entering a new menu level by using the
X.B j
Xreply described below.  The current menu level (i.e. number of nested
Xgoto commands) will be shown in the prompt line as "<N>" (in reverse
Xvideo).
X.PP
XThe goto command is very powerful, but unfortunately also a little bit
Xtricky at first sight, because the facilities it provides depend on
Xthe context in which the command is used.
X.PP
XWhen executed, the goto command will prompt you for the name of the
Xnewsgroup, folder, or file to open.  It will use the first letter
Xyou enter to distinguish these three possibilities:
X.TP
X.B return
XAn empty answer is equivalent to the current newsgroup.
X.TP
X\fIletter\fP
XThe answer is taken to be the name of a newsgroup.
X.TP
X.I +
X.br
XThe answer is taken to be the name of a folder.  If only `+' is
Xentered, it is equivalent to the default save file for the current
Xgroup.
X.TP
X\fI\&/ or ./ or ~/\fP
XThe answer is taken to be the name of a file, either relative to the
Xcurrent directory, relative to your home directory, or an absolute
Xpath name for the file.
X.TP
X.B %
XIn reading mode, this reply corresponds to reading the current article
X(and splitting it as a digest).  In selection mode, it will prompt for
Xan article on the menu to read.
X.TP
X.B @
XThis choice is equivalent to the archive file for the current group.
X\fInnmaster\fP maintains archive files with all old and current
Xarticles for the groups which have the auto-archive option set in the
XGROUPS file (see \fInnmaster\fP(8)).
X.TP
X\fB=\fP and \fInumber\fP
XThese answers are equivalent to the same answers described below
Xapplied to the current group (e.g. \fBG return =\fP and \fBG =\fP are
Xquivalent).
X.LP
XSpecifying a folder, a file, or an article (with \fB%\fP) will cause
X\fInn\fP to treat the file like a digest and split it into separate
Xarticles (not physically!)  which are then presented on a menu in the
Xusual way, allowing you to read or save individual subarticles from
Xthe folder.
X.LP
XWhen you enter a group name, \fInn\fP will ask you how many articles
Xin the group you want to see on the menu.  You can give the following
Xanswers:
X.TP
X.I a number N
XIn this case you will get the newest N articles in the group, or if
Xyou specified the current group (by hitting \fBreturn\fP to the group
Xname prompt or entering the number directly), you will get that many
X\fIextra\fP articles included on the same menu (without creating a new
Xmenu level).
X.TP
X.B j
XThis answer can only be given if there are unread articles in the
Xgroup.  It will instruct nn to jump directly to the specified group in
Xthe presentation sequence \fIwithout\fP creating a new menu level.
X.TP
X.B u
XThis instructs \fInn\fP to present the \fIunread\fP articles in the
Xgroup (if there are any).  If you have already read the group (in the
Xcurrent invocation of \fInn\fP), the \fBu\fP answer will instruct
X\fInn\fP to present the articles that were unread when you entered
X\fInn\fP.
X.TP
X.B a
XThis instruct \fInn\fP to present \fBall\fP articles in the group.
X.TP
X\fBs\fP\fIword\fP or \fB=\fP\fIword\fP
XThis instructs \fInn\fP to search \fIall\fP articles in the groups,
Xbut only present the articles containing the word \fIword\fP in the
Xsubject.  Notice that case is ignored when searching for the word in
Xthe subject lines.
X.TP
X\fBn\fP\fIword\fP
XSame as the \fBs\fP form except that it searched for articles where
Xthe sender \fIname\fP matches \fIword\fP.
X.TP
X\fBe\fP\fIword\fP
XSame as the \fBs\fP form except that it Psearched for articles where
X\fIeither\fP the subject or the sender name matches \fIword\fP.
X.TP
X\fIword\fP = \fB/\fP\fIregexp\fP
XWhen the first character of the \fIword\fP specified with the \fBs\fP,
X\fBn\fP, and \fBe\fP forms is a slash `/', the rest of the input is
Xinterpreted as a regular expression to search for.  Notice that
Xregular expression matching is case insensitive when
X\fBcase-fold-search\fP is set (default).
X.TP
X.B return
XThe meaning of an empty answer depends on the context: if there are
Xunread articles in the specified group the unread articles will be
Xpresented, otherwise \fIall\fP articles in the group will be included
Xin the menu.
X.LP
XIf you specified the current group, and the menu already contains all
Xthe available articles, \fInn\fP will directly prompt for a word to
Xsearch for in the subject of all articles (the prompt will be an equal
Xsign.)
X.LP
XWhen the goto command creates a new menu level, \fInn\fP will not
Xperform auto kill or selection in the group.  You can use the \fB+\fP
Xcommand in menu mode to perform the auto-selections.
X.LP
XThere are three commands in the goto family:
X.TP
X\&\fBG\fP	{\fBgoto-group\fP}
XThis is the general goto command described above.
X.TP
X\&\fBB\fP	{\fBback-group\fP}
XBackup one or more groups.  You can hit this key one or more times to
Xgo back in the groups already presented (including those without new
Xarticles); when you have found the group you are looking for, hit
X\fBspace\fP to enter it.
X.TP
X\&\fBA\fP	{\fBadvance-group\fP}
XAdvance one or more groups.  This command is similar to the \fBB\fP
Xcommand, but operates in the opposite direction.
X.TP
X\&\fBN\fP	{\fBnext-group\fP}
XWhen used within an \fBA\fP or \fBB\fP command, it skips forward to
Xthe next group in the sequence with unread articles or which has
Xpreviously been visited.
X.TP
X\&\fBP\fP	{\fBprevious\fP}
XWhen used within an \fBA\fP or \fBB\fP command, it skips backwards to
Xthe preceding group in the sequence with unread articles or which has
Xpreviously been visited.
X.LP
XOnce you have entered an \fBA\fP or \fBB\fPcommand, you can freely mix
Xthe \fBA\fP, \fBB\fP, \fBP\fP, and \fBN\fP commands to find the group
Xyou want, and you can also use the \fBG\fP command to be prompted for
Xa group name.
X.LP
XTo show the use of the goto command some typical examples on
Xits use are given below:
X.sp
X.nf
X.I "Present the unread articles in the dk.general group"
X.sp 0.5v
X 	\fBG\fP dk.general \fBreturn\fP \fBu\fP
X.sp
X.I "Jump directly to the gnu.emacs group and continue from there"
X.sp 0.5v
X 	\fBG\fP gnu.emacs \fBreturn\fP \fBj\fP
X.sp
X.I "Include the last 10 READ articles in the current group menu"
X.sp 0.5v
X 	\fBG\fP 10 \fBreturn\fP
X.sp
X.I "Find all articles in rec.music.misc on the subject Floyd"
X.sp 0.5v
X 	\fBG\fP rec.music.misc \fBreturn\fP
X 	\fB=\fP floyd \fBreturn\fP
X.sp 0.5v
X.sp
X.I "Open the folder +nn"
X.sp 0.5v
X 	\fBG\fP +nn \fBreturn\fP
X.sp
X.I "Split current article as a digest (in reading mode)"
X.sp 0.5v
X 	\fBG\fP \fB%\fP
X
X.fi
X.LP
X\fBRelated variables\fP:
Xcase-fold-search, default-save-file
X.SH AUTOMATIC KILL AND SELECTION
XWhen there is a subject or an author which you are either very
Xinterested in, or find completely uninteresting, you can easily
Xinstruct \fInn\fP to \fIauto-select\fP or \fIauto-kill\fP articles
Xwith specific subjects or from specific authors.  These instructions
Xare stored in a \fIkill file\fP, and the most common types of entries
Xcan be created using the following command:
X.TP
X\&\fBK\fP	{\fBkill-select\fP}
XCreate an entry in your personal kill file.  The contents of the entry
Xis specified during a short dialog that is described in details below.
XThis command is available in both selection and reading mode.
X.LP
XEntries in the kill file may apply to a single newsgroup or to all
Xnewsgroups.  Furthermore, entries may be permanent or they may be
Xexpired a given number of days after their entry.
X.LP
XTo increase performance, \fInn\fP uses a compiled version of the kill
Xfile which is read in when \fInn\fP is invoked.  The compiled kill
Xfile will automatically be updated if the normal kill file has been
Xmodified.
X.LP
XThe following dialog is used to build the kill file entry:
X.TP
X\fIAUTO (k)ill or (s)elect (CR => Kill subject 1 month)\fP
XIf you simply want \fInn\fP to kill all articles with the subject of
Xthe current article (in reading mode) or a specific article (which
X\fInn\fP will prompt for in selection mode), just hit \fBreturn\fP.
XThis will cause \fInn\fP to create an entry in the kill file to kill
Xthe current (or specified) subject in the current group for a period
Xof 30 days (which should be enough for the discussion to die out).
X.sp 0.5v
XIf this "default behaviour" is not what you want, just answer either
X\fIk\fP or \fIs\fP to kill or select articles, respectively, which
Xwill bring you on to the rest of the questions.
X.TP
X\fIAUTO SELECT on (s)ubject or (n)ame  (s)\fP
X(The \fISELECT\fP will be substituted with \fIKILL\fP depending on the
Xprevious answer).  Here you specify whether you want the kill or
Xselect to depend on the subject of the article (\fBs\fP or
X\fBspace\fP), or on the name of the author (\fBn\fP).
X.TP
X\fISELECT NAME:\fP
X(Again \fISELECT\fP may be substituted with \fIKILL\fP and
X\fISUBJECT\fP may replace \fINAME\fP).  You must now enter a name (or
Xsubject) to select (or kill).  In reading mode, you may just hit
X\fBreturn\fP (or \fB%\fP) to use the name (or subject) of the current
Xarticle.  In selection mode, you can use the name (or subject) from an
Xarticle on the menu by answering with \fB%\fP followed by the
Xcorresponding article identifier.
X.sp 0.5v
XWhen the name or subject is taken from an article (the current or one
Xfrom the menu), \fInn\fP will only select or kill articles where the
Xname or subject matches the original name or subject exactly including
Xcase.
X.sp 0.5v
XIf the first character typed at the prompt is a slash `/', the rest of
Xthe line is used as a \fIregular expression\fP which is used to match
Xthe name or subject (case \fIin\fPsensitive).
X.sp 0.5v
XOtherwise, \fInn\fP will select or kill articles which \fIcontain\fP
Xthe specified string anywhere in the name or subject (ignoring case).
X.TP
X\fISELECT in (g)roup `dk.general' or in (a)ll groups  (g)\fP
XYou must now specify whether the selection or kill should apply to the
Xcurrent group only (\fBg\fP or \fBspace\fP) or to all groups (\fBa\fP).
X.TP
X\fILifetime of entry in days (p)ermanent  (30)\fP
XYou can now specify the lifetime of the entry, either by entering a
Xnumber specifying the number of days the entry should be active, or
X\fBp\fP to specify the entry as a permanent entry.  An empty reply is
Xequivalent to 30 days.
X.TP
X\fICONFIRM SELECT ....\fP
XFinally, you will be asked to confirm the entry, and you should
Xespecially note the presence or absence of the word \fBexact\fP which
Xspecify whether an exact match applies for the entry.
X.LP
X\fBRelated variables\fP:
Xkill.
X.SH THE FORMAT OF THE KILL FILE
XThe kill file consists of one line for each entry.  Empty lines and
Xlines starting with a # character are ignored.  \fInn\fP automatically
Xplaces a # character in the first position of expired entries when it
Xcompiles the kill file.  You can then edit the kill file manually from
Xtime to time to clean out these entries.
X.LP
XEach line has the following format
X.br
X  [\fIexpire time\fP :] [\fIgroup name\fP] : \fIflags\fP : \fIstring\fP [: \fIstring\fP]...
X.br
X.LP
XPermanent entries have no \fIexpire time\fP (in which case the colon
Xis omitted as well!).  Otherwise, the \fIexpire time\fP defines the
Xtime (as a time_t value) when the entry should be expired.
X.LP
XThe \fIgroup name\fP field can have three forms:
X.TP
X\fInews.group.name\fP
XIf it is the name of a single news group (e.g. comp.unix), 
Xthe entry applies to that group only.
X.TP
X\fB/\fP\fIregular expression\fP
XIf it starts with a slash `/' followed by a \fIregular expression\fP
X(e.g. /^news\e..*), the entry applies to all groups whose name are
Xmatched by the regular expression.
X.TP
X\fIempty\fP
XAn empty group field will apply the entry to \fIall\fP groups.
X.LP
XThe \fIflags\fP field consists of a list of characters which
Xidentifies the type of entry, and the interpretation of each
X\fIstring\fP field.  When used, the flag characters must be used in
Xthe order in which they are desctibed below:
X.TP
X\fB~\fP	(optional)
X.br
XWhen this flag is present on any of the entries for a specific group,
Xit causes all entires which \fIare not auto-selected\fP to be killed.
XThis is a simple way to say: I'm interested in this and that, but
Xnothing else.
X.TP
X\fB+\fP	or \fB!\fP (optional)
X.br
XSpecify an auto-select \fB+\fP or an auto-kill \fB!\fP entry,
Xrespectively.  If neither are used, the article is neither selected
Xnor killed which is useful in combination with the `\fB~\fP' flag.
X.LP
XFor each \fIstring\fP, the \fIflags\fP field must contain the following
Xcharacters defining the interpretation of the corresponding
X\fIstring\fP:
X.TP
X\fBn\fP or \fBs\fP (mandatory)
X.br
XSpecify whether the corresponding string applies to the name \fBn\fP
Xor to the subject \fBs\fP of an article.
X.TP
X\fB/\fP (optional)
X.br
XSpecifies that the corresponding \fIstring\fP is a \fBregular expression\fP
Xwhich the sender or subject is matched against.  If not specified, a simple
Xstring match is performed using the given \fIstring\fP.
X.TP
X\fB=\fP (optional)
X.br
XSpecifies that the match against the name or subject is \fIcase
Xsensitive\fP.  Furthermore, when regular expression matching
Xis \fInot\fP used, the name or subject must be of the same length
Xof the \fIstring\fP to match.
XOtherwise, the match will be case insensitive, and a \fIstring\fP may
Xoccur anywhere in the name or subject to match.
X.TP
X\fB|\fP or \fB&\fP (mandatory if multiple strings)
X.br
XIf more than one string is specified, the set of \fIflags\fP
Xcorresponding to each \fIstring\fP must be separated by either an
X\fIor operator\fP `\fB|\fP' or an \fIand operator\fP `\fB&\fP'.  The
Xand operator has a higher precedence than the or operator, e.g.  a
Xcomplex match expression \fIa|b&c|d\fP will succeed if either of
X\fIa\fP, \fIb&c\fP, or \fId\fP matches.
X.LP
XThe \fIstring\fP field in the entry is the name, subject or regular
Xexpression that will be matched against the name or subject of each
Xarticle in the group (or all groups).  Colons and backslashes must be
Xescaped with a backslash in the string.
X.LP
XExample 1:  Auto-select articles from `Tom Collins' (exact) on subject
X`News' in all groups:
X.sp 0.5v
X    :+n=&s:Tom Collins:News
X.sp
XExample 2:  Kill all articles which are neither from `Tom' or `Eve' in
Xsome.group.  Select only articles from Eve:
X.sp 0.5v
X    some.group:~n:Tom
X.br
X    some.group:+n:Eve
X.sp
XThe second example can also be written as a single entry with an or
Xoperator (in this case, the select/kill attribute only applies to the succeeding strings):
X.br
X    some.group:~n|+n:Tom:Eve
X.LP
XTo remove expired entries, to "undo" a \fBK\fP command, and to make
Xthe more advanced entries with more than one string, you will have to
Xedit the kill file manually.  To recompile the file, you can use the
X\fB:compile\fP command.  When you invoke \fInn\fP, it will also
Xrecompile the kill file if the compiled version is out of dat.
X.SH SHELL ESCAPES
XThe
X.B !
Xcommands available in selection and reading mode are identical in
Xoperation (with one exception).  When you enter the shell escape
Xcommand, you will be prompted for a shell command.  This command will
Xbe fed to the shell specified in the \fBshell\fP variable (default
Xloaded from the SHELL environment variable or /bin/sh) after the
Xfollowing substitutions have been performed on the command:
X.TP
X\fIFile name expansion\fP
XThe ealier described file name expansions will be performed on all
Xarguments.
X.TP
X.B $G
Xwill be substituted with the name of the current news group.
X.TP
X.B $L
Xwill be substituted with the \fIlast component\fP of the name of the
Xcurrent news group.
X.TP
X.B $F
Xwill be substituted with the name of the current news group with the
Xperiods replaced by slashes.
X.TP
X.B $N
Xwill be substituted with the (local) article number (only defined in
Xreading mode).
X.TP
X.B $A
Xis replaced by the full path name of the file containing the current article
X(only defined in reading mode).
X.TP
X.B %
XSame as $A.
X.TP
X.B $(VAR)
Xis replaced by the string value of the environment variable \fIVAR\fP.
X.LP
XWhen the shell command is completed, you will be asked to hit any key
Xto continue.  If you hit the
X.B !
Xkey again, you will be prompted for a new shell command.  Any other
Xkey will redraw the screen and return you to the mode you came from.
X.LP
X\fBRelated variables\fP:
Xshell, shell-restrictions.
X.SH MISCELLANEOUS COMMANDS
XBelow are more useful commands which are available in both
Xselection and reading modes.
X.TP
X\&\fBU\fP	{\fBunsub\fP}
XUnsubscribe to the current group.  You will not see this group
Xanymore unless you explicitly request it.  If the variable
X\fBunsubscribe-mark-read\fP is set, all articles in the group will be
Xmarked read when you unsubscribe.
X  If the variable \fBkeep-unsubscribed\fP is not set, the group will
Xbe removed from .newsrc.  If you are not subscribing to the group, you
Xwill be given the possibility to \fIresubscribe\fP to the group!  This
Xmay be used in connection with the \fBG\fP command to resubscribe a
Xgroup.
X.TP
X\&\fBC\fP	{\fBcancel\fP}
XCancel (delete) an article in the current group or folder.  Cancelling
Xarticles in a folder will cause the folder to be rewritten when it is
Xclosed.  In selection mode, you will be prompted for the identifier of
Xthe article to cancel.  Normal users can only cancel their own
Xarticles.
X.TP
X\&\fBY\fP	{\fBoverview\fP}
XProvide an overview of the groups with unread articles.
X.TP
X\&\fB"\fP	{\fBlayout\fP}
XChange menu layout in selection mode.  The menu will be redrawn using
Xthe next layout (cycling through ..., 2, 3, 4, 0, 1, ...)
X.LP
XMost of the commands in \fInn\fP are bound to a key and can be activated
Xby a single keystroke.  However, there are a few commands that
Xcannot be bound to a key directly.
X.LP
XAs shown in the keystroke command descriptions, all commands have a
Xname, and it is possible to activate a command by name with the
X\fIextended command\fP key (\fB:\fP).  Hitting this key will prompt
Xyou for the name of a command (and parameters).  For example, an
Xalternative to hitting the \fBR\fP key to reply to an article is to
Xenter the extended command \fB:reply\fP followed by \fBreturn\fP.  The
X\fB:post\fP and \fB:unshar\fP commands described earlier can also be
Xbound to a key.  The complete list of commands which can be bound to
Xkeys is provided in the section on Key Mappings below.
X.LP
XThe following extended commands \fIcannot\fP be bound to a key, mainly
Xbecause they require additional parameters on the prompt line, or
Xbecause it should not be possible to activate them too easily.
X.TP
X\fB:admin\fP
XEnter administrative mode.  This is identical in operation to the
X.IR nnadmin (1M)
Xprogram.
X.TP
X\fB:bug\fP
XPrepare and send a bug report to the nn-bugs mailing address.
X.TP
X\fB:cd\fP [ \fIdirectory\fP ]
XChange current working directory.  If the directory argument is not provided,
X\fInn\fP will prompt for it.
X.TP
X\fB:compile\fP
XRecompile the \fIkill\fP file.  This is not necessary under normal
Xoperation since \fInn\fP automatically compiles the file on start-up
Xif it has changed, but it can be used if you modify the kill file
Xwhile \fInn\fP is suspended.
X.TP
X\fB:coredump\fP
XAbort with a core dump.  For debugging purposes only.
X.TP
X\fB:define\fP \fImacro\fP
XDefine macro number \fImacro\fP as described in the Macro Definition
Xsection below.  If \fImacro\fP is omitted, the next free macro number
Xwill be chosen.
X.TP
X\fB:dump\fP \fItable\fP
XSame as the \fB:show\fP command described below.
X.TP
X\fB:help\fP [ \fIsubject\fP ]
XProvide online help on the specified subject.  If you omit the
Xsubject, a list of the available topics will be given.
X.TP
X\fB:local\fP \fIvariable\fP [ \fIvalue\fP ]
XMake the variable local to the current group.  Subsequent changes to
Xthe variable will only be effective until the current group is left.
XIf a value is specified, it will be assigned to the local variable.
XTo assign a new value to a boolean variable, the values \fBon\fP and
X\fBoff\fP must be used.
X.TP
X\fB:man\fP
XCall up the online manual.  The manual is presented as a normal folder
Xwith the program name in the `From' field and the section title in the
X\&`subject' field.  All the normal commands related to a folder works
Xfor the online manual as well, e.g. you can save and print sections of
Xthe manual.
X.TP
X\fB:map\fP \fIarguments\fP
XThis is the command used for binding commands to the keys.  It is
Xfully described in the Key Mapping section below.
X.TP
X\fB:mkdir\fP [ \fIdirectory\fP ]
XCreate the directory (and the directories in its path).  It will
Xprompt for at directory name if the argument is omitted.
X.TP
X\fB:pwd\fP
XPrint path name of current working directory on message line.
X.TP
X\fB:q\fP
XHas no effect besides redrawing the screen if necessary.  If an
Xextended command (one which is prefixed by a :) produces any output
Xrequirering the screen to be redrawn, the screen will not be redrawn
Ximmediately if the variable \fBdelayed-redraw\fP is set (useful on
Xslow terminals).  Instead another \fB:\fP prompt is shown to allow you
Xto enter a new extended command immediately.  It is sufficient to hit
X.B return
Xto redraw the screen, but it has been my experience that entering
X.B q return
Xin this situation happens quite often, so it was made a no-op.
X.TP
X\fB:q!\fP
XQuit \fInn\fP without updating the \fB.newsrc\fP file.
X.TP
X\fB:Q\fP
XQuit \fInn\fP.  This is equivalent to the normal
X.B Q
Xcommand.
X.TP
X\fB:rmail\fP
XOpen your mailbox (see the \fBmail\fP variable) as a folder to
Xread the incoming messages.  This is \fInot\fP a full mail interface
X(you cannot delete messages, no cc: on replies, etc), but it can give
Xyou a quick glance at new mail without leaving \fInn\fP.
X.TP
X\fB:set\fP \fIvariable\fP [ \fIvalue\fP ]
XSet a boolean variable to true or assign the value to a string or
Xinteger variable.  The
X.B :set
Xcommand is described in details in the section on VARIABLES.
X.TP
X\fB:sh\fP
XSuspend \fInn\fP, or if that is not possible, spawn an interactive shell.
X.TP
X\fB:show groups\fP \fImode\fP
XShow the total number or the number of unread articles in the current
Xgroup, depending on \fImode\fP: \fBall\fP (list the number of unread
Xarticles in all groups including groups which you have unsubscribed
Xto), \fBtotal\fP (list the total number of articles in all existing
Xgroups), \fBunsub\fP (list unsubscribed groups only).  Any other
X\fImode\fP results in a listing of the number of unread articles in
Xall subscribed groups including those you have suppressed with the `!'
Xsymbol in the group presentation sequence.  To get just the currently
Xunread groups in the presentation sequence, use the `Y'
X{\fBoverview\fP} command.
X.TP
X\fB:show kill\fP
XShow the kill entries that applies to the current group and to all groups.
X.TP
X\fB:show rc\fP [ \fIgroup\fP ]
XShow the .newsrc and select file entries for the current or the
Xspecified group.
X.TP
X\fB:show map\fP [ \fImode\fP ]
XShow the key bindings in the current or specified mode.
X.TP
X\fB:sort\fP [ \fImode\fP ]
XReorder the articles on the menu according to \fImode\fP or if omitted
Xto the default \fBsort-mode\fP.  The following sorting modes are
Xavailable: \fBarrival\fP (list articles in the order in which they
Xarrived on the system), \fBsubject\fP (articles with identical
Xsubjects are grouped and ordered after age of the oldest article in
Xthe group), lexical (subjects in lexicographical order), \fBage\fP
X(articles ordered after posting date only), and \fBsender\fP (articles
Xordered after sender's name).
X.TP
X\fB:unset\fP \fIvariable\fP
XToggle a boolean variable.
X.TP
X\fB:unread\fP [ \fIgroup\fP ] [ \fIarticles\fP ]
XMark the current (or specified) group as unread.  If the
X\fIarticles\fP argument is omitted, the number of unread articles in
Xthe group will be set to the number of unread articles when \fInn\fP
Xwas invoked.  Otherwise, the argument specifies the number of unread
Xarticles.
X.TP
X\fB:unset\fP \fIvariable\fP
XSet a boolean variable to false or clear an integer variable.
X.TP
X\fB:x\fP
XQuit \fInn\fP and \fBmark\fP all articles in the current group as
X\fIread\fP!
X.LP
X\fBRelated variables\fP:
Xbackup, bug-report-address, delayed-redraw, keep-unsubscribed,
Xunsubscribe-mark-read, mail, pager, sort-mode.
X.\" ENDPART B
END_OF_FILE
  if test 32572 -ne `wc -c <'man/nn.1.B'`; then
    echo shar: \"'man/nn.1.B'\" unpacked with wrong size!
  fi
  # end of 'man/nn.1.B'
fi
if test -f 'master.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'master.c'\"
else
  echo shar: Extracting \"'master.c'\" \(19338 characters\)
  sed "s/^X//" >'master.c' <<'END_OF_FILE'
X/*
X *	(c) Copyright 1990, Kim Fabricius Storm.  All rights reserved.
X *
X *	nn database daemon (nnmaster)
X *
X *	maintains the article header database.
X */
X
X#include <signal.h>
X#include <errno.h>
X#include "config.h"
X#include "db.h"
X#include "proto.h"
X
Ximport char *bin_directory;
X
X/*
X * nnmaster options:
X *
X *	-e [N]	expire a group if more than N articles are gone
X *	-r N	repeat every N minutes
X *
X *	-f	foreground execution (use with -r)
X *	-y N	retry N times on error
X *
X *	-E [N]	expire mode (see expire.c) [N==1 if omitted]
X *	-F	Run ONLY expire ONCE and exit.
X *	-R N	auto-recollect mode (see expire.c)
X *
X *	-C	check consistency of database on start-up
X *	-b	include 'bad' articles (disables -B)
X *	-B	remove 'bad' articles (just unlink the files)
X *	-O N	Consider articles older than N days as bad articles.
X *
X *	-I [N]	initialize [ limit to N articles in each group ]
X *	-G	reread groups file.
X *	-X	clean ignored groups.
X *
X *	-l"MSG"	lock database with message MSG
X *	-l	unlock database
X *	-i	ignore lock (run collection on locked database)
X *	-k	kill the running master and take over.
X *
X *	-Q	quiet: don't write fatal errors to /dev/console (if no syslog).
X *	-t	trace collection of each group
X *	-v	print version and exit
X *	-u	update even if active is not modified
X *	-w	send wakeup to real master
X *	-Ltypes	exclude 'types' entries from the log
X *	-D [N]	debug, N = +(1 => verbose, 2 => nntp trace)
X *
X *	[master group]...	Collect these groups only.
X */
X
X#include "options.h"
X
X
Ximport int
X    dont_write_console,
X    expire_method,
X    expire_level,
X    recollect_method,
X    reread_groups_file,
X    ignore_bad_articles,
X#ifdef NNTP
X    nntp_local_server,
X    nntp_debug,
X#endif
X    remove_bad_articles,
X    retry_on_error;
X
Ximport time_t
X    max_article_age;
X
Ximport char
X    *log_entry_filter;
X
Xexport int
X    trace = 0,
X    debug_mode = 0,
X#ifdef NNTP
X    silent = 1,
X    no_update = 0,
X#endif
X    Debug = 0;
X
Xstatic int
X    check_on_startup = 0,
X    clean_ignored = 0,
X    expire_once = 0,
X    foreground = 0,
X    ignore_lock = 0,
X    initialize = -1,
X    kill_running = 0,
X    max_age_days = 0,
X    prt_vers = 0,
X    unconditional = 0,
X    wakeup_master = 0;
X
Xstatic unsigned
X    repeat_delay = 0;
X
X
Xstatic char
X    *lock_message = NULL;
X
X
XOption_Description(master_options) {
X
X    'b', Bool_Option( ignore_bad_articles ),
X    'B', Bool_Option( remove_bad_articles ),
X    'C', Bool_Option( check_on_startup ),
X    'D', Int_Option_Optional( debug_mode, 1 ),
X    'e', Int_Option_Optional( expire_level, 1 ),
X    'E', Int_Option_Optional( expire_method, 1 ),
X    'f', Bool_Option( foreground ),
X    'F', Bool_Option( expire_once ),
X    'G', Bool_Option( reread_groups_file ),
X#ifdef NNTP
X    'H', Bool_Option( nntp_local_server ),
X#endif
X    'i', Bool_Option( ignore_lock ),
X    'I', Int_Option_Optional( initialize, 0 ),
X    'k', Bool_Option( kill_running ),
X    'l', String_Option_Optional( lock_message, "" ),
X    'L', String_Option( log_entry_filter ),
X    'O', Int_Option( max_age_days ),
X    'Q', Bool_Option( dont_write_console ),
X    'r', Int_Option_Optional( repeat_delay, 10 ),
X    'R', Int_Option( recollect_method ),
X    't', Bool_Option( trace ),
X    'u', Bool_Option( unconditional ),
X    'v', Bool_Option( prt_vers ),
X    'w', Bool_Option( wakeup_master ),
X    'X', Bool_Option( clean_ignored ),
X    'y', Int_Option( retry_on_error ),
X    '\0',
X};
X
Ximport char *master_directory, *db_directory, *news_active;
X
Xstatic int unlock_on_exit = 0;
X
X/*
X * nn_exit() --- called whenever a program exits.
X */
X
Xnn_exit(n)
X{
X#ifdef NNTP
X    if (use_nntp) nntp_cleanup();
X#endif /* NNTP */
X    close_master();
X
X    if (unlock_on_exit)
X	proto_lock(I_AM_MASTER, PL_CLEAR);
X
X    if (n)
X	log_entry('E', "Abnormal termination, exit=%d", n);
X    else
X    if (unlock_on_exit)
X	log_entry('M', "Master terminated%s", s_hangup ? " (hangup)" : "");
X
X    exit(n);
X}
X
X
Xstatic clean_group_internal(gh)	/* no write */
Xregister group_header *gh;
X{
X    gh->first_db_article = 0;
X    gh->last_db_article = 0;
X
X    if (gh->data_write_offset > (off_t)0) {
X	gh->data_write_offset = (off_t)0;
X	(void)open_data_file(gh, 'd', -1);
X    }
X    
X    if (gh->index_write_offset) {
X	gh->index_write_offset = (off_t)0;
X	(void)open_data_file(gh, 'x', -1);
X    }
X    
X    gh->master_flag &= ~(M_EXPIRE | M_BLOCKED);
X    if ((gh->master_flag & M_IGNORE_GROUP) == 0)
X	gh->master_flag |= M_BLOCKED;
X
X}
X
Xclean_group(gh)	/* does write */
Xgroup_header *gh;
X{
X    if (trace)
X	log_entry('T', "CLEAN %s", gh->group_name);
X    if (debug_mode)
X	printf("CLEAN %s\n", gh->group_name);
X
X    clean_group_internal(gh);
X
X    db_write_group(gh);
X}
X
Xextern long collect_group();
Xextern long expire_group();
X
Xstatic char **restrictions = NULL;
Xstatic int *restr_len, *restr_excl;
X
Xstatic group_restriction(gh)
Xregister group_header *gh;
X{
X    register char **rp;
X    register int *lp, *xp;
X
X    if (restrictions == NULL) return;
X
X    for (rp = restrictions, lp = restr_len, xp = restr_excl; *lp > 0; rp++, lp++, xp++)
X	if (strncmp(gh->group_name, *rp, *lp) == 0) {
X	    if (*xp) break;
X	    return;
X	}
X
X    if (*lp == 0) return;
X
X    gh->master_flag |= M_IGNORE_G;
X}
X
Xstatic set_group_restrictions(restr, n)
Xchar **restr;
Xint n;
X{
X    register group_header *gh;
X    register int i;
X
X    restrictions = restr;
X    restr_len = newobj(int, n + 1);
X    restr_excl = newobj(int, n);
X
X    for (i = 0; i < n; i++) {
X	if (restrictions[i][0] == '!') {
X	    restr_excl[i] = 1;
X	    restrictions[i]++;
X	} else
X	    restr_excl[i] = 0;
X	restr_len[i] = strlen(restrictions[i]);
X    }
X
X    restr_len[n] = -1;
X
X    Loop_Groups_Header(gh) {
X	if (gh->master_flag & M_IGNORE_GROUP) continue;
X	group_restriction(gh);
X	if (clean_ignored && (gh->master_flag & M_IGNORE_G)) {
X	    log_entry('X', "Group %s ignored", gh->group_name);
X	    clean_group(gh);
X	}
X    }
X}
X
X/*
X * add new group to master file
X */
X
Xgroup_header *add_new_group(name)
Xchar *name;
X{
X    register group_header *gh;
X
X    if (master.free_groups <= 0)
X	db_expand_master();
X
X    master.free_groups--;
X
X    gh = &active_groups[master.number_of_groups];
X
X    gh->group_name_length = strlen(name);
X    gh->group_name = copy_str(name);
X
X    gh->group_num = master.number_of_groups++;
X    gh->creation_time = cur_time();
X
X    db_append_group(gh);
X
X    group_restriction(gh);	/* done after append to avoid setting ! */
X    clean_group(gh);
X
X    db_write_master();
X
X    sort_groups();
X
X    log_entry('C', "new group: %s (%d)", gh->group_name, gh->group_num);
X
X    return gh;
X}
X
X
Xstatic visit_active_file()
X{
X    FILE *act;
X    FILE *nntp_act = NULL;
X
X#ifdef NNTP
X    if (!use_nntp)		/* copy 'active' to DB/ACTIVE */
X	nntp_act = open_file(relative(db_directory, "ACTIVE"), OPEN_CREATE | MUST_EXIST);
X#endif
X
X    act = open_file(news_active, OPEN_READ|MUST_EXIST);
X
X    read_active_file(act, nntp_act);
X
X    master.last_size = ftell(act);
X
X    fclose(act);
X
X#ifdef NNTP
X    if (nntp_act != NULL) fclose(nntp_act);
X#endif
X}
X
X
X/*
X *	Build initial master file.
X */
X
Xstatic build_master()
X{
X    char command[512];
X    char groupname[512];
X    group_header *groups, *next_g, *gh;
X    FILE *src;
X    int lcount, use_group_file, found_nn_group = 0;
X
X    printf("Confirm initialization by typing 'OK': ");
X    fl;
X    gets(command);
X    if (strcmp(command, "OK")) {
X	printf("No initialization\n");
X	nn_exit(0);
X    }
X
X    if (chdir(master_directory) < 0)	/* so we can use open_file (?) */
X	sys_error("master");
X
X#ifdef NNTP
X    if (use_nntp && nntp_get_active() < 0)
X	    sys_error("Can't get active file");
X#endif
X    /* check active file for duplicates */
X
X    sprintf(command, "awk 'NF>0{print $1}' %s | sort | uniq -d", news_active);
X
X    src = popen(command, "r");
X    if (src == NULL)
X	sys_error("popen(%s) failed", command);
X    
X    for (lcount = 0; fgets(groupname, 512, src); lcount++) {
X	if (lcount == 0)
X	    printf("\n%s contains duplicate entries for the following groups:",
X		   news_active);
X
X	fputs(groupname, stdout);
X    }
X
X    pclose(src);
X
X    if (lcount > 0) {
X	printf("Do you want to repair this file before continuing ? (y)");
X	gets(command);
X	if (s_hangup ||
X	    command[0] == NUL || command[0] == 'y' || command[0] == 'Y')
X	    nn_exit(0);
X    }
X
X    /* if a "GROUPS" file exist offer to use that, else */
X    /* read group names from active file */
X
X    use_group_file = 0;
X
X    if (open_groups(OPEN_READ)) {
X	printf("\nA GROUPS file already exist -- reuse it? (y)");
X	fl;
X	gets(command);
X	if (command[0] == NUL || command[0] == 'y' || command[0] == 'Y') {
X	    use_group_file = 1;
X	} else
X	    close_groups();
X	if (s_hangup) return;
X    }
X
X    printf("\nBuilding %s/MASTER file\n", db_directory);
X    fl;
X
X    if (!use_group_file) {
X	sprintf(command, "awk 'NF>0{print $1}' %s | sort -u", news_active);
X
X	src = popen(command, "r");
X	if (src == NULL)
X	    sys_error("popen(%s) failed", command);
X    }
X
X    open_master(OPEN_CREATE);
X
X    master.db_magic = NNDB_MAGIC;
X    master.last_scan = 0;
X    master.number_of_groups = 0;
X    strcpy(master.db_lock, "Initializing database");
X
X    db_write_master();
X
X    groups = next_g = newobj(group_header, 1);
X    next_g->next_group = NULL;
X
X    for (;;) {
X	if (s_hangup) goto intr;
X	gh = newobj(group_header, 1);
X
X	gh->master_flag = 0;
X
X	if (use_group_file) {
X	    gh->group_name_length = 0;
X	    if (db_parse_group(gh, 0) <= 0) break;
X	} else {
X	    if (fgets(groupname, 512, src) == NULL) break;
X
X	    gh->group_name_length = strlen(groupname) - 1;	/* strip NL */
X	    groupname[gh->group_name_length] = NUL;
X	    gh->creation_time = 0;
X	    gh->group_name = copy_str(groupname);
X	    gh->archive_file = NULL;
X	}
X
X	gh->group_num = master.number_of_groups++;
X
X	if (trace || debug_mode)
X	    printf("%4d '%s' (%d)\n", gh->group_num, gh->group_name,
X		   gh->group_name_length);
X
X	next_g->next_group = gh;
X	next_g = gh;
X	gh->next_group = NULL;
X
X	init_group(gh);	/* for clean_group() */
X
X	/* moderation flag will be set by first visit_active_file call */
X
X	if (strcmp(gh->group_name, "control") == 0)
X	    gh->master_flag |= M_CONTROL;
X
X	if (strcmp(gh->group_name, "news.software.nn") == 0)
X	    found_nn_group++;
X
X	gh->master_flag &= ~M_MUST_CLEAN;
X	clean_group_internal(gh);
X	gh->master_flag |= M_VALID;	/* better than the reverse */
X	db_write_group(gh);
X    }
X
X    if (use_group_file)
X	close_groups();
X    else
X	pclose(src);
X
X    printf("%s %s/GROUPS file\n",
X	   use_group_file ? "Updating" : "Building", db_directory);
X    sprintf(command, "cd %s ; [ -f GROUPS ] && (rm -f GROUPS~ ; mv GROUPS GROUPS~)",
X	    db_directory);
X    system(command);
X
X    open_groups(OPEN_CREATE|MUST_EXIST);
X
X    for (gh = groups->next_group; gh != NULL; gh = gh->next_group)
X	db_append_group(gh);
X
X    close_groups();
X
X    if (initialize > 0) {
X	printf("Setting articles per group limit to %d...\n", initialize);
X	db_write_master();
X	open_master(OPEN_READ);
X	open_master(OPEN_UPDATE);
X	visit_active_file();
X	Loop_Groups_Header(gh) {
X	    gh->first_db_article = gh->last_a_article - initialize + 1;
X	    if (gh->first_db_article <= gh->first_a_article) continue;
X	    gh->last_db_article = gh->first_db_article - 1;
X	    if (gh->last_db_article < 0) gh->last_db_article = 0;
X	    db_write_group(gh);
X	}
X    }
X    
X    master.db_lock[0] = NUL;
X    master.db_created = cur_time();
X
X    db_write_master();
X
X    close_master();
X
X    printf("Done\n");
X    fl;
X
X    log_entry('M', "Master data base initialized");
X
X    if (!found_nn_group)
X	printf("\nNotice: nn's own news group `news.software.nn' was not found\n");
X
X    return;
X
X intr:
X    printf("\nINTERRUPT\n\nDatabase NOT completed\n");
X    log_entry('M', "Master data base initialization not completed (INTERRUPTED)");
X}
X
Xstatic set_lock_message()
X{
X    open_master(OPEN_UPDATE);
X    db_read_master();
X    if (lock_message[0] || master.db_lock[0]) {
X	strncpy(master.db_lock, lock_message, DB_LOCK_MESSAGE);
X	master.db_lock[DB_LOCK_MESSAGE-1] = NUL;
X	db_write_master();
X	printf("DATABASE %sLOCKED\n", lock_message[0] ? "" : "UN");
X    }
X}
X
Xstatic do_reread_groups()
X{
X    register group_header *gh;
X
X    open_master(OPEN_UPDATE);
X    Loop_Groups_Header(gh)
X	if (gh->master_flag & M_MUST_CLEAN) {
X	    gh->master_flag &= ~M_MUST_CLEAN;
X	    clean_group(gh);
X	} else
X	    db_write_group(gh);
X    master.last_scan = 0;
X    db_write_master();
X    close_master();
X    log_entry('M', "Reread GROUPS file");
X}
X
X
Xmain(argc, argv)
Xint argc;
Xchar **argv;
X{
X    register group_header *gh;
X    time_t age_active;
X    int group_selection;
X    int temp;
X
X    umask(002);			/* avoid paranoia */
X
X    who_am_i = I_AM_MASTER;
X
X    init_global();
X
X    group_selection =
X	parse_options(argc, argv, (char *)NULL, master_options, (char *)NULL);
X
X    if (debug_mode) {
X#ifdef NNTP
X	nntp_debug = debug_mode & 2;
X#endif
X	debug_mode = debug_mode & 1;
X    }
X
X    if (debug_mode) {
X	extern sig_type catch_hangup();
X	signal(SIGINT, catch_hangup);
X    }
X
X    if (wakeup_master) {
X	if (proto_lock(I_AM_MASTER, PL_WAKEUP) < 0)
X	    printf("master is not running\n");
X	exit(0);
X    }
X
X    if (prt_vers) {
X	printf("nnmaster release %s\n", version_id);
X	exit(0);
X    }
X
X    if (kill_running) {
X	for (temp = 10; --temp >= 0; sleep(3))
X	    if (proto_lock(I_AM_MASTER, PL_TERMINATE) < 0) break;
X
X	if (temp < 0) {
X	    printf("The running master will not die....!\n");
X	    log_entry('E', "Could not kill running master");
X	    exit(1);
X	}
X
X	if (repeat_delay == 0 && !foreground &&
X	    !reread_groups_file && lock_message == NULL &&
X	    group_selection == 0 && initialize < 0)
X	    exit(0);
X    }
X
X    if (proto_lock(I_AM_MASTER, PL_SET) != 0) {
X	printf("The master is already running\n");
X	exit(0);
X    }
X    unlock_on_exit = 1;
X
X#ifdef NNTP
X    nntp_check();
X#endif
X
X    if (initialize >= 0) {
X	build_master();
X	nn_exit(0);
X    }
X
X    if (lock_message != NULL) {
X	set_lock_message();
X	if (repeat_delay == 0 && !foreground &&
X	    !reread_groups_file && group_selection == 0)
X	    nn_exit(0);
X    }
X
X    open_master(OPEN_READ);
X
X    if (!ignore_lock && master.db_lock[0]) {
X	printf("Database locked (unlock with -l or ignore with -i)\n");
X	nn_exit(88);
X    }
X
X    if (reread_groups_file) {
X	do_reread_groups();
X	nn_exit(0);
X    }
X
X    if (!debug_mode) {
X	close(0);
X	close(1);
X	close(2);
X	if (open("/dev/null", 2) == 0) dup(0), dup(0);
X    }
X
X    if (repeat_delay && !debug_mode && !foreground) {
X	while ((temp = fork()) < 0) sleep(1);
X	if (temp) exit(0);	/* not nn_exit() !!! */
X
X	process_id = getpid();	/* init_global saved parent's pid */
X
X	proto_lock(I_AM_MASTER, PL_TRANSFER);
X
X#ifdef DETATCH_TERMINAL
X	DETATCH_TERMINAL
X#endif
X    }
X
X    log_entry('M', "Master started -r%d -e%d %s-E%d",
X	      repeat_delay, expire_level,
X	      expire_once ? "-F " : "", expire_method);
X
X    if (check_on_startup) {
X	char cmd[FILENAME];
X	sprintf(cmd, "%s/nnadmin Z", bin_directory);
X	system(cmd);
X	log_entry('M', "Database validation completed");
X    }
X
X    repeat_delay *= 60;
X
X    init_digest_parsing();
X
X    open_master(OPEN_UPDATE);
X
X    if (group_selection)
X	set_group_restrictions(argv + 1, group_selection);
X
X    if (max_age_days && !use_nntp) /* we have to stat spool files */
X	max_article_age = cur_time() - (time_t)max_age_days * 24 * 60 * 60;
X    else
X	max_article_age = 0;
X
X    if (expire_once) {
X	if (group_selection)	/* mark selected groups for expire */
X	    Loop_Groups_Header(gh) {
X		if (gh->master_flag & M_IGNORE_GROUP) continue;
X		gh->master_flag |= M_EXPIRE;
X	    }
X	unconditional = 1;
X    }
X
X    for (;;) {
X#ifdef NNTP
X	if (use_nntp && nntp_get_active() < 0) {
X	    nntp_close_server();
X	    current_group = NULL; /* for init_group */
X	    log_entry('N', "Can't access active file --- %s",
X		      repeat_delay ? "sleeping" : "terminating");
X	    if (repeat_delay == 0)
X		nn_exit(1);
X	    sleep(repeat_delay);
X	    continue;
X	}
X#endif
X
X	age_active = file_exist(news_active, "fr");
X	if (!use_nntp && age_active == (time_t)0)
X	    sys_error("Cannot access active file");
X
X	if (unconditional) {
X	    master.last_scan = age_active - 60;
X	    unconditional = 0;
X	}
X
X	if (!receive_admin() && age_active <= master.last_scan) {
X	    if (repeat_delay == 0) break;
X	    if (s_hangup) break;
X#ifdef NNTP
X	    if (use_nntp) nntp_cleanup();
X#endif
X	    if (debug_mode) {
X		printf("NONE (*** SLEEP ***)\n");
X		continue;
X	    }
X
X	    if (trace) log_entry('T', "none");
X	    sleep(repeat_delay);
X	    if (s_hangup) break;
X	    continue;
X	}
X
X	visit_active_file();
X
X	if (do_expire())
X	    if (!expire_once && do_collect())
X		master.last_scan = age_active;
X
X	db_write_master();
X
X	if (expire_once || s_hangup) break;
X	if (repeat_delay == 0) break;
X
X#ifdef NNTP
X	if (use_nntp) nntp_cleanup();
X#endif
X	if (!debug_mode) sleep(repeat_delay);
X	if (s_hangup) break;
X    }
X
X    nn_exit(0);
X    /*NOTREACHED*/
X}
X
X
X
X/*
X * receive commands from administrator
X */
X
Xreceive_admin()
X{
X    FILE *gate;
X    char buffer[128], *bp;
X    char command, opt, *user_date;
X    int32 arg;
X    int must_collect;
X    register group_header *gh;
X
X    gate = open_file(relative(master_directory, "GATE"), OPEN_READ | OPEN_UNLINK);
X    if (gate == NULL) return 0;
X
X    sleep(2);	/* give administrator time to flush buffers */
X
X    must_collect = 0;
X
X    while (fgets(buffer, 128, gate)) {
X	bp = buffer;
X
X	command = *bp++;
X	if (*bp++ != ';') continue;
X
X	arg = atol(bp);
X	if (arg >= master.number_of_groups) continue;
X	gh = (arg >= 0) ? &active_groups[arg] : NULL;
X	if ((bp = strchr(bp, ';')) == NULL) continue;
X	bp++;
X
X	opt = *bp++;
X	if (*bp++ != ';') continue;
X
X	arg = atol(bp);
X	if ((bp = strchr(bp, ';')) == NULL) continue;
X
X	user_date = ++bp;
X	if ((bp = strchr(bp, ';')) == NULL) continue;
X	*bp++ = NUL;
X	if (*bp != NL) continue;
X
X	log_entry('A', "RECV %c %s %c %ld (%s)",
X		  command, gh == NULL ? "(all)" : gh->group_name, opt, arg, user_date);
X
X	switch (command) {
X
X	 case SM_SET_OPTION:
X	    switch (opt){
X	     case 'r':
X		repeat_delay = arg;
X		continue;
X	     case 'e':
X		expire_level = arg;
X		continue;
X	     case 't':
X		trace = (arg < 0) ? !trace : arg;
X		continue;
X	    }
X
X	 case SM_EXPIRE:
X	    if (gh) {
X		gh->master_flag |= M_EXPIRE | M_BLOCKED;
X		db_write_group(gh);
X		break;
X	    }
X	    Loop_Groups_Header(gh) {
X		if (gh->master_flag & M_IGNORE_GROUP) continue;
X		if (gh->index_write_offset == 0) continue;
X		if (gh->master_flag & M_EXPIRE) continue;
X		gh->master_flag |= M_EXPIRE;
X		db_write_group(gh);
X	    }
X	    break;
X
X	 case SM_SET_FLAG:
X	    if (opt == 's')
X		gh->master_flag |= (flag_type)arg;
X	    else
X		gh->master_flag &= ~(flag_type)arg;
X	    db_write_group(gh);
X	    continue;
X
X	 case SM_RECOLLECT:	/* recollect */
X	    if (gh) {
X		if ((gh->master_flag & M_IGNORE_GROUP) == 0)
X		    clean_group(gh);
X	    } else
X		Loop_Groups_Header(gh)
X		    if ((gh->master_flag & M_IGNORE_GROUP) == 0)
X			clean_group(gh);
X	    break;
X
X	 case SM_SCAN_ONCE:	/* unconditional pass */
X	    unconditional++;
X	    break;
X
X	 default:
X	    continue;
X	}
X	must_collect = 1;
X    }
X
X    fclose(gate);
X
X    return must_collect;
X}
X
X
Xwrite_error()
X{
X    /*
X     * should wait for problems to clear out rather than die...
X     */
X    sys_error("DISK WRITE ERROR");
X}
X
X/*
X * dummy routines - should never be called by master
X */
X
X/*VARARGS*/
Xuser_error()
X{
X    dummy_error("user_error");
X}
X
Xdummy_error(name)
Xchar *name;
X{
X    sys_error("Dummy routine called by master: %s", name);
X}
X
X#ifdef HAVE_JOBCONTROL
Xsuspend_nn()
X{}
X#endif
X
X#ifdef NNTP /* XXX */
X/*VARARGS*/
Xmsg() {}
Xuser_delay(n) {}
X#endif /* NNTP Bogus */
END_OF_FILE
  if test 19338 -ne `wc -c <'master.c'`; then
    echo shar: \"'master.c'\" unpacked with wrong size!
  fi
  # end of 'master.c'
fi
echo shar: End of archive 7 \(of 22\).
cp /dev/null ark7isdone
MISSING=""
for I in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked all 22 archives.
    rm -f ark[1-9]isdone ark[1-9][0-9]isdone
else
    echo You still must unpack the following archives:
    echo "        " ${MISSING}
fi
exit 0

exit 0 # Just in case...
