osi-wakeup-return-zero-on-doing-a-wakeup-20021008
[openafs.git] / src / afs / afs_user.c
1 /*
2  * Copyright 2000, International Business Machines Corporation and others.
3  * All Rights Reserved.
4  * 
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  */
9
10 /*
11  * Implements:
12  */
13 #include <afsconfig.h>
14 #include "../afs/param.h"
15
16 RCSID("$Header$");
17
18 #include "../afs/stds.h"
19 #include "../afs/sysincludes.h" /* Standard vendor system headers */
20
21 #if !defined(UKERNEL)
22 #include <net/if.h>
23 #include <netinet/in.h>
24
25 #ifdef AFS_SGI62_ENV
26 #include "../h/hashing.h"
27 #endif
28 #if !defined(AFS_HPUX110_ENV) && !defined(AFS_LINUX20_ENV) && !defined(AFS_DARWIN60_ENV)
29 #include <netinet/in_var.h>
30 #endif /* ! AFS_HPUX110_ENV */
31 #endif /* !defined(UKERNEL) */
32
33 #include "../afs/afsincludes.h" /* Afs-based standard headers */
34 #include "../afs/afs_stats.h"   /* afs statistics */
35
36 #if     defined(AFS_SUN56_ENV)
37 #include <inet/led.h>
38 #include <inet/common.h>
39 #if   defined(AFS_SUN58_ENV)
40 #include <netinet/ip6.h>
41 #endif
42 #include <inet/ip.h>
43 #endif
44
45
46 /* Exported variables */
47 afs_rwlock_t afs_xuser;
48 struct unixuser *afs_users[NUSERS];
49
50
51 /* Forward declarations */
52 void afs_ResetAccessCache(afs_int32 uid, int alock);
53
54 /*
55  * Called with afs_xuser, afs_xserver and afs_xconn locks held, to delete
56  * appropriate conn structures for au
57  */
58 static void RemoveUserConns(register struct unixuser *au)
59 {
60     register int i;
61     register struct server *ts;
62     register struct srvAddr *sa;
63     register struct conn *tc, **lc;
64
65     AFS_STATCNT(RemoveUserConns);
66     for (i=0;i<NSERVERS;i++) {
67         for (ts = afs_servers[i]; ts; ts=ts->next) {
68             for (sa = ts->addr; sa; sa = sa->next_sa) { 
69                 lc = &sa->conns;
70                 for (tc = *lc; tc; lc = &tc->next, tc = *lc) {
71                     if (tc->user == au && tc->refCount == 0) {
72                         *lc = tc->next;
73                         AFS_GUNLOCK();
74                         rx_DestroyConnection(tc->id);
75                         AFS_GLOCK();
76                         afs_osi_Free(tc, sizeof(struct conn));
77                         break;  /* at most one instance per server */
78                     } /*Found unreferenced connection for user*/
79                 } /*For each connection on the server*/
80             }
81         } /*For each server on chain*/
82     } /*For each chain*/
83
84 } /*RemoveUserConns*/
85
86
87 /* Called from afs_Daemon to garbage collect unixusers no longer using system,
88  * and their conns.  The aforce parameter tells the function to flush all
89  * *unauthenticated* conns, no matter what their expiration time; it exists
90  * because after we choose our final rx epoch, we want to stop using calls with
91  * other epochs as soon as possible (old file servers act bizarrely when they
92  * see epoch changes).
93  */
94 void afs_GCUserData(int aforce)
95 {
96     register struct unixuser *tu, **lu, *nu;
97     register int i;
98     afs_int32 now, delFlag;
99
100     AFS_STATCNT(afs_GCUserData);
101     /* Obtain locks in valid order */
102     ObtainWriteLock(&afs_xuser,95);
103     ObtainReadLock(&afs_xserver);
104     ObtainWriteLock(&afs_xconn,96);
105     now = osi_Time();
106     for (i=0;i<NUSERS;i++) {
107         for (lu = &afs_users[i], tu = *lu; tu; tu = nu) {
108             delFlag = 0;        /* should we delete this dude? */
109             /* Don't garbage collect users in use now (refCount) */
110             if (tu->refCount == 0) {
111                 if (tu->states & UHasTokens) {
112                     /*
113                      * Give ourselves a little extra slack, in case we
114                      * reauthenticate
115                      */
116                     if (tu->ct.EndTimestamp < now - NOTOKTIMEOUT)
117                         delFlag = 1;
118                 }
119                 else {
120                     if (aforce || (tu->tokenTime < now - NOTOKTIMEOUT))
121                         delFlag = 1;
122                 }
123             }
124             nu = tu->next;
125             if (delFlag) {
126                 *lu = tu->next;
127                 RemoveUserConns(tu);
128                 if (tu->stp)
129                     afs_osi_Free(tu->stp, tu->stLen);
130                 if (tu->exporter)
131                     EXP_RELE(tu->exporter);
132                 afs_osi_Free(tu, sizeof(struct unixuser));
133             }
134             else {
135                 lu = &tu->next;
136             }
137         }
138     }
139     ReleaseWriteLock(&afs_xconn);
140     ReleaseWriteLock(&afs_xuser);
141     ReleaseReadLock(&afs_xserver);
142
143 } /*afs_GCUserData*/
144
145
146 /*
147  * Check for unixusers who encountered bad tokens, and reset the access
148  * cache for these guys.  Can't do this when token expiration detected,
149  * since too many locks are set then.
150  */
151 void afs_CheckTokenCache(void)
152 {
153     register int i;
154     register struct unixuser *tu;
155     afs_int32 now;
156
157     AFS_STATCNT(afs_CheckCacheResets);
158     ObtainReadLock(&afs_xvcache);
159     ObtainReadLock(&afs_xuser);
160     now = osi_Time();
161     for (i=0;i<NUSERS;i++) {
162         for (tu=afs_users[i]; tu; tu=tu->next) {
163             register afs_int32 uid;
164
165             /*
166              * If tokens are still good and user has Kerberos tickets,
167              * check expiration
168              */
169             if (!(tu->states & UTokensBad) && tu->vid != UNDEFVID) {
170                 if (tu->ct.EndTimestamp < now) {
171                     /*
172                      * This token has expired, warn users and reset access
173                      * cache.
174                      */
175 #ifdef notdef
176                     /* I really hate this message - MLK */
177                     afs_warn("afs: Tokens for user of AFS id %d for cell %s expired now\n",
178                            tu->vid, afs_GetCell(tu->cell)->cellName);
179 #endif
180                     tu->states |= (UTokensBad | UNeedsReset);
181                 }
182             }
183             if (tu->states & UNeedsReset) {
184                 tu->states &= ~UNeedsReset;
185                 uid = tu->uid;
186                 afs_ResetAccessCache(uid, 0);
187             }
188         }
189     }
190     ReleaseReadLock(&afs_xuser);
191     ReleaseReadLock(&afs_xvcache);
192
193 } /*afs_CheckTokenCache*/
194
195
196 void afs_ResetAccessCache(afs_int32 uid, int alock)
197 {
198     register int i;
199     register struct vcache *tvc;
200     struct axscache *ac;
201
202     AFS_STATCNT(afs_ResetAccessCache);
203     if (alock)
204         ObtainReadLock(&afs_xvcache);
205     for(i=0;i<VCSIZE;i++) {
206         for(tvc=afs_vhashT[i]; tvc; tvc=tvc->hnext) {
207           /* really should do this under cache write lock, but that.
208              is hard to under locking hierarchy */
209          if (tvc->Access && (ac = afs_FindAxs(tvc->Access, uid))) {
210             afs_RemoveAxs (&tvc->Access, ac);
211           }
212         }
213     }
214     if (alock)
215         ReleaseReadLock(&afs_xvcache);
216
217 } /*afs_ResetAccessCache*/
218
219
220 /*
221  * Ensure all connections make use of new tokens.  Discard incorrectly-cached
222  * access info.
223  */
224 void afs_ResetUserConns (register struct unixuser *auser)
225 {
226     int i;
227     struct srvAddr *sa;
228     struct conn *tc;
229     
230     AFS_STATCNT(afs_ResetUserConns);
231     ObtainReadLock(&afs_xsrvAddr);
232     ObtainWriteLock(&afs_xconn,98);
233
234     for (i=0;i<NSERVERS;i++) {
235         for (sa = afs_srvAddrs[i]; sa; sa=sa->next_bkt) {
236                 for (tc = sa->conns; tc; tc=tc->next) {
237                     if (tc->user == auser) {
238                         tc->forceConnectFS = 1;
239                     }
240                 }
241         }
242     }
243
244     ReleaseWriteLock(&afs_xconn);
245     ReleaseReadLock(&afs_xsrvAddr);
246     afs_ResetAccessCache(auser->uid, 1);
247     auser->states &= ~UNeedsReset;
248 } /*afs_ResetUserConns*/
249
250
251 struct unixuser *afs_FindUser(afs_int32 auid, afs_int32 acell, afs_int32 locktype)
252 {
253     register struct unixuser *tu;
254     register afs_int32 i;
255
256     AFS_STATCNT(afs_FindUser);
257     i = UHash(auid);
258     ObtainWriteLock(&afs_xuser,99);
259     for(tu = afs_users[i]; tu; tu = tu->next) {
260         if (tu->uid == auid && ((tu->cell == acell) || (acell == -1))) {
261             tu->refCount++;
262             ReleaseWriteLock(&afs_xuser);
263             return tu;
264         }
265     }
266     ReleaseWriteLock(&afs_xuser);
267     return NULL;
268
269 } /*afs_FindUser*/
270
271
272 /*------------------------------------------------------------------------
273  * EXPORTED afs_ComputePAGStats
274  *
275  * Description:
276  *      Compute a set of stats concerning PAGs used by this machine.
277  *
278  * Arguments:
279  *      None.
280  *
281  * Returns:
282  *      Nothing.
283  *
284  * Environment:
285  *      The results are put in the structure responsible for keeping
286  *      detailed CM stats.  Note: entries corresponding to a single PAG
287  *      will appear on the identical hash chain, so sweeping the chain
288  *      will find all entries related to a single PAG.
289  *
290  * Side Effects:
291  *      As advertised.
292  *------------------------------------------------------------------------*/
293
294 void afs_ComputePAGStats(void)
295 {
296     register struct unixuser *currPAGP;           /*Ptr to curr PAG*/
297     register struct unixuser *cmpPAGP;            /*Ptr to PAG being compared*/
298     register struct afs_stats_AuthentInfo *authP; /*Ptr to stats area*/
299     int curr_Record;                              /*Curr record */
300     int currChain;                                /*Curr hash chain*/
301     int currChainLen;                             /*Length of curr hash chain*/
302     int currPAGRecords;                           /*# records in curr PAG*/
303
304     /*
305      * Lock out everyone else from scribbling on the PAG entries.
306      */
307     ObtainReadLock(&afs_xuser);
308
309     /*
310      * Initialize the tallies, then sweep through each hash chain.  We
311      * can't bzero the structure, since some fields are cumulative over
312      * the CM's lifetime.
313      */
314     curr_Record = 0;
315     authP = &(afs_stats_cmfullperf.authent);
316     authP->curr_PAGs             = 0;
317     authP->curr_Records          = 0;
318     authP->curr_AuthRecords      = 0;
319     authP->curr_UnauthRecords    = 0;
320     authP->curr_MaxRecordsInPAG  = 0;
321     authP->curr_LongestChain     = 0;
322
323     for (currChain = 0; currChain < NUSERS; currChain++) {
324         currChainLen = 0;
325         for (currPAGP = afs_users[currChain]; currPAGP;
326              currPAGP = currPAGP->next) 
327           {
328             /*
329              * Bump the number of records on this current chain, along with
330              * the total number of records in existence.
331              */
332             currChainLen++;
333             curr_Record++;
334             /*
335              * We've found a previously-uncounted PAG.  If it's been deleted
336              * but just not garbage-collected yet, we step over it.
337              */
338             if (currPAGP->vid == UNDEFVID)
339                 continue;
340
341             /*
342              * If this PAG record has already been ``counted', namely
343              * associated with a PAG and tallied, clear its bit and move on.
344              */
345             (authP->curr_Records)++;
346             if (currPAGP->states & UPAGCounted) {
347                 currPAGP->states &= ~UPAGCounted;
348                 continue;
349             } /*We've counted this one already*/
350
351
352
353             /*
354              * Jot initial info down, then sweep down the rest of the hash
355              * chain, looking for matching PAG entries.  Note: to properly
356              * ``count'' the current record, we first compare it to itself
357              * in the following loop.
358              */
359             (authP->curr_PAGs)++;
360             currPAGRecords = 0;
361
362             for (cmpPAGP = currPAGP; cmpPAGP; cmpPAGP = cmpPAGP->next) {
363                 if (currPAGP->uid == cmpPAGP->uid) {
364                     /*
365                      * The records belong to the same PAG.  First, mark the
366                      * new record as ``counted'' and bump the PAG size.
367                      * Then, record the state of its ticket, if any.
368                      */
369                     cmpPAGP->states |= UPAGCounted;
370                     currPAGRecords++;
371                     if ((cmpPAGP->states & UHasTokens) &&
372                         !(cmpPAGP->states & UTokensBad))
373                         (authP->curr_AuthRecords)++;
374                     else
375                         (authP->curr_UnauthRecords)++;
376                 } /*Records belong to same PAG*/
377             } /*Compare to rest of PAG records in chain*/
378
379             /*
380              * In the above comparisons, the current PAG record has been
381              * marked as counted.  Erase this mark before moving on.
382              */
383             currPAGP->states &= ~UPAGCounted;
384
385             /*
386              * We've compared our current PAG record with all remaining
387              * PAG records in the hash chain.  Update our tallies, and
388              * perhaps even our lifetime high water marks.  After that,
389              * remove our search mark and advance to the next comparison
390              * pair.
391              */
392             if (currPAGRecords > authP->curr_MaxRecordsInPAG) {
393                 authP->curr_MaxRecordsInPAG = currPAGRecords;
394                 if (currPAGRecords > authP->HWM_MaxRecordsInPAG)
395                     authP->HWM_MaxRecordsInPAG = currPAGRecords;
396             }
397         } /*Sweep a hash chain*/
398
399         /*
400          * If the chain we just finished zipping through is the longest we've
401          * seen yet, remember this fact before advancing to the next chain.
402          */
403         if (currChainLen > authP->curr_LongestChain) {
404             authP->curr_LongestChain = currChainLen;
405             if (currChainLen > authP->HWM_LongestChain)
406                 authP->HWM_LongestChain = currChainLen;
407         }
408
409     } /*For each hash chain in afs_user*/
410
411     /*
412      * Now that we've counted everything up, we can consider all-time
413      * numbers.
414      */
415     if (authP->curr_PAGs > authP->HWM_PAGs)
416         authP->HWM_PAGs = authP->curr_PAGs;
417     if (authP->curr_Records > authP->HWM_Records)
418         authP->HWM_Records = authP->curr_Records;
419
420     /*
421      * People are free to manipulate the PAG structures now.
422      */
423     ReleaseReadLock(&afs_xuser);
424
425 } /*afs_ComputePAGStats*/
426
427
428 struct unixuser *afs_GetUser(register afs_int32 auid, 
429         afs_int32 acell, afs_int32 locktype)
430 {
431     register struct unixuser *tu, *pu=0;
432     register afs_int32 i;
433     register afs_int32 RmtUser = 0;
434
435     AFS_STATCNT(afs_GetUser);
436     i = UHash(auid);
437     ObtainWriteLock(&afs_xuser,104);
438     for (tu = afs_users[i]; tu; tu = tu->next) {
439         if (tu->uid == auid) {
440             RmtUser = 0;
441             pu = NULL;
442             if (tu->exporter) {
443                 RmtUser = 1;
444                 pu = tu;
445             }
446             if (tu->cell == -1 && acell != -1) {
447                 /* Here we setup the real cell for the client */
448                 tu->cell = acell;
449                 tu->refCount++;
450                 ReleaseWriteLock(&afs_xuser);
451                 return tu;
452             } else
453                 if (tu->cell == acell || acell == -1) {
454                     tu->refCount++;
455                     ReleaseWriteLock(&afs_xuser);
456                     return tu;
457                 }               
458         }
459     }
460     tu = (struct unixuser *) afs_osi_Alloc(sizeof(struct unixuser));
461 #ifndef AFS_NOSTATS
462     afs_stats_cmfullperf.authent.PAGCreations++;
463 #endif /* AFS_NOSTATS */
464     memset((char *)tu, 0, sizeof(struct unixuser));
465     tu->next = afs_users[i];
466     afs_users[i] = tu;
467     if (RmtUser) {
468         /*
469          * This is for the case where an additional unixuser struct is
470          * created because the remote client is accessing a different cell;
471          * we simply rerecord relevant information from the original
472          * structure
473          */
474         if (pu && pu->exporter) {
475             (void) EXP_HOLD(tu->exporter = pu->exporter);
476         }
477     }
478     tu->uid = auid;
479     tu->cell = acell;
480     tu->vid = UNDEFVID;
481     tu->refCount = 1;
482     tu->tokenTime = osi_Time();
483     ReleaseWriteLock(&afs_xuser);
484     return tu;
485
486 } /*afs_GetUser*/
487
488
489 void afs_PutUser(register struct unixuser *au, afs_int32 locktype)
490 {
491     AFS_STATCNT(afs_PutUser);
492     --au->refCount;
493 } /*afs_PutUser*/
494
495
496 /*
497  * Set the primary flag on a unixuser structure, ensuring that exactly one
498  * dude has the flag set at any time for a particular unix uid.
499  */
500 void afs_SetPrimary(register struct unixuser *au, register int aflag)
501 {
502     register struct unixuser *tu;
503     register int i;
504     struct unixuser *pu;
505
506     AFS_STATCNT(afs_SetPrimary);
507     i = UHash(au->uid);
508     pu = NULL;
509     ObtainWriteLock(&afs_xuser,105);
510     /*
511      * See if anyone is this uid's primary cell yet; recording in pu the
512      * corresponding user
513      */
514     for (tu=afs_users[i]; tu; tu=tu->next) {
515         if (tu->uid == au->uid && (tu->states & UPrimary)) {
516             pu = tu;
517         }
518     }
519     if (pu && !(pu->states & UHasTokens)) {
520         /*
521          * Primary user has unlogged, don't treat him as primary any longer;
522          * note that we want to treat him as primary until now, so that
523          * people see a primary identity until now.
524          */
525         pu->states &= ~UPrimary;
526         pu = NULL;
527     }
528     if (aflag == 1) {
529         /* setting au to be primary */
530         if (pu) pu->states &= ~UPrimary;
531         au->states |= UPrimary;
532     }
533     else
534         if (aflag == 0) {
535             /* we don't know if we're supposed to be primary or not */
536             if (!pu || au == pu) {
537                 au->states |= UPrimary;
538             }
539             else
540                 au->states &= ~UPrimary;
541     }
542     ReleaseWriteLock(&afs_xuser);
543
544 } /*afs_SetPrimary*/
545
546
547 #if AFS_GCPAGS
548
549 /*
550  * Called by osi_TraverseProcTable (from afs_GCPAGs) for each 
551  * process in the system.
552  * If the specified process uses a PAG, clear that PAG's temporary
553  * 'deleteme' flag.
554  */
555
556 /*
557  * This variable keeps track of the number of UID-base
558  * tokens in the afs_users table. When it's zero
559  * the per process loop in GCPAGs doesn't have to
560  * check processes without pags against the afs_users table.
561  */
562 static afs_int32 afs_GCPAGs_UIDBaseTokenCount=0;
563
564 /*
565  * These variables keep track of the number of times
566  * afs_GCPAGs_perproc_func() is called.  If it is not called at all when
567  * walking the process table, there is something wrong and we should not
568  * prematurely expire any tokens.
569  */
570 static size_t afs_GCPAGs_perproc_count=0;
571 static size_t afs_GCPAGs_cred_count=0;
572
573 /*
574  * LOCKS: afs_GCPAGs_perproc_func requires write lock on afs_xuser
575  */
576 void afs_GCPAGs_perproc_func(AFS_PROC *pproc)
577 {
578     afs_int32 pag, hash, uid;
579     const struct AFS_UCRED *pcred;
580
581     afs_GCPAGs_perproc_count++;
582
583     pcred = afs_osi_proc2cred(pproc);
584     if(!pcred) 
585         return;
586
587     afs_GCPAGs_cred_count++;
588
589     pag = PagInCred(pcred);
590 #if defined(AFS_DARWIN_ENV) || defined(AFS_FBSD40_ENV)
591     uid = (pag != NOPAG ? pag : pcred->cr_uid);
592 #else
593     uid = (pag != NOPAG ? pag : pcred->cr_ruid);
594 #endif
595     hash = UHash(uid);
596
597     /* if this token is PAG based, or it's UID based and 
598        UID-based tokens exist */
599     if((pag != NOPAG) || (afs_GCPAGs_UIDBaseTokenCount)) {
600         /* find the entries for this uid in all cells and clear the not
601          * referenced flag.  Can't use afs_FindUser, because it just returns
602          * the specific cell asked for, or the first one found. 
603          */
604         struct unixuser *pu;
605         for(pu = afs_users[hash]; pu; pu = pu->next) {
606             if (pu->uid == uid) {
607                 if(pu->states & TMP_UPAGNotReferenced) {
608                     /* clear the 'deleteme' flag for this entry */
609                     pu->states &= ~TMP_UPAGNotReferenced;
610                     if(pag == NOPAG) {
611                         /* This is a uid based token that hadn't 
612                            previously been cleared, so decrement the
613                            outstanding uid based token count */
614                         afs_GCPAGs_UIDBaseTokenCount--;
615                     }
616                 }
617             }
618         }
619     }
620 }
621
622 /*
623  * Go through the process table, find all unused PAGs 
624  * and cause them to be deleted during the next GC.
625  *
626  * returns the number of PAGs marked for deletion
627  *
628  * On AIX we free PAGs when the last accessing process exits,
629  * so this routine is not needed.
630  *
631  * In AFS WebSecure, we explicitly call unlog when we remove
632  * an entry in the login cache, so this routine is not needed.
633  */
634
635 afs_int32 afs_GCPAGs(afs_int32 *ReleasedCount)
636 {
637     struct unixuser *pu;
638     int i;
639
640     if (afs_gcpags != AFS_GCPAGS_OK) {
641         return 0;
642     }
643
644     *ReleasedCount = 0;
645
646     /* first, loop through afs_users, setting the temporary 'deleteme' flag */
647     ObtainWriteLock(&afs_xuser,419);
648     afs_GCPAGs_UIDBaseTokenCount=0;
649     for(i=0; i < NUSERS; i++) {
650         for(pu = afs_users[i]; pu; pu = pu->next) {
651             pu->states |= TMP_UPAGNotReferenced;
652             if (((pu->uid >> 24) & 0xff) != 'A') {
653                 /* this is a uid-based token, */
654                 /* increment the count */
655                 afs_GCPAGs_UIDBaseTokenCount++;
656             }
657         }
658     }
659
660     /* Now, iterate through the systems process table, 
661      * for each process, mark it's PAGs (if any) in use.
662      * i.e. clear the temporary deleteme flag.
663      */
664     afs_GCPAGs_perproc_count=0;
665     afs_GCPAGs_cred_count=0;
666
667     afs_osi_TraverseProcTable();
668
669     /* If there is an internal problem and afs_GCPAGs_perproc_func()
670      * does not get called, disable gcpags so that we do not
671      * accidentally expire all the tokens in the system.
672      */
673     if (afs_gcpags == AFS_GCPAGS_OK && !afs_GCPAGs_perproc_count) {
674         afs_gcpags = AFS_GCPAGS_EPROCWALK;
675     }
676
677     if (afs_gcpags == AFS_GCPAGS_OK && !afs_GCPAGs_cred_count) {
678         afs_gcpags = AFS_GCPAGS_ECREDWALK;
679     }
680
681     /* Now, go through afs_users again, any that aren't in use
682      * (temp deleteme flag still set) will be marked for later deletion,
683      * by setting their expire times to 0.
684      */
685     for(i=0; i < NUSERS; i++) {
686         for(pu = afs_users[i]; pu; pu = pu->next) {
687             if(pu->states & TMP_UPAGNotReferenced) {
688                 
689                 /* clear the temp flag */
690                 pu->states &= ~TMP_UPAGNotReferenced;
691                 
692                 /* Is this entry on behalf of a 'remote' user ?
693                  * i.e. nfs translator, etc.
694                  */
695                 if(!pu->exporter && afs_gcpags == AFS_GCPAGS_OK) {
696                     /* set the expire times to 0, causes 
697                      * afs_GCUserData to remove this entry 
698                      */
699                     pu->ct.EndTimestamp = 0;
700                     pu->tokenTime = 0;
701                     
702                     (*ReleasedCount)++;   /* remember how many we marked (info only) */
703                 }
704             }
705         }
706     }
707
708     ReleaseWriteLock(&afs_xuser);
709
710     return 0;
711 }
712
713 #endif  /* AFS_GCPAGS */