Subject: v23i011: Netnews transmission daemon, Part01/03 Newsgroups: comp.sources.unix Approved: rsalz@uunet.UU.NET X-Checksum-Snefru: ed5379d6 139a46d7 43496c0a 496142d4 Submitted-by: Chris Myers Posting-number: Volume 23, Issue 11 Archive-name: newsxd/part01 [ This is used at UUNET; is that becoming the seal of approval for Usenet software? --r$ ] The newsxd program is a configurable daemon for controlling the transmission of netnews. It allows the definition of multiple categories of service, by setting a number of parameters defining things such as how often news is to be transmitted, the number of news transmitters that can be active at one time, the maximum time a transmitter may spend sending netnews to a single host, and the maximum system load at which netnews transmitters may be started. newsxd can also be used to start up other programs which must be restarted upon exit, or which must be run at regular intervals with greater granularity than cron(8) allows. #! /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: README MANIFEST config.c newsxd.8 # Wrapped by rsalz@litchi.bbn.com on Fri Jul 13 15:03:56 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 1 (of 3)."' if test -f 'README' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'README'\" else echo shar: Extracting \"'README'\" \(1385 characters\) sed "s/^X//" >'README' <<'END_OF_FILE' XThe newsxd program is a configurable daemon for controlling the Xtransmission of netnews. It allows the definition of multiple Xcategories of service, by setting a number of parameters defining Xthings such as how often news is to be transmitted, the number of news Xtransmitters that can be active at one time, the maximum time a Xtransmitter may spend sending netnews to a single host, and the maximum Xsystem load at which netnews transmitters may be started. newsxd can Xalso be used to start up other programs which must be restarted upon Xexit, or which must be run at regular intervals with greater Xgranularity than cron(8) allows. X X newsxd Installation X XTo compile newsxd, you need to do the following things: X X1) Edit newsxd.h, and make any changes necessary to the default X locations for the newsxd data files. Also select whether you want to X have newsxd log to syslog or to it's own log file. X X2) Edit the Makefile and define the correct locations to store newsxd X and it's manpage on your system. X X3) make depend X X4) make install X X5) Create a newsxd.conf file and install it in the appropriate location X (the default is /usr/local/etc/newsxd.conf). Sample configuration files X are included as complex.conf and simple.conf. X X6) Turn off any nntpxmit's, sendbatch's, etc. so they don't conflict with X the ones started by newsxd. X X7) Run newsxd END_OF_FILE if test 1385 -ne `wc -c <'README'`; then echo shar: \"'README'\" unpacked with wrong size! fi # end of 'README' fi if test -f 'MANIFEST' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'MANIFEST'\" else echo shar: Extracting \"'MANIFEST'\" \(593 characters\) sed "s/^X//" >'MANIFEST' <<'END_OF_FILE' X File Name Archive # Description X---------------------------------------------------------- XREADME 1 XMANIFEST 1 This shipping list XMakefile 3 Xcomplex.conf 3 Xconfig.c 1 Xdefs.h 2 Xlog.c 3 Xmain.c 3 Xnewsxd.8 1 Xnewsxd.conf 3 Xnewsxd.h 3 Xpatchlevel.h 2 Xprocess.c 2 Xsimple.conf 2 Xutil.c 2 Xversion.c 2 END_OF_FILE if test 593 -ne `wc -c <'MANIFEST'`; then echo shar: \"'MANIFEST'\" unpacked with wrong size! fi # end of 'MANIFEST' fi if test -f 'config.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'config.c'\" else echo shar: Extracting \"'config.c'\" \(28527 characters\) sed "s/^X//" >'config.c' <<'END_OF_FILE' X/* X * #include X > X > Copyright (c) 1989 Washington University in Saint Louis, Missouri and X > Chris Myers. All rights reserved. X > X > Permission is hereby granted to copy, reproduce, redistribute or X > otherwise use this software as long as: (1) there is no monetary X > profit gained specifically from the use or reproduction of this X > software, (2) it is not sold, rented, traded, or otherwise marketed, X > (3) the above copyright notice and this paragraph is included X > prominently in any copy made, and (4) that the name of the University X > is not used to endorse or promote products derived from this software X > without the specific prior written permission of the University. X > THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR X > IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED X > WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. X > X */ X X#include "defs.h" X X/*************************************************************************/ X/* FUNCTION : getclass */ X/* PURPOSE : Return a pointer to the class structure given the name of */ X/* a class */ X/* ARGUMENTS : The name of a class */ X/*************************************************************************/ X Xstruct class * Xgetclass(classname) X char *classname; X X{ Xstruct class *classptr; X X if (strlen(classname) > MAXCLASSNAMELEN - 1) { X Dprintf("(getclass): classname %s is too long, no match\n", classname); X return((struct class *) NULL); X } X X foreach (classptr, classlist) { X Dprintf("(getclass): now checking class %s\n", classptr->classname); X if (strcmp(classname, classptr->classname) == 0) break; X } X X return(classptr); X X} X X/*************************************************************************/ X/* FUNCTION : addclass */ X/* PURPOSE : Add a class to or modify a class in the class list */ X/* ARGUMENTS : All the information needed to define a class... */ X/*************************************************************************/ X Xvoid Xaddclass(classname,maxxmits,interval,startint,maxload,ttl,ttlpenalty,nice,flags, debugflags) X char *classname, *debugflags; X int maxxmits, interval, startint, maxload, ttl, ttlpenalty, nice, flags; X X{ Xstruct class *classptr = classlist; Xchar *flagbuf; Xint loop, X newclass = 0; X X if (strlen(classname) > MAXCLASSNAMELEN - 1) { X logerr("(addclass) Class name %s too long, max %d - ignoring\n", X classname, MAXCLASSNAMELEN - 1); X return; X } X X if ((classptr = getclass(classname)) == NULL) { X Dprintf("(addclass): CALLOCing new class structure\n"); X classptr = (struct class *) calloc(1, sizeof(struct class)); X if (classptr == NULL) { X logerr("(addclass) Can't calloc struct class for class %s\n", X classname); X return; X } X for (loop = 0; loop < MAXCLASSXMITTERS; loop++) X classptr->slots[loop] = '.'; X newclass++; X } else if (classptr->valid == 1) { X logerr("(addclass) Duplicated class %s\n", classname); X return; X } X X /* X * Build class structure for this class and insert at the head of the X * class list X */ X X (void) strcpy(classptr->classname, classname); X classptr->maxxmits = maxxmits; X classptr->options.maxload = maxload; X classptr->options.interval = interval; X classptr->options.startint = startint; X classptr->options.ttl = ttl; X classptr->options.deltanice = nice; X classptr->options.ttlpenalty = ttlpenalty; X classptr->valid = 1; X classptr->xmitsernum = 0; X classptr->members = 0; X classptr->debugargc = 0; X X if (debug && strlen(debugflags) > 0) { X if ((flagbuf = (char *) calloc(1, strlen(debugflags) + 1)) != NULL) { X (void) strcpy(flagbuf, debugflags); X while (1) { X classptr->debugargv[classptr->debugargc++] = flagbuf; X if ((flagbuf = STRCHR(flagbuf, '|')) == (char *) NULL) break; X *flagbuf++ = '\0'; X } X Dprintf("(addclass) %s: debugargc = %d\n", classname, X classptr->debugargc); X } X } X X /* X * Set all of the class flags. flag is a bitmap. X */ X X for (loop = 0; loop < MAXCLASSFLAGS; loop++) { X if (flags & (1 << loop)) X classptr->flags[loop] = 1; X else X classptr->flags[loop] = 0; X Dprintf("(addclass): flags[%d] = %d\n", loop, X classptr->flags[loop]); X } X X /* X * Check to see if we are just overwriting a class description, if so don't X * wipe the pointer to the next entry in the list. X */ X X if (newclass) { X classptr->next = classlist; X classlist = classptr; X } X X Dprintf("(addclass): classlist = %d\n", classlist); X} X X/*************************************************************************/ X/* FUNCTION : addhost */ X/* PURPOSE : Add a host to the list of hosts to transmit to */ X/* ARGUMENTS : Name of the host, class name, start and stop times */ X/*************************************************************************/ X Xvoid Xaddhost(hostname, classname, times, deltanice, maxload, interval, startint, X ttl, ttlpenalty, flags) X X char *hostname, *classname, *times, *flags; X int deltanice, maxload, interval, startint, ttl, ttlpenalty; X X{ Xstruct host *hostptr, X *loopptr, X *lastptr = NULL; Xstruct class *classptr; Xint loop; Xchar *flagbuf; X X if (strlen(hostname) > MAXHOSTNAMELEN - 1) { X logerr("(addhost) Host name %s too long, max %d - ignoring\n", X classname, MAXHOSTNAMELEN - 1); X return; X } X X if (strlen(times) > MAXTIMENAMELEN - 1) { X logerr("(addhost) Host time %s too long, max %d - ignoring\n", X classname, MAXTIMENAMELEN - 1); X return; X } X X if ((classptr = getclass(classname)) == NULL) { X logerr("(addhost) Host's (%s) class %s doesn't exist - ignoring host\n", X hostname, classname); X return; X } X X if (classptr->valid == 0) { X logerr("(addhost) Host's (%s) class %s is invalid - ignoring host\n", X hostname, classname); X return; X } X X if (strcmp(classname, "DEFAULT") == 0) { X logerr("(addhost) Host (%s) can't be a member of class DEFAULT\n", X hostname); X return; X } X X lastptr = NULL; X foreach (hostptr, hostlist) { X Dprintf("(addhost): now checking host %s\n", hostptr->hostname); X if (strcmp(hostname, hostptr->hostname) == 0) break; X lastptr = hostptr; X } X X if (hostptr != NULL) { X if (hostptr->valid == 1) { X logerr("(addhost) Duplicated host %s\n", hostname); X return; X } X if (lastptr != NULL) { X lastptr->next = hostptr->next; X Dprintf("(addhost) host %s next was %s, now %s\n", lastptr->hostname, X hostptr->hostname, hostptr->next ? hostptr->next->hostname : NULL); X } else X hostlist = hostptr->next; X } else { X Dprintf("(addhost): CALLOCing new host structure\n"); X hostptr = (struct host *) calloc(1, sizeof(struct host)); X if (hostptr == NULL) { X logerr("(addhost) Can't calloc struct host for host %s\n", X hostname); X return; X } X } X X (void) strcpy(hostptr->hostname, hostname); X (void) strcpy(hostptr->class, classname); X (void) strcpy(hostptr->times, times); X hostptr->valid = 1; X hostptr->options.deltanice = deltanice; X hostptr->options.maxload = maxload; X hostptr->options.interval = interval; X hostptr->options.startint = startint; X hostptr->options.ttl = ttl; X hostptr->options.ttlpenalty = ttlpenalty; X hostptr->xmitsernum = 0; X hostptr->xargc = 0; X classptr->members++; X X if (flags && strlen(flags) > 0) { X if ((flagbuf = (char *) calloc(1, strlen(flags) + 1)) != NULL) { X (void) strcpy(flagbuf, flags); X while (1) { X hostptr->xargv[hostptr->xargc++] = flagbuf; X if ((flagbuf = STRCHR(flagbuf, '|')) == (char *) NULL) break; X *flagbuf++ = '\0'; X } X } X } X X for (loop = 0; loop < hostptr->xargc; loop++) X Dprintf("host %s: flag[%d] = %s\n", hostname, loop, hostptr->xargv[loop]); X X /* X * If this is a new host, do a two-key insertion sort based on the X * hostname and name of the transmission class. Makes things look nice X * in the status display... X */ X X lastptr = NULL; X X if (hostlist == NULL) { X Dprintf("(addhost): %s IS hostlist now\n", hostname); X hostlist = hostptr; X } else { X foreach (loopptr, hostlist) { X if (((strcmp(hostptr->hostname, loopptr->hostname) < 0) & X (strcmp(hostptr->class, loopptr->class) == 0)) | X (strcmp(hostptr->class, loopptr->class) < 0)) { X Dprintf("(addhost): Inserting %s before %s\n", hostname, X loopptr->hostname); X hostptr->next = loopptr; X if (lastptr != NULL) lastptr->next = hostptr; X if (loopptr == hostlist) hostlist = hostptr; X break; X } X lastptr = loopptr; X } X if (loopptr == NULL) { X Dprintf("(addhost): appending host %s to hostlist\n", hostname); X lastptr->next = hostptr; /* append to current host list */ X hostptr->next = NULL; X } X } X Dprintf("(addhost): hostlist = %d\n", hostlist); X} X X/*************************************************************************/ X/* FUNCTION : make_invalid */ X/* PURPOSE : Mark all hosts and classes as invalid */ X/* ARGUMENTS : none */ X/*************************************************************************/ X Xvoid Xmake_invalid() X X{ Xstruct class *classptr, X *nextclass; Xstruct host *hostptr, X *nexthost; X X dprintf("(make_invalid) marking all classes as invalid\n"); X for (classptr = classlist; classptr != NULL; classptr = nextclass) { X Dprintf("(make_invalid) marking class %s as invalid\n", X classptr->classname); X classptr->valid = 0; X classptr->members = 0; X nextclass = classptr->next; X } X X dprintf("(make_invalid) marking all hosts as invalid\n"); X for (hostptr = hostlist; hostptr != NULL; hostptr = nexthost) { X Dprintf("(make_invalid) marking host %s as invalid\n", hostptr->hostname); X hostptr->valid = 0; X nexthost = hostptr->next; X } X X} X X/*************************************************************************/ X/* FUNCTION : clear_invalid */ X/* PURPOSE : Remove all hosts and classes which are not valid anymore */ X/* ARGUMENTS : none */ X/*************************************************************************/ X Xvoid Xclear_invalid() X X{ Xstruct class *lastclass = NULL, X *classptr, X *nextclass; Xstruct host *lasthost = NULL, X *hostptr, X *nexthost; Xint loop; X X dprintf("(clear_invalid) clearing all invalid hosts\n"); X for (hostptr = hostlist; hostptr != NULL; hostptr = nexthost) { X nexthost = hostptr->next; X if (hostptr->valid == 0) { X if (hostptr->pid > 0) { X dprintf("(clear_invalid): killing transmitter for host %s\n", X hostptr->hostname); X if (kill(hostptr->pid, SIGTERM) != 0) xmit_done(-hostptr->pid); X } X Dprintf("(clear_invalid): clearing host %s\n", hostptr->hostname); X if (hostlist == hostptr) hostlist = nexthost; X (void) free(hostptr); X if (lasthost) lasthost->next = nexthost; X } else { X lasthost = hostptr; X } X } X X dprintf("(clear_invalid) clearing all invalid classes\n"); X for (classptr = classlist; classptr != NULL; classptr = nextclass) { X nextclass = classptr->next; X if (classptr->valid == 0) { X Dprintf("(clear_invalid): clearing class %s\n", classptr->classname); X for (loop = 0; loop < classptr->xargc; loop++) { X Dprintf("(clear_invalid): clearing class %s argv %d\n", X classptr->classname, loop); X (void) free(classptr->xargv[loop]); X } X if (classlist == classptr) classlist = nextclass; X (void) free(classptr); X if (lastclass) lastclass->next = nextclass; X } else { X lastclass = classptr; X } X } X} X X/*************************************************************************/ X/* FUNCTION : read_config */ X/* PURPOSE : Read the newsxd configuration file */ X/* ARGUMENTS : none */ X/*************************************************************************/ X Xvoid Xread_config(sig) X int sig; X X{ XFILE *config; X Xchar line[MAXPATHLEN], /* buffer used for inputting lines */ X buf[10][MAXPATHLEN], /* buffers for various command parameters */ X *comment; /* used to trim comments from input lines */ X Xint loop, X deltanice, /* Amount to change proc's nice for xmit */ X optioncnt, /* number of parameters found by sscanf */ X maxxmits, /* number of allowed simul. transmitters */ X interval, /* interval between xmits to a single host */ X startint, /* interval between starting xmits 4 a class */ X maxload, /* max allowed load for starting new xmits */ X ttl, /* maximum time-to-live for a transmitter */ X ttlpenalty, /* time penalty for exceeding ttl */ X flags; /* option flags for this transmission class */ X Xstruct class *classptr; /* used to traverse the class list */ Xstruct stat statbuf; X X if (!(config = fopen(configfile, "r"))) { X logerr("Couldn't open config file (%s) - aborting\n", configfile); X (void) exit(1); X } X X /* X * Mark all hosts and classes as undefined, but don't delete 'em yet X */ X X if (sig) log(LOG_INFO, "reinitializing\n"); X X make_invalid(); X X CONFIGCHANGED = 1; X#ifdef FAKESYSLOG X CONFIGCHANGEDFILE = 1; X#endif X daemon_idle = 0; /* Allow newsxd to begin running the queue again */ X X /* X * Reset all of the various data files to their default location X */ X X (void) strcpy(batchfile, default_batchfile); X (void) strcpy(workfile, default_workfile); X (void) strcpy(xmitlogs, default_xmitlogs); X X locking = 0; /* locking defaults to OFF */ X tallying = 0; /* use of the tally file defaults to OFF */ X queueinterval = 60; /* default queue run interval of 1 minute */ X X /* X * Add the DEFAULT pseudo-class so that a default xmitter can be specified X */ X X addclass("DEFAULT", 0, 0, 0, 0, 0, 0, 0, 0, 0); X X while (fgets(line, 255, config) != NULL) { X X if ((comment = index(line, '#')) != NULL) X (void) strcpy(comment, "\n"); X X Dprintf("> %s", line); X X if (sscanf(line, "tallyfile %s\n", tallyfile) > 0) { X dprintf("tallyfile is %s\n", tallyfile); X tallying = 1; X } X X if (sscanf(line, "batchfile %s\n", batchfile) > 0) X dprintf("batchfile is %s\n", batchfile); X X if (sscanf(line, "xmitlogs %s\n", xmitlogs) > 0) { X dprintf("xmitlogs is %s\n", xmitlogs); continue; } X X if (sscanf(line, "workfile %s\n", workfile) > 0) X dprintf("workfile is %s\n", workfile); X X if (sscanf(line, "queueinterval %d\n", &queueinterval) > 0) X dprintf("queueinterval is %d\n", queueinterval); X X if ((optioncnt = sscanf(line, "class %s %s %s %s %s %s %s %s", buf[0], X buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], buf[8])) > 0) { X X maxxmits = 1; X interval = 60; X startint = 0; X maxload = 99; X ttl = 999999; X ttlpenalty = 0; X flags = 0; X deltanice = 0; X *buf[9] = '\0'; X X Dprintf("optioncnt = %d\n", optioncnt); X X for (loop = 1; loop < optioncnt; loop++) { X (void) sscanf(buf[loop], "maxxmits=%d", &maxxmits); X (void) sscanf(buf[loop], "maxload=%d", &maxload); X (void) sscanf(buf[loop], "nice=%d", &deltanice); X (void) sscanf(buf[loop], "interval=%d/%d", &interval, &startint); X (void) sscanf(buf[loop], "ttl=%d/%d", &ttl, &ttlpenalty); X (void) sscanf(buf[loop], "debug=%s", buf[9]); X if (strcmp(buf[loop], "noworkfile") == 0) flags |= (1< 0) { X X classptr = getclass(buf[0]); X if (!classptr | (classptr->valid == 0)) { X logerr("(read_config) xmit class for invalid class %s ignored\n", X buf[0]); X } X X if (stat(buf[1], &statbuf) != 0) { X logerr("(read_config) xmit program %s for class %s unavailable\n", X buf[1], buf[0]); X logerr("(read_config) no xmit for class %s, invalidating class\n", X buf[0]); X classptr->valid = 0; X continue; X } X X (void) strcpy(classptr->xpath, buf[1]); X for (loop = 2; loop < optioncnt; loop++) { X Dprintf("class %s: setting xmit parm %d = %s\n", X classptr->classname, loop - 2, buf[loop]); X classptr->xargv[loop-2] = (char *) malloc(strlen(buf[loop])+1); X (void) strcpy(classptr->xargv[loop-2], buf[loop]); X } X classptr->xargv[optioncnt - 2] = NULL; X classptr->xargc = optioncnt - 2; X } X X if ((optioncnt = sscanf(line, "host %s %s %s %s %s %s %s %s", buf[0], X buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7])) > 0) { X X Dprintf("optioncnt = %d\n", optioncnt); X X interval = 0; X startint = 0; X maxload = 0; X ttl = 0; X ttlpenalty = 0; X deltanice = 0; X *buf[9] = '\0'; X X for (loop = 3; loop < optioncnt; loop++) { X (void) sscanf(buf[loop], "maxload=%d", &maxload); X (void) sscanf(buf[loop], "interval=%d/%d", &interval, &startint); X (void) sscanf(buf[loop], "ttl=%d/%d", &ttl, &ttlpenalty); X (void) sscanf(buf[loop], "nice=%d", &deltanice); X (void) sscanf(buf[loop], "flags=%s", buf[9]); X } X X dprintf("host %s, class %s, times %s, deltanice %d, maxload %d, intvl %d/%d, ttl %d/%d, flags=\"%s\"\n", X buf[0], buf[1], buf[2], deltanice, maxload, interval, startint, X ttl, ttlpenalty, buf[9]); X X addhost(buf[0], buf[1], buf[2], deltanice, maxload, interval, startint, X ttl, ttlpenalty, buf[9]); X } X } X X /* X * Remove any hosts or classes not defined anymore X */ X X clear_invalid(); X X (void) fclose(config); X} X X/*************************************************************************/ X/* FUNCTION : dump_config */ X/* PURPOSE : Write the current configuration and status of newsxd to */ X/* a file */ X/* ARGUMENTS : none */ X/*************************************************************************/ X Xvoid Xdump_config() X X{ Xstruct host *hostptr; Xstruct class *classptr; X Xchar lastxmittime[30], /* bunches of buffers for ctime */ X buf1[30], X buf2[30], X pid[30]; /* holds (pid) or "none" */ X XFILE *status; X Xint curtime; X X if ((debug <= 0) || (DEBUG <= 0)) { X status = fopen(statusfile, "w"); X if (status == NULL) { X logerr("Can't open statusfile (%s, \"w\")\n", statusfile); X return; X } X } else X status = stderr; X X curtime = time((long *) NULL); X (void) fprintf(status, "%s", ctime(&curtime)); X if (daemon_idle) (void) fprintf(status, "Newsxd is idled\n"); X X if (classlist == NULL) { X (void) fprintf(status, "No valid classes defined!\n"); X return; X } X X (void) fputc('\n', status); X foreach (classptr, classlist) { X if (strcmp(classptr->classname, "DEFAULT") == 0) continue; X X (void) fprintf(status, "Class %-7.7s : %2d active transmitters (%2d max)", X classptr->classname, classptr->curxmits, classptr->maxxmits); X if (getla() > classptr->options.maxload) X (void) fprintf(status, " [no new xmits, load %d > %d max]", X getla(), classptr->options.maxload); X (void) fputc('\n', status); X } X X (void) fputc('\n', status); X (void) fprintf(status, " Service Valid Start Last XMIT Xmitter Why Not\n"); X (void) fprintf(status, "Hostname Class Times Started pid Running\n"); X (void) fprintf(status, "----------------------- ------- ------------- --------- ------- ----------\n"); X X if (hostlist == NULL) { X (void) fprintf(status, "No valid hosts defined!\n"); X } X X foreach (hostptr, hostlist) { X if (hostptr->lasttime == NULL) X (void) strcpy(lastxmittime, "* NEVER *"); X else { X (void) sscanf(ctime(&hostptr->lasttime), "%s %*s %*s %s", buf1, buf2); X (void) sprintf(lastxmittime, "%s %5.5s", buf1, buf2); X } X X (void) sprintf(pid, (hostptr->pid) ? "%d" : "none", hostptr->pid); X X (void) fprintf(status, "%-23.23s %-7s %-13.13s %9s %5s ", X hostptr->hostname, hostptr->class, hostptr->times, lastxmittime, pid); X X if (daemon_idle && hostptr->whynot != WN_RUNNING) X (void) fputs("newsxd Idl", status); X else X (void) fputs(wnlist[hostptr->whynot], status); X (void) fputc('\n', status); X } X if ((debug <= 0) || (DEBUG <= 0)) (void) fclose(status); X} X X/*************************************************************************/ X/* FUNCTION : dump_info */ X/* PURPOSE : Dump newsxd's data structures to a file. */ X/* ARGUMENTS : none */ X/*************************************************************************/ X Xvoid Xdump_info() X X{ Xstruct host *hostptr; Xstruct class *classptr; X Xlong curtime; X Xint loop, which; X XFILE *status; X Xchar outfile[MAXPATHLEN]; X X if ((debug <= 0) || (DEBUG <= 0)) { X (void) sprintf(outfile, "%s.dump", statusfile); X status = fopen(outfile, "w"); X if (status == NULL) { X logerr("Can't open statusfile (%s, \"w\")\n", statusfile); X return; X } X } else X status = stderr; X X curtime = time((long *) NULL); X X (void) fprintf(status, "Newsxd data structures at %s\n", ctime(&curtime)); X X (void) fprintf(status, "debug is %s, DEBUG is %s, newsxdebug is %s\n", X debug ? "On" : "Off", DEBUG ? "On" : "Off", newsxdebug ? "On" : "Off"); X X (void) fprintf(status, "Queue runs occur every %d seconds, newsxd %s idled\n\n", X queueinterval, daemon_idle ? "IS" : "is not"); X X (void) fprintf(status, "Active transmitter PID mappings:\n"); X X which = 0; X for (loop = 0; loop < MAXXMITTERS; loop++) { X if (pidlist[loop] != 0) { X if (!which) (void) fputc('\n', status); X (void) fprintf(status, "pid %5d --> %-25.25s ", pidlist[loop], X pidmap[loop]->hostname); X which = !which; X } X } X X (void) fputs("\n\n", status); X X (void) fprintf(status, "batchfile is %s\n", batchfile); X (void) fprintf(status, "workfile is %s\n", workfile); X (void) fprintf(status, "xmitlogs is %s\n", xmitlogs); X (void) fprintf(status, "newsxd status written to %s\n", statusfile); X (void) fprintf(status, "newsxd pid written to %s\n", pidfile); X (void) fprintf(status, "newsxd config file is %s\n", configfile); X (void) fprintf(status, "\n"); X X if (classlist == NULL) (void) fprintf(status, "NO DEFINED CLASSES\n"); X X foreach (classptr, classlist) { X (void) fprintf(status, "CLASS %s (%02d members)\n\n", classptr->classname, X classptr->members); X (void) fprintf(status, " xmit prog %s", classptr->xpath); X for (loop = 0; loop < classptr->xargc; loop++) X (void) fprintf(status, " %s", classptr->xargv[loop]); X (void) fprintf(status, "\n"); X (void) fprintf(status, " xmitters %02d running, %02d max\n", X classptr->curxmits, classptr->maxxmits); X (void) fprintf(status, " last xmit %s", ctime(&classptr->laststart)); X (void) fprintf(status, " sernum %d\n", classptr->xmitsernum); X X (void) fprintf(status, " options "); X if (classptr->options.deltanice) X (void) fprintf(status, " nice=%d", classptr->options.deltanice); X if (classptr->options.interval) X (void) fprintf(status, " interval=%d", classptr->options.interval); X if (classptr->options.startint) X (void) fprintf(status, " startint=%d", classptr->options.startint); X if (classptr->options.ttl) X (void) fprintf(status, " ttl=%d", classptr->options.ttl); X if (classptr->options.ttlpenalty) X (void) fprintf(status, " ttlpenalty=%d", classptr->options.ttlpenalty); X if (classptr->options.maxload) X (void) fprintf(status, " maxload=%d", classptr->options.maxload); X X (void) fputc('\n', status); X (void) fprintf(status, " slots "); X for (loop = 0; loop < MAXCLASSXMITTERS; loop++) { X if ((loop > 0) & ((loop % 50) == 0)) X (void) fputs("\n ", status); X (void) fputc(classptr->slots[loop], status); X } X X (void) fputs("\n\n", status); X } X X if (hostlist == NULL) (void) fprintf(status, "NO DEFINED HOSTS\n"); X X foreach (hostptr, hostlist) { X (void) fprintf(status, "HOST %s\n", hostptr->hostname); X X (void) fprintf(status, " class %s\n", hostptr->class); X (void) fprintf(status, " xmit times %s\n", hostptr->times); X (void) fprintf(status, " xmit pid %d\n", hostptr->pid); X (void) fprintf(status, " last xmit %s", hostptr->lasttime ? X ctime(&hostptr->lasttime) : "NEVER\n"); X (void) fprintf(status, " penalty to %s", hostptr->penaltytime ? X ctime(&hostptr->penaltytime) : "NONE\n"); X (void) fprintf(status, " xmitsernum %d\n", hostptr->xmitsernum); X (void) fprintf(status, " whynot %s (%d)\n", X wnlist[hostptr->whynot], hostptr->whynot); X (void) fprintf(status, " xmit slot %d\n", hostptr->classslot); X (void) fprintf(status, " xmit flags"); X X for (loop = 0; loop < hostptr->xargc; loop++) X (void) fprintf(status, " %s", hostptr->xargv[loop]); X X (void) fputc('\n', status); X (void) fprintf(status, " options "); X if (hostptr->options.deltanice) X (void) fprintf(status, " nice=%d", hostptr->options.deltanice); X if (hostptr->options.interval) X (void) fprintf(status, " interval=%d", hostptr->options.interval); X if (hostptr->options.startint) X (void) fprintf(status, " startint=%d", hostptr->options.startint); X if (hostptr->options.ttl) X (void) fprintf(status, " ttl=%d", hostptr->options.ttl); X if (hostptr->options.ttlpenalty) X (void) fprintf(status, " ttlpenalty=%d", hostptr->options.ttlpenalty); X if (hostptr->options.maxload) X (void) fprintf(status, " maxload=%d", hostptr->options.maxload); X X (void) fputs("\n\n", status); X } X X if ((debug <= 0) || (DEBUG <= 0)) (void) fclose(status); X} END_OF_FILE if test 28527 -ne `wc -c <'config.c'`; then echo shar: \"'config.c'\" unpacked with wrong size! fi # end of 'config.c' fi if test -f 'newsxd.8' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'newsxd.8'\" else echo shar: Extracting \"'newsxd.8'\" \(17954 characters\) sed "s/^X//" >'newsxd.8' <<'END_OF_FILE' X.TH newsxd 8 X.SH NAME Xnewsxd \- daemon for managing the transmission of news X.SH SYNTAX X.B newsxd X[\-debug] [\-DEBUG] [\-newsxdebug] [\-c \fIfile\fR] [\-v] X[\-pid \fIfile\fR\|] [\-status \fIfile\fR] [\-log \fIfile\fR] X.br X.SH DESCRIPTION XThe X.I newsxd Xprogram Xis a configurable daemon for controlling the transmission Xof netnews. It allows the definition of multiple categories of service, Xby setting a number of parameters defining things such as how often news is to Xbe transmitted, the number of news transmitters that can be active at one Xtime, the maximum time a transmitter may spend sending netnews to a single Xhost, and the maximum system load at which netnews transmitters may be Xstarted. X.I newsxd Xcan also be used to start up other programs which must be restarted Xupon exit, or which must be run at regular intervals with greater granularity Xthan X.I cron(8) Xallows. X.PP XWhen starting up, the X.I newsxd Xprogram reads a configuration file X(usually \fI/usr/local/etc/newsxd.conf\fR) which specifies all of the Xclasses of service and their characteristics, where certain data files Xare to be stored and what hosts netnews is to be transmitted to. X.PP X.I newsxd Xis a daemon, and while it replaces such programs as X.I nntpsend, Xit is not meant to be run from X.I cron(8), Xbut rather from /etc/rc.local. If you have multiple copies of X.I newsxd Xrunning the results will be unpredictable. X.SH OPTIONS X.IP -debug 15 XEnables debugging. If debugging is enabled, X.I newsxd Xwill not switch to daemon mode (i.e. will not detach itself from the Xcontrolling terminal). X.IP -DEBUG XEnables painfully verbose debugging. X.IP -newsxdebug XEnables debugging when running X.I nntpxmit. XAlso causes X.I nntpxmit Xto be run synchronously X.I (newsxd Xwill wait for the X.I nntpxmit Xto complete before continuing with other hosts). X.IP "-c \fIfile\fR" XSpecifies an alternate configuration file for X.I newsxd Xto load. X.IP "-pid \fIfile\fR" XSpecifies an alternate file for X.I newsxd Xto write its PID into when starting up. X.IP "-status \fIfile\fR" XSpecifies an alternate file for X.I newsxd Xto write its status into upon receipt of a QUIT signal. X.IP "-log \fIfile\fR" XSpecifies an alternate file for X.I newsxd Xto write its logging information into when FAKESYSLOG is being used. X.IP "-v" XDisplays the version of newsxd that you are running. \fINewsxd\fR Xdoes NOT start running if the -v switch is specified. X.RE X.PP XThe configuration file for X.I newsxd Xconsists of commands from the list below. Comments are preceded by Xpound (\#) signs and continue to the end of the line. Case is significant. X.PP X Xclass \fItype [options]\fR X Xdefines a class of service. \fItype\fR can be any printable string less than Xeight characters long (0, 5, 9, high, low, boring, etc.) There are a number Xof options that can be specified (as a space-separated list) that will modify Xthe behavior of a service class: X.RS +.5i X.IP maxxmits=\fIn\fR 15 Xspecifies the maximum number of netnews transmitters that X.I newsxd Xwill allow to be running simultaneously for this service class. The Xdefault value is 1. X.IP interval=\fIn[/m]\fR Xspecifies the start-to-start interval (in seconds) between successive Xinvocations of a netnews transmitter for a single host. The default value Xis 60 seconds. The optional parameter \fIm\fR specifies the number of Xseconds to wait between starting new transmitters for ANY host in this Xservice class; this is useful to prevent many transmitters from being Xstarted simultaneously and spiking the system load. The intra-class Xstartup interval, \fIm\fR, should not divide the \fIqueueinterval\fR X(below) evenly. X.IP maxload=\fIn\fR Xdefines the maximum load at which X.I newsxd Xwill start transmitters for this service class. If the load passes beyond Xthis value, no new transmitters will be started until the load drops. Any Xcurrently running transmitters will not be affected. X.IP ttl=\fIn[/m]\fR Xgives the maximum time-to-live in seconds for any invocation of a netnews Xtransmitter. The default value is 999999 seconds (about 11 days). The Xoptional parameter, \fIm\fR, gives the number of seconds to penalize a Xtransmitter that has exceeded its ttl; this is the number of seconds to Xprevent a new transmitter from starting up for the penalized host. X.IP noworkfile XNormally the article batch file for the host is renamed before executing Xthe netnews transmitter as defined by the \fIworkfile\fR below. This option Xcauses the renaming to be bypassed. X.IP nobatchfile XNormally an article batch file must exist for the host before X.I newsxd Xwill execute the transmitter for the host. If this option is defined, Xthe check for a batch file will be bypassed. X.RE X.IN -5 X.PP X Xxmit \fIclassname programpath programname [parameters]\fR X Xdefines an alternate netnews transmission program, for which command-line Xoptions can be specified. You *MUST* define a transmission program for the Xclass X.I DEFAULT, Xwhich will be used as the default news transmitter for all other classes. XSee the example configuration shown below. \fINewsxd\fR will use the Xenvironment variable PATH that it was invoked with to search for the program Xif an explicit path is not given. X.RS +.5i X.IP classname 15 Xis the name of an already-defined service class which is to Xuse this transmission program. X.IP programpath Xis the path of the file to execute X.IP programname Xis the name to be passed to the program as argv[0]; Xusually the last component of \fIprogrampath\fR. X.IP [parameters] Xoptional parameters and options (up to nine) to be Xpassed to the news transmitter. In X.I parameters, Xthe following conversions will be made: X.RS 15 X.IP %f 5 Xwill be replaced with the per-host flags specified by the X.I host Xcommand (see below). X.IP %h Xwill be replaced with the host name. X.IP %w Xwill be replaced with the name of the workfile for the host. X.IP %b Xwill be replaced with the name of the batchfile for the host. X.IP %s Xwill be replaced by an integer which is unique to all of the active Xtransmitters for that class. Newsxd will select the lowest unused integer Xeach time a transmitter is started. These 'slot numbers' start at 0. X.RE X.RE X.PP X Xqueueinterval \fIseconds\fR X Xspecifies the number of seconds between "queue runs". During each queue Xrun X.I newsxd Xwill check each host to see if a netnews transmitter can be started for Xthat host. The queueinterval should be at least as small as the smallest Xinterval specified in all of the \fIclass\fR definitions. The default value Xfor \fIqueueinterval\fR is 60 seconds. X.PP X Xbatchfile \fIfile\fR X Xspecifies the file path where the batch files (files listing article paths Xor IDs to be used by a netnews transmitter) are stored. A %s in \fIfile\fR Xwill be replaced by the name of the current host. Unless the X.I nobatchfile Xoption was specified for the host's service class, this Xfile must exist before a netnews transmitter will be executed for the host. XThe default value for \fIbatchfile\fR is defined in newsxd.h and is normally X\fI/usr/spool/batch/%s\fR. X.PP X Xworkfile \fIfile\fR X Xspecifies the file path which the batch files are to be renamed to before Xexecuting the netnews transmitter. A %s in \fIfile\fR will be replaced by Xthe name of the current host. If the \fInoworkfile\fR option was specified Xfor this host's service class, the batch file will NOT be renamed. The Xdefault value for \fIworkfile\fR is defined in newsxd.h and is normally X\fI/usr/spool/batch/%s.work\fR. X.PP X Xxmitlogs \fIfile\fR X Xwhere to log the output of each netnews transmitter. If you need to keep Xthe information printed by a transmitter, use this option. If you don't Xneed to use up the disk space, leave \fIxmitlogs\fR undefined and output Xwill be sent to /dev/null. X.PP X Xhost \fIhostname classname xmittimes [options]\fR X Xdefines a host which to which X.I newsxd Xwill transmit netnews. X.RS +.5i X.IP hostname 15 XThe name of the host to pass to the netnews transmitter. X.IP classname XThe name of the service class that this host is in. X.IP xmittimes XThe time(s) to allow new transmitters to start for this host. See the Xmanpage for X.I L.sys(5) Xfor the format of X.I xmittimes. X.IP [options] XThere are some options which can be used to modify the behavior of the Xnetnews transmitter for a host: X.RS +.5i X.IP nice=\fIn\fR 15 XThis optional value is added to the \fInice(3)\fR of the netnews Xtransmitter. To be able to run at higher priorities (a lower \fInice\fR Xvalue), X.I newsxd Xmust be X.I setuid(3) Xroot; otherwise X.I newsxd Xshould be X.I setuid(3) Xnews. If no X\fIdeltanice\fR value is specified, the nice of the transmitter is not Xmodified by X.I newsxd. X.IP flags=\fIn\fR 15 XUsed to specify optional flags to be passed to the netnews transmitter for Xthe host. The flags must be separated by vertical bars (|), not spaces, Xin the X.I newsxd.conf Xfile. Note that "-l something" would be TWO flags and you would say Xflags=-l|something. X.I Newsxd Xwill automatically separate the flags before calling the netnews Xtransmitter. XXXX X.IP ttl=\fIn[/m]\fR XThe \fIttl\fR option may be used to override the default value established Xby the \fIclass\fR command (described above). X.IP interval=\fIn[/m]\fR XThe \fIinterval\fR option may be used to override the default value established Xby the \fIclass\fR command (described above). X.IP maxload=\fIn\fR XThe \fImaxload\fR option may be used to override the default value established Xby the \fIclass\fR command (described above). X.IP maxxmits=\fIn\fR XThe \fImaxxmits\fR option may be used to override the default value established Xby the \fIclass\fR command (described above). X.RE X.IN -5 X.PP X.SH "SIMPLE EXAMPLE" XThe following is an example of a simple configuration file: X X.nf X.ft CW X# Define a service class that just runs an nntpxmit to each host X# every hour. Don't run nntpxmit if the load goes over 8. X Xclass normal maxxmits=1 interval=3600 maxload=8 X X# Define the default news transmitter Xxmit DEFAULT /usr/local/lib/news/nntpxmit nntpxmit %h:%w X X# Check the list of hosts every 10 minutes to see if we can send news to X# someone yet. Xqueueinterval 600 X X# In all of the following options, %s is replaced by the host name of the X# system being sent to. X X# File news places articles paths/ids in Xbatchfile /usr/spool/batch/%s X X# File a news transmitter wants articles paths/ids in Xworkfile /usr/spool/batch/%s.work X X# Hosts to send news to. Each line is of the format: X# CLASS VALID START X# host HOSTNAME NAME TIMES X Xhost dorothy normal Any Xhost toto normal Any Xhost wizard normal Any Xhost witch normal Any Xhost tinman normal Any Xhost lion normal Any X.ft P X.fi X.PP X.RE X.LP X.SH "COMPLEX EXAMPLE" XThe following is an example of a more complex configuration file that Xuses most of the options supported by X.I newsxd: X X.nf X.ft CW X# Define a series of service classes for sending news to other sites: X# nlink NNTPlink Transmits (Continuous NNTPXMITs) X# high High Priority Transmits (Top-40) X# med Medium Prio Transmits (Non Top-40) X# low Low Priority Transmits (Endnodes) X# batch Batched (with sendbatch) newsfeeds X Xclass nlink maxxmits=20 interval=60 maxload=10 noworkfile Xxmit nlink /usr/local/lib/news/nntplink nntplink %h:%b X Xclass high maxxmits=99 interval=60/20 maxload=8 ttl=1800 X Xclass med maxxmits=5 interval=300 maxload=6 ttl=1800/60 X Xclass low maxxmits=3 interval=1200 maxload=5 ttl=1100/60 X Xclass batch maxxmits=1 interval=3600 maxload=5 ttl=1800/1800 Xxmit batch /usr/local/lib/news/sendbatch sendbatch %f %h X X# Define the default news transmitter Xxmit DEFAULT /usr/local/lib/news/nntpxmit nntpxmit %h:%w X X# Check the transmit queue every seconds (this should be at least as X# low as the smallest "interval" in all of the transmission classes). Xqueueinterval 20 X X# In all of the following options, %s is replaced by the host name of the X# system being sent to. X X# File news places articles paths/ids in Xbatchfile /usr/spool/batch/%s X X# File a news transmitter wants articles paths/ids in Xworkfile /usr/spool/batch/%s.work X X# Where to log the output of a news transmitter (default is /dev/null) X# xmitlogs /usr/spool/batch/%s.log X X# Hosts to send news to. Each line is of the format: X# CLASS VALID START X# host HOSTNAME NAME TIMES OPTIONS X Xhost dorothy low Any nice=10 Xhost toto low Any nice=10 Xhost wizard low Any nice=10 Xhost witch low Any nice=10 Xhost tinman low Any nice=10 Xhost lion low Any nice=10 Xhost cactus.biz.com low Any nice=10 Xhost endnode.foobar.edu low Any2000-0500 nice=10 Xhost biggernode.foobar.edu med SaSu|Wk1730-0730 Xhost bignode.company.com med Any Xhost midsize.company.com med Any Xhost university.podunk.edu med Any Xhost mrbackbone.bigu.edu high Any Xhost gateway.bizness.com high Any Xhost supernews.hellou.edu high Any Xhost mrnntp.aloha.edu high Any Xhost hello.world.edu high Any Xhost supernews.foou.edu nlink Any Xhost backbone.newssite.edu nlink Any Xhost fred batch Any nice=20 flags=-s500000 interval=86400 Xhost barney batch Any nice=20 flags=-s250000 interval=86400 Xhost wilma batch Any nice=20 flags=-s500000 Xhost betty batch Any nice=20 flags=-s500000|-m500000 Xhost kitty batch SaSu|Wk1730-730 flags=-c|-s250000 Xhost dino batch SaSu|Wk1730-730 flags=-c|-s250000 Xhost bambam batch Sa interval=86400 X X# Notes: Only send news to biggernode.foobar.edu during non-business hours X# endnode.foobar.edu only wants news transmitted from 8PM to 5AM. X# Only send news to fred and barney once per day (every 24 hours). X# Do one batching run for bambam each Saturday. X.ft P X.fi X.PP X.RE X.LP X.SH "CONTROLLING NEWSXD" XThe following signals have the specified effect when sent to the Xserver X.I named Xprocess using the X.I kill Xcommand: X.IP SIGHUP 13 XCauses newsxd to reload newsxd.conf and remove (kill) any hosts or Xtransmitters which are no longer defined. X.IP SIGTERM XCauses newsxd to shut down, killing all active transmitters X.IP SIGQUIT XDumps the current newsxd status to X.I /usr/tmp/newsxd.status X.IP SIGIOT XCauses newsxd to kill all active transmitters and go into idle mode. XSend a HUP signal to newsxd to return to normal operating mode. X.IP SIGIO XCauses newsxd to go into idle mode, where no new transmitters are started. XSend a HUP signal to newsxd to return to normal operating mode. X.IP SIGTRAP XCauses newsxd to write a DETAILED list of the contents of all important Xinternal data structures to X.I /usr/tmp/newsxd.status.dump X.IP SIGUSR1 XIncreases the current debugging level. Caution: the debugging information Xcan get very voluminous, especially if DEBUG is enabled. X.IP SIGUSR2 XDecreases the current debugging level. X.SH "THE STATUS FILE" XThe X.I newsxd.status Xfile, which is created when a SIGQUIT signal is sent to X.I newsxd, Xshows the current state of all of the hosts managed by X.I newsxd. XThere are several pieces of information listed for each host, Xincluding the host name, the times during which a new netnews transmitter Xcan be started, the last time a netnews transmitter was started, and a X"Why Not Running" field which indicates why a netnews transmitter is not Xcurrently running for that host. The various types of "Why Not Running" Xmessages include: X X.IP "TTL Penlty" 15 XThe netnews transmitter for the host ran longer than the time-to-live Xdefined for the service class, and the host is currently being penalized. X.IP "TTL Kill" XThe netnews transmitter for the host was just killed because it ran Xlonger than the time-to-live defined for the service class. X.IP "Class Intv" XNo new transmitters in this service class can start up yet because the Xintra-class transmitter startup interval hasn't passed. See the X.I interval Xoption under X.I class, Xabove. X.IP "Host Intvl" XNo new transmitters for this host can start up yet because a transmitter Xhas already been run for this host recently (see the X.I interval Xoption under X.I class, Xabove). X.IP "High Load" XThe system load is too high to start new netnews transmitters for this Xservice class. See the X.I maxload Xoption under X.I class, Xabove. X.IP "Wrong Time" XThe current time doesn't fit any of the allowed startup times specified Xfor the host. X.IP "Max Xmits" XThe maximum number of netnews transmitters for this class are already Xrunning. See the X.I maxxmits Xoption under X.I class, Xabove. X.IP "RUNNING" XA netnews transmitter for this host is currently running. X.IP "No Work" XThere are currently no articles to send to this host. X.IP "Bad Rename" X.I newsxd Xcouldn't rename the host's X.I batchfile Xto the X.I workfile. X.IP "NotMyTurn" XThe host has already had a chance to transmit news and now it's time for Xthe other hosts to get a chance. X.I Newsxd Xuses a round-robin scheduling algorithm to insure the fair transmission Xof netnews. X X.SH RESTRICTIONS XDoes not do exponential backoff when the transmitter for a host fails. X.SH BUGS XNo known bugs exist in X.I newsxd, Xbut the wish list of features to add is quite long... X.SH FILES X.IP /usr/local/etc/newsxd.conf 28 X.I newsxd Xconfiguration file X.IP /usr/tmp/newsxd.pid XProcess ID number X.IP /usr/tmp/newsxd.status XDump of X.I newsxd Xstatus X.IP /usr/spool/batch/* Xbatch and work files for X.I newsxd Xand netnews transmitters. X.SH "SEE ALSO" Xnntpxmit(1) X.SH AUTHOR XChris Myers END_OF_FILE if test 17954 -ne `wc -c <'newsxd.8'`; then echo shar: \"'newsxd.8'\" unpacked with wrong size! fi # end of 'newsxd.8' fi echo shar: End of archive 1 \(of 3\). cp /dev/null ark1isdone MISSING="" for I in 1 2 3 ; do if test ! -f ark${I}isdone ; then MISSING="${MISSING} ${I}" fi done if test "${MISSING}" = "" ; then echo You have unpacked all 3 archives. rm -f ark[1-9]isdone else echo You still must unpack the following archives: echo " " ${MISSING} fi exit 0 exit 0 # Just in case...