home *** CD-ROM | disk | FTP | other *** search
/ Source Code 1992 March / Source_Code_CD-ROM_Walnut_Creek_March_1992.iso / usenet / altsrcs / 2 / 2372 < prev    next >
Encoding:
Internet Message Format  |  1990-12-28  |  48.0 KB

  1. From: ggw%wolves@cs.duke.edu (Gregory G. Woodbury)
  2. Newsgroups: alt.sources
  3. Subject: TRN Fixup package
  4. Message-ID: <1990Dec20.045313.13761@wolves.uucp>
  5. Date: 20 Dec 90 04:53:13 GMT
  6. X-Checksum-Snefru: 0dd634f9 bb72ee14 592bcd82 5442e620
  7.  
  8. Submitted-by: ggw@wolves.UUCP
  9. Archive-name: trnfix/part01
  10.  
  11. This shar contains the files that are missing from the c.s.u. posting of
  12. trn.
  13.  
  14. The files here already have patch1 applied!
  15.  
  16. After unpacking all of the CSU parts and applying the patch (and letting
  17. parts of the patch process fail), unpack this archive and then run Configure
  18. and continue.
  19.  
  20. I think all the missing pieces are here, I can build trn so I generated
  21. a differences list in the directories between the tar extract and the csu
  22. extract and added the one file that was not covered.
  23.  
  24.                     Enjoy
  25.                     Greg
  26.  
  27. ---- Cut Here and feed the following to sh ----
  28. #!/bin/sh
  29. # This is trnfix, a shell archive (shar 3.47)
  30. # made 12/20/1990 04:40 UTC by ggw@wolves.UUCP
  31. # Source directory /news/Src/TRN/Src
  32. #
  33. # existing files will NOT be overwritten unless -c is specified
  34. #
  35. # This shar contains:
  36. # length  mode       name
  37. # ------ ---------- ------------------------------------------
  38. #   1276 -rwxr-x--- mt.check.SH
  39. #   8949 -rw-r----- mthreads.1
  40. #  28049 -rw-r----- mthreads.c
  41. #   1279 -rw-r----- mthreads.h
  42. #   2251 -rw-r----- ndir.c
  43. #   1491 -rw-r----- ndir.h
  44. #
  45. # ============= mt.check.SH ==============
  46. if test -f 'mt.check.SH' -a X"$1" != X"-c"; then
  47.     echo 'x - skipping mt.check.SH (File already exists)'
  48. else
  49. echo 'x - extracting mt.check.SH (Text)'
  50. sed 's/^X//' << 'SHAR_EOF' > 'mt.check.SH' &&
  51. Xcase $CONFIG in
  52. X    '') . ./config.sh ;;
  53. Xesac
  54. Xecho "Extracting mt.check (with variable substitutions)"
  55. X$spitshell >mt.check <<!GROK!THIS!
  56. X$startsh
  57. X# $Header: mt.check.SH,v 4.3.3.2 90/08/20 16:42:10 davison Trn $
  58. X#
  59. X# $Log:    mt.check.SH,v $
  60. X# Revision 4.3.3.2  90/08/20  16:42:10  davison
  61. X# Changed email address.
  62. X# 
  63. X# Revision 4.3.3.1  90/06/20  23:00:07  davison
  64. X# Initial Trn Release
  65. X# 
  66. X# mt.check - daily maintenance for mt.log
  67. X# 
  68. X# Check mt.log for earth-shattering errors, and mail them to \$gurus if found.
  69. X# Then move the mt.log file into a week-long history chain.
  70. X#
  71. X# Usage: mt.check
  72. X#
  73. X
  74. Xgurus="$newsadmin"
  75. Xtmp="/tmp/mt.c\$\$"
  76. X
  77. XPATH=/bin:/usr/bin
  78. Xexport PATH
  79. X
  80. Xumask 002
  81. X
  82. Xtrap "rm -f \$tmp ; exit 0" 0 1 2 15
  83. X
  84. Xcd $rnlib
  85. X
  86. X$egrep " \\*\\*\$" mt.log >\$tmp
  87. X
  88. Xif test -s \$tmp ; then
  89. X    (cat <<EOT
  90. XTo: \$gurus
  91. XSubject: mthreads error!
  92. X
  93. XThe following errors were reported in mt.log.  Please report this fact
  94. Xto Wayne Davison (davison@dri.com or ...!uunet!drivax!davison) for his
  95. Xattention.  The affected newsgroups can't be updated until this bug is
  96. Xfixed, or the offending articles are expired.
  97. X
  98. XEOT
  99. X    cat \$tmp) | mail \$gurus
  100. Xfi
  101. X
  102. Xmv mt.log.6 mt.log.7
  103. Xmv mt.log.5 mt.log.6
  104. Xmv mt.log.4 mt.log.5
  105. Xmv mt.log.3 mt.log.4
  106. Xmv mt.log.2 mt.log.3
  107. Xmv mt.log   mt.log.2
  108. Xtouch mt.log
  109. X
  110. Xexit 0
  111. SHAR_EOF
  112. chmod 0750 mt.check.SH ||
  113. echo 'restore of mt.check.SH failed'
  114. Wc_c="`wc -c < 'mt.check.SH'`"
  115. test 1276 -eq "$Wc_c" ||
  116.     echo 'mt.check.SH: original size 1276, current size' "$Wc_c"
  117. fi
  118. # ============= mthreads.1 ==============
  119. if test -f 'mthreads.1' -a X"$1" != X"-c"; then
  120.     echo 'x - skipping mthreads.1 (File already exists)'
  121. else
  122. echo 'x - extracting mthreads.1 (Text)'
  123. sed 's/^X//' << 'SHAR_EOF' > 'mthreads.1' &&
  124. X''' $Header: mthreads.1,v 4.3.3.2 90/08/20 16:42:32 davison Trn $
  125. X''' 
  126. X''' $Log:    mthreads.1,v $
  127. X''' Revision 4.3.3.2  90/08/20  16:42:32  davison
  128. X''' Document new command-line interface.
  129. X''' 
  130. X''' Revision 4.3.3.1  90/07/21  20:03:37  davison
  131. X''' Initial Trn Release
  132. X''' 
  133. X''' 
  134. X.de Sh
  135. X.br
  136. X.ne 5
  137. X.PP
  138. X\fB\\$1\fR
  139. X.PP
  140. X..
  141. X.de Sp
  142. X.if t .sp .5v
  143. X.if n .sp
  144. X..
  145. X.de Ip
  146. X.br
  147. X.ie \\n.$>=3 .ne \\$3
  148. X.el .ne 3
  149. X.IP "\\$1" \\$2
  150. X..
  151. X'''
  152. X'''     Set up \*(-- to give an unbreakable dash;
  153. X'''     string Tr holds user defined translation string.
  154. X'''     Bell System Logo is used as a dummy character.
  155. X'''
  156. X.tr \(bs-|\(bv\*(Tr
  157. X.ie n \{\
  158. X.ds -- \(bs-
  159. X.if (\n(.H=4u)&(1m=24u) .ds -- \(bs\h'-12u'\(bs\h'-12u'-\" diablo 10 pitch
  160. X.if (\n(.H=4u)&(1m=20u) .ds -- \(bs\h'-12u'\(bs\h'-8u'-\" diablo 12 pitch
  161. X.ds L" ""
  162. X.ds R" ""
  163. X.ds L' '
  164. X.ds R' '
  165. X'br\}
  166. X.el\{\
  167. X.ds -- \(em\|
  168. X.tr \*(Tr
  169. X.ds L" ``
  170. X.ds R" ''
  171. X.ds L' `
  172. X.ds R' '
  173. X'br\}
  174. X.TH MTHREADS 1 LOCAL
  175. X.UC 6
  176. X.SH NAME
  177. Xmthreads - threaded database manager for trn
  178. X.SH SYNOPSIS
  179. X.B mthreads [-d[MM]] [-e[HHMM]] [-aDfknv] [hierarchy_list]
  180. X.SH DESCRIPTION
  181. X.I Mthreads
  182. Xmanages the thread files that are used by the
  183. X.IR trn (1)
  184. Xnewsreader.
  185. X\*(L"Thread files\*(R" are used to store information about the news
  186. Xarticles and how they are all related to one another.
  187. X.PP
  188. X.I Mthreads
  189. Xneeds to be run
  190. Xperiodically \*(-- either in single-pass mode out of cron,
  191. Xor in daemon mode out of the boot script \*(-- to update the thread
  192. Xinformation whenever new news is received.
  193. XA site that gets its news feed during the night and doesn't need local
  194. Xpostings processed throughout the day can run
  195. X.I mthreads
  196. Xin single-pass mode once a day.
  197. XIf more processing is needed, either run
  198. X.I mthreads
  199. Xmore often or run it in daemon mode.
  200. XIn daemon mode, a background process is forked off that wakes up every 10
  201. Xminutes (by default) to check if the active file has been updated.
  202. X.SH INSTALLATION
  203. X.I Mthreads
  204. Xin installed in the RNLIB directory chosen during configuration.
  205. XWhen it is run for the first time, it will automatically create a file called
  206. X.I active2
  207. Xin the same directory.
  208. XThis file is essentially a copy of the active file that keeps the newsgroup
  209. Xtotals from the last run in one place.
  210. XIt is also used to choose which groups are to be processed into thread files.
  211. XAll groups start out as \*(L"unthreaded\*(R" unless they are turned on with
  212. Xa command like:
  213. X.IP
  214. Xmthreads all
  215. X.PP
  216. Xwhich would create thread file for all the groups.
  217. XFor testing purposes it is a good idea to start out small with a command
  218. Xlike:
  219. X.IP
  220. Xmthreads news
  221. X.PP
  222. Xwhich would thread only the news hierarchy.
  223. XThread processing can be turned on or off for individual groups or entire
  224. Xhierarchies by specifying the groups in a syntax very similar to that used
  225. Xin the sys file.
  226. XFor example, to turn on all of soc and talk except for talk.politics, and
  227. Xto turn off news.lists, use the following command once:
  228. X.IP
  229. Xmthreads soc,talk,!talk.politics,!news.lists
  230. X.PP
  231. XIf mthreads complains that another mthreads process is already running,
  232. Xit can be killed with the command:
  233. X.IP
  234. Xmthreads -k
  235. X.PP
  236. Xand then repeat the prior command after giving it a little time to die.
  237. X.PP
  238. XOnce all the desired groups are turned on, it is not necessary to
  239. Xspecify a hierarchy list for the normal functioning of mthreads.
  240. XIt can be used, however, to customize which new groups get turned on
  241. Xas they are created.
  242. X.SH LOGGING
  243. XAs mthreads executes, some status information (including error messages) 
  244. Xis placed in
  245. Xthe file mt.log in the RNLIB directory.
  246. XThis file will grow without bounds, and should be scanned periodically for
  247. Xerrors, and trimmed in size when it grows too large.
  248. XSee the shell script
  249. X.I mt.check
  250. Xfor an mt.log maintainer that will send mail if it finds database errors.
  251. X.SH OPTIONS
  252. X.TP 5
  253. X.B \-a
  254. Xis used to automatically turn on thread processing for new news groups as
  255. Xthey are created.
  256. XThe default is to leave new groups unthreaded.
  257. X.TP 5
  258. X.B \-D
  259. Xspecifies a debugging mode where only the groups mentioned on the
  260. Xcommand-line are processed \*(-- all other groups are left unchanged.
  261. X.TP 5
  262. X.B \-d
  263. Xis used to specify the daemon mode, where
  264. X.I mthreads
  265. Xforks a background task that periodically wakes up and checks for an updated
  266. Xactive file.
  267. XThe number of minutes to wait after the completion of the last pass can
  268. Xbe specified after the '-d' option (e.g. -d20), otherwise it will default to
  269. X10 minutes.
  270. X.TP 5
  271. X.B \-e
  272. Xtells
  273. X.I mthreads
  274. Xto run an enhanced expiration check on the database.
  275. XWithout this option, only articles below the minimum field in the active
  276. Xfile are expired.
  277. XWith this option, all unexpired articles in the database are stat()'ed to
  278. Xsee if they actually exist.
  279. XIn single-pass mode the
  280. X.B -e
  281. Xoption always affects the current pass \*(-- use it
  282. Xonce a day after expire has run.
  283. XIn daemon mode, the
  284. X.B -e
  285. Xoption will cause one pass a day to be the enhanced expire pass.
  286. XBy default, this is the first time mthreads wakes up after 12:30 am.
  287. XIf a different time is desired, it can be specified in the form HHMM 
  288. X(e.g. -e2359).
  289. X.TP 5
  290. X.B -f
  291. Xis used to force
  292. X.I mthreads
  293. Xto open each and every thread file to see which ones really need to be
  294. Xupdated, not just the ones that differ in the active/active2 comparison.
  295. XIt will also remove any extraneous thread files from unthreaded groups
  296. X(which should only occur if you manually change the active2 file).
  297. XThis option should only be used when manipulating the thread files in
  298. Xunorthodox ways.
  299. X.TP 5
  300. X.B -k
  301. Xcan be used to terminate the currently running mthreads, just as if it
  302. Xhad received a terminate signal.
  303. XWhen this option is specified, no other activity is performed.
  304. X.TP 5
  305. X.B -n
  306. Xtells
  307. X.I mthreads
  308. Xthat no actual processing of thread files is to be performed.
  309. XThis can be used to just adjust which groups are to be processed, without
  310. Xactually doing any of the processing right away.
  311. X.TP 5
  312. X.B -v
  313. Xselects additional levels of verbosity in the log file.
  314. XThe default (without -v) is to log mthread's startup, the totals for each
  315. Xpass, the inclusion of the enhanced expire option, and major database errors.
  316. XAdd one
  317. X.B -v
  318. Xto get extra reference line problems logged into the file.
  319. XAdd a second and a third for even more (useless?) information.
  320. X.TP 5
  321. X.B hierarchy_list
  322. XThe hierarchy list is used to turn thread processing on or off for the listed
  323. Xgroups.
  324. XThe groups are specified in a manner very similar to the news software's
  325. Xsys file:  \*(L"news\*(R" matches all groups in news; \*(L"!news\*(R" excludes
  326. Xall groups in news; \*(L"comp.all.ibm.pc,!comp.all.ibm.pc.all\*(L" matches both
  327. Xcomp.sys.ibm.pc and comp.binaries.ibm.pc, but not comp.binaries.ibm.pc.d.
  328. X.Sp
  329. XThe hierarchy_list is also used in conjunction with the debug flag to only
  330. Xprocess one or more groups for testing purposes.
  331. X.SH OUTPUT
  332. XWhen
  333. X.I mthreads
  334. Xis run in single-pass mode it generates a stream a status characters on
  335. Xstdout that present a visual display of what is happening.  If 
  336. Xsingle-pass mode is used for regular processing, this output can be
  337. Xredirected to /dev/null.
  338. X.Sp
  339. XThe output definitions:
  340. X.br
  341. X    \&'.' = group's entry is up-to-date
  342. X.br
  343. X    \&':' = group processed -- no change
  344. X.br
  345. X    \&'#' = group processed
  346. X.br
  347. X    \&'-' = group processed -- is now empty
  348. X.br
  349. X    \&'x' = group excluded in active
  350. X.br
  351. X    \&'X' = group excluded in active2
  352. X.br
  353. X    \&'*' = chdir failed (might be ok)
  354. X.br
  355. X    \&'!' = write failed (is NOT ok)
  356. X.br
  357. X    \&'e' = informational error
  358. X.br
  359. X    \&'E' = database-affecting error -- please report!
  360. X.SH CONFIGURATION
  361. XDuring the configuration of
  362. X.I trn
  363. Xa choice was made about where to place the thread data files.
  364. XThey either exist as a .thread file in each group's spool directory, or
  365. Xthey are a group.th file in a one-off directory structure on another drive.
  366. XSee the THREAD_DIR definition in config.h to review or change this definition.
  367. X.SH REBUILDING
  368. XIf the thread files are ever removed, also remove the file db.init in
  369. Xthe RNLIB directory.
  370. XThis file contains the byte-order of the machine that generated the database,
  371. Xand needs to be removed to truly start from scratch.
  372. XAn easy way to get
  373. X.I mthreads
  374. Xto remove all the files except for db.init is to specify the command:
  375. X.IP
  376. Xmthreads !all
  377. X.PP
  378. XThis also turns off thread processing for all groups.
  379. X.SH "ERROR HANDLING"
  380. XIf the active2 file is removed or corrupted, it will
  381. Xbe automatically rebuilt in the normal course of operation.
  382. XThe record of which groups should be threaded will be lost, however.
  383. XMissing/corrupted thread files are automatically re-built.
  384. X.SH EXAMPLES
  385. XRecommended commands to run on a regular basis are:
  386. X.IP
  387. Xmthreads -adve0630
  388. X.PP
  389. Xto start up an mthreads daemon in verbose logging mode that automatically
  390. Xthreads new groups and performs an extended expire at 6:30 am, or:
  391. X.IP
  392. Xmthreads -e >/dev/null
  393. X.PP
  394. Xto run an mthreads single-pass with extended expire that leaves new groups
  395. Xunthreaded.
  396. X.SH FILES
  397. X/usr/lib/news/active
  398. X.br
  399. X$RNLIB/active2
  400. X.br
  401. X$RNLIB/mt.log
  402. X.br
  403. X$RNLIB/db.init
  404. X.br
  405. X$RNLIB/LOCKmthreads
  406. X.br
  407. XLots of thread data files.
  408. X.SH AUTHOR
  409. XWayne Davison <davison@dri.com> <uunet!drivax!davison>
  410. SHAR_EOF
  411. chmod 0640 mthreads.1 ||
  412. echo 'restore of mthreads.1 failed'
  413. Wc_c="`wc -c < 'mthreads.1'`"
  414. test 8949 -eq "$Wc_c" ||
  415.     echo 'mthreads.1: original size 8949, current size' "$Wc_c"
  416. fi
  417. # ============= mthreads.c ==============
  418. if test -f 'mthreads.c' -a X"$1" != X"-c"; then
  419.     echo 'x - skipping mthreads.c (File already exists)'
  420. else
  421. echo 'x - extracting mthreads.c (Text)'
  422. sed 's/^X//' << 'SHAR_EOF' > 'mthreads.c' &&
  423. X/* $Header: mthreads.c,v 4.3.3.2 90/08/20 16:43:19 davison Trn $
  424. X**
  425. X** $Log:    mthreads.c,v $
  426. X** Revision 4.3.3.2  90/08/20  16:43:19  davison
  427. X** Implemented new command-line interface and database upgrading.
  428. X** 
  429. X** Revision 4.3.3.1  90/07/24  22:24:17  davison
  430. X** Initial Trn Release
  431. X** 
  432. X*/
  433. X
  434. X/* mthreads.c -- for making and updating a discussion-thread database
  435. X**
  436. X** We use the active file as our high/low counts for each group, and create
  437. X** an active2 file of our own to keep track of the high/lows of the database.
  438. X** When fully updated, the two files should be identical.  This gives us
  439. X** quick access to the last processed high/low counts without opening
  440. X** each data file, PLUS it allows trn to use the fake active file as if it
  441. X** were the real thing to keep it from seeing articles before they are
  442. X** processed.  If the active2 file is removed or corrupted, it will be
  443. X** automatically repaired in the normal course of operation.  We update
  444. X** the file IN PLACE so that trn can keep it open all the time.  Normally
  445. X** the size of the file does not change, so it is easy to do.  In those
  446. X** rare instances where a news admin shuffles the real active file, we
  447. X** take it all in stride by throwing a little memory at the problem.
  448. X**
  449. X** Usage:  mthreads [-d[MM]] [-e[HHMM]] [-aDfknv] [hierarchy_list]
  450. X*/
  451. X
  452. X#include "EXTERN.h"
  453. X#include "common.h"
  454. X#ifdef SERVER
  455. X#include "server.h"
  456. X#endif
  457. X#include "INTERN.h"
  458. X#include "mthreads.h"
  459. X
  460. X#ifdef TZSET
  461. X#include <time.h>
  462. X#else
  463. X#include <sys/time.h>
  464. X#include <sys/timeb.h>
  465. X#endif
  466. X
  467. XFILE *fp_lock, *fp_log;
  468. X
  469. Xstruct stat filestat;
  470. X
  471. Xstatic char line[256];
  472. Xstatic char line2[256];
  473. X
  474. X/* If you want to change the field size, do it once and then leave it alone. */
  475. Xchar fmt_active2[] = "%s %06ld %06ld %c\n";
  476. X
  477. Xchar *filename;
  478. X
  479. Xtypedef struct _active_line {
  480. X    struct _active_line *link;
  481. X    char *name;
  482. X    long last;
  483. X    long first;
  484. X    char type;
  485. X} ACTIVE_LINE;
  486. X
  487. X#define Nullact Null(ACTIVE_LINE*)
  488. X
  489. XACTIVE_LINE *line_root = Nullact, *last_line = Nullact, *pline = Nullact;
  490. X
  491. Xbool force_flag = FALSE, kill_mthreads = FALSE, no_processing = FALSE;
  492. Xbool add_new = FALSE, rebuild = FALSE, grevious_error;
  493. Xint daemon_delay = 0, log_verbosity = 0, debug = 0;
  494. Xlong expire_time = 0;
  495. Xchar *hierarchy_list = NULL;
  496. Xlong truncate_len = -1;
  497. X
  498. Xchar nullstr[] = "";
  499. X
  500. XBMAP my_bmap, mt_bmap;
  501. X
  502. X#ifdef TZSET
  503. Xtime_t tnow;
  504. X#else
  505. Xstruct timeb ftnow;
  506. X#endif
  507. X
  508. X#define TIMER_FIRST 1
  509. X#define TIMER_DEFAULT (10 * 60)
  510. X
  511. Xint added_groups, removed_groups, action;
  512. X
  513. X#define NG_DEFAULT    0
  514. X#define NG_MATCH    1
  515. X#define NG_SKIP        2
  516. X
  517. X#ifdef SERVER
  518. Xchar *server;
  519. X#else
  520. Xtime_t last_modified;
  521. X#endif
  522. X
  523. Xchar *thread_name(), *file_exp();
  524. Xvoid makethreads(), interrupt(), alarm_handler(), wrap_it_up();
  525. X
  526. Xmain( argc, argv )
  527. Xint  argc;
  528. Xchar *argv[];
  529. X{
  530. X    int fd;
  531. X    long pid;
  532. X
  533. X    while( --argc ) {
  534. X    if( **++argv == '-' ) {
  535. X        while( *++*argv ) {
  536. X        switch( **argv ) {
  537. X        case 'a':        /* automatically thread new groups */
  538. X            add_new = TRUE;
  539. X            break;
  540. X        case 'D':
  541. X            debug++;
  542. X            break;
  543. X        case 'd':
  544. X            if( *++*argv <= '9' && **argv >= '0' ) {
  545. X            daemon_delay = atoi( *argv ) * 60;
  546. X            while( *++*argv <= '9' && **argv >= '0' ) {
  547. X                ;
  548. X            }
  549. X            } else {
  550. X            daemon_delay = TIMER_DEFAULT;
  551. X            }
  552. X            --*argv;
  553. X            break;
  554. X        case 'e': {
  555. X            struct tm *ts;
  556. X            long desired;
  557. X
  558. X            (void) time( &expire_time );
  559. X            ts = localtime( &expire_time );
  560. X
  561. X            if( *++*argv <= '9' && **argv >= '0' ) {
  562. X            desired = atol( *argv );
  563. X            if( desired/100 > 23 || desired%100 > 59 ) {
  564. X                fprintf( stderr, "Illegal expire time: '%04d'\n",
  565. X                desired );
  566. X                exit( 1 );
  567. X            }
  568. X            desired = (desired/100)*60 + desired%100;
  569. X            while( *++*argv <= '9' && **argv >= '0' ) {
  570. X                ;
  571. X            }
  572. X            } else {
  573. X            desired = 30;            /* 0030 = 12:30am */
  574. X            }
  575. X            --*argv;
  576. X            desired -= ts->tm_hour * 60 + ts->tm_min;
  577. X            if( desired < 0 ) {
  578. X            desired += 24 * 60;
  579. X            }
  580. X            expire_time += desired * 60 - ts->tm_sec;
  581. X            break;
  582. X         }
  583. X        case 'f':
  584. X            force_flag = TRUE;
  585. X            break;
  586. X        case 'k':
  587. X            kill_mthreads = TRUE;
  588. X            break;
  589. X        case 'n':
  590. X            no_processing = TRUE;
  591. X            break;
  592. X        case 'v':
  593. X            log_verbosity++;
  594. X            break;
  595. X        default:
  596. X            fprintf( stderr, "Unknown option: '%c'\n", **argv );
  597. X            exit( 1 );
  598. X        }
  599. X        }
  600. X    } else {
  601. X        if( hierarchy_list ) {
  602. X        fprintf( stderr, "Specify the newsgroups in one comma-separated list.\n" );
  603. X        exit( 1 );
  604. X        }
  605. X        hierarchy_list = *argv;
  606. X    }
  607. X    }
  608. X
  609. X    /* Set up a nice friendly umask. */
  610. X    umask( 002 );
  611. X
  612. X    /* What time is it? */
  613. X#ifdef TZSET
  614. X    (void) time( &tnow );
  615. X    (void) tzset();
  616. X#else
  617. X    (void) ftime( &ftnow );
  618. X#endif
  619. X
  620. X    /* Make sure we're not already running by creating a lock file.
  621. X    ** (I snagged this method from C news.)
  622. X    */
  623. X    sprintf( line, "%s.%d", file_exp( "%X/LOCK" ), getpid() );
  624. X    if( (fp_lock = fopen( line, "w" )) == Nullfp ) {
  625. X    fprintf( stderr, "Unable to create lock temporary `%s'.\n", line );
  626. X    exit( 1 );
  627. X    }
  628. X    fprintf( fp_lock, "%d\n", getpid() );
  629. X    fclose( fp_lock );
  630. X
  631. X    /* Try to link to lock file. */
  632. X    filename = file_exp( "%X/LOCKmthreads" );
  633. X  dolink:
  634. X    if( link( line, filename ) < 0 ) {
  635. X      long otherpid;
  636. X    /* Try to avoid possible race with daemon starting up. */
  637. X    sleep (5);
  638. X    if( (fp_lock = fopen( filename, "r")) == Nullfp ) {
  639. X        fprintf( stderr, "unable to open %s\n", filename );
  640. X        unlink( line );
  641. X        exit( 1 );
  642. X    }
  643. X    if( fscanf( fp_lock, "%ld", &otherpid ) != 1) { 
  644. X        fprintf( stderr, "unable to read pid from %s\n", filename );
  645. X        unlink( line );
  646. X        fclose( fp_lock );
  647. X        exit( 1 );
  648. X    }
  649. X    fclose( fp_lock );
  650. X    if( kill( otherpid, kill_mthreads ? SIGTERM : 0 ) == -1 ) {
  651. X        if( unlink( filename ) == -1 ) {
  652. X        fprintf( stderr, "unable to unlink lockfile %s\n", filename );
  653. X        unlink( line );
  654. X        exit( 1 );
  655. X        }
  656. X        if( !kill_mthreads ) {
  657. X        goto dolink;
  658. X        }
  659. X    }
  660. X    unlink( line );
  661. X    if( kill_mthreads ) {
  662. X        fprintf( stderr, "killing currently running mthreads.\n" );
  663. X        exit( 0 );
  664. X    } else {
  665. X        fprintf( stderr, "mthreads is already running.\n" );
  666. X        exit( 1 );
  667. X    }
  668. X    }
  669. X
  670. X    unlink( line );            /* remove temporary LOCK.<pid> file */
  671. X
  672. X    if( kill_mthreads ) {
  673. X    fprintf( stderr, "mthreads is not running.\n" );
  674. X    exit( 1 );
  675. X    }
  676. X
  677. X    /* Open our log file */
  678. X    filename = file_exp( "%X/mt.log" );
  679. X    if( (fp_log = fopen( filename, "a" )) == Nullfp ) {
  680. X    fprintf( stderr, "Unable to open `%s'.\n", filename );
  681. X    exit( 1 );
  682. X    }
  683. X
  684. X    if( sigset( SIGHUP, SIG_IGN ) != SIG_IGN ) {
  685. X    sigset( SIGHUP, interrupt );
  686. X    }
  687. X    if( sigset( SIGINT, SIG_IGN ) != SIG_IGN ) {
  688. X    sigset( SIGINT, interrupt );
  689. X    }
  690. X    if( sigset( SIGQUIT, SIG_IGN ) != SIG_IGN ) {
  691. X    sigset( SIGQUIT, interrupt );
  692. X    }
  693. X    sigset( SIGTERM, interrupt );
  694. X    sigset( SIGBUS, interrupt );
  695. X    sigset( SIGSEGV, interrupt );
  696. X#ifdef SIGTTIN
  697. X    sigset( SIGTTIN, SIG_IGN );
  698. X    sigset( SIGTTOU, SIG_IGN );
  699. X#endif
  700. X    sigset( SIGALRM, SIG_IGN );
  701. X#ifdef lint
  702. X    alarm_handler();            /* foolishness for lint's sake */
  703. X    interrupt( 14 );
  704. X#endif
  705. X
  706. X    /* Ensure this machine has the right byte-order for the database */
  707. X    filename = file_exp( "%X/db.init" );
  708. X    if( (fp_lock = fopen( filename, "r")) == Nullfp
  709. X     || fread( &mt_bmap, 1, sizeof (BMAP), fp_lock ) < sizeof (BMAP)-1 ) {
  710. X    if( fp_lock != Nullfp ) {
  711. X        fclose( fp_lock );
  712. X    }
  713. X     write_db_init:
  714. X    mybytemap( &mt_bmap );
  715. X    if( (fp_lock = fopen( filename, "w" )) == Nullfp ) {
  716. X        log_entry( "Unable to create file: `%s'.\n", filename );
  717. X        exit( 1 );
  718. X    }
  719. X    mt_bmap.version = DB_VERSION;
  720. X    fwrite( &mt_bmap, 1, sizeof (BMAP), fp_lock );
  721. X    fclose( fp_lock );
  722. X    } else {
  723. X    int i;
  724. X
  725. X    fclose( fp_lock );
  726. X    if( mt_bmap.version != DB_VERSION ) {
  727. X        if( mt_bmap.version == DB_VERSION-1 ) {
  728. X        rebuild = TRUE;
  729. X        log_entry( "Upgrading database to version %d.\n", DB_VERSION );
  730. X        goto write_db_init;
  731. X        }
  732. X        log_entry( "** Database is not the right version (%d instead of %d) **\n",
  733. X        mt_bmap.version, DB_VERSION );
  734. X        exit( 1 );
  735. X    }
  736. X    mybytemap( &my_bmap );
  737. X    for( i = 0; i < sizeof (LONG); i++ ) {
  738. X        if( my_bmap.l[i] != mt_bmap.l[i]
  739. X         || (i < sizeof (WORD) && my_bmap.w[i] != mt_bmap.w[i]) ) {
  740. X        log_entry( "\
  741. X** Byte-order conflict -- re-run from a compatible machine **\n\
  742. X\t\tor remove the current thread files, including db.init **\n" );
  743. X        exit( 1 );
  744. X        }
  745. X    }
  746. X    }
  747. X
  748. X#ifdef SERVER
  749. X    server = getserverbyfile( SERVER_FILE );
  750. X    if( server == NULL ) {
  751. X    log_entry( "Couldn't find name of news server.\n" );
  752. X    exit( 1 );
  753. X    }
  754. X#endif
  755. X
  756. X    /* If we're not in daemon mode, run through once and quit. */
  757. X    if( !daemon_delay ) {
  758. X    log_entry( "mthreads single pass started.\n" );
  759. X    setbuf( stdout, Nullch );
  760. X    extra_expire = (expire_time != 0);
  761. X    makethreads();
  762. X    } else {
  763. X    /* For daemon mode, we cut ourself off from anything tty-related and
  764. X    ** run in the background (involves forks, but no knives).
  765. X    */
  766. X    close( 0 );
  767. X    if( open( "/dev/null", 2 ) != 0 ) {
  768. X        fprintf( stderr, "unable to open /dev/null!\n" );
  769. X        exit( 1 );
  770. X    }
  771. X    close( 1 );
  772. X    close( 2 );
  773. X    dup( 0 );
  774. X    dup( 0 );
  775. X    while( (pid = fork()) < 0 ) {
  776. X        sleep( 2 );
  777. X    }
  778. X    if( pid ) {
  779. X        exit( 0 );
  780. X    }
  781. X#ifdef TIOCNOTTY
  782. X    if( (fd = open( "/dev/tty", 1 )) >= 0 ) {
  783. X        ioctl( fd, TIOCNOTTY, (int*)0 );
  784. X        close( fd );
  785. X    }
  786. X#else
  787. X    (void) setpgrp();
  788. X    while( (pid = fork()) < 0 ) {
  789. X        sleep( 2 );
  790. X    }
  791. X    if( pid ) {
  792. X        exit( 0 );
  793. X    }
  794. X#endif
  795. X    /* Put our pid in the lock file for death detection */
  796. X    if( (fp_lock = fopen( file_exp( "%X/LOCKmthreads" ), "w" )) != Nullfp ) {
  797. X        fprintf( fp_lock, "%d\n", getpid() );
  798. X        fclose( fp_lock );
  799. X    }
  800. X
  801. X    log_entry( "mthreads daemon started.\n" );
  802. X
  803. X#ifndef SERVER
  804. X    last_modified = 0;
  805. X#endif
  806. X    sigset( SIGALRM, alarm_handler );
  807. X
  808. X    /* Start timer -- first interval is shorter than all others */
  809. X    alarm( TIMER_FIRST );
  810. X    for( ;; ) {
  811. X        if( caught_interrupt ) {
  812. X        wrap_it_up();
  813. X        /* NORETURN */
  814. X        }
  815. X        pause();        /* let alarm go off */
  816. X        if( caught_interrupt ) {
  817. X        wrap_it_up();
  818. X        /* NORETURN */
  819. X        }
  820. X        alarm( 0 );
  821. X
  822. X        /* Re-open our log file, if needed */
  823. X        if( !fp_log && !(fp_log = fopen( file_exp( "%X/mt.log" ), "a" )) ) {
  824. X        exit( 1 );
  825. X        }
  826. X#ifndef SERVER
  827. X        if( stat( file_exp( ACTIVE ), &filestat ) < 0 ) {
  828. X        log_entry( "Unable to stat active file -- quitting.\n" );
  829. X        exit( 1 );
  830. X        }
  831. X#endif
  832. X        if( expire_time && time( 0L ) > expire_time ) {
  833. X        expire_time += 24L * 60 * 60;
  834. X        extra_expire = TRUE;
  835. X        }
  836. X#ifdef SERVER
  837. X        if( 1 ) {        /* always compare files */
  838. X#else
  839. X        if( extra_expire || filestat.st_mtime != last_modified ) {
  840. X        last_modified = filestat.st_mtime;
  841. X#endif
  842. X        makethreads();
  843. X        }
  844. X        alarm( daemon_delay );
  845. X        fclose( fp_log );    /* close the log file while we sleep */
  846. X        fp_log = Nullfp;
  847. X    } /* for */
  848. X    }/* if */
  849. X
  850. X    wrap_it_up();
  851. X}
  852. X
  853. Xvoid
  854. Xalarm_handler()
  855. X{
  856. X    sigset( SIGALRM, alarm_handler );
  857. X}
  858. X
  859. Xvoid
  860. Xinterrupt( sig )
  861. Xint sig;
  862. X{
  863. X    /* Re-open our log file, if needed */
  864. X    if( fp_log || (fp_log = fopen( file_exp( "%X/mt.log" ), "a" )) ) {
  865. X    if( sig == SIGTERM ) {
  866. X        log_entry( "mthreads halted.\n", sig);
  867. X    } else {
  868. X        log_entry( "** interrupt %d **\n", sig);
  869. X    }
  870. X    }
  871. X    if( !daemon_delay ) {
  872. X    printf( "interrupt %d!\n", sig );
  873. X    }
  874. X    /* Flag interrupt occurred -- main loop attempts an orderly retreat. */
  875. X    caught_interrupt = TRUE;
  876. X}
  877. X
  878. Xvoid
  879. Xwrap_it_up()
  880. X{
  881. X    unlink( file_exp( "%X/LOCKmthreads" ) );        /* remove lock */
  882. X
  883. X    exit( 0 );
  884. X}
  885. X
  886. X/* Process the active file, creating/modifying the active2 file and
  887. X** creating/modifying the thread data files.
  888. X*/
  889. Xvoid
  890. Xmakethreads()
  891. X{
  892. X    register char *cp, *cp2;
  893. X    FILE *fp_active, *fp_active2, *fp_active3;
  894. X    long first, last, first2, last2;
  895. X    char ch, ch2;
  896. X    char data_file_open;
  897. X    bool update_successful;
  898. X    bool eof_active = FALSE, eof_active2 = FALSE;
  899. X
  900. X#ifdef SERVER
  901. X    switch( server_init( server ) ) {
  902. X    case OK_NOPOST:
  903. X    case OK_CANPOST:
  904. X    break;
  905. X    case ERR_ACCESS:
  906. X    log_entry( "Server %s rejected connection -- quitting.\n", server );
  907. X    exit( 1 );
  908. X    default:
  909. X    log_entry( "Couldn't connect with server %s -- sleeping.\n", server );
  910. X    return;
  911. X    }
  912. X    put_server( "LIST" );    /* ask server for the active file */
  913. X    get_server( line, sizeof line );
  914. X    if( *line != CHAR_OK ) {
  915. X    log_entry( "Unable to get active file from server -- sleeping.\n" );
  916. X    close_server();
  917. X    return;
  918. X    }
  919. X    if( (fp_active = fopen( file_exp( ACTIVE1 ), "w+" )) == Nullfp ) {
  920. X    log_entry( "Unable to write the active1 file.\n" );
  921. X    exit( 1 );
  922. X    }
  923. X    while( 1 ) {
  924. X    if( caught_interrupt ) {
  925. X        wrap_it_up();
  926. X        /* NORETURN */
  927. X    }
  928. X    if( get_server( line, sizeof line ) < 0 ) {
  929. X        log_entry( "Server failed to send entire active file -- sleeping.\n" );
  930. X        fclose( fp_active );
  931. X        close_server();
  932. X        return;
  933. X    }
  934. X    if( *line == '.' ) {
  935. X        break;
  936. X    }
  937. X    fputs( line, fp_active );
  938. X    putc( '\n', fp_active );
  939. X    }
  940. X    fseek( fp_active, 0L, 0 );        /* rewind for read */
  941. X#else
  942. X    if( (fp_active = fopen( file_exp( ACTIVE ), "r" )) == Nullfp ) {
  943. X    log_entry( "Unable to open the active file.\n" );
  944. X    exit( 1 );
  945. X    }
  946. X#endif
  947. X    filename = file_exp( ACTIVE2 );
  948. X    if( (fp_active3 = fopen( filename, "r+" )) == Nullfp ) {
  949. X    if( (fp_active3 = fopen( filename, "w" )) == Nullfp ) {
  950. X        log_entry( "Unable to open the active2 file for update.\n" );
  951. X        exit( 1 );
  952. X    }
  953. X    }
  954. X    if( (fp_active2 = fopen( filename, "r" )) == Nullfp ) {
  955. X    log_entry( "Unable to open the active2 file.\n" );
  956. X    exit( 1 );
  957. X    }
  958. X    if( caught_interrupt ) {
  959. X    wrap_it_up();
  960. X    /* NORETURN */
  961. X    }
  962. X    if( extra_expire && log_verbosity ) {
  963. X    log_entry( "Using enhanced expiration for this pass.\n" );
  964. X    }
  965. X
  966. X    processed_groups = added_groups = removed_groups = 0;
  967. X    added_articles = expired_articles = 0;
  968. X
  969. X    /* Loop through entire active file. */
  970. X    for( ;; ) {
  971. X    if( eof_active || !fgets( line, sizeof line, fp_active ) ) {
  972. X        if( eof_active2 && !line_root ) {
  973. X        break;
  974. X        }
  975. X        eof_active = TRUE;
  976. X        ch = 'x';
  977. X    } else {
  978. X        if( !(cp = index( line, ' ' )) ) {
  979. X        log_entry( "active line has no space: %s\n", line );
  980. X        continue;
  981. X        }
  982. X        *cp = '\0';
  983. X        if( sscanf( cp+1, "%ld %ld %c", &last, &first, &ch ) != 3 ) {
  984. X        log_entry( "active digits corrupted: %s %s\n", line, cp+1 );
  985. X        continue;
  986. X        }
  987. X    }
  988. X    data_file_open = 0;
  989. X    /* If we've allocated some lines in memory while searching for
  990. X    ** newsgroups (they've scrambled the active file on us), check
  991. X    ** them first.
  992. X    */
  993. X    last_line = Nullact;
  994. X    for( pline = line_root; pline; pline = pline->link ) {
  995. X        if( eof_active || strEQ( line, pline->name ) ) {
  996. X        strcpy( line2, pline->name );
  997. X        free( pline->name );
  998. X        first2 = pline->first;
  999. X        last2 = pline->last;
  1000. X        ch2 = pline->type;
  1001. X        if( last_line ) {
  1002. X            last_line->link = pline->link;
  1003. X        } else {
  1004. X            line_root = pline->link;
  1005. X        }
  1006. X        free( pline );
  1007. X        break;
  1008. X        }
  1009. X        last_line = pline;
  1010. X    }/* for */
  1011. X    /* If not found yet, check the active2 file. */
  1012. X    if( !pline ) {
  1013. X        for( ;; ) {
  1014. X        if( eof_active2 || !fgets( line2, sizeof line2, fp_active2 ) ) {
  1015. X            /* At end of file, check if the thread data file exists.
  1016. X            ** If so, use its high/low values.  Else, default to
  1017. X            ** some initial values.
  1018. X            */
  1019. X            eof_active2 = TRUE;
  1020. X            if( eof_active ) {
  1021. X            break;
  1022. X            }
  1023. X            strcpy( line2, line );
  1024. X            if( (data_file_open = init_data( thread_name( line ) )) ) {
  1025. X            last2 = total.last;
  1026. X            first2 = total.first;
  1027. X            ch2 = 'y';
  1028. X            } else {
  1029. X            total.last = last2 = last;
  1030. X            total.first = first2 = first;
  1031. X            if( add_new ) {
  1032. X                ch2 = (ch == '=' ? 'x' : ch);
  1033. X                added_groups++;
  1034. X            } else {
  1035. X                ch2 = (ch == '=' ? 'X' : toupper( ch ));
  1036. X            }
  1037. X            }
  1038. X            data_file_open++;        /* (1 == empty, 2 == open) */
  1039. X            break;
  1040. X        }
  1041. X        if( !(cp2 = index( line2, ' ' )) ) {
  1042. X            log_entry( "active2 line has no space: %s\n", line2 );
  1043. X            continue;
  1044. X        }
  1045. X        *cp2 = '\0';
  1046. X        if( sscanf( cp2+1,"%ld %ld %c",&last2,&first2,&ch2 ) != 3 ) {
  1047. X            log_entry( "active2 digits corrupted: %s %s\n",
  1048. X            line2, cp2+1 );
  1049. X            continue;
  1050. X        }
  1051. X        /* Check if we're still in-sync */
  1052. X        if( eof_active || strEQ( line, line2 ) ) {
  1053. X            break;
  1054. X        }
  1055. X        /* Nope, we've got to go looking for this line somewhere
  1056. X        ** down in the file.  Save each non-matching line in memory
  1057. X        ** as we go.
  1058. X        */
  1059. X        pline = (ACTIVE_LINE*)safemalloc( sizeof (ACTIVE_LINE) );
  1060. X        pline->name = savestr( line2 );
  1061. X        pline->last = last2;
  1062. X        pline->first = first2;
  1063. X        pline->type = ch2;
  1064. X        pline->link = Nullact;
  1065. X        if( !last_line ) {
  1066. X            line_root = pline;
  1067. X        } else {
  1068. X            last_line->link = pline;
  1069. X        }
  1070. X        last_line = pline;
  1071. X        }/* for */
  1072. X        if( eof_active && eof_active2 ) {
  1073. X        break;
  1074. X        }
  1075. X    }/* if !pline */
  1076. X    if( eof_active ) {
  1077. X        strcpy( line, line2 );
  1078. X        if( truncate_len < 0 ) {
  1079. X        truncate_len = ftell( fp_active3 );
  1080. X        }
  1081. X    }
  1082. X    if( rebuild ) {
  1083. X        unlink( thread_name( line ) );
  1084. X    }
  1085. X    update_successful = FALSE;
  1086. X    if( hierarchy_list ) {
  1087. X        action = ngmatch( hierarchy_list, line );
  1088. X    } else {
  1089. X        action = NG_DEFAULT;
  1090. X    }
  1091. X    switch( action ) {
  1092. X    case NG_DEFAULT:
  1093. X        if( ch2 < 'a' ) {
  1094. X        action = NG_SKIP;
  1095. X        } else {
  1096. X        action = NG_MATCH;
  1097. X        }
  1098. X        break;
  1099. X    case NG_MATCH:                /* add if unthreaded */
  1100. X        if( ch2 < 'a' ) {
  1101. X        last2 = first2 - 1;
  1102. X        added_groups++;
  1103. X        }
  1104. X        break;
  1105. X    case NG_SKIP:                /* remove if threaded */
  1106. X        if( ch2 >= 'a' && !debug ) {
  1107. X        unlink( thread_name( line ) );
  1108. X        removed_groups++;
  1109. X        }
  1110. X        break;
  1111. X    }
  1112. X    if( caught_interrupt || (debug && action != NG_MATCH) ) {
  1113. X        dont_read_data( data_file_open );    /* skip silently */
  1114. X    } else if( ch == 'x' || ch == '=' ) {
  1115. X        if( !daemon_delay ) {        /* skip 'x'ed groups */
  1116. X        putchar( 'x' );
  1117. X        }
  1118. X        ch = (action == NG_SKIP ? 'X' : 'x');
  1119. X        if( (ch2 >= 'a' && ch2 != 'x') || force_flag ) {
  1120. X        /* Remove thread file if group is newly 'x'ed out */
  1121. X        unlink( thread_name( line ) );
  1122. X        }
  1123. X        update_successful = TRUE;
  1124. X        dont_read_data( data_file_open );
  1125. X    } else if( action == NG_SKIP ) {    /* skip excluded groups */
  1126. X        if( !daemon_delay ) {
  1127. X        putchar( 'X' );
  1128. X        }
  1129. X        ch = toupper( ch );
  1130. X        if( force_flag ) {
  1131. X        unlink( thread_name( line ) );
  1132. X        }
  1133. X        update_successful = TRUE;
  1134. X        dont_read_data( data_file_open );
  1135. X    } else if( no_processing ) {
  1136. X        if( !daemon_delay ) {
  1137. X        putchar( ',' );
  1138. X        }
  1139. X        ch2 = ch;
  1140. X        dont_read_data( data_file_open );
  1141. X    } else if( !force_flag && !extra_expire && !rebuild
  1142. X     && first == first2 && last == last2 ) {
  1143. X        /* We're up-to-date here.  Skip it. */
  1144. X        if( !daemon_delay ) {
  1145. X        putchar( '.' );
  1146. X        }
  1147. X        update_successful = TRUE;
  1148. X        dont_read_data( data_file_open );
  1149. X    } else {
  1150. X        /* Looks like we need to process something. */
  1151. X#ifdef SERVER
  1152. X        sprintf( line2, "GROUP %s", line );
  1153. X        put_server( line2 );        /* go to next group */
  1154. X        if( get_server( line2, sizeof line2 ) < 0 || *line2 != CHAR_OK ) {
  1155. X        log_entry( "NNTP failure on group `%s'.\n", line );
  1156. X#else
  1157. X        cp = line2;
  1158. X        while( (cp = index( cp, '.' )) ) {
  1159. X        *cp = '/';
  1160. X        }
  1161. X        filename = file_exp( line2 );    /* relative to spool dir */
  1162. X        if( chdir( filename ) < 0 ) {
  1163. X        if (errno != ENOENT) {
  1164. X            log_entry( "Unable to chdir to `%s'.\n", filename );
  1165. X        } else {
  1166. X            update_successful = TRUE;
  1167. X        }
  1168. X#endif
  1169. X        if( !daemon_delay ) {
  1170. X            putchar( '*' );
  1171. X        }
  1172. X        dont_read_data( data_file_open );
  1173. X        } else {
  1174. X        filename = thread_name( line );
  1175. X        /* Try to open the data file only if we didn't try it
  1176. X        ** in the name matching code above.
  1177. X        */
  1178. X        if( !data_file_open-- ) {    /* (0 == haven't tried yet) */
  1179. X            if( !(data_file_open = init_data( filename )) ) {
  1180. X            total.last = first - 1;
  1181. X            total.first = first;
  1182. X            }
  1183. X        }
  1184. X        strcpy( line2, filename );
  1185. X        cp = rindex( line2, '/' ) + 1;
  1186. X
  1187. X        if( data_file_open ) {        /* (0 == empty, 1 == open) */
  1188. X            if( !read_data() ) {    /* did read fail? */
  1189. X#ifndef DEBUG
  1190. X            unlink( filename );    /* trash input file */
  1191. X#else
  1192. X            strcpy( cp, "bad.read" );
  1193. X            rename( filename, line2 );
  1194. X#endif
  1195. X            data_file_open = init_data( filename );
  1196. X            total.last = first - 1;
  1197. X            total.first = first;
  1198. X            }
  1199. X        }
  1200. X        grevious_error = FALSE;
  1201. X        process_articles( first, last );
  1202. X        processed_groups++;
  1203. X        if( caught_interrupt ) {
  1204. X            processed_groups--;    /* save nothing -- no update */
  1205. X        } else if( !added_count && !expired_count && last == last2 ) {
  1206. X            (void) write_data( Nullch );
  1207. X            if( !daemon_delay ) {
  1208. X            putchar( ':' );
  1209. X            }
  1210. X            update_successful = TRUE;
  1211. X        } else if( !total.root ) {
  1212. X            /* When the data file goes empty, remove it. */
  1213. X            unlink( filename );
  1214. X            expired_articles += expired_count;
  1215. X            if( !daemon_delay ) {
  1216. X            putchar( '-' );
  1217. X            }
  1218. X            update_successful = TRUE;
  1219. X        } else {
  1220. X            strcpy( cp, ".new" );    /* write data as .new */
  1221. X            if( write_data( line2 ) && !grevious_error ) {
  1222. X            rename( line2, filename );
  1223. X            added_articles += added_count;
  1224. X            expired_articles += expired_count;
  1225. X            if( !daemon_delay ) {
  1226. X                putchar( '#' );
  1227. X            }
  1228. X            update_successful = TRUE;
  1229. X            } else {
  1230. X#ifndef DEBUG
  1231. X            unlink( line2 );    /* blow-away bad write */
  1232. X#else
  1233. X            cp = rindex( filename, '/' ) + 1;
  1234. X            strcpy( cp, "bad.write" );
  1235. X            rename( line2, filename );
  1236. X#endif
  1237. X            if( !daemon_delay ) {
  1238. X                putchar( '!' );
  1239. X            }
  1240. X            }/* if */
  1241. X        }/* if */
  1242. X        }/* if */
  1243. X    }/* if */
  1244. X    /* Finally, update the active2 entry for this newsgroup. */
  1245. X    if( update_successful ) {
  1246. X        fprintf( fp_active3, fmt_active2, line, last, first, ch );
  1247. X    } else {
  1248. X        fprintf( fp_active3, fmt_active2, line, last2, first2, ch2 );
  1249. X    }
  1250. X    }/* for */
  1251. X
  1252. X#ifdef SERVER
  1253. X    close_server();
  1254. X#endif
  1255. X    fclose( fp_active );
  1256. X    fclose( fp_active2 );
  1257. X    fclose( fp_active3 );
  1258. X
  1259. X    if( truncate_len >= 0 ) {
  1260. X#ifdef TRUNCATE
  1261. X    if( truncate( file_exp( ACTIVE2 ), truncate_len ) == -1 )
  1262. X        log_entry( "Unable to truncate the active2 file.\n" );
  1263. X#else
  1264. X#ifdef CHSIZE
  1265. X    int fd;
  1266. X    if( (fd = open( file_exp( ACTIVE2 ), O_RDWR )) == -1 )
  1267. X        log_entry( "Unable to open the active2 file for truncation.\n" );
  1268. X    else {
  1269. X        if( chsize( fd, truncate_len ) == -1 )
  1270. X        log_entry( "Unable to truncate the active2 file.\n" );
  1271. X        close( fd );
  1272. X    }
  1273. X#else
  1274. X    filename = file_exp( ACTIVE2 );
  1275. X    sprintf( line, "%s.new", filename );
  1276. X    if( (fp_active3 = fopen( line, "w" )) == Nullfp ) {
  1277. X        log_entry( "Unable to create the active2.new file.\n" );
  1278. X    } else if( (fp_active2 = fopen( filename, "r" )) == Nullfp ) {
  1279. X        fclose( fp_active3 );
  1280. X        unlink( line );
  1281. X        log_entry( "Unable to open the active2 file.\n" );
  1282. X    } else {
  1283. X        while( ftell( fp_active3 ) < truncate_len ) {
  1284. X        if( !fgets( line2, sizeof line2, fp_active2 ) ) {
  1285. X            break;
  1286. X        }
  1287. X        fputs( line2, fp_active3 );
  1288. X        }
  1289. X        sprintf( line2, "%s.old", filename );
  1290. X        rename( filename, line2 );
  1291. X        rename( line, filename );
  1292. X        fclose( fp_active2 );
  1293. X        fclose( fp_active3 );
  1294. X    }
  1295. X#endif /* not XENIX */
  1296. X#endif /* not TRUNCATE */
  1297. X    }
  1298. X
  1299. X    sprintf( line, "Processed %d group%s:  added %d article%s, expired %d.\n",
  1300. X    processed_groups, processed_groups == 1 ? nullstr : "s",
  1301. X    added_articles, added_articles == 1 ? nullstr : "s",
  1302. X    expired_articles );
  1303. X
  1304. X    if( processed_groups ) {
  1305. X    log_entry( line );
  1306. X    }
  1307. X
  1308. X    if( !daemon_delay ) {
  1309. X    putchar( '\n' );
  1310. X    fputs( line, stdout );
  1311. X    }
  1312. X    if( added_groups ) {
  1313. X    sprintf( line, "Turned %d group%s on.\n", added_groups,
  1314. X        added_groups == 1 ? nullstr : "s" );
  1315. X    log_entry( line );
  1316. X    if( !daemon_delay ) {
  1317. X        fputs( line, stdout );
  1318. X    }
  1319. X    }
  1320. X    if( removed_groups ) {
  1321. X    sprintf( line, "Turned %d group%s off.\n", removed_groups,
  1322. X        removed_groups == 1 ? nullstr : "s" );
  1323. X    log_entry( line );
  1324. X    if( !daemon_delay ) {
  1325. X        fputs( line, stdout );
  1326. X    }
  1327. X    }
  1328. X    extra_expire = FALSE;
  1329. X    rebuild = FALSE;
  1330. X}
  1331. X
  1332. X/*
  1333. X** ngmatch - newsgroup name matching
  1334. X**
  1335. X** returns NG_MATCH for a positive patch, NG_SKIP for a negative match,
  1336. X** and NG_DEFAULT if the group doesn't match at all.
  1337. X**
  1338. X** "all" in a pattern is a wildcard that matches exactly one word;
  1339. X** it does not cross "." (NGDELIM) delimiters.
  1340. X**
  1341. X** This matching code was borrowed from C news.
  1342. X*/
  1343. X
  1344. X#define ALL "all"            /* word wildcard */
  1345. X
  1346. X#define NGNEG '!'
  1347. X#define NGSEP ','
  1348. X#define NGDELIM '.'
  1349. X
  1350. Xint
  1351. Xngmatch( ngpat, grp )
  1352. Xchar *ngpat, *grp;
  1353. X{
  1354. X    register char *patp;        /* point at current pattern */
  1355. X    register char *patcomma;
  1356. X    register int depth;
  1357. X    register int faildeepest = 0, hitdeepest = 0;    /* in case no match */
  1358. X    register bool negation;
  1359. X
  1360. X    for( patp = ngpat; patp != Nullch; patp = patcomma ) {
  1361. X    negation = FALSE;
  1362. X    patcomma = index( patp, NGSEP );
  1363. X    if( patcomma != Nullch ) {
  1364. X        *patcomma = '\0';    /* will be restored below */
  1365. X    }
  1366. X    if( *patp == NGNEG ) {
  1367. X        ++patp;
  1368. X        negation = TRUE;
  1369. X    }
  1370. X    depth = onepatmatch( patp, grp ); /* try 1 pattern, 1 group */
  1371. X    if( patcomma != Nullch ) {
  1372. X        *patcomma++ = NGSEP;    /* point after the comma */
  1373. X    }
  1374. X    if( depth == 0 ) {        /* mis-match */
  1375. X        ;                /* ignore it */
  1376. X    } else if( negation ) {
  1377. X        /* record depth of deepest negated matched word */
  1378. X        if( depth > faildeepest ) {
  1379. X        faildeepest = depth;
  1380. X        }
  1381. X    } else {
  1382. X        /* record depth of deepest plain matched word */
  1383. X        if( depth > hitdeepest ) {
  1384. X        hitdeepest = depth;
  1385. X        }
  1386. X    }
  1387. X    }
  1388. X    if( hitdeepest > faildeepest ) {
  1389. X    return NG_MATCH;
  1390. X    } else if( faildeepest ) {
  1391. X    return NG_SKIP;
  1392. X    } else {
  1393. X    return NG_DEFAULT;
  1394. X    }
  1395. X}
  1396. X
  1397. X/*
  1398. X** Match a pattern against a group by looking at each word of pattern in turn.
  1399. X**
  1400. X** On a match, return the depth (roughly, ordinal number * k) of the rightmost
  1401. X** word that matches.  If group runs out first, the match fails; if pattern
  1402. X** runs out first, it succeeds.  On a failure, return zero.
  1403. X*/
  1404. Xint
  1405. Xonepatmatch( patp, grp )
  1406. Xchar *patp, *grp;
  1407. X{
  1408. X    register char *rpatwd;        /* used by word match (inner loop) */
  1409. X    register char *patdot, *grdot;    /* point at dots after words */
  1410. X    register char *patwd, *grwd;    /* point at current words */
  1411. X    register int depth = 0;
  1412. X
  1413. X    for( patwd = patp, grwd = grp;
  1414. X     patwd != Nullch && grwd != Nullch;
  1415. X     patwd = patdot, grwd = grdot
  1416. X    ) {
  1417. X    register bool match = FALSE;
  1418. X    register int incr = 20;
  1419. X
  1420. X    /* null-terminate words */
  1421. X    patdot = index(patwd, NGDELIM);
  1422. X    if( patdot != Nullch ) {
  1423. X        *patdot = '\0';        /* will be restored below */
  1424. X    }
  1425. X    grdot = index( grwd, NGDELIM );
  1426. X    if( grdot != Nullch ) {
  1427. X        *grdot = '\0';        /* will be restored below */
  1428. X    }
  1429. X    /*
  1430. X     * Match one word of pattern with one word of group.
  1431. X     * A pattern word of "all" matches any group word,
  1432. X     * but isn't worth as much.
  1433. X     */
  1434. X#ifdef FAST_STRCMP
  1435. X    match = STREQ( patwd, grwd );
  1436. X    if( !match && STREQ( patwd, ALL ) ) {
  1437. X        match = TRUE;
  1438. X        --incr;
  1439. X    }
  1440. X#else
  1441. X    for( rpatwd = patwd; *rpatwd == *grwd++; ) {
  1442. X        if( *rpatwd++ == '\0' ) {
  1443. X        match = TRUE;        /* literal match */
  1444. X        break;
  1445. X        }
  1446. X    }
  1447. X    if( !match ) {
  1448. X        /* ugly special case match for "all" */
  1449. X        rpatwd = patwd;
  1450. X        if( *rpatwd++ == 'a' && *rpatwd++ == 'l'
  1451. X         && *rpatwd++ == 'l' && *rpatwd   == '\0' ) {
  1452. X        match = TRUE;
  1453. X         --incr;
  1454. X        }
  1455. X    }
  1456. X#endif                /* FAST_STRCMP */
  1457. X
  1458. X    if( patdot != Nullch ) {
  1459. X        *patdot++ = NGDELIM;    /* point after the dot */
  1460. X    }
  1461. X    if( grdot != Nullch ) {
  1462. X        *grdot++ = NGDELIM;
  1463. X    }
  1464. X    if( !match ) {
  1465. X        depth = 0;        /* words differed - mismatch */
  1466. X        break;
  1467. X    }
  1468. X    depth += incr;
  1469. X    }
  1470. X    /* if group name ran out before pattern, then match fails */
  1471. X    if( grwd == Nullch && patwd != Nullch ) {
  1472. X    depth = 0;
  1473. X    }
  1474. X    return depth;
  1475. X}
  1476. X
  1477. X/* Generate a log entry with timestamp.
  1478. X*/
  1479. X/*VARARGS1*/
  1480. Xvoid
  1481. Xlog_entry( fmt, arg1, arg2 )
  1482. Xchar *fmt;
  1483. Xlong arg1;
  1484. Xlong arg2;
  1485. X{
  1486. X    time_t now;
  1487. X
  1488. X    (void) time( &now );
  1489. X    fprintf( fp_log, "%.12s ", ctime( &now )+4 );
  1490. X    fprintf( fp_log, fmt, arg1, arg2 );
  1491. X    fflush( fp_log );
  1492. X}
  1493. X
  1494. X/* Generate an log entry, with 'E'rror flagging (non-daemon mode), time-stamp,
  1495. X** and newsgroup name.
  1496. X*/
  1497. X/*VARARGS1*/
  1498. Xvoid
  1499. Xlog_error( fmt, arg1, arg2, arg3 )
  1500. Xchar *fmt;
  1501. Xlong arg1;
  1502. Xlong arg2;
  1503. Xlong arg3;
  1504. X{
  1505. X    log_entry( "%s: ", line );
  1506. X    fprintf( fp_log, fmt, arg1, arg2, arg3 );
  1507. X    fflush( fp_log );
  1508. X    if( *fmt == '*' ) {
  1509. X    grevious_error = TRUE;
  1510. X    if( !daemon_delay ) {
  1511. X        putchar( 'E' );
  1512. X    }
  1513. X    }
  1514. X    else {
  1515. X    if( !daemon_delay ) {
  1516. X        putchar( 'e' );
  1517. X    }
  1518. X    }
  1519. X}
  1520. X
  1521. X#ifndef RENAME
  1522. Xint
  1523. Xrename( old, new )
  1524. Xchar    *old, *new;
  1525. X{
  1526. X    struct stat st;
  1527. X
  1528. X    if( stat( old, &st ) == -1 ) {
  1529. X    return -1;
  1530. X    }
  1531. X    if( unlink( new ) == -1 && errno != ENOENT ) {
  1532. X    return -1;
  1533. X    }
  1534. X    if( link( old, new ) == -1 ) {
  1535. X    return -1;
  1536. X    }
  1537. X    if( unlink( old ) == -1 ) {
  1538. X    int e = errno;
  1539. X    (void) unlink( new );
  1540. X    errno = e;
  1541. X    return -1;
  1542. X    }
  1543. X    return 0;
  1544. X}
  1545. X#endif /*RENAME*/
  1546. SHAR_EOF
  1547. chmod 0640 mthreads.c ||
  1548. echo 'restore of mthreads.c failed'
  1549. Wc_c="`wc -c < 'mthreads.c'`"
  1550. test 28049 -eq "$Wc_c" ||
  1551.     echo 'mthreads.c: original size 28049, current size' "$Wc_c"
  1552. fi
  1553. # ============= mthreads.h ==============
  1554. if test -f 'mthreads.h' -a X"$1" != X"-c"; then
  1555.     echo 'x - skipping mthreads.h (File already exists)'
  1556. else
  1557. echo 'x - extracting mthreads.h (Text)'
  1558. sed 's/^X//' << 'SHAR_EOF' > 'mthreads.h' &&
  1559. X/* $Header: mthreads.h,v 4.3.3.2 90/08/20 16:44:29 davison Trn $
  1560. X**
  1561. X** $Log:    mthreads.h,v $
  1562. X** Revision 4.3.3.2  90/08/20  16:44:29  davison
  1563. X** New entries for new command-line interface.
  1564. X** 
  1565. X** Revision 4.3.3.1  90/06/20  22:55:27  davison
  1566. X** Initial Trn Release
  1567. X** 
  1568. X*/
  1569. X
  1570. X#ifdef lint
  1571. X#include "mt-lint.h"
  1572. X#endif
  1573. X#include "threads.h"
  1574. X
  1575. XEXT TOTAL total;
  1576. X
  1577. XEXT int processed_groups;
  1578. XEXT int added_articles, added_count;
  1579. XEXT int expired_articles, expired_count;
  1580. XEXT bool extra_expire INIT(FALSE);
  1581. XEXT bool caught_interrupt INIT(FALSE);
  1582. X
  1583. XEXT char *strings INIT(0);
  1584. XEXT WORD *subject_cnts INIT(0);
  1585. XEXT WORD *author_cnts INIT(0);
  1586. XEXT WORD *ids INIT(0);
  1587. X
  1588. XEXT SUBJECT **subject_array;
  1589. XEXT ROOT **root_array;
  1590. XEXT AUTHOR **author_array;
  1591. XEXT ARTICLE **article_array;
  1592. X
  1593. XEXT PACKED_ROOT p_root;
  1594. XEXT PACKED_ARTICLE p_article;
  1595. X
  1596. XEXT ROOT *root_root;
  1597. XEXT AUTHOR *author_root;
  1598. X
  1599. X#ifndef DOINIT
  1600. XEXT DOMAIN unk_domain;
  1601. X#else
  1602. XDOMAIN unk_domain = {
  1603. X    ".unknown.", NULL, NULL
  1604. X};
  1605. X#endif
  1606. X
  1607. Xint ngmatch(), onepatmatch();
  1608. X
  1609. Xvoid log_entry(), log_error();
  1610. X
  1611. Xvoid mybytemap();
  1612. Xint read_data(), write_data();
  1613. Xvoid dont_read_data(), process_data();
  1614. X
  1615. Xvoid process_articles();
  1616. X
  1617. Xchar *thread_name(), *file_exp(), *savestr();
  1618. X
  1619. X#ifndef lint
  1620. Xchar *safemalloc();
  1621. Xvoid free(), Free();
  1622. X#endif
  1623. X
  1624. X#define Nullart Null(ARTICLE*)
  1625. SHAR_EOF
  1626. chmod 0640 mthreads.h ||
  1627. echo 'restore of mthreads.h failed'
  1628. Wc_c="`wc -c < 'mthreads.h'`"
  1629. test 1279 -eq "$Wc_c" ||
  1630.     echo 'mthreads.h: original size 1279, current size' "$Wc_c"
  1631. fi
  1632. # ============= ndir.c ==============
  1633. if test -f 'ndir.c' -a X"$1" != X"-c"; then
  1634.     echo 'x - skipping ndir.c (File already exists)'
  1635. else
  1636. echo 'x - extracting ndir.c (Text)'
  1637. sed 's/^X//' << 'SHAR_EOF' > 'ndir.c' &&
  1638. X/* $Header: ndir.c,v 4.3.3.1 90/06/20 22:38:20 davison Trn $
  1639. X *
  1640. X * $Log:    ndir.c,v $
  1641. X * Revision 4.3.3.1  90/06/20  22:38:20  davison
  1642. X * Initial Trn Release
  1643. X * 
  1644. X * Revision 4.3.1.3  85/05/23  11:19:24  lwall
  1645. X * Oops, shouldn't have included sys/types.h again.
  1646. X * 
  1647. X * Revision 4.3.1.2  85/05/15  14:46:00  lwall
  1648. X * Changed short to ino_t, which may be ushort on some systems.
  1649. X * 
  1650. X * Revision 4.3.1.1  85/05/10  11:35:34  lwall
  1651. X * Branch for patches.
  1652. X * 
  1653. X * Revision 4.3  85/05/01  11:42:55  lwall
  1654. X * Baseline for release with 4.3bsd.
  1655. X * 
  1656. X */
  1657. X
  1658. X#include "EXTERN.h"
  1659. X#include "common.h"
  1660. X#include "INTERN.h"
  1661. X#include "ndir.h"
  1662. X
  1663. X#ifdef USENDIR
  1664. X/*
  1665. X * support for Berkeley directory reading routine on a V7 file system
  1666. X */
  1667. X
  1668. X/*
  1669. X * open a directory.
  1670. X */
  1671. XDIR *
  1672. Xopendir(name)
  1673. Xchar *name;
  1674. X{
  1675. X    register DIR *dirp;
  1676. X    register int fd;
  1677. X    char *malloc();
  1678. X
  1679. X    if ((fd = open(name, 0)) == -1)
  1680. X        return NULL;
  1681. X    if ((dirp = (DIR *)malloc(sizeof(DIR))) == NULL) {
  1682. X        close (fd);
  1683. X        return NULL;
  1684. X    }
  1685. X    dirp->dd_fd = fd;
  1686. X    dirp->dd_loc = 0;
  1687. X    return dirp;
  1688. X}
  1689. X
  1690. X/*
  1691. X * read an old style directory entry and present it as a new one
  1692. X */
  1693. X#ifndef pyr
  1694. X#define    ODIRSIZ    14
  1695. X
  1696. Xstruct    olddirect {
  1697. X    ino_t    od_ino;
  1698. X    char    od_name[ODIRSIZ];
  1699. X};
  1700. X#else    an Pyramid in the ATT universe
  1701. X#define    ODIRSIZ    248
  1702. X
  1703. Xstruct    olddirect {
  1704. X    long    od_ino;
  1705. X    short    od_fill1, od_fill2;
  1706. X    char    od_name[ODIRSIZ];
  1707. X};
  1708. X#endif
  1709. X
  1710. X/*
  1711. X * get next entry in a directory.
  1712. X */
  1713. Xstruct direct *
  1714. Xreaddir(dirp)
  1715. Xregister DIR *dirp;
  1716. X{
  1717. X    register struct olddirect *dp;
  1718. X    static struct direct dir;
  1719. X
  1720. X    for (;;) {
  1721. X        if (dirp->dd_loc == 0) {
  1722. X            dirp->dd_size = read(dirp->dd_fd, dirp->dd_buf,
  1723. X                DIRBLKSIZ);
  1724. X            if (dirp->dd_size <= 0)
  1725. X                return NULL;
  1726. X        }
  1727. X        if (dirp->dd_loc >= dirp->dd_size) {
  1728. X            dirp->dd_loc = 0;
  1729. X            continue;
  1730. X        }
  1731. X        dp = (struct olddirect *)(dirp->dd_buf + dirp->dd_loc);
  1732. X        dirp->dd_loc += sizeof(struct olddirect);
  1733. X        if (dp->od_ino == 0)
  1734. X            continue;
  1735. X        dir.d_ino = dp->od_ino;
  1736. X        strncpy(dir.d_name, dp->od_name, ODIRSIZ);
  1737. X        dir.d_name[ODIRSIZ] = '\0'; /* insure null termination */
  1738. X        dir.d_namlen = strlen(dir.d_name);
  1739. X        dir.d_reclen = DIRSIZ(&dir);
  1740. X        return (&dir);
  1741. X    }
  1742. X}
  1743. X
  1744. X/*
  1745. X * close a directory.
  1746. X */
  1747. Xvoid
  1748. Xclosedir(dirp)
  1749. Xregister DIR *dirp;
  1750. X{
  1751. X    close(dirp->dd_fd);
  1752. X    dirp->dd_fd = -1;
  1753. X    dirp->dd_loc = 0;
  1754. X    free(dirp);
  1755. X}
  1756. X
  1757. X#endif /* USENDIR */
  1758. SHAR_EOF
  1759. chmod 0640 ndir.c ||
  1760. echo 'restore of ndir.c failed'
  1761. Wc_c="`wc -c < 'ndir.c'`"
  1762. test 2251 -eq "$Wc_c" ||
  1763.     echo 'ndir.c: original size 2251, current size' "$Wc_c"
  1764. fi
  1765. # ============= ndir.h ==============
  1766. if test -f 'ndir.h' -a X"$1" != X"-c"; then
  1767.     echo 'x - skipping ndir.h (File already exists)'
  1768. else
  1769. echo 'x - extracting ndir.h (Text)'
  1770. sed 's/^X//' << 'SHAR_EOF' > 'ndir.h' &&
  1771. X/* $Header: ndir.h,v 4.3.2.1 90/04/17 15:28:13 sob Exp $
  1772. X *
  1773. X * $Log:    ndir.h,v $
  1774. X * Revision 4.3.2.1  90/04/17  15:28:13  sob
  1775. X * Altered to include correct directory include file.
  1776. X * 
  1777. X * Revision 4.3  85/05/01  11:43:00  lwall
  1778. X * Baseline for release with 4.3bsd.
  1779. X * 
  1780. X */
  1781. X
  1782. X#ifdef LIBNDIR
  1783. X#   include <ndir.h>
  1784. X#else
  1785. X#   ifndef USENDIR
  1786. X#    include DIRINC
  1787. X#   else
  1788. X
  1789. X#ifndef DEV_BSIZE
  1790. X#define    DEV_BSIZE    512
  1791. X#endif
  1792. X#define DIRBLKSIZ    DEV_BSIZE
  1793. X#define    MAXNAMLEN    255
  1794. X
  1795. Xstruct    direct {
  1796. X    long    d_ino;            /* inode number of entry */
  1797. X    short    d_reclen;        /* length of this record */
  1798. X    short    d_namlen;        /* length of string in d_name */
  1799. X    char    d_name[MAXNAMLEN + 1];    /* name must be no longer than this */
  1800. X};
  1801. X
  1802. X/*
  1803. X * The DIRSIZ macro gives the minimum record length which will hold
  1804. X * the directory entry.  This requires the amount of space in struct direct
  1805. X * without the d_name field, plus enough space for the name with a terminating
  1806. X * null byte (dp->d_namlen+1), rounded up to a 4 byte boundary.
  1807. X */
  1808. X#undef DIRSIZ
  1809. X#define DIRSIZ(dp) \
  1810. X    ((sizeof (struct direct) - (MAXNAMLEN+1)) + (((dp)->d_namlen+1 + 3) &~ 3))
  1811. X
  1812. X/*
  1813. X * Definitions for library routines operating on directories.
  1814. X */
  1815. Xtypedef struct _dirdesc {
  1816. X    int    dd_fd;
  1817. X    long    dd_loc;
  1818. X    long    dd_size;
  1819. X    char    dd_buf[DIRBLKSIZ];
  1820. X} DIR;
  1821. X#ifndef NULL
  1822. X#define NULL 0
  1823. X#endif
  1824. Xextern    DIR *opendir();
  1825. Xextern    struct direct *readdir();
  1826. Xextern    long telldir();
  1827. Xextern    void seekdir();
  1828. X#define rewinddir(dirp)    seekdir((dirp), (long)0)
  1829. Xextern    void closedir();
  1830. X
  1831. X#   endif
  1832. X#endif
  1833. SHAR_EOF
  1834. chmod 0640 ndir.h ||
  1835. echo 'restore of ndir.h failed'
  1836. Wc_c="`wc -c < 'ndir.h'`"
  1837. test 1491 -eq "$Wc_c" ||
  1838.     echo 'ndir.h: original size 1491, current size' "$Wc_c"
  1839. fi
  1840. exit 0
  1841. -- 
  1842. Gregory G. Woodbury @ The Wolves Den UNIX, Durham NC
  1843. UUCP: ...dukcds!wolves!ggw   ...mcnc!wolves!ggw           [use the maps!]
  1844. Domain: ggw@cds.duke.edu     ggw%wolves@mcnc.mcnc.org
  1845. [The line eater is a boojum snark! ]           <standard disclaimers apply>
  1846.