Update NEWS for 1.6.22
[pkg-k5-afs_openafs.git] / src / ptserver / db_verify.c
blob50bd037a3fae5bd44a63595a0048502586024338
1 /*
2 * Copyright 2000, International Business Machines Corporation and others.
3 * All Rights Reserved.
5 * This software has been released under the terms of the IBM Public
6 * License. For details, see the LICENSE file in the top-level source
7 * directory or online at http://www.openafs.org/dl/license10.html
8 */
11 #include <afsconfig.h>
12 #include <afs/param.h>
16 * (3) Define a structure, idused, instead of an
17 * array of long integers, idmap, to count group
18 * memberships. These structures are on a linked
19 * list, with each structure containing IDCOUNT
20 * slots for id's.
21 * (4) Add new functions to processs the structure
22 * described above:
23 * zeromap(), idcount(), inccount().
24 * (5) Add code, primarily in WalkNextChain():
25 * 1. Test id's, allowing groups within groups.
26 * 2. Count the membership list for supergroups,
27 * and follow the continuation chain for
28 * supergroups.
29 * (6) Add fprintf statements for various error
30 * conditions.
33 #include <afs/stds.h>
34 #include <sys/types.h>
35 #ifdef AFS_NT40_ENV
36 #include <winsock2.h>
37 #include <WINNT/afsevent.h>
38 #include <io.h>
39 #else
40 #include <netdb.h>
41 #include <netinet/in.h>
42 #include <sys/file.h>
43 #endif
44 #include <stdio.h>
45 #include <string.h>
46 #include <errno.h>
47 #include <fcntl.h>
48 #include <afs/cellconfig.h>
49 #include <afs/afsutil.h>
50 #include <ubik.h>
51 #include <afs/cmd.h>
52 #include <afs/com_err.h>
54 #include "ptint.h"
55 #include "pterror.h"
56 #include "ptserver.h"
57 #include "ptuser.h"
58 #include "display.h"
60 struct prheader cheader;
61 int fd;
62 const char *pr_dbaseName;
63 char *whoami = "db_verify";
64 #define UBIK_HEADERSIZE 64
66 afs_int32
67 printheader(struct prheader *h)
69 printf("Version = %d\n", ntohl(h->version));
70 printf("Header Size = %d\n", ntohl(h->headerSize));
71 printf("Free Ptr = 0x%x\n", ntohl(h->freePtr));
72 printf("EOF Ptr = 0x%x\n", ntohl(h->eofPtr));
73 printf("Max Group ID = %d\n", ntohl(h->maxGroup));
74 printf("Max User ID = %d\n", ntohl(h->maxID));
75 printf("Max Foreign ID = %d\n", ntohl(h->maxForeign));
76 /* printf("Max Sub/Super ID = %d\n", ntohl(h->maxInst)); */
77 printf("Orphaned groups = %d\n", ntohl(h->orphan));
78 printf("User Count = %d\n", ntohl(h->usercount));
79 printf("Group Count = %d\n", ntohl(h->groupcount));
80 /* printf("Foreign Count = %d\n", ntohl(h->foreigncount)); NYI */
81 /* printf("Sub/super Count = %d\n", ntohl(h->instcount)); NYI */
82 printf("Name Hash = %d buckets\n", HASHSIZE);
83 printf("ID Hash = %d buckets\n", HASHSIZE);
84 return 0;
87 static afs_int32
88 pr_Read(afs_int32 pos, void *buff, afs_int32 len)
90 afs_int32 code;
92 code = lseek(fd, UBIK_HEADERSIZE + pos, 0);
93 if (code == -1)
94 return errno;
96 code = read(fd, buff, len);
97 if (code != len)
98 return -1;
99 if (code == -1)
100 return errno;
102 return 0;
105 /* InitDB ()
106 * Initializes the a transaction on the database and reads the header into
107 * the static variable cheader. If successful it returns a read-locked
108 * transaction. If ubik reports that cached database info should be up to date
109 * the cheader structure is not re-read from the ubik.
112 afs_int32
113 ReadHeader(void)
115 afs_int32 code;
117 code = pr_Read(0, (char *)&cheader, sizeof(cheader));
118 if (code) {
119 afs_com_err(whoami, code, "couldn't read header");
120 return code;
122 /* Check and see if database exists and is approximately OK. */
123 if (ntohl(cheader.headerSize) != sizeof(cheader)
124 || ntohl(cheader.eofPtr) == 0) {
125 if (code)
126 return code;
127 afs_com_err(whoami, PRDBBAD, "header is bad");
128 return PRDBBAD;
130 return 0;
133 static afs_int32
134 IDHash(afs_int32 x)
136 /* returns hash bucket for x */
137 return ((abs(x)) % HASHSIZE);
140 static afs_int32
141 NameHash(char *aname)
143 /* returns hash bucket for aname */
144 unsigned int hash = 0;
145 int i;
146 /* stolen directly from the HashString function in the vol package */
147 for (i = strlen(aname), aname += i - 1; i--; aname--)
148 hash = (hash * 31) + (*(unsigned char *)aname - 31);
149 return (hash % HASHSIZE);
152 #define MAP_NAMEHASH 1
153 #define MAP_IDHASH 2
154 #define MAP_HASHES (MAP_NAMEHASH | MAP_IDHASH)
155 #define MAP_CONT 4
156 #define MAP_FREE 8
157 #define MAP_OWNED 0x10
158 #define MAP_RECREATE 0x20
160 struct misc_data {
161 int nEntries; /* number of database entries */
162 int anon; /* found anonymous Id */
163 afs_int32 maxId; /* user */
164 afs_int32 minId; /* group */
165 afs_int32 maxForId; /* foreign user id */
166 #if defined(SUPERGROUPS)
167 #define IDCOUNT 512
168 struct idused {
169 int idstart;
170 afs_int32 idcount[IDCOUNT];
171 struct idused *idnext;
172 } *idmap;
173 #else
174 int idRange; /* number of ids in map */
175 afs_int32 *idmap; /* map of all id's: midId is origin */
176 #endif /* SUPERGROUPS */
177 int nusers; /* counts of each type */
178 int ngroups;
179 int nforeigns;
180 int ninsts;
181 int ncells;
182 int maxOwnerLength; /* longest owner chain */
183 int maxContLength; /* longest chain of cont. blks */
184 int orphanLength; /* length of orphan list */
185 int freeLength; /* length of free list */
186 int verbose;
187 int listuheader;
188 int listpheader;
189 int listentries;
190 FILE *recreate; /* stream for recreate instructions */
193 #if defined(SUPERGROUPS)
194 void zeromap(struct idused *idmap);
195 void inccount(struct idused **idmapp, int id);
196 int idcount(struct idused **idmapp, int id);
197 #endif
200 readUbikHeader(struct misc_data *misc)
202 int offset, r;
203 struct ubik_hdr uheader;
205 offset = lseek(fd, 0, 0);
206 if (offset != 0) {
207 printf("error: lseek to 0 failed: %d %d\n", offset, errno);
208 return (-1);
211 /* now read the info */
212 r = read(fd, &uheader, sizeof(uheader));
213 if (r != sizeof(uheader)) {
214 printf("error: read of %" AFS_SIZET_FMT " bytes failed: %d %d\n",
215 sizeof(uheader), r, errno);
216 return (-1);
219 uheader.magic = ntohl(uheader.magic);
220 uheader.size = ntohs(uheader.size);
221 uheader.version.epoch = ntohl(uheader.version.epoch);
222 uheader.version.counter = ntohl(uheader.version.counter);
224 if (misc->listuheader) {
225 printf("Ubik Header\n");
226 printf(" Magic = 0x%x\n", uheader.magic);
227 printf(" Size = %u\n", uheader.size);
228 printf(" Version.epoch = %u\n", uheader.version.epoch);
229 printf(" Version.counter = %u\n", uheader.version.counter);
232 if (uheader.size != UBIK_HEADERSIZE)
233 printf("Ubik header size is %u (should be %u)\n", uheader.size,
234 UBIK_HEADERSIZE);
235 if (uheader.magic != UBIK_MAGIC)
236 printf("Ubik header magic is 0x%x (should be 0x%x)\n", uheader.magic,
237 UBIK_MAGIC);
239 return (0);
242 afs_int32
243 ConvertDiskAddress(afs_uint32 ea, int *eiP)
245 int i;
247 *eiP = -1;
249 if (ea < sizeof(cheader))
250 return PRDBADDR;
251 if (ea >= ntohl(cheader.eofPtr))
252 return PRDBADDR;
253 ea -= sizeof(cheader);
254 i = ea / sizeof(struct prentry);
255 if (i * sizeof(struct prentry) != ea)
256 return PRDBADDR;
257 /* if ((i < 0) || (i >= misc->nEntries)) return PRDBADDR; */
258 *eiP = i;
259 return 0;
263 PrintEntryError(struct misc_data *misc, afs_int32 ea, struct prentry *e, int indent)
266 pr_PrintEntry(stderr, /*net order */ 0, ea, e, indent);
267 return 0;
271 PrintContError(struct misc_data *misc, afs_int32 ea, struct contentry *c, int indent)
273 pr_PrintContEntry(stderr, /*net order */ 0, ea, c, indent);
274 return 0;
277 afs_int32
278 WalkHashTable(afs_int32 hashtable[], /* hash table to walk */
279 int hashType, /* hash function to use */
280 char map[], /* one byte per db entry */
281 struct misc_data *misc) /* stuff to keep track of */
283 afs_int32 code;
284 int hi; /* index in hash table */
285 afs_int32 ea; /* entry's db addr */
286 int ei; /* entry's index */
287 char bit; /* bits to check for in map */
288 struct prentry e;
289 afs_int32 next_ea;
290 afs_int32 id;
291 afs_int32 flags;
292 afs_int32 hash;
294 bit = hashType;
296 for (hi = 0; hi < HASHSIZE; hi++) {
297 ea = 0;
298 next_ea = ntohl(hashtable[hi]);
299 while (next_ea) {
300 code = ConvertDiskAddress(next_ea, &ei);
301 if (code) {
302 fprintf(stderr, "Bad chain address %d\n", next_ea);
303 if (ea) {
304 fprintf(stderr, "Last entry in chain:\n");
305 if (PrintEntryError(misc, ea, &e, 2))
306 return PRDBBAD;
308 fprintf(stderr, "Skipping remainder of hash bucket %d\n", hi);
309 break;
311 ea = next_ea;
312 code = pr_Read(ea, (char *)&e, sizeof(e));
313 if (code)
314 return code;
316 id = ntohl(e.id);
318 if (((e.flags & htonl((PRGRP | PRINST))) == 0)
319 && (strchr(e.name, '@'))) {
320 /* Foreign user */
321 if (id > misc->maxForId)
322 misc->maxForId = id;
323 } else {
324 if (id == ANONYMOUSID)
325 misc->anon++;
326 else if (id > misc->maxId)
327 misc->maxId = id;
328 if (id < misc->minId)
329 misc->minId = id;
332 switch (hashType) {
333 case MAP_NAMEHASH:
334 next_ea = ntohl(e.nextName);
335 hash = NameHash(e.name);
336 break;
337 case MAP_IDHASH:
338 next_ea = ntohl(e.nextID);
339 hash = IDHash(id);
340 break;
341 default:
342 fprintf(stderr, "unknown hash table type %d\n", hashType);
343 return PRBADARG;
346 if (map[ei] & bit) {
347 fprintf(stderr,
348 "Entry found twice in hash table: bucket %d\n", hi);
349 if (hi != hash)
350 fprintf(stderr, "also in wrong bucket: should be in %d\n",
351 hash);
352 if (PrintEntryError(misc, ea, &e, 2))
353 return PRDBBAD;
354 break;
356 map[ei] |= bit;
358 flags = ntohl(e.flags);
359 switch (flags & PRTYPE) {
360 case PRFREE:
361 fprintf(stderr, "ENTRY IS FREE");
362 goto abort;
363 case PRCONT:
364 fprintf(stderr, "ENTRY IS CONTINUATION");
365 goto abort;
366 case PRGRP:
367 case PRUSER:
368 break;
369 case PRCELL:
370 case PRFOREIGN:
371 case PRINST:
372 fprintf(stderr, "ENTRY IS unexpected type (flags=0x%x)\n",
373 flags);
374 break;
375 default:
376 fprintf(stderr, "ENTRY IS OF unknown type (flags=0x%x)\n",
377 flags);
378 goto abort;
381 if (hash != hi) {
382 fprintf(stderr, "entry hashed in bucket %d should be %d\n",
383 hi, hash);
384 abort:
385 if (PrintEntryError(misc, ea, &e, 2))
386 return PRDBBAD;
387 continue;
391 return 0;
394 afs_int32
395 WalkNextChain(char map[], /* one byte per db entry */
396 struct misc_data *misc, /* stuff to keep track of */
397 afs_int32 ea, struct prentry *e)
399 afs_int32 head;
400 int bit;
401 afs_int32 code;
402 struct contentry c; /* continuation entry */
403 afs_int32 na; /* next thread */
404 int ni;
405 afs_int32 eid = 0;
406 int count = 0; /* number of members, set to > 9999 if */
407 /* list ends early */
408 int i;
409 int noErrors = 1;
410 int length; /* length of chain */
411 #if defined(SUPERGROUPS)
412 int sgcount = 0; /* number of sgentrys */
413 afs_int32 sghead;
414 #define g (((struct prentryg *)e))
415 #endif
417 if (e) {
418 head = ntohl(e->next);
419 eid = ntohl(e->id);
420 bit = MAP_CONT;
421 #if defined(SUPERGROUPS)
422 sghead = ntohl(g->next);
423 #endif
424 for (i = 0; i < PRSIZE; i++) {
425 afs_int32 id = ntohl(e->entries[i]);
426 if (id == PRBADID)
427 continue;
428 else if (id) {
429 int eid_s, id_s;
430 count++;
431 /* in case the ids are large, convert to pure sign. */
432 if (id > 0)
433 id_s = 1;
434 else
435 id_s = -1;
436 if (eid > 0)
437 eid_s = 1;
438 else
439 eid_s = -1;
440 #if defined(SUPERGROUPS)
441 if (id_s > 0 && eid_s > 0) {
442 fprintf(stderr,
443 "User can't be member of user in membership list\n");
444 if (PrintEntryError(misc, ea, e, 2))
445 return PRDBBAD;
446 noErrors = 0;
448 #else
449 if (id_s * eid_s > 0) { /* sign should be different */
450 fprintf(stderr,
451 "Bad user/group dicotomy in membership list\n");
452 if (PrintEntryError(misc, ea, e, 2))
453 return PRDBBAD;
454 noErrors = 0;
456 #endif /* SUPERGROUPS */
457 /* count each user as a group, and each group a user is in */
458 #if defined(SUPERGROUPS)
459 if (!(id < 0 && eid < 0) && (id != ANONYMOUSID))
460 inccount(&misc->idmap, id);
461 #else
462 if ((id >= misc->minId) && (id <= misc->maxId)
463 && (id != ANONYMOUSID))
464 misc->idmap[id - misc->minId]++;
465 #endif /* SUPERGROUPS */
466 } else if (head)
467 count = 9999;
468 else
469 break;
471 #if defined(SUPERGROUPS)
472 sghead = ntohl(g->nextsg);
473 if ((e->flags & htonl(PRGRP))) {
474 for (i = 0; i < SGSIZE; ++i) {
475 afs_int32 id = ntohl(g->supergroup[i]);
476 if (id == PRBADID)
477 continue;
478 else if (id) {
479 if (id > 0) {
480 fprintf(stderr,
481 "User can't be member of supergroup list\n");
482 if (PrintEntryError(misc, ea, e, 2))
483 return PRDBBAD;
484 noErrors = 0;
486 sgcount++;
487 inccount(&misc->idmap, id);
491 #endif /* SUPERGROUPS */
492 } else {
493 head = ntohl(cheader.freePtr);
494 #if defined(SUPERGROUPS)
495 sghead = 0;
496 #endif
497 bit = MAP_FREE;
500 #if defined(SUPERGROUPS)
501 length = 0;
502 for (na = sghead; na; na = ntohl(c.next)) {
503 code = ConvertDiskAddress(na, &ni);
504 if (code) {
505 fprintf(stderr, "Bad SGcontinuation ptr %d", na);
506 if (PrintEntryError(misc, ea, e, 2))
507 return PRDBBAD;
508 if (na != sghead) {
509 fprintf(stderr, "last block: \n");
510 if (PrintContError(misc, na, &c, 4))
511 return PRDBBAD;
513 return 0;
515 code = pr_Read(na, (char *)&c, sizeof(c));
516 if (code)
517 return code;
518 length++;
520 if (map[ni]) {
521 fprintf(stderr, "Continuation entry reused\n");
522 if (PrintEntryError(misc, ea, e, 2))
523 return PRDBBAD;
524 if (PrintContError(misc, na, &c, 4))
525 return PRDBBAD;
526 noErrors = 0;
527 break;
529 map[ni] |= bit;
530 if ((ntohl(c.id) != eid)) {
531 fprintf(stderr, "Continuation id mismatch\n");
532 if (PrintEntryError(misc, ea, e, 2))
533 return PRDBBAD;
534 if (PrintContError(misc, na, &c, 4))
535 return PRDBBAD;
536 noErrors = 0;
537 continue;
540 /* update membership count */
541 for (i = 0; i < COSIZE; i++) {
542 afs_int32 id = ntohl(c.entries[i]);
543 if (id == PRBADID)
544 continue;
545 else if (id) {
546 int id_s;
547 sgcount++;
548 /* in case the ids are large, convert to pure sign. */
549 if (id > 0)
550 id_s = 1;
551 else
552 id_s = -1;
553 if (id_s > 0) {
554 fprintf(stderr,
555 "User can't be member of supergroup list\n");
556 if (PrintEntryError(misc, ea, e, 2))
557 return PRDBBAD;
558 if (PrintContError(misc, na, &c, 4))
559 return PRDBBAD;
560 noErrors = 0;
562 /* count each user as a group, and each group a user is in */
563 if ((id != ANONYMOUSID))
564 inccount(&misc->idmap, id);
565 } else if (c.next)
566 count = 9999;
567 else
568 break;
571 if (length > misc->maxContLength)
572 misc->maxContLength = length;
573 #endif /* SUPERGROUPS */
574 length = 0;
575 for (na = head; na; na = ntohl(c.next)) {
576 code = ConvertDiskAddress(na, &ni);
577 if (code) {
578 fprintf(stderr, "Bad continuation ptr %d", na);
579 if (e == 0)
580 fprintf(stderr, "walking free list");
581 else if (PrintEntryError(misc, ea, e, 2))
582 return PRDBBAD;
583 if (na != head) {
584 fprintf(stderr, "last block: \n");
585 if (PrintContError(misc, na, &c, 4))
586 return PRDBBAD;
588 return 0;
590 code = pr_Read(na, (char *)&c, sizeof(c));
591 if (code)
592 return code;
593 length++;
595 if (map[ni]) {
596 fprintf(stderr, "Continuation entry reused\n");
597 if (e == 0)
598 fprintf(stderr, "walking free list");
599 else if (PrintEntryError(misc, ea, e, 2))
600 return PRDBBAD;
601 if (PrintContError(misc, na, &c, 4))
602 return PRDBBAD;
603 noErrors = 0;
604 break;
606 map[ni] |= bit;
607 if (e && (ntohl(c.id) != eid)) {
608 fprintf(stderr, "Continuation id mismatch\n");
609 if (e == 0)
610 fprintf(stderr, "walking free list");
611 else if (PrintEntryError(misc, ea, e, 2))
612 return PRDBBAD;
613 if (PrintContError(misc, na, &c, 4))
614 return PRDBBAD;
615 noErrors = 0;
616 continue;
619 /* update membership count */
620 if (e)
621 for (i = 0; i < COSIZE; i++) {
622 afs_int32 id = ntohl(c.entries[i]);
623 if (id == PRBADID)
624 continue;
625 else if (id) {
626 int eid_s, id_s;
627 count++;
628 /* in case the ids are large, convert to pure sign. */
629 if (id > 0)
630 id_s = 1;
631 else
632 id_s = -1;
633 if (eid > 0)
634 eid_s = 1;
635 else
636 eid_s = -1;
637 #if defined(SUPERGROUPS)
638 if (id_s > 0 && eid_s > 0) {
639 fprintf(stderr,
640 "User can't be member of user in membership list\n");
641 if (PrintEntryError(misc, ea, e, 2))
642 return PRDBBAD;
643 if (PrintContError(misc, na, &c, 4))
644 return PRDBBAD;
645 noErrors = 0;
647 #else
648 if (id_s * eid_s > 0) { /* sign should be different */
649 fprintf(stderr,
650 "Bad user/group dicotomy in membership list\n");
651 if (PrintEntryError(misc, ea, e, 2))
652 return PRDBBAD;
653 if (PrintContError(misc, na, &c, 4))
654 return PRDBBAD;
655 noErrors = 0;
657 #endif /* SUPERGROUPS */
658 /* count each user as a group, and each group a user is in */
659 #if defined(SUPERGROUPS)
660 if (!(id < 0 && eid < 0) && (id != ANONYMOUSID))
661 inccount(&misc->idmap, id);
662 #else
663 if ((id >= misc->minId) && (id <= misc->maxId)
664 && (id != ANONYMOUSID))
665 misc->idmap[id - misc->minId]++;
666 #endif /* SUPERGROUPS */
667 } else if (c.next)
668 count = 9999;
669 else
670 break;
673 if (e && noErrors && (count != ntohl(e->count))) {
674 #if defined(SUPERGROUPS)
675 if (count >= 9999)
676 fprintf(stderr, "Membership list ends early\n");
677 #else
678 if (count > 9999)
679 fprintf(stderr, "Membership list ends early\n");
680 #endif /* SUPERGROUPS */
681 fprintf(stderr, "Count was %d should be %d\n", count,
682 ntohl(e->count));
683 if (PrintEntryError(misc, ea, e, 2))
684 return PRDBBAD;
685 #if defined(SUPERGROUPS)
686 noErrors = 0;
688 if (e && (e->flags & htonl(PRGRP)) && (sgcount != ntohl(g->countsg))) {
689 fprintf(stderr, "SGCount was %d should be %d\n", sgcount,
690 ntohl(g->countsg));
691 if (PrintEntryError(misc, ea, e, 2))
692 return PRDBBAD;
693 #endif
696 if (e) {
697 if (length > misc->maxContLength)
698 misc->maxContLength = length;
699 } else
700 misc->freeLength = length;
702 return 0;
703 #if defined(SUPERGROUPS)
704 #undef g
705 #endif
708 afs_int32
709 WalkOwnedChain(char map[], /* one byte per db entry */
710 struct misc_data *misc, /* stuff to keep track of */
711 afs_int32 ea, struct prentry *e)
713 afs_int32 head;
714 afs_int32 code;
715 struct prentry te; /* next entry in owner chain */
716 afs_int32 na; /* next thread */
717 int ni;
718 afs_int32 eid = 0;
719 int length; /* length of chain */
721 if (e) {
722 head = ntohl(e->owned);
723 eid = ntohl(e->id);
724 } else
725 head = ntohl(cheader.orphan);
727 length = 0;
728 for (na = head; na; na = ntohl(te.nextOwned)) {
729 code = ConvertDiskAddress(na, &ni);
730 if (code) {
731 fprintf(stderr, "Bad owned list ptr %d", na);
732 if (e == 0)
733 fprintf(stderr, "walking orphan list");
734 else if (PrintEntryError(misc, ea, e, 2))
735 return PRDBBAD;
736 if (na != head) {
737 fprintf(stderr, "last block: \n");
738 if (PrintEntryError(misc, na, &te, 4))
739 return PRDBBAD;
741 return 0;
743 code = pr_Read(na, (char *)&te, sizeof(te));
744 if (code)
745 return code;
746 length++;
748 if ((ntohl(te.flags) & PRTYPE) == PRCONT) {
749 fprintf(stderr, "Continuation entry found on owner chain\n");
750 if (e == 0)
751 fprintf(stderr, "walking orphan list");
752 else if (PrintEntryError(misc, ea, e, 2))
753 return PRDBBAD;
754 if (PrintEntryError(misc, na, &te, 4))
755 return PRDBBAD;
756 break;
758 if (map[ni] & MAP_OWNED) {
759 fprintf(stderr, "Entry on multiple owner chains\n");
760 if (e == 0)
761 fprintf(stderr, "walking orphan list");
762 else if (PrintEntryError(misc, ea, e, 2))
763 return PRDBBAD;
764 if (PrintEntryError(misc, na, &te, 4))
765 return PRDBBAD;
766 break;
768 map[ni] |= MAP_OWNED;
769 if ((map[ni] & MAP_HASHES) != MAP_HASHES) {
770 fprintf(stderr, "Owned entry not hashed properly\n");
771 abort:
772 if (e == 0)
773 fprintf(stderr, "walking orphan list");
774 else if (PrintEntryError(misc, ea, e, 2))
775 return PRDBBAD;
776 if (PrintEntryError(misc, na, &te, 4))
777 return PRDBBAD;
778 continue;
780 if (e) {
781 if (ntohl(te.owner) != eid) {
782 fprintf(stderr, "Owner id mismatch\n");
783 goto abort;
785 } else /* orphan */ if (te.owner) {
786 fprintf(stderr, "Orphan group owner not zero\n");
787 goto abort;
791 if (e) {
792 if (length > misc->maxOwnerLength)
793 misc->maxOwnerLength = length;
794 } else
795 misc->orphanLength = length;
797 return 0;
800 afs_int32
801 WalkChains(char map[], /* one byte per db entry */
802 struct misc_data *misc) /* stuff to keep track of */
804 afs_int32 code;
805 int ei;
806 afs_int32 ea; /* entry's db addr */
807 struct prentry e;
808 afs_int32 id;
809 int type;
811 /* check all entries found in hash table walks */
812 for (ei = 0; ei < misc->nEntries; ei++)
813 if (map[ei] & MAP_HASHES) {
814 ea = ei * sizeof(struct prentry) + sizeof(cheader);
815 code = pr_Read(ea, (char *)&e, sizeof(e));
816 if (code)
817 return code;
819 if ((map[ei] & MAP_HASHES) != MAP_HASHES) {
820 fprintf(stderr, "entry not in both hashtables\n");
821 if ((map[ei] & MAP_NAMEHASH) != MAP_NAMEHASH)
822 fprintf(stderr, "--> entry not in Name hashtable\n");
823 if ((map[ei] & MAP_IDHASH) != MAP_IDHASH)
824 fprintf(stderr, "--> entry not in ID hashtable\n");
826 abort:
827 if (PrintEntryError(misc, ea, &e, 2))
828 return PRDBBAD;
829 continue;
832 id = ntohl(e.id);
834 type = ntohl(e.flags) & PRTYPE;
835 switch (type) {
836 case PRGRP:
837 if (id >= 0) {
838 fprintf(stderr, "Group id not negative\n");
839 goto abort;
841 /* special case sysadmin: it owns itself */
842 if (id == SYSADMINID) {
843 if (ntohl(e.owner) != SYSADMINID) {
844 fprintf(stderr,
845 "System:administrators doesn't own itself\n");
846 goto abort;
849 code = WalkOwnedChain(map, misc, ea, &e);
850 if (code)
851 return code;
852 code = WalkNextChain(map, misc, ea, &e);
853 if (code)
854 return code;
855 misc->ngroups++;
856 break;
857 case PRUSER:
858 if (id <= 0) {
859 #if defined(SUPERGROUPS)
860 fprintf(stderr, "User id not positive\n");
861 #else
862 fprintf(stderr, "User id negative\n");
863 #endif
864 goto abort;
867 /* Users are owned by sysadmin, but sysadmin doesn't have an owner
868 * chain. Check this then set the owned bit. */
869 if (ntohl(e.owner) != SYSADMINID) {
870 fprintf(stderr,
871 "User not owned by system:administrators\n");
872 goto abort;
874 if (e.nextOwned) {
875 fprintf(stderr, "User has owned pointer\n");
876 goto abort;
878 map[ei] |= MAP_OWNED;
880 code = WalkOwnedChain(map, misc, ea, &e);
881 if (code)
882 return code;
883 code = WalkNextChain(map, misc, ea, &e);
884 if (code)
885 return code;
886 if (strchr(e.name, '@') == 0) {
887 misc->nusers++; /* Not a foreign user */
888 } else {
889 misc->nforeigns++; /* A foreign user */
891 break;
892 case PRFREE:
893 case PRCONT:
894 case PRCELL:
895 misc->ncells++;
896 break;
897 case PRFOREIGN:
898 fprintf(stderr,
899 "ENTRY IS unexpected type [PRFOREIGN] (flags=0x%x)\n",
900 ntohl(e.flags));
901 break;
902 case PRINST:
903 misc->ninsts++;
904 break;
905 default:
906 fprintf(stderr, "entry with unexpected type");
907 goto abort;
911 return 0;
914 afs_int32
915 GC(char map[], struct misc_data *misc)
917 afs_int32 code;
918 int ei;
919 afs_int32 ea;
920 struct prentry e;
921 char m;
923 for (ei = 0; ei < misc->nEntries; ei++) {
924 ea = ei * sizeof(struct prentry) + sizeof(cheader);
925 code = pr_Read(ea, (char *)&e, sizeof(e));
926 if (code)
927 return code;
928 m = map[ei];
929 if (m == 0) {
930 fprintf(stderr, "Unreferenced entry:");
931 if (PrintEntryError(misc, ea, &e, 2))
932 return PRDBBAD;
934 /* all users and groups should be owned, and their membership counts
935 * should be okay */
936 else if ((m & MAP_HASHES) == MAP_HASHES) {
937 afs_int32 id;
938 int refCount;
939 if (!(m & MAP_OWNED)) {
940 fprintf(stderr, "Entry not on any owner chain:\n");
941 if (PrintEntryError(misc, ea, &e, 2))
942 return PRDBBAD;
944 id = ntohl(e.id);
945 #if defined(SUPERGROUPS)
946 if ((id != ANONYMOUSID)
947 && ((refCount = idcount(&misc->idmap, id)) != ntohl(e.count)))
948 #else
949 if ((id >= misc->minId) && (id <= misc->maxId)
950 && (id != ANONYMOUSID)
951 && ((refCount = misc->idmap[id - misc->minId]) !=
952 ntohl(e.count)))
953 #endif /* SUPERGROUPS */
955 afs_int32 na;
956 fprintf(stderr,
957 "Entry membership count is inconsistent: %d entries refer to this one\n",
958 refCount);
959 if (PrintEntryError(misc, ea, &e, 2))
960 return PRDBBAD;
962 /* get continuation blocks too */
963 for (na = ntohl(e.next); na; na = ntohl(e.next)) {
964 int ni;
965 code = ConvertDiskAddress(na, &ni);
966 if (code)
967 return code;
968 code = pr_Read(na, (char *)&e, sizeof(e));
969 if (code)
970 return code;
971 if (PrintEntryError(misc, na, &e, 4))
972 return PRDBBAD;
977 return 0;
980 char *
981 QuoteName(char *s)
983 char *qs;
984 if (strpbrk(s, " \t")) {
985 qs = (char *)malloc(strlen(s) + 3);
986 strcpy(qs, "\"");
987 strcat(qs, s);
988 strcat(qs, "\"");
989 } else
990 qs = s;
991 return qs;
994 afs_int32
995 DumpRecreate(char map[], struct misc_data *misc)
997 afs_int32 code;
998 int ei;
999 afs_int32 ea;
1000 struct prentry e;
1001 afs_int32 id;
1002 afs_int32 flags;
1003 afs_int32 owner;
1004 char *name;
1005 int builtinUsers = 0;
1006 int createLow = 0; /* users uncreate from here */
1007 #if defined(SUPERGROUPS)
1008 struct idused *idmap; /* map of all id's */
1009 #else
1010 afs_int32 *idmap; /* map of all id's */
1011 #endif
1012 int found;
1013 FILE *rc;
1015 rc = misc->recreate;
1016 idmap = misc->idmap;
1017 #if defined(SUPERGROUPS)
1018 zeromap(idmap);
1019 #else
1020 memset(idmap, 0, misc->idRange * sizeof(misc->idmap[0]));
1021 #endif
1022 do {
1023 found = 0;
1024 for (ei = createLow; ei < misc->nEntries; ei++) {
1025 if ((map[ei] & MAP_HASHES) && (map[ei] & MAP_RECREATE) == 0) {
1026 afs_int32 mask;
1027 afs_int32 access;
1028 int gq, uq;
1030 ea = ei * sizeof(struct prentry) + sizeof(cheader);
1031 code = pr_Read(ea, (char *)&e, sizeof(e));
1032 if (code)
1033 return code;
1035 if (misc->listentries)
1036 pr_PrintEntry(stdout, 0 /*not in host order */ , ea, &e,
1039 id = ntohl(e.id);
1040 flags = ntohl(e.flags);
1041 owner = ntohl(e.owner);
1042 name = QuoteName(e.name);
1044 if (!strcmp(e.name, "system:administrators")
1045 || !strcmp(e.name, "system:anyuser")
1046 || !strcmp(e.name, "system:authuser")
1047 || !strcmp(e.name, "system:backup")
1048 || !strcmp(e.name, "anonymous")) {
1049 builtinUsers++;
1050 goto user_done;
1053 /* check for duplicate id. This may still lead to duplicate
1054 * names. */
1055 #if defined(SUPERGROUPS)
1056 if (idcount(&idmap, id))
1057 #else
1058 if (idmap[id - misc->minId])
1059 #endif
1061 fprintf(stderr, "Skipping entry with duplicate id %di\n",
1062 id);
1063 goto user_done;
1066 /* If owner doesn't exist skip for now, unless we're our own
1067 * owner. If so, a special case allows a group to own itself
1068 * if caller is sysadmin. This leaves only owner cycles to
1069 * deal with. */
1071 if ((owner < misc->minId) || (owner > misc->maxId)) {
1072 if (owner == ANONYMOUSID)
1073 fprintf(stderr,
1074 "Warning: id %di is owned by ANONYMOUS; using sysadmin instead\n",
1075 id);
1076 else
1077 fprintf(stderr,
1078 "Bogus owner (%d) of id %di; using sysadmin instead\n",
1079 owner, id);
1080 owner = SYSADMINID;
1082 if (id == owner) {
1083 fprintf(stderr, "Warning: group %s is self owning\n",
1084 name);
1085 } else if (owner == 0) {
1086 fprintf(stderr,
1087 "Warning: orphan group %s will become self owning.\n",
1088 name);
1089 owner = id;
1091 #if defined(SUPERGROUPS)
1092 else if (!idcount(&idmap, owner))
1093 goto user_skip;
1094 #else
1095 else if (idmap[owner - misc->minId] == 0)
1096 goto user_skip;
1097 #endif
1099 if (rc)
1100 fprintf(rc, "cr %s %d %d\n", name, id, owner);
1102 gq = uq = access = mask = 0;
1103 if (flags & PRACCESS) {
1104 access = (flags >> PRIVATE_SHIFT);
1105 mask |= PR_SF_ALLBITS;
1107 if (flags & PRQUOTA) {
1108 gq = ntohl(e.ngroups);
1109 uq = ntohl(e.nusers);
1110 mask |= PR_SF_NGROUPS | PR_SF_NUSERS;
1112 if (mask && rc) {
1113 fprintf(rc, "sf %d %x %x %d %d\n", id, mask, access, gq,
1114 uq);
1116 user_done:
1117 map[ei] |= MAP_RECREATE;
1118 #if defined(SUPERGROUPS)
1119 if (id != ANONYMOUSID)
1120 inccount(&idmap, id);
1121 #else
1122 if (id != ANONYMOUSID)
1123 idmap[id - misc->minId]++;
1124 #endif
1125 found++;
1127 /* bump low water mark if possible */
1128 if (ei == createLow)
1129 createLow++;
1130 user_skip:;
1132 misc->verbose = 0;
1133 } while (found);
1135 /* Now create the entries with circular owner dependencies and make them
1136 * own themselves. This is the only way to create them with the correct
1137 * names. */
1138 for (ei = 0; ei < misc->nEntries; ei++)
1139 if (((map[ei] & MAP_HASHES) == MAP_HASHES)
1140 && (map[ei] & MAP_RECREATE) == 0) {
1141 ea = ei * sizeof(struct prentry) + sizeof(cheader);
1142 code = pr_Read(ea, (char *)&e, sizeof(e));
1143 if (code)
1144 return code;
1146 id = ntohl(e.id);
1147 name = QuoteName(e.name);
1148 fprintf(stderr, "Warning: group %s in self owning cycle\n", name);
1149 if (rc)
1150 fprintf(rc, "cr %s %d %d\n", name, id, id);
1151 #if defined(SUPERGROUPS)
1152 inccount(&idmap, id);
1153 #else
1154 idmap[id - misc->minId]++;
1155 #endif
1157 for (ei = 0; ei < misc->nEntries; ei++)
1158 if (((map[ei] & MAP_HASHES) == MAP_HASHES)
1159 && (map[ei] & MAP_RECREATE) == 0) {
1160 ea = ei * sizeof(struct prentry) + sizeof(cheader);
1161 code = pr_Read(ea, (char *)&e, sizeof(e));
1162 if (code)
1163 return code;
1165 owner = ntohl(e.owner);
1166 #if defined(SUPERGROUPS)
1167 if (!idcount(&idmap, owner))
1168 #else
1169 if (idmap[owner - misc->minId] == 0)
1170 #endif
1172 fprintf(stderr,
1173 "Skipping chown of '%s' to non-existant owner %di\n",
1174 e.name, owner);
1175 } else if (rc)
1176 fprintf(rc, "ce %d \"\" %d 0\n", ntohl(e.id), e.owner);
1179 if (rc == 0)
1180 return 0;
1182 /* Reconstruct membership information based on the groups' user lists. */
1183 for (ei = 0; ei < misc->nEntries; ei++) {
1184 if ((map[ei] & MAP_HASHES) == MAP_HASHES) {
1185 ea = ei * sizeof(struct prentry) + sizeof(cheader);
1186 code = pr_Read(ea, (char *)&e, sizeof(e));
1187 if (code)
1188 return code;
1190 id = ntohl(e.id);
1191 flags = ntohl(e.flags);
1193 if ((id < 0) && (flags & PRGRP)) {
1194 int count = 0;
1195 afs_int32 na;
1196 int i;
1197 for (i = 0; i < PRSIZE; i++) {
1198 afs_int32 uid = ntohl(e.entries[i]);
1199 if (uid == 0)
1200 break;
1201 if (uid == PRBADID)
1202 continue;
1203 #if !defined(SUPERGROUPS)
1204 if (uid > 0) {
1205 #endif
1206 fprintf(rc, "au %d %d\n", uid, id);
1207 count++;
1208 #if !defined(SUPERGROUPS)
1209 } else
1210 fprintf(stderr, "Skipping %di in group %di\n", uid,
1211 id);
1212 #endif
1214 na = ntohl(e.next);
1215 while (na) {
1216 struct contentry c;
1217 code = pr_Read(na, (char *)&c, sizeof(c));
1218 if (code)
1219 return code;
1221 if ((id == ntohl(c.id)) && (c.flags & htonl(PRCONT))) {
1222 for (i = 0; i < COSIZE; i++) {
1223 afs_int32 uid = ntohl(c.entries[i]);
1224 if (uid == 0)
1225 break;
1226 if (uid == PRBADID)
1227 continue;
1228 #if !defined(SUPERGROUPS)
1229 if (uid > 0) {
1230 #endif
1231 fprintf(rc, "au %d %d\n", uid, id);
1232 count++;
1233 #if !defined(SUPERGROUPS)
1234 } else
1235 fprintf(stderr, "Skipping %di in group %di\n",
1236 uid, id);
1237 #endif
1239 } else {
1240 fprintf(stderr, "Skipping continuation block at %d\n",
1241 na);
1242 break;
1244 na = ntohl(c.next);
1246 if (count != ntohl(e.count))
1247 fprintf(stderr,
1248 "Group membership count problem found %d should be %d\n",
1249 count, ntohl(e.count));
1250 } else if ((id < 0) || (flags & PRGRP)) {
1251 fprintf(stderr, "Skipping group %di\n", id);
1255 return 0;
1258 afs_int32
1259 CheckPrDatabase(struct misc_data *misc) /* info & statistics */
1261 afs_int32 code;
1262 afs_int32 eof;
1263 int n;
1264 char *map; /* map of each entry in db */
1266 eof = ntohl(cheader.eofPtr);
1267 eof -= sizeof(cheader);
1268 n = eof / sizeof(struct prentry);
1269 if ((eof < 0) || (n * sizeof(struct prentry) != eof)) {
1270 code = PRDBBAD;
1271 afs_com_err(whoami, code,
1272 "eof ptr no good: eof=%d, sizeof(prentry)=%" AFS_SIZET_FMT,
1273 eof, sizeof(struct prentry));
1274 abort:
1275 return code;
1277 if (misc->verbose)
1278 printf("Database has %d entries\n", n);
1279 map = (char *)malloc(n);
1280 memset(map, 0, n);
1281 misc->nEntries = n;
1283 if (misc->verbose) {
1284 printf("\nChecking name hash table\n");
1285 fflush(stdout);
1287 code = WalkHashTable(cheader.nameHash, MAP_NAMEHASH, map, misc);
1288 if (code) {
1289 afs_com_err(whoami, code, "walking name hash");
1290 goto abort;
1292 if (misc->verbose) {
1293 printf("\nChecking id hash table\n");
1294 fflush(stdout);
1296 code = WalkHashTable(cheader.idHash, MAP_IDHASH, map, misc);
1297 if (code) {
1298 afs_com_err(whoami, code, "walking id hash");
1299 goto abort;
1302 /* hash walk calculates min and max id */
1303 #if defined(SUPERGROUPS)
1304 misc->idmap = 0;
1305 #else
1306 n = ((misc->maxId > misc->maxForId) ? misc->maxId : misc->maxForId);
1307 misc->idRange = n - misc->minId + 1;
1308 misc->idmap = (afs_int32 *) malloc(misc->idRange * sizeof(afs_int32));
1309 if (!misc->idmap) {
1310 afs_com_err(whoami, 0, "Unable to malloc space for max ids of %d",
1311 misc->idRange);
1312 code = -1;
1313 goto abort;
1315 memset(misc->idmap, 0, misc->idRange * sizeof(misc->idmap[0]));
1316 #endif /* SUPERGROUPS */
1318 if (misc->verbose) {
1319 printf("\nChecking entry chains\n");
1320 fflush(stdout);
1322 code = WalkChains(map, misc);
1323 if (code) {
1324 afs_com_err(whoami, code, "walking chains");
1325 goto abort;
1327 if (misc->verbose) {
1328 printf("\nChecking free list\n");
1329 fflush(stdout);
1331 code = WalkNextChain(map, misc, 0, 0);
1332 if (code) {
1333 afs_com_err(whoami, code, "walking free list");
1334 goto abort;
1336 if (misc->verbose) {
1337 printf("\nChecking orphans list\n");
1338 fflush(stdout);
1340 code = WalkOwnedChain(map, misc, 0, 0);
1341 if (code) {
1342 afs_com_err(whoami, code, "walking orphan list");
1343 goto abort;
1346 if (misc->verbose) {
1347 printf("\nChecking for unreferenced entries\n");
1348 fflush(stdout);
1350 code = GC(map, misc);
1351 if (code) {
1352 afs_com_err(whoami, code, "looking for unreferenced entries");
1353 goto abort;
1356 DumpRecreate(map, misc); /* check for owner cycles */
1357 if (misc->recreate)
1358 fclose(misc->recreate);
1360 if (misc->anon != 2) /* once for each hash table */
1361 fprintf(stderr, "Problems with ANON=%d\n", misc->anon);
1362 if (misc->ncells || misc->ninsts)
1363 fprintf(stderr, "Unexpected entry type\n");
1364 if (misc->nusers != ntohl(cheader.usercount)) {
1365 fprintf(stderr,
1366 "User count inconsistent: should be %d, header claims: %d\n",
1367 misc->nusers, ntohl(cheader.usercount));
1369 if (misc->ngroups != ntohl(cheader.groupcount)) {
1370 fprintf(stderr,
1371 "Group count inconsistent: should be %d, header claims: %d\n",
1372 misc->ngroups, ntohl(cheader.groupcount));
1374 if (misc->maxId > ntohl(cheader.maxID))
1375 fprintf(stderr,
1376 "Database's max user Id (%d) is smaller than largest user's Id (%d).\n",
1377 ntohl(cheader.maxID), misc->maxId);
1378 if (misc->minId < ntohl(cheader.maxGroup))
1379 fprintf(stderr,
1380 "Database's max group Id (%d) is smaller than largest group's Id (%d).\n",
1381 ntohl(cheader.maxGroup), misc->minId);
1383 if (misc->verbose) {
1384 printf("\nMaxId = %d, MinId = %d, MaxForeignId = %d\n", misc->maxId,
1385 misc->minId, misc->maxForId);
1386 printf
1387 ("Free list is %d entries in length, %d groups on orphan list\n",
1388 misc->freeLength, misc->orphanLength);
1389 printf
1390 ("The longest owner list is %d, the longest continuation block chain is %d\n",
1391 misc->maxOwnerLength, misc->maxContLength);
1392 printf("%d users ; %d foreign users ; and %d groups\n", misc->nusers,
1393 misc->nforeigns, misc->ngroups);
1396 free(map);
1397 return code;
1400 #include "AFS_component_version_number.c"
1403 WorkerBee(struct cmd_syndesc *as, void *arock)
1405 afs_int32 code;
1406 char *recreateFile;
1407 struct misc_data misc; /* info & statistics */
1409 initialize_PT_error_table();
1410 initialize_U_error_table();
1412 pr_dbaseName = AFSDIR_SERVER_PRDB_FILEPATH;
1413 memset(&misc, 0, sizeof(misc));
1415 pr_dbaseName = as->parms[0].items->data; /* -database */
1416 misc.listuheader = (as->parms[1].items ? 1 : 0); /* -uheader */
1417 misc.listpheader = (as->parms[2].items ? 1 : 0); /* -pheader */
1418 misc.listentries = (as->parms[3].items ? 1 : 0); /* -entries */
1419 misc.verbose = (as->parms[4].items ? 1 : 0); /* -verbose */
1420 recreateFile = (as->parms[5].items ? as->parms[5].items->data : NULL); /* -rebuild */
1422 fd = open(pr_dbaseName, O_RDONLY, 0);
1423 if (fd == -1) {
1424 afs_com_err(whoami, errno, "Open failed on db %s", pr_dbaseName);
1425 exit(2);
1428 /* Read the ubik header */
1429 if (misc.listuheader) {
1430 readUbikHeader(&misc);
1433 code = ReadHeader();
1434 if (code)
1435 return code;
1436 if (misc.listpheader)
1437 printheader(&cheader);
1439 if (recreateFile) {
1440 misc.recreate = fopen(recreateFile, "w");
1441 if (misc.recreate == 0) {
1442 afs_com_err(whoami, errno,
1443 "can't create file for recreation instructions: %s",
1444 recreateFile);
1445 exit(4);
1448 code = CheckPrDatabase(&misc);
1449 if (code) {
1450 afs_com_err(whoami, code, "Checking prserver database");
1451 exit(3);
1453 exit(0);
1457 main(int argc, char *argv[])
1459 struct cmd_syndesc *ts;
1461 setlinebuf(stdout);
1463 ts = cmd_CreateSyntax(NULL, WorkerBee, NULL, "PRDB check");
1464 cmd_AddParm(ts, "-database", CMD_SINGLE, CMD_REQUIRED, "ptdb_file");
1465 cmd_AddParm(ts, "-uheader", CMD_FLAG, CMD_OPTIONAL,
1466 "Display UBIK header");
1467 cmd_AddParm(ts, "-pheader", CMD_FLAG, CMD_OPTIONAL,
1468 "Display KADB header");
1469 cmd_AddParm(ts, "-entries", CMD_FLAG, CMD_OPTIONAL, "Display entries");
1470 cmd_AddParm(ts, "-verbose", CMD_FLAG, CMD_OPTIONAL, "verbose");
1471 cmd_AddParm(ts, "-rebuild", CMD_SINGLE, CMD_OPTIONAL | CMD_HIDE,
1472 "out_file");
1474 return cmd_Dispatch(argc, argv);
1478 #if defined(SUPERGROUPS)
1480 /* new routines to deal with very large ID numbers */
1482 void
1483 zeromap(struct idused *idmap)
1485 while (idmap) {
1486 memset(idmap->idcount, 0, sizeof idmap->idcount);
1487 idmap = idmap->idnext;
1491 void
1492 inccount(struct idused **idmapp, int id)
1494 struct idused *idmap;
1496 if (IDCOUNT & (IDCOUNT - 1)) {
1497 fprintf(stderr, "IDCOUNT must be power of 2!\n");
1498 exit(1);
1500 while ((idmap = *idmapp) != NULL) {
1501 if (idmap->idstart == (id & ~(IDCOUNT - 1)))
1502 break;
1503 idmapp = &idmap->idnext;
1505 if (!idmap) {
1506 idmap = calloc(1, sizeof *idmap);
1507 if (!idmap) {
1508 perror("idmap");
1509 exit(1);
1511 idmap->idstart = id & ~(IDCOUNT - 1);
1512 idmap->idnext = *idmapp;
1513 *idmapp = idmap;
1515 ++idmap->idcount[id & (IDCOUNT - 1)];
1519 idcount(struct idused **idmapp, int id)
1521 struct idused *idmap;
1523 if (IDCOUNT & (IDCOUNT - 1)) {
1524 fprintf(stderr, "IDCOUNT must be power of 2!\n");
1525 exit(1);
1527 while ((idmap = *idmapp) != NULL) {
1528 if (idmap->idstart == (id & ~(IDCOUNT - 1))) {
1529 return idmap->idcount[id & (IDCOUNT - 1)];
1531 idmapp = &idmap->idnext;
1533 return 0;
1535 #endif /* SUPERGROUPS */