*** nnrpd/group.c.orig Fri Jan 29 18:51:57 1993 --- nnrpd/group.c Sat Jan 27 19:33:38 1996 *************** *** 1,4 **** ! /* $Revision: 1.13 $ ** ** Newsgroups and the active file. */ --- 1,4 ---- ! /* $Revision: 1.32 $ ** ** Newsgroups and the active file. */ *************** *** 5,11 **** --- 5,45 ---- #include "nnrpd.h" #include "mydir.h" + /* + * [++doug] Configuration for using shared memory. If GROUP_SHARED + * is left undefined, the rest can be ignored. + * + * When using shared memory, we statically allocate the GRPentries + * array and the GRPactive array. Otherwise, need a storage allocator + * to manage space in shared memory. This means putting a fixed limit + * on the number of groups and things. + * + * Define XSEM_UNDO to be SEM_UNDO if you want to use the kernel + * semaphore undo feature which fixes up a semaphore if a process dies + * while holding it. However, the default system-wide limit on undo + * structs is 30 under HPUX 8.0. Since the fwpnews server often has + * over 200 nnrpds running, would need to increase semmnu in dfile to + * a few hundred. If XSEM_UNDO is set to 0, then killing an nnrpd process + * while it holds the semaphore will block all other nnrpds from running + * until reboot. Or until some other process removes or resets the semaphore. + * The idea is to not kill nnrpds. + * + * [++russell Jan 27 1996 ] + * Found a problem under Solaris 2.4 whereby the shm ptr returned wasn't + * always constant, so I had to removed the shared mem version of GRPentries, + * GRPnextentry and GRPtable. + * (Seems to have stopped core dumps) + */ + #define GROUP_SHARED /* Use shared memory */ + #ifdef GROUP_SHARED + # define MAX_ACTIVE_SIZE 500000 /* Max size of active file */ + # define MAX_GROUPS 10000 /* Max number of groups in active */ + # define RELOAD_DELAY 30 /* min #secs before reloading active */ + # define XSEM_UNDO 0 /* Don't use kernel undo */ + #endif + + /* ** Newsgroup hashing stuff. See comments in innd/ng.c. */ *************** *** 15,34 **** #define GRP_SIZE 512 #define GRP_BUCKET(j) &GRPtable[j & (GRP_SIZE - 1)] ! typedef struct _GRPHASH { ! int Size; ! int Used; ! GROUPENTRY **Groups; ! } GRPHASH; ! STATIC GRPHASH GRPtable[GRP_SIZE]; ! STATIC GROUPENTRY *GRPentries; ! STATIC int GRPbuckets; STATIC int GRPsize; /* ** See if a given newsgroup exists. */ GROUPENTRY * --- 49,170 ---- #define GRP_SIZE 512 #define GRP_BUCKET(j) &GRPtable[j & (GRP_SIZE - 1)] ! #define NEXTGROUP(g) GRPnextentry[(g) - GRPentries] + #ifdef GROUP_SHARED ! GROUPENTRY *_GRPtable[GRP_SIZE]; /* 2k */ ! GROUPENTRY *_GRPnextentry[MAX_GROUPS]; /* 40k */ ! GROUPENTRY *_GRPentries; ! time_t GRPtimehash; /* time last loaded */ ! ! static struct shared { ! BOOL _GRPvalid; /* loaded ok */ ! time_t _GRPtimestamp; /* time last loaded */ ! int _GRPsize; /* number of groups */ ! char _GRPactive[MAX_ACTIVE_SIZE]; /* 500k */ ! } *shm; ! ! #define GRPsize shm->_GRPsize ! #define GRPactive shm->_GRPactive ! #define GRPtimestamp shm->_GRPtimestamp ! #define GRPvalid shm->_GRPvalid ! #define GRPtable _GRPtable ! #define GRPentries _GRPentries ! #define GRPnextentry _GRPnextentry ! ! #include ! #include ! #include ! ! #define SHMKEY ((key_t)12347) ! #define SEMKEY ((key_t)12348) ! ! static int SemId; ! static int ShmId; ! ! /* ! * Routines to enforce mutual exclusion on shared memory. Any ! * nnrpd process can update and must make sure that nobody is ! * reading it at the time. ! */ ! static int shm_alloc() ! { ! if ((SemId = semget(SEMKEY, 1, 0666 | IPC_CREAT)) < 0) { ! syslog(L_ERROR, "%s Can't create semaphore %m", ClientHost); ! return (FALSE); ! } ! if ((ShmId = shmget(SHMKEY, sizeof(*shm), 0666 | IPC_CREAT)) < 0) { ! syslog(L_ERROR, "%s Can't create shared mem segment %m", ClientHost); ! return (FALSE); ! } ! shm = (struct shared *)shmat(ShmId, (void *)0, 0); ! if (shm == (struct shared *)-1) { ! syslog(L_ERROR, "%s Can't attach shared memory %m", ClientHost); ! return (FALSE); ! } ! return (TRUE); ! } ! ! /* ! * lock and unlock borrowed from Stevens. ! */ ! static struct sembuf op_lock[2] = { ! 0, 0, 0, /* Wait for sem0 to become 0 */ ! 0, 1, XSEM_UNDO /* Then increment by 1 */ ! }; ! ! static struct sembuf op_unlock[1] = { ! 0, -1, (IPC_NOWAIT|XSEM_UNDO) /* Decrement by 1 (to 0) */ ! }; ! ! static int shm_lock() ! { ! static int shm_allocated; ! ! if (! shm_allocated) { ! if (! shm_alloc()) ! return (FALSE); ! shm_allocated = TRUE; ! } ! if (semop(SemId, op_lock, 2) < 0) { ! syslog(L_ERROR, "%s acquiring semaphore %m", ClientHost); ! return (FALSE); ! } ! return (TRUE); ! } ! ! static void shm_unlock() ! { ! if (semop(SemId, op_unlock, 1) < 0) ! syslog(L_ERROR, "%s unlocking semaphore %m", ClientHost); ! } ! ! #else ! STATIC int GRPsize; + STATIC GROUPENTRY *GRPtable[GRP_SIZE]; + STATIC GROUPENTRY *GRPentries; + STATIC GROUPENTRY *GRPnextentry; + STATIC char *GRPactive; + #define shm_lock() TRUE + #define shm_unlock() + #endif + /* + ** [++doug] + ** GRPfind called from: newgroups command -- once for each new group + ** group command -- once + ** validnewsgroups -- once for each newsgroup in + ** Newsgroups: line. + ** + ** The group command is by far the most used. During peak time with + ** ~200 readers, called about once every two seconds. + */ + + /* ** See if a given newsgroup exists. */ GROUPENTRY * *************** *** 37,53 **** { register char *p; register unsigned int j; ! register int i; ! register GROUPENTRY **gpp; ! GRPHASH *htp; char c; /* SUPPRESS 6 *//* Over/underflow from plus expression */ GRP_HASH(group, p, j); ! htp = GRP_BUCKET(j); ! for (c = *group, gpp = htp->Groups, i = htp->Used; --i >= 0; gpp++) ! if (c == gpp[0]->Name[0] && EQ(group, gpp[0]->Name)) ! return gpp[0]; return NULL; } --- 173,224 ---- { register char *p; register unsigned int j; ! register GROUPENTRY *gp; char c; + if (! shm_lock()) { + return (NULL); + } + #ifdef GROUP_SHARED + if (! GRPvalid) { + shm_unlock(); + if (! GetGroupList()) + return (NULL); + if (! shm_lock()) + return (NULL); + if (! GRPvalid) + return (NULL); + } + #endif /* SUPPRESS 6 *//* Over/underflow from plus expression */ + if (GRPtimehash <= GRPtimestamp) ReHashGroupList(); GRP_HASH(group, p, j); ! c = *group; ! for (gp = *GRP_BUCKET(j); gp != NULL; gp = NEXTGROUP(gp)) { ! if (c == gp->Name[0] && EQ(group, gp->Name)) { ! #ifdef GROUP_SHARED ! static GROUPENTRY grp; ! static char groupname[256]; ! static char groupalias[256]; ! ! strncpy(groupname, gp->Name, sizeof(groupname) - 1); ! grp.Name = groupname; ! grp.High = gp->High; ! grp.Low = gp->Low; ! grp.Flag = gp->Flag; ! if (gp->Alias != NULL) ! strncpy(groupalias, gp->Alias, sizeof(groupalias) - 1); ! else ! groupalias[0] = '\0'; ! grp.Alias = groupalias; ! shm_unlock(); ! return (&grp); ! #else ! return gp; ! #endif ! } ! } ! shm_unlock(); return NULL; } *************** *** 55,79 **** STATIC void GRPhash() { - register char *p; register int i; register GROUPENTRY *gp; register unsigned int j; ! register GRPHASH *htp; ! /* Set up the default hash buckets. */ ! GRPbuckets = GRPsize / GRP_SIZE; ! if (GRPbuckets == 0) ! GRPbuckets = 1; ! if (GRPtable[0].Groups) ! for (i = GRP_SIZE, htp = GRPtable; --i >= 0; htp++) ! htp->Used = 0; ! else ! for (i = GRP_SIZE, htp = GRPtable; --i >= 0; htp++) { ! htp->Size = GRPbuckets; ! htp->Groups = NEW(GROUPENTRY*, htp->Size); ! htp->Used = 0; ! } /* Now put all groups into the hash table. */ for (i = GRPsize, gp = GRPentries; --i >= 0; gp++) { --- 226,240 ---- STATIC void GRPhash() { register int i; register GROUPENTRY *gp; register unsigned int j; ! register GROUPENTRY **htp; ! register char *p; ! /* Clear out the hash table. */ ! for (i = GRP_SIZE, htp = GRPtable; --i >= 0; htp++) ! *htp = NULL; /* Now put all groups into the hash table. */ for (i = GRPsize, gp = GRPentries; --i >= 0; gp++) { *************** *** 80,96 **** /* SUPPRESS 6 *//* Over/underflow from plus expression */ GRP_HASH(gp->Name, p, j); htp = GRP_BUCKET(j); ! if (htp->Used >= htp->Size) { ! htp->Size += GRPbuckets; ! RENEW(htp->Groups, GROUPENTRY*, htp->Size); ! } ! htp->Groups[htp->Used++] = gp; } /* Note that we don't sort the buckets. */ } /* ** Read the active file into memory, sort it, and set the number of ** newsgroups read in. Return TRUE if okay, FALSE on error. --- 241,288 ---- /* SUPPRESS 6 *//* Over/underflow from plus expression */ GRP_HASH(gp->Name, p, j); htp = GRP_BUCKET(j); ! NEXTGROUP(gp) = *htp; ! *htp = gp; } /* Note that we don't sort the buckets. */ } + /* + ** [+++russell] + ** Reload the Active file shared mem variables because the shared mem + ** copy changed while we weren't looking + */ + BOOL + ReHashGroupList() + { + register char *p; + register GROUPENTRY *gp; + register int i; + time_t tt; + if (GRPentries != NULL) DISPOSE(GRPentries); + GRPentries = NEW(GROUPENTRY, GRPsize); + for (i = 0, gp = GRPentries, p = GRPactive; iName = p; + while (*p++ != '\0'); + + /* Get the high mark. */ + gp->High = atol(p); + while (*p++ != '\0'); + + /* Get the low mark. */ + gp->Low = atol(p); + while (*p++ != '\0'); + + gp->Flag = *p; + gp->Alias = gp->Flag == NF_FLAG_ALIAS ? p + 1 : NULL; + while (*p++ != '\0'); + } + + GRPhash(); + GRPtimehash = time((time_t *)&tt); + } /* ** Read the active file into memory, sort it, and set the number of ** newsgroups read in. Return TRUE if okay, FALSE on error. *************** *** 98,133 **** BOOL GetGroupList() { - static char *active; register char *p; register char *q; register GROUPENTRY *gp; register int i; /* If re-scanning, free previous groups. */ ! if (active != NULL) { ! DISPOSE(active); DISPOSE(GRPentries); } /* Get the new file. */ ! active = ReadInFile(ACTIVE, (struct stat *)NULL); ! if (active == NULL) { syslog(L_ERROR, "%s cant read %s %m", ClientHost, ACTIVE); return FALSE; } /* Count lines. */ ! for (p = active, i = 0; (p = strchr(p, '\n')) != NULL; p++, i++) continue; /* Fill in the group array. */ GRPentries = NEW(GROUPENTRY, i); ! for (i = 0, gp = GRPentries, p = active; *p; i++, gp++, p = q + 1) { gp->Name = p; if ((p = strchr(p, ' ')) == NULL) { syslog(L_ERROR, "%s internal no_space1 \"%.20s...\"", ClientHost, gp->Name); return FALSE; } *p++ = '\0'; --- 290,371 ---- BOOL GetGroupList() { register char *p; register char *q; register GROUPENTRY *gp; register int i; + time_t tt; + if (! shm_lock()) + return (FALSE); + + #ifdef GROUP_SHARED + /* + * Don't reload the active file if any nnrpd process has loaded it + * within the last RELOAD_DELAY seconds. Saves a bit of I/O. + */ + if (GRPtimestamp + RELOAD_DELAY > time((time_t *)&tt)) { + ReHashGroupList(); + shm_unlock(); + return (TRUE); + } + + /* + * Mark data in shared memory invalid. That way, if one nnrpd + * screws up shared memory while attempting to reload active, the + * others will notice and also try to reload. + */ + GRPvalid = 0; + #endif + /* If re-scanning, free previous groups. */ ! #ifdef GROUP_SHARED ! if (GRPentries != NULL) { DISPOSE(GRPentries); + DISPOSE(GRPnextentry); } + #else + if (GRPactive != NULL) { + DISPOSE(GRPactive); + DISPOSE(GRPentries); + DISPOSE(GRPnextentry); + } + #endif /* Get the new file. */ ! #ifdef GROUP_SHARED ! if (! xReadInFile(ACTIVE, GRPactive, MAX_ACTIVE_SIZE)) { ! #else ! GRPactive = ReadInFile(ACTIVE, (struct stat *)NULL); ! if (GRPactive == NULL) { ! #endif ! shm_unlock(); syslog(L_ERROR, "%s cant read %s %m", ClientHost, ACTIVE); return FALSE; } /* Count lines. */ ! for (p = GRPactive, i = 0; (p = strchr(p, '\n')) != NULL; p++, i++) continue; /* Fill in the group array. */ + #ifdef GROUP_SHARED + if (i > MAX_GROUPS) { + shm_unlock(); + syslog(L_ERROR, "%s too many groups (%d)", ClientHost, i); + return (FALSE); + } GRPentries = NEW(GROUPENTRY, i); ! #else ! GRPentries = NEW(GROUPENTRY, i); ! GRPnextentry = NEW(GROUPENTRY *, i); ! #endif ! for (i = 0, gp = GRPentries, p = GRPactive; *p; i++, gp++, p = q + 1) { gp->Name = p; if ((p = strchr(p, ' ')) == NULL) { syslog(L_ERROR, "%s internal no_space1 \"%.20s...\"", ClientHost, gp->Name); + shm_unlock(); return FALSE; } *p++ = '\0'; *************** *** 136,141 **** --- 374,380 ---- if ((q = strchr(p, ' ')) == NULL) { syslog(L_ERROR, "%s internal no_space2 \"%.20s...\"", ClientHost, gp->Name); + shm_unlock(); return FALSE; } *q++ = '\0'; *************** *** 145,150 **** --- 384,390 ---- if ((p = strchr(q, ' ')) == NULL) { syslog(L_ERROR, "%s internal no_space3 \"%.20s...\"", ClientHost, gp->Name); + shm_unlock(); return FALSE; } *p++ = '\0'; *************** *** 154,159 **** --- 394,400 ---- if ((q = strchr(p, '\n')) == NULL) { syslog(L_ERROR, "%s internal newline \"%.20s...\"", ClientHost, gp->Name); + shm_unlock(); return FALSE; } *q = '\0'; *************** *** 163,171 **** --- 404,469 ---- GRPsize = i; GRPhash(); + #ifdef GROUP_SHARED + GRPtimestamp = time(&tt); + GRPvalid = 1; + #endif + shm_unlock(); return TRUE; } + + #ifdef GROUP_SHARED + + /* + ** Used for reading active file into shared memory. + ** + ** [++doug] + ** + ** It isn't clear to me what ensures that nnrpd never attempts to + ** open the active while innd is writing it. Even if the writev + ** that innd uses is atomic, there is still a window between where + ** it opens (and truncates) the active file, and where it writes the + ** current contents. Anyway, this problem, if it really is a problem, + ** existed before my mods. + */ + int xReadInFile(name, buf, bufsize) + char *name; + char *buf; + int bufsize; + { + int fd; + struct stat mystat; + int oerrno; + + if ((fd = open(name, O_RDONLY)) < 0) + return FALSE; + + if (fstat(fd, &mystat) < 0) { + oerrno = errno; + (void)close(fd); + errno = oerrno; + return FALSE; + } + if (mystat.st_size >= bufsize) { + errno = ENOMEM; + return (FALSE); + } + + if (xread(fd, buf, mystat.st_size) < 0) { + oerrno = errno; + (void)close(fd); + errno = oerrno; + return FALSE; + } + + /* Terminate the string; terminate the routine. */ + buf[mystat.st_size] = '\0'; + (void)close(fd); + return (TRUE); + } + + #endif /* ** Sorting predicate to put newsgroup names into numeric order.