libafs: Implement unixuser RW locks
[openafs.git] / src / afs / afs_nfsclnt.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 #include <afsconfig.h>
11 #include "afs/param.h"
12
13
14 #if !defined(AFS_NONFSTRANS) || defined(AFS_AIX_IAUTH_ENV)
15 #include "afs/sysincludes.h"    /* Standard vendor system headers */
16 #include "afsincludes.h"        /* Afs-based standard headers */
17 #include "afs/afs_stats.h"      /* statistics */
18 #include "afs/nfsclient.h"
19 #include "rx/rx_globals.h"
20 #include "afs/pagcb.h"
21
22 void afs_nfsclient_hold(), afs_PutNfsClientPag(), afs_nfsclient_GC();
23 static void afs_nfsclient_getcreds();
24 int afs_nfsclient_sysname(), afs_nfsclient_stats(), afs_nfsclient_checkhost();
25 afs_uint32 afs_nfsclient_gethost();
26 #ifdef AFS_AIX_IAUTH_ENV
27 int afs_allnfsreqs, afs_nfscalls;
28 #endif
29
30 /* routines exported to the "AFS exporter" layer */
31 struct exporterops nfs_exportops = {
32     afs_nfsclient_reqhandler,
33     afs_nfsclient_hold,
34     afs_PutNfsClientPag,        /* Used to be afs_nfsclient_rele */
35     afs_nfsclient_sysname,
36     afs_nfsclient_GC,
37     afs_nfsclient_stats,
38     afs_nfsclient_checkhost,
39     afs_nfsclient_gethost
40 };
41
42
43 struct nfsclientpag *afs_nfspags[NNFSCLIENTS];
44 afs_lock_t afs_xnfspag /*, afs_xnfsreq */ ;
45 extern struct afs_exporter *afs_nfsexporter;
46
47 /* Creates an nfsclientpag structure for the (uid, host) pair if one doesn't
48  * exist. RefCount is incremented and it's time stamped. */
49 static struct nfsclientpag *
50 afs_GetNfsClientPag(afs_int32 uid, afs_uint32 host)
51 {
52     struct nfsclientpag *np;
53     afs_int32 i, now;
54
55 #if defined(AFS_SGIMP_ENV)
56     osi_Assert(ISAFS_GLOCK());
57 #endif
58     AFS_STATCNT(afs_GetNfsClientPag);
59     i = NHash(host);
60     now = osi_Time();
61     ObtainWriteLock(&afs_xnfspag, 314);
62     for (np = afs_nfspags[i]; np; np = np->next) {
63         if (np->uid == uid && np->host == host) {
64             np->refCount++;
65             np->lastcall = now;
66             ReleaseWriteLock(&afs_xnfspag);
67             return np;
68         }
69     }
70     /* next try looking for NOPAG dude, if we didn't find an exact match */
71     for (np = afs_nfspags[i]; np; np = np->next) {
72         if (np->uid == NOPAG && np->host == host) {
73             np->refCount++;
74             np->lastcall = now;
75             ReleaseWriteLock(&afs_xnfspag);
76             return np;
77         }
78     }
79     np = afs_osi_Alloc(sizeof(struct nfsclientpag));
80     osi_Assert(np != NULL);
81     memset(np, 0, sizeof(struct nfsclientpag));
82     /* Copy the necessary afs_exporter fields */
83     memcpy((char *)np, (char *)afs_nfsexporter, sizeof(struct afs_exporter));
84     np->next = afs_nfspags[i];
85     afs_nfspags[i] = np;
86     np->uid = uid;
87     np->host = host;
88     np->refCount = 1;
89     np->lastcall = now;
90     ReleaseWriteLock(&afs_xnfspag);
91     return np;
92 }
93
94
95 /* Decrement refCount; must always match a previous afs_FindNfsClientPag/afs_GetNfsClientPag call .
96 It's also called whenever a unixuser structure belonging to the remote user associated with the nfsclientpag structure, np, is garbage collected. */
97 void
98 afs_PutNfsClientPag(np)
99      struct nfsclientpag *np;
100 {
101 #if defined(AFS_SGIMP_ENV)
102     osi_Assert(ISAFS_GLOCK());
103 #endif
104     AFS_STATCNT(afs_PutNfsClientPag);
105     --np->refCount;
106 }
107
108
109 /* Return the nfsclientpag structure associated with the (uid, host) or
110  * {pag, host} pair, if pag is nonzero. RefCount is incremented and it's
111  * time stamped. */
112 static struct nfsclientpag *
113 afs_FindNfsClientPag(afs_int32 uid, afs_uint32 host, afs_int32 pag)
114 {
115     struct nfsclientpag *np;
116     afs_int32 i;
117
118 #if defined(AFS_SGIMP_ENV)
119     osi_Assert(ISAFS_GLOCK());
120 #endif
121     AFS_STATCNT(afs_FindNfsClientPag);
122     i = NHash(host);
123     ObtainWriteLock(&afs_xnfspag, 315);
124     for (np = afs_nfspags[i]; np; np = np->next) {
125         if (np->host == host) {
126             if ((pag && pag == np->pag) || (!pag && (uid == np->uid))) {
127                 np->refCount++;
128                 np->lastcall = osi_Time();
129                 ReleaseWriteLock(&afs_xnfspag);
130                 return np;
131             }
132         }
133     }
134     /* still not there, try looking for a wildcard dude */
135     for (np = afs_nfspags[i]; np; np = np->next) {
136         if (np->host == host) {
137             if (np->uid == NOPAG) {
138                 np->refCount++;
139                 np->lastcall = osi_Time();
140                 ReleaseWriteLock(&afs_xnfspag);
141                 return np;
142             }
143         }
144     }
145     ReleaseWriteLock(&afs_xnfspag);
146     return NULL;
147 }
148
149
150 /* routine to initialize the exporter, made global so we can call it
151  * from pioctl calls.
152  */
153 struct afs_exporter *afs_nfsexported = 0;
154 static afs_int32 init_nfsexporter = 0;
155
156 void
157 afs_nfsclient_init(void)
158 {
159 #if defined(AFS_SGIMP_ENV)
160     osi_Assert(ISAFS_GLOCK());
161 #endif
162     if (!init_nfsexporter) {
163         extern struct afs_exporter *exporter_add();
164
165         init_nfsexporter = 1;
166         LOCK_INIT(&afs_xnfspag, "afs_xnfspag");
167         afs_nfsexported =
168             exporter_add(0, &nfs_exportops, EXP_EXPORTED, EXP_NFS, NULL);
169     }
170 }
171
172
173 /* Main handler routine for the NFS exporter. It's called in the early
174  * phases of any remote call (via the NFS server or pioctl).
175  */
176 int
177 afs_nfsclient_reqhandler(struct afs_exporter *exporter,
178                          afs_ucred_t **cred,
179                          afs_uint32 host, afs_int32 *pagparam,
180                          struct afs_exporter **outexporter)
181 {
182     struct nfsclientpag *np, *tnp;
183     extern struct unixuser *afs_FindUser(), *afs_GetUser();
184     struct unixuser *au = 0;
185     afs_int32 uid, pag, code = 0;
186
187     AFS_ASSERT_GLOCK();
188     AFS_STATCNT(afs_nfsclient_reqhandler);
189     if (!afs_nfsexporter)
190         afs_nfsexporter = afs_nfsexported;
191
192     afs_nfsexporter->exp_stats.calls++;
193     if (!(afs_nfsexporter->exp_states & EXP_EXPORTED)) {
194         /* No afs requests accepted as long as EXPORTED flag is turned 'off'.
195          * Set/Reset via a pioctl call (fs exportafs). Note that this is on
196          * top of the /etc/exports nfs requirement (i.e. /afs must be
197          * exported to all or whomever there too!)
198          */
199         afs_nfsexporter->exp_stats.rejectedcalls++;
200         return EINVAL;
201     }
202 /*    ObtainWriteLock(&afs_xnfsreq); */
203     pag = PagInCred(*cred);
204 #if defined(AFS_SUN510_ENV)
205     uid = crgetuid(*cred);
206 #else
207     uid = afs_cr_uid(*cred);
208 #endif
209     /* Do this early, so pag management knows */
210     afs_set_cr_rgid(*cred, NFSXLATOR_CRED);     /* Identify it as nfs xlator call */
211     if ((afs_nfsexporter->exp_states & EXP_CLIPAGS) && pag != NOPAG) {
212         uid = pag;
213     } else if (pag != NOPAG) {
214         /* Do some minimal pag verification */
215         if (pag > getpag()) {
216             pag = NOPAG;        /* treat it as not paged since couldn't be good  */
217         } else {
218             if ((au = afs_FindUser(pag, -1, READ_LOCK))) {
219                 if (!au->exporter) {
220                     pag = NOPAG;
221                     afs_PutUser(au, READ_LOCK);
222                     au = NULL;
223                 }
224             } else
225                 pag = NOPAG;    /*  No unixuser struct so pag not trusted  */
226         }
227     }
228     np = afs_FindNfsClientPag(uid, host, 0);
229     afs_Trace4(afs_iclSetp, CM_TRACE_NFSREQH, ICL_TYPE_INT32, pag,
230                ICL_TYPE_LONG, afs_cr_uid(*cred), ICL_TYPE_INT32, host,
231                ICL_TYPE_POINTER, np);
232     /* If remote-pags are enabled, we are no longer interested in what PAG
233      * they claimed, and from here on we should behave as if they claimed
234      * none at all, which is to say we use the (local) pag named in the
235      * nfsclientpag structure (if any).  This is deferred until here so
236      * that we can log the PAG they claimed.
237      */
238     if ((afs_nfsexporter->exp_states & EXP_CLIPAGS))
239         pag = NOPAG;
240     if (!np) {
241         /* Even if there is a "good" pag coming in we don't accept it if no
242          * nfsclientpag struct exists for the user since that would mean
243          * that the translator rebooted and therefore we ignore all older
244          * pag values
245          */
246         if ((code = setpag(cred, -1, &pag, 0))) {
247             if (au)
248                 afs_PutUser(au, READ_LOCK);
249 /*          ReleaseWriteLock(&afs_xnfsreq);             */
250 #if defined(KERNEL_HAVE_UERROR)
251             setuerror(code);
252 #endif
253             return (code);
254         }
255         np = afs_GetNfsClientPag(uid, host);
256         np->pag = pag;
257         np->client_uid = afs_cr_uid(*cred);
258     } else {
259         if (pag == NOPAG) {
260             if ((code = setpag(cred, np->pag, &pag, 0))) {
261                 afs_PutNfsClientPag(np);
262 /*              ReleaseWriteLock(&afs_xnfsreq); */
263 #if defined(KERNEL_HAVE_UERROR)
264                 setuerror(code);
265 #endif
266                 return (code);
267             }
268         } else if (au->exporter
269                    && ((struct afs_exporter *)np != au->exporter)) {
270             tnp = (struct nfsclientpag *)au->exporter;
271             if (tnp->uid && (tnp->uid != (afs_int32) - 2)) {    /* allow "root" initiators */
272                 /* Pag doesn't belong to caller; treat it as an unpaged call too */
273                 if ((code = setpag(cred, np->pag, &pag, 0))) {
274                     afs_PutNfsClientPag(np);
275                     afs_PutUser(au, READ_LOCK);
276                     /*      ReleaseWriteLock(&afs_xnfsreq);     */
277 #if defined(KERNEL_HAVE_UERROR)
278                     setuerror(code);
279 #endif
280                     return (code);
281                 }
282                 afs_nfsexporter->exp_stats.invalidpag++;
283             }
284         }
285     }
286     if (au)
287         afs_PutUser(au, READ_LOCK);
288     /* do not get a lock on au; afs_nfsclient_getcreds may write-lock the
289      * same unixuser */
290     au = afs_GetUser(pag, -1, 0);
291     if (!(au->exporter)) {      /* Created new unixuser struct */
292         np->refCount++;         /* so it won't disappear */
293         au->exporter = (struct afs_exporter *)np;
294         if ((afs_nfsexporter->exp_states & EXP_CALLBACK))
295             afs_nfsclient_getcreds(au);
296     } else while (au->states & UNFSGetCreds) {
297         afs_osi_Sleep((void *)au);
298     }
299     *pagparam = pag;
300     *outexporter = (struct afs_exporter *)np;
301     afs_PutUser(au, 0);
302 /*    ReleaseWriteLock(&afs_xnfsreq);   */
303     return 0;
304 }
305
306 void
307 afs_nfsclient_getcreds(struct unixuser *au)
308 {
309     struct nfsclientpag *np = (struct nfsclientpag *)(au->exporter);
310     struct rx_securityClass *csec;
311     struct rx_connection *tconn;
312     union tokenUnion *tokenPtr;
313     struct rxkadToken *token;
314     SysNameList tsysnames;
315     CredInfos tcreds;
316     CredInfo *tcred;
317     struct unixuser *tu;
318     struct cell *tcell;
319     int code, i, cellnum;
320
321     au->states |= UNFSGetCreds;
322     memset(&tcreds, 0, sizeof(tcreds));
323     memset(&tsysnames, 0, sizeof(tsysnames));
324
325     /* Get a connection */
326     /* This sucks a little.  We should cache the connections or something.
327      * But at this point I don't yet think it's worth the effort.
328      */
329     csec = rxnull_NewClientSecurityObject();
330     AFS_GUNLOCK();
331     tconn = rx_NewConnection(np->host, htons(7001), PAGCB_SERVICEID, csec, 0);
332     AFS_GLOCK();
333
334     /* Get the sysname, if needed */
335     if (!np->sysnamecount) {
336         AFS_GUNLOCK();
337         code = PAGCB_GetSysName(tconn, np->uid, &tsysnames);
338         AFS_GLOCK();
339         if (code ||
340             tsysnames.SysNameList_len <= 0 ||
341             tsysnames.SysNameList_len > MAXNUMSYSNAMES)
342             goto done;
343
344         for(i = 0; i < np->sysnamecount; i++)
345             afs_osi_Free(np->sysname[i], MAXSYSNAME);
346
347         np->sysnamecount = tsysnames.SysNameList_len;
348         for(i = 0; i < np->sysnamecount; i++)
349             np->sysname[i] = tsysnames.SysNameList_val[i].sysname;
350         afs_osi_Free(tsysnames.SysNameList_val,
351                      tsysnames.SysNameList_len * sizeof(SysNameEnt));
352     }
353
354     /* Get credentials */
355     AFS_GUNLOCK();
356     code = PAGCB_GetCreds(tconn, np->uid, &tcreds);
357     AFS_GLOCK();
358     if (code)
359         goto done;
360
361     /* Now, set the credentials they gave us... */
362     for (i = 0; i < tcreds.CredInfos_len; i++) {
363         tcred = &tcreds.CredInfos_val[i];
364
365         /* Find the cell.  If it is unknown to us, punt this entry. */
366         tcell = afs_GetCellByName(tcred->cellname, READ_LOCK);
367         afs_osi_Free(tcred->cellname, strlen(tcred->cellname) + 1);
368         if (!tcell) {
369             memset(tcred->ct.HandShakeKey, 0, 8);
370             memset(tcred->st.st_val, 0, tcred->st.st_len);
371             afs_osi_Free(tcred->st.st_val, tcred->st.st_len);
372             continue;
373         }
374         cellnum = tcell->cellNum;
375         afs_PutCell(tcell, READ_LOCK);
376
377         /* Find the appropriate unixuser.  This might be the same as
378          * the one we were passed (au), but that's OK.
379          */
380         tu = afs_GetUser(np->pag, cellnum, WRITE_LOCK);
381         if (!(tu->exporter)) {  /* Created new unixuser struct */
382             np->refCount++;             /* so it won't disappear */
383             tu->exporter = (struct afs_exporter *)np;
384         }
385
386         afs_FreeTokens(&tu->tokens);
387
388         /* Add a new rxkad token. Using the afs_AddRxkadToken interface
389          * would require another copy, so we do this the hard way */
390         tokenPtr = afs_AddToken(&tu->tokens, 2);
391         token = &tokenPtr->rxkad;
392         token->ticket = tcred->st.st_val;
393         token->ticketLen = tcred->st.st_len;
394
395         /* copy the clear token */
396         memset(&token->clearToken, 0, sizeof(token->clearToken));
397         memcpy(token->clearToken.HandShakeKey, tcred->ct.HandShakeKey, 8);
398         memset(tcred->ct.HandShakeKey, 0, 8);
399         token->clearToken.AuthHandle     = tcred->ct.AuthHandle;
400         token->clearToken.ViceId         = tcred->ct.ViceId;
401         token->clearToken.BeginTimestamp = tcred->ct.BeginTimestamp;
402         token->clearToken.EndTimestamp   = tcred->ct.EndTimestamp;
403
404         /* Set everything else, reset connections, and move on. */
405         tu->viceId = tcred->vid;
406         tu->states |= UHasTokens;
407         tu->states &= ~UTokensBad;
408         afs_SetPrimary(tu, !!(tcred->states & UPrimary));
409         tu->tokenTime = osi_Time();
410         afs_ResetUserConns(tu);
411         afs_PutUser(tu, WRITE_LOCK);
412     }
413     afs_osi_Free(tcreds.CredInfos_val, tcreds.CredInfos_len * sizeof(CredInfo));
414
415 done:
416     AFS_GUNLOCK();
417     rx_DestroyConnection(tconn);
418     AFS_GLOCK();
419     au->states &= ~UNFSGetCreds;
420     afs_osi_Wakeup((void *)au);
421 }
422
423
424 /* It's called whenever a new unixuser structure is created for the remote
425  * user associated with the nfsclientpag structure, np */
426 void
427 afs_nfsclient_hold(struct nfsclientpag *np)
428 {
429 #if defined(AFS_SGIMP_ENV)
430     osi_Assert(ISAFS_GLOCK());
431 #endif
432     AFS_STATCNT(afs_nfsclient_hold);
433     np->refCount++;
434 }
435
436
437 /* check if this exporter corresponds to the specified host */
438 int
439 afs_nfsclient_checkhost(struct nfsclientpag *np, afs_uint32 host)
440 {
441     if (np->type != EXP_NFS)
442         return 0;
443     return np->host == host;
444 }
445
446
447 /* get the host for this exporter, or 0 if there is an error */
448 afs_uint32
449 afs_nfsclient_gethost(struct nfsclientpag *np)
450 {
451     if (np->type != EXP_NFS)
452         return 0;
453     return np->host;
454 }
455
456
457 /* if inname is non-null, a new system name value is set for the remote
458  * user (inname contains the new sysname). In all cases, outname returns
459  * the current sysname value for this remote user */
460 int
461 afs_nfsclient_sysname(struct nfsclientpag *np, char *inname,
462                       char ***outname, int *num, int allpags)
463 {
464     struct nfsclientpag *tnp;
465     afs_int32 i;
466     char *cp;
467     int count, t;
468 #if defined(AFS_SGIMP_ENV)
469     osi_Assert(ISAFS_GLOCK());
470 #endif
471     AFS_STATCNT(afs_nfsclient_sysname);
472     if (allpags > 0) {
473         /* update every client, not just the one making the request */
474         i = NHash(np->host);
475         ObtainWriteLock(&afs_xnfspag, 315);
476         for (tnp = afs_nfspags[i]; tnp; tnp = tnp->next) {
477             if (tnp != np && tnp->host == np->host)
478                 afs_nfsclient_sysname(tnp, inname, outname, num, -1);
479         }
480         ReleaseWriteLock(&afs_xnfspag);
481     }
482     if (inname) {
483             for(count=0; count < np->sysnamecount;++count) {
484                 afs_osi_Free(np->sysname[count], MAXSYSNAME);
485                 np->sysname[count] = NULL;
486             }
487         for(count=0; count < *num;++count) {
488             np->sysname[count]= afs_osi_Alloc(MAXSYSNAME);
489             osi_Assert(np->sysname[count] != NULL);
490         }
491         cp = inname;
492         for(count=0; count < *num;++count) {
493             t = strlen(cp);
494             memcpy(np->sysname[count], cp, t+1); /* include null */
495             cp += t+1;
496         }
497         np->sysnamecount = *num;
498     }
499     if (allpags >= 0) {
500         /* Don't touch our arguments when called recursively */
501         *outname = np->sysname;
502         *num = np->sysnamecount;
503         if (!np->sysname[0])
504             return ENODEV; /* XXX */
505     }
506     return 0;
507 }
508
509
510 /* Garbage collect routine for the nfs exporter. When pag is -1 then all
511  * entries are removed (used by the nfsclient_shutdown routine); else if
512  * it's non zero then only the entry with that pag is removed, else all
513  * "timedout" entries are removed. TimedOut entries are those who have no
514  * "unixuser" structures associated with them (i.e. unixusercnt == 0) and
515  * they haven't had any activity the last NFSCLIENTGC seconds */
516 void
517 afs_nfsclient_GC(struct afs_exporter *exporter,
518                  afs_int32 pag)
519 {
520     struct nfsclientpag *np, **tnp, *nnp;
521     afs_int32 i, delflag;
522         int count;
523
524 #if defined(AFS_SGIMP_ENV)
525     osi_Assert(ISAFS_GLOCK());
526 #endif
527     AFS_STATCNT(afs_nfsclient_GC);
528     ObtainWriteLock(&afs_xnfspag, 316);
529     for (i = 0; i < NNFSCLIENTS; i++) {
530         for (tnp = &afs_nfspags[i], np = *tnp; np; np = nnp) {
531             nnp = np->next;
532             delflag = 0;
533             if (np->refCount == 0 && np->lastcall < osi_Time() - NFSCLIENTGC)
534                 delflag = 1;
535             if ((pag == -1) || (!pag && delflag)
536                 || (pag && (np->refCount == 0) && (np->pag == pag))) {
537                 *tnp = np->next;
538                 for(count=0; count < np->sysnamecount;++count) {
539                         afs_osi_Free(np->sysname[count], MAXSYSNAME);
540                 }
541                 afs_osi_Free(np, sizeof(struct nfsclientpag));
542             } else {
543                 tnp = &np->next;
544             }
545         }
546     }
547     ReleaseWriteLock(&afs_xnfspag);
548 }
549
550
551 int
552 afs_nfsclient_stats(struct afs_exporter *export)
553 {
554     /* Nothing much to do here yet since most important stats are collected
555      * directly in the afs_exporter structure itself */
556     AFS_STATCNT(afs_nfsclient_stats);
557     return 0;
558 }
559
560 #ifdef AFS_AIX41_ENV
561 /* This is exposed so that vop_fid can test it, even if iauth is not
562  *  installed.
563  */
564 extern int afs_iauth_initd;
565 #endif
566
567 #ifdef AFS_AIX_IAUTH_ENV
568 char *afs_nfs_id = "AFSNFSTRANS";
569 /* afs_iauth_verify is the AFS authenticator for NFS.
570  *
571  * always returns 0.
572  */
573 int
574 afs_iauth_verify(long id, fsid_t * fsidp, long host, int uid,
575                  afs_ucred_t *credp, struct exportinfo *exp)
576 {
577     int code;
578     struct nfsclientpag *nfs_pag;
579     afs_int32 dummypag;
580     struct afs_exporter *outexporter = 0;
581
582
583     /* Still needs basic test to see if exporter is on. And need to check the
584      * whole no submounts bit.
585      */
586
587     if (id != (long)id)
588         return 0;               /* not us. */
589
590     /* Only care if it's AFS */
591     if ((fsidp->val[0] != AFS_VFSMAGIC) || (fsidp->val[1] != AFS_VFSFSID)) {
592         return 0;
593     }
594
595     AFS_GLOCK();
596     code =
597         afs_nfsclient_reqhandler((struct afs_exporter *)0, &credp, host,
598                                  &dummypag, &outexporter);
599     if (!code && outexporter)
600         EXP_RELE(outexporter);
601
602     if (code) {
603         /* ensure anonymous cred. */
604         afs_set_cr_uid(credp, (uid_t) -2;       /* anonymous */
605         afs_set_cr_ruid(credp, (uid_t) -2;
606     }
607
608     /* Mark this thread as an NFS translator thread. */
609     afs_set_cr_rgid(credp, NFSXLATOR_CRED);
610
611     AFS_GUNLOCK();
612     return 0;
613 }
614
615 /* afs_iauth_register - register the iauth verify routine. Returns 0 on success
616  * and -1 on failure. Can fail because DFS has already registered.
617  */
618 int
619 afs_iauth_register(void)
620 {
621     if (nfs_iauth_register((unsigned long)afs_nfs_id, afs_iauth_verify))
622         return -1;
623     else {
624         afs_iauth_initd = 1;
625         return 0;
626     }
627 }
628
629 /* afs_iauth_unregister - unregister the iauth verify routine. Called on shutdown.
630  */
631 void
632 afs_iauth_unregister(void)
633 {
634     if (afs_iauth_initd)
635         nfs_iauth_unregister((unsigned long)afs_nfs_id);
636     afs_iauth_initd = 0;
637 }
638 #endif /* AFS_AIX_IAUTH_ENV */
639
640
641
642 void
643 shutdown_nfsclnt(void)
644 {
645 #if defined(AFS_SGIMP_ENV)
646     osi_Assert(ISAFS_GLOCK());
647 #endif
648     AFS_STATCNT(afs_nfsclient_shutdown);
649 #ifdef AFS_AIX_IAUTH_ENV
650     afs_iauth_register();
651 #endif
652     afs_nfsclient_GC(afs_nfsexporter, -1);
653     init_nfsexporter = 0;
654 }
655 #endif /* AFS_NONFSTRANS */