nfs4_idmap.c revision 8ad9cc092881e79ce158e2a519edf49045957f80
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* There are well defined policies for mapping uid and gid values to and
* from utf8 strings, as specified in RFC 3530. The protocol ops that are
* most significantly affected by any changes in policy are GETATTR and
* SETATTR, as these have different behavior depending on whether the id
* mapping code is executing on the client or server. Thus, the following
* rules represents the latest incantation of the id mapping policies.
*
* 1) For the case in which the nfsmapid(1m) daemon has _never_ been
* started, the policy is to _always_ work with stringified uid's
* and gid's
*
* 2) For the case in which the nfsmapid(1m) daemon _was_ started but
* has either died or become unresponsive, the mapping policies are
* as follows:
*
* Server Client
* .-------------------------------.---------------------------------.
* | | |
* | . Respond to req by replying | . If attr string does not have |
* | success and map the [u/g]id | '@' sign, attempt to decode |
* | into its literal id string | a stringified id; map to |
* | | *ID_NOBODY if not an encoded |
* | | id. |
* | | |
* GETATTR | | . If attr string _does_ have |
* | | '@' sign |
* | | Map to *ID_NOBODY on failure. |
* | | |
* | nfs_idmap_*id_str | nfs_idmap_str_*id |
* +-------------------------------+---------------------------------+
* | | |
* | . Respond to req by returning | . _Must_ map the user's passed |
* | ECOMM, which will be mapped | in [u/g]id into it's network |
* | to NFS4ERR_DELAY to clnt | attr string, so contact the |
* | | daemon, retrying forever if |
* | Server must not allow the | necessary, unless interrupted |
* SETATTR | mapping to *ID_NOBODY upon | |
* | lack of communication with | Client _should_ specify the |
* | the daemon, which could | correct attr string for a |
* | result in the file being | SETATTR operation, otherwise |
* | inadvertently given away ! | it can also result in the |
* | | file being inadvertently |
* | | given away ! |
* | | |
* | nfs_idmap_str_*id | nfs_idmap_*id_str |
* `-------------------------------'---------------------------------'
*
* 3) Lastly, in order to leverage better cache utilization whenever
* communication with nfsmapid(1m) is currently hindered, cache
* entry eviction is throttled whenever nfsidmap_daemon_dh == NULL.
*
*
* Server-side behavior for upcall communication errors
* ====================================================
*
* GETATTR - Server-side GETATTR *id to attr string conversion policies
* for unresponsive/dead nfsmapid(1m) daemon
*
* a) If the *id is *ID_NOBODY, the string "nobody" is returned
*
* b) If the *id is not *ID_NOBODY _and_ the nfsmapid(1m) daemon
* _is_ operational, the daemon is contacted to convert the
*
* c) If the nfsmapid(1m) daemon has died or has become unresponsive,
* the server returns status == NFS4_OK for the GETATTR operation,
* and returns a strigified [u/g]id to let the client map it into
* the appropriate value.
*
* SETATTR - Server-side SETATTR attr string to *id conversion policies
* for unresponsive/dead nfsmapid(1m) daemon
*
* a) If the otw string is a stringified uid (ie. does _not_ contain
* an '@' sign and is of the form "12345") then the literal uid is
* decoded and it is used to perform the mapping.
*
* b) If, on the other hand, the otw string _is_ of the form
* the SETATTR operation _must_ fail w/NFS4ERR_DELAY, as the server
* cannot default to *ID_NOBODY, which would allow a file to be
* given away by setting it's owner or owner_group to "nobody".
*/
#include <sys/pathname.h>
#include <sys/sysmacros.h>
#include <nfs/nfsid_map.h>
#include <nfs/nfs4_idmap_impl.h>
/*
* Truly global modular globals
*/
static zone_key_t nfsidmap_zone_key;
static list_t nfsidmap_globals_list;
static kmutex_t nfsidmap_globals_lock;
static kmem_cache_t *nfsidmap_cache;
static int nfs4_idcache_tout;
/*
* Some useful macros
*/
#define TIMEOUT(x) (gethrestime_sec() > \
(x + nfs4_idcache_tout))
/*
* Max length of valid id string including the trailing null
*/
#define _MAXIDSTRLEN 11
/*
* Pearson's string hash
*
* See: Communications of the ACM, June 1990 Vol 33 pp 677-680
*/
{ \
int i; \
\
\
for (i = 0; i < (len); i++) { \
} \
}
{ \
}
/*
* Prototypes
*/
static void *nfs_idmap_init_zone(zoneid_t);
static void nfs_idmap_fini_zone(zoneid_t, void *);
static int is_stringified_id(utf8string *);
static void init_pkp_tab(void);
static void nfs_idmap_reclaim(void *);
static void nfs_idmap_cache_reclaim(idmap_cache_info_t *);
static void nfs_idmap_cache_create(idmap_cache_info_t *, const char *);
static void nfs_idmap_cache_destroy(idmap_cache_info_t *);
static void nfs_idmap_cache_flush(idmap_cache_info_t *);
uint_t *, utf8string *);
static void nfs_idmap_cache_rment(nfsidmap_t *);
/*
* Initialization routine for NFSv4 id mapping
*/
void
nfs_idmap_init(void)
{
/*
* Initialize Pearson's Table
*/
init_pkp_tab();
/*
* Initialize the kmem cache
*/
NULL, 0);
/*
*/
if (!nfs4_idcache_tout)
/*
* Initialize the list of nfsidmap_globals
*/
/*
* Initialize the zone_key_t for per-zone idmaps
*/
}
/*
* Called only when module was not loaded properly
*/
void
nfs_idmap_fini(void)
{
(void) zone_key_delete(nfsidmap_zone_key);
}
/*ARGSUSED*/
static void *
{
struct nfsidmap_globals *nig;
nig->nig_msg_done = 0;
/*
* nfsidmap certainly isn't running.
*/
/*
* Create the idmap caches
*/
/*
* Add to global list.
*/
return (nig);
}
/*ARGSUSED*/
static void
{
/*
* Remove from list.
*/
/*
* Destroy the idmap caches
*/
/*
* Cleanup
*/
if (nig->nfsidmap_daemon_dh)
}
/*
* Convert a user utf-8 string identifier into its local uid.
*/
int
{
int error;
const char *whoami = "nfs_idmap_str_uid";
struct nfsidmap_globals *nig;
*uid = UID_NOBODY;
}
/*
* If "nobody", just short circuit and bail
*/
*uid = UID_NOBODY;
return (0);
}
/*
* Start-off with upcalls disabled, and once nfsmapid(1m) is up and
* running, we'll leverage it's first flush to let the kernel know
* when it's up and available to perform mappings. Also, on client
* only, be smarter about when to issue upcalls by checking the
* string for existence of an '@' sign. If no '@' sign, then we just
* make our best effort to decode the string ourselves.
*/
if (dh)
if (dh)
/*
* If we get a numeric value, but we only do so because
* we are nfsmapid, return ENOTSUP to indicate a valid
* response, but not to cache it.
*/
return (ENOTSUP);
return (error);
}
/* cache hit */
return (0);
}
/* cache miss */
if (!error) {
/* Should never provide daemon with bad args */
case NFSMAPID_OK:
/*
* Valid mapping. Cache it.
*/
break;
case NFSMAPID_NUMSTR:
/*
* string came in as stringified id. Don't cache !
*
* nfsmapid(1m) semantics have changed in order to
* support diskless clients. Thus, for stringified
* ahead and map them, returning no error.
*/
break;
case NFSMAPID_BADDOMAIN:
/*
* Make the offending "user@domain" string readily
* available to D scripts that enable the probe.
*/
/* FALLTHROUGH */
case NFSMAPID_INVALID:
case NFSMAPID_UNMAPPABLE:
case NFSMAPID_INTERNAL:
case NFSMAPID_BADID:
case NFSMAPID_NOTFOUND:
default:
/*
* For now, treat all of these errors as equal.
*
* Return error on the server side, then the
* server returns NFS4_BADOWNER to the client.
* On client side, just map to UID_NOBODY.
*/
if (isserver)
else
*uid = UID_NOBODY;
break;
}
return (error);
}
/*
* We got some door error
*/
switch (error) {
case EINTR:
/*
* If we took an interrupt we have to bail out.
*/
return (EINTR);
}
/*
* We may have gotten EINTR for other reasons like the
* door being revoked on us, instead of trying to
* extract this out of the door handle, sleep
* and try again, if still revoked we will get EBADF
* next time through.
*/
/* FALLTHROUGH */
case EAGAIN: /* process may be forking */
/*
* Back off for a bit
*/
goto retry;
default: /* Unknown must be fatal */
case EBADF: /* Invalid door */
case EINVAL: /* Not a door, wrong target */
/*
* A fatal door error, if our failing door handle is the
* current door handle, clean up our state and
* mark the server dead.
*/
}
if (isserver)
return (ECOMM);
/*
* Note: We've already done optimizations above to check
* _know_ this _can't_ be a stringified uid.
*/
if (!nig->nig_msg_done) {
"!%s: Can't communicate with mapping daemon "
"nfsmapid", whoami);
}
*uid = UID_NOBODY;
return (0);
}
/* NOTREACHED */
}
/*
* Convert a uid into its utf-8 string representation.
*/
int
{
int error;
const char *whoami = "nfs_idmap_uid_str";
struct nfsidmap_globals *nig;
/*
* If the supplied uid is "nobody", then we don't look at the
* cache, since we DON'T cache it in the u2s_cache. We cannot
* tell two strings apart from caching the same uid.
*/
if (uid == UID_NOBODY) {
return (0);
}
/*
* Start-off with upcalls disabled, and once nfsmapid(1m) is
* up and running, we'll leverage it's first flush to let the
* kernel know when it's up and available to perform mappings.
* We fall back to answering with stringified uid's.
*/
if (dh)
if (dh)
return (0);
}
/* cache hit */
return (0);
}
/* cache miss */
if (!error) {
/* Should never provide daemon with bad args */
case NFSMAPID_OK:
/*
* We now have a valid result from the
* user-land daemon, so cache the result (if need be).
* Load return value first then do the caches.
*/
break;
case NFSMAPID_INVALID:
case NFSMAPID_UNMAPPABLE:
case NFSMAPID_INTERNAL:
case NFSMAPID_BADDOMAIN:
case NFSMAPID_BADID:
case NFSMAPID_NOTFOUND:
default:
/*
* For now, treat all of these errors as equal.
*/
break;
}
return (error);
}
/*
* We got some door error
*/
switch (error) {
case EINTR:
/*
* If we took an interrupt we have to bail out.
*/
return (EINTR);
}
/*
* We may have gotten EINTR for other reasons like the
* door being revoked on us, instead of trying to
* extract this out of the door handle, sleep
* and try again, if still revoked we will get EBADF
* next time through.
*/
/* FALLTHROUGH */
case EAGAIN: /* process may be forking */
/*
* Back off for a bit
*/
goto retry;
default: /* Unknown must be fatal */
case EBADF: /* Invalid door */
case EINVAL: /* Not a door, wrong target */
/*
* A fatal door error, if our failing door handle is the
* current door handle, clean up our state and
* mark the server dead.
*/
}
/*
* Log error on client-side only
*/
"!%s: Can't communicate with mapping daemon "
"nfsmapid", whoami);
}
return (0);
}
/* NOTREACHED */
}
/*
* Convert a group utf-8 string identifier into its local gid.
*/
int
{
int error;
const char *whoami = "nfs_idmap_str_gid";
struct nfsidmap_globals *nig;
*gid = GID_NOBODY;
}
/*
* If "nobody", just short circuit and bail
*/
*gid = GID_NOBODY;
return (0);
}
/*
* Start-off with upcalls disabled, and once nfsmapid(1m) is up and
* running, we'll leverage it's first flush to let the kernel know
* when it's up and available to perform mappings. Also, on client
* only, be smarter about when to issue upcalls by checking the
* string for existence of an '@' sign. If no '@' sign, then we just
* make our best effort to decode the string ourselves.
*/
if (dh)
if (dh)
/*
* If we get a numeric value, but we only do so because
* we are nfsmapid, return ENOTSUP to indicate a valid
* response, but not to cache it.
*/
return (ENOTSUP);
return (error);
}
/* cache hit */
return (0);
}
/* cache miss */
if (!error) {
/* Should never provide daemon with bad args */
case NFSMAPID_OK:
/*
* Valid mapping. Cache it.
*/
error = 0;
break;
case NFSMAPID_NUMSTR:
/*
* string came in as stringified id. Don't cache !
*
* nfsmapid(1m) semantics have changed in order to
* support diskless clients. Thus, for stringified
* ahead and map them, returning no error.
*/
break;
case NFSMAPID_BADDOMAIN:
/*
* Make the offending "group@domain" string readily
* available to D scripts that enable the probe.
*/
/* FALLTHROUGH */
case NFSMAPID_INVALID:
case NFSMAPID_UNMAPPABLE:
case NFSMAPID_INTERNAL:
case NFSMAPID_BADID:
case NFSMAPID_NOTFOUND:
default:
/*
* For now, treat all of these errors as equal.
*
* Return error on the server side, then the
* server returns NFS4_BADOWNER to the client.
* On client side, just map to GID_NOBODY.
*/
if (isserver)
else
*gid = GID_NOBODY;
break;
}
return (error);
}
/*
* We got some door error
*/
switch (error) {
case EINTR:
/*
* If we took an interrupt we have to bail out.
*/
return (EINTR);
}
/*
* We may have gotten EINTR for other reasons like the
* door being revoked on us, instead of trying to
* extract this out of the door handle, sleep
* and try again, if still revoked we will get EBADF
* next time through.
*/
/* FALLTHROUGH */
case EAGAIN: /* process may be forking */
/*
* Back off for a bit
*/
goto retry;
default: /* Unknown must be fatal */
case EBADF: /* Invalid door */
case EINVAL: /* Not a door, wrong target */
/*
* A fatal door error, clean up our state and
* mark the server dead.
*/
}
if (isserver)
return (ECOMM);
/*
* Note: We've already done optimizations above to check
* _know_ this _can't_ be a stringified gid.
*/
if (!nig->nig_msg_done) {
"!%s: Can't communicate with mapping daemon "
"nfsmapid", whoami);
}
*gid = GID_NOBODY;
return (0);
}
/* NOTREACHED */
}
/*
* Convert a gid into its utf-8 string representation.
*/
int
{
int error;
const char *whoami = "nfs_idmap_gid_str";
struct nfsidmap_globals *nig;
/*
* If the supplied gid is "nobody", then we don't look at the
* cache, since we DON'T cache it in the u2s_cache. We cannot
* tell two strings apart from caching the same gid.
*/
if (gid == GID_NOBODY) {
return (0);
}
/*
* Start-off with upcalls disabled, and once nfsmapid(1m) is
* up and running, we'll leverage it's first flush to let the
* kernel know when it's up and available to perform mappings.
* We fall back to answering with stringified gid's.
*/
if (dh)
if (dh)
return (0);
}
/* cache hit */
return (0);
}
/* cache miss */
if (!error) {
/* Should never provide daemon with bad args */
case NFSMAPID_OK:
/*
* We now have a valid result from the
* user-land daemon, so cache the result (if need be).
* Load return value first then do the caches.
*/
break;
case NFSMAPID_INVALID:
case NFSMAPID_UNMAPPABLE:
case NFSMAPID_INTERNAL:
case NFSMAPID_BADDOMAIN:
case NFSMAPID_BADID:
case NFSMAPID_NOTFOUND:
default:
/*
* For now, treat all of these errors as equal.
*/
break;
}
return (error);
}
/*
* We got some door error
*/
switch (error) {
case EINTR:
/*
* If we took an interrupt we have to bail out.
*/
return (EINTR);
}
/*
* We may have gotten EINTR for other reasons like the
* door being revoked on us, instead of trying to
* extract this out of the door handle, sleep
* and try again, if still revoked we will get EBADF
* next time through.
*/
/* FALLTHROUGH */
case EAGAIN: /* process may be forking */
/*
* Back off for a bit
*/
goto retry;
default: /* Unknown must be fatal */
case EBADF: /* Invalid door */
case EINVAL: /* Not a door, wrong target */
/*
* A fatal door error, if our failing door handle is the
* current door handle, clean up our state and
* mark the server dead.
*/
}
/*
* Log error on client-side only
*/
"!%s: Can't communicate with mapping daemon "
"nfsmapid", whoami);
}
return (0);
}
/* NOTREACHED */
}
/* -- idmap cache management -- */
/*
* Cache creation and initialization routine
*/
static void
{
int i;
KM_SLEEP);
}
}
/*
* Cache destruction routine
*
* Ops per hash queue
*
* - dequeue cache entries
* - release string storage per entry
* - release cache entry storage
* - destroy HQ lock when HQ is empty
* - once all HQ's empty, release HQ storage
*/
static void
{
int i;
/*
* We can safely destroy per-queue locks since the only
* other entity that could be mucking with this table is the
* kmem reaper thread which does everything under
* nfsidmap_globals_lock (which we're holding).
*/
}
void
{
struct nfsidmap_globals *nig;
/*
* nfsmapid(1m) up and running; enable upcalls
* State:
* 0 Just flush caches
* 1 Re-establish door knob
*/
/*
* When reestablishing the nfsmapid we need to
* not only purge the idmap cache but also
* While heavyweight, this should almost never happen
*/
dnlc_purge();
/*
* Invalidate the attrs of all rnodes to force new uid and gids
*/
if (nig->nfsidmap_daemon_dh)
nig->nig_msg_done = 0;
}
}
/*
* Cache flush routine
*
* The only serialization required it to hold the hash chain lock
* when destroying cache entries. There is no need to prevent access
* to all hash chains while flushing. It is possible that (valid)
* entries could be cached in later hash chains after we start flushing.
* It is unfortunate that the entry will be instantly destroyed, but
* it isn't a major concern. This is only a cache. It'll be repopulated.
*
* Ops per hash queue
*
* - dequeue cache entries
* - release string storage per entry
* - release cache entry storage
*/
static void
{
int i;
nfsidmap_t *p, *next;
/*
* remove list from hash header so we can release
* the lock early.
*/
p = hq->hq_lru_forw;
/*
* Iterate over the orphan'd list and free all elements.
* There's no need to bother with remque since we're
* freeing the entire list.
*/
while (p != (nfsidmap_t *)hq) {
if (p->id_val != 0)
p = next;
}
}
}
static void
{
int i;
nfsidmap_t *p;
/*
* If the daemon is down, do not flush anything
*/
return;
continue;
/*
* Start at end of list and work backwards since LRU
*/
/*
* List is LRU. If trailing end does not
* contain stale entries, then no need to
* continue.
*/
break;
}
}
}
/*
* Callback reclaim function for VM. We reap timed-out entries from all hash
* tables in all zones.
*/
/* ARGSUSED */
void
nfs_idmap_reclaim(void *arg)
{
struct nfsidmap_globals *nig;
}
}
/*
* Search the specified cache for the existence of the specified utf-8
* string. If found, the corresponding mapping is returned in id_buf and
* the cache entry is updated to the head of the LRU list. The computed
* hash queue number, is returned in hashno.
*/
static uint_t
{
nfsidmap_t *p;
char *rqst_c_str;
uint_t found_stat = 0;
/*
* Illegal string, return not found.
*/
return (0);
}
/*
* Compute hash queue
*/
/*
* Look for the entry in the HQ
*/
/*
* Check entry for staleness first, as user's id
* may have changed and may need to be remapped.
* Note that we don't evict entries from the cache
* if we're having trouble contacting nfsmapid(1m)
*/
continue;
}
/*
* Compare equal length strings
*/
/*
* Found it. Update it and load return value.
*/
remque(p);
p->id_time = gethrestime_sec();
found_stat = 1;
break;
}
}
}
if (rqst_c_str != NULL)
return (found_stat);
}
/*
* Search the specified cache for the existence of the specified utf8
* string, as it may have been inserted before this instance got a chance
* to do it. If NOT found, then a new entry is allocated for the specified
* cache, and inserted. The hash queue number is obtained from hash_number
* if the behavior is HQ_HASH_HINT, or computed otherwise.
*/
static void
{
char *c_str;
nfsidmap_t *p;
/*
* This shouldn't fail, since already successful at lkup.
* So, if it does happen, just drop the request-to-insert
* on the floor.
*/
return;
/*
* Obtain correct hash queue to insert new entry in
*/
switch (behavior) {
case HQ_HASH_HINT:
break;
case HQ_HASH_FIND:
default:
break;
}
/*
* Look for an existing entry in the cache. If one exists
* update it, and return. Otherwise, allocate a new cache
* entry, initialize it and insert it.
*/
/*
* Check entry for staleness first, as user's id
* may have changed and may need to be remapped.
* Note that we don't evict entries from the cache
* if we're having trouble contacting nfsmapid(1m)
*/
continue;
}
/*
* Compare equal length strings
*/
/*
* Move to front, and update time.
*/
remque(p);
p->id_time = gethrestime_sec();
return;
}
}
}
/*
* Not found ! Alloc, init and insert new entry
*/
}
/*
* Search the specified cache for the existence of the specified id.
* If found, the corresponding mapping is returned in u8s and the
* cache entry is updated to the head of the LRU list. The computed
* hash queue number, is returned in hashno.
*/
static uint_t
{
uint_t found_stat = 0;
nfsidmap_t *p;
/*
* Compute hash queue
*/
/*
* Look for the entry in the HQ
*/
/*
* Check entry for staleness first, as user's id
* may have changed and may need to be remapped.
* Note that we don't evict entries from the cache
* if we're having trouble contacting nfsmapid(1m)
*/
continue;
}
/*
* Found it. Load return value and move to head
*/
remque(p);
p->id_time = gethrestime_sec();
found_stat = 1;
break;
}
}
return (found_stat);
}
/*
* Search the specified cache for the existence of the specified id,
* as it may have been inserted before this instance got a chance to
* do it. If NOT found, then a new entry is allocated for the specified
* cache, and inserted. The hash queue number is obtained from hash_number
* if the behavior is HQ_HASH_HINT, or computed otherwise.
*/
static void
{
nfsidmap_t *p;
/*
* Obtain correct hash queue to insert new entry in
*/
switch (behavior) {
case HQ_HASH_HINT:
break;
case HQ_HASH_FIND:
default:
break;
}
/*
* Look for an existing entry in the cache. If one exists
* update it, and return. Otherwise, allocate a new cache
* entry, initialize and insert it.
*/
/*
* Check entry for staleness first, as user's id
* may have changed and may need to be remapped.
* Note that we don't evict entries from the cache
* if we're having trouble contacting nfsmapid(1m)
*/
continue;
}
/*
* Found It ! Move to front, and update time.
*/
remque(p);
p->id_time = gethrestime_sec();
return;
}
}
/*
* Not found ! Alloc, init and insert new entry
*/
}
/*
* Remove and free one cache entry
*/
static void
{
remque(p);
if (p->id_val != 0)
}
#ifndef UID_MAX
#endif
#ifndef isdigit
#endif
static int
{
int i;
for (i = 0; i < u8s->utf8string_len; i++)
return (0);
return (1);
}
int
{
long tmp;
int convd;
char ids[_MAXIDSTRLEN];
/*
* "nobody" unless we can actually decode it.
*/
*id = UID_NOBODY;
/*
* We're here because it has already been determined that the
* string contains no '@' _or_ the nfsmapid daemon has yet to
* be started.
*/
if (!is_stringified_id(u8s))
return (0);
/*
* If utf8string_len is greater than _MAXIDSTRLEN-1, then the id
* is going to be greater than UID_MAX. Return id of "nobody"
* right away.
*/
/*
* Make sure we pass a NULL terminated 'C' string to ddi_strtol
*/
return (0);
}
}
static void
{
char ids[_MAXIDSTRLEN];
}
/* -- Utility functions -- */
/*
* Initialize table in pseudo-random fashion
* for use in Pearson's string hash algorithm.
*
* See: Communications of the ACM, June 1990 Vol 33 pp 677-680
*/
static void
init_pkp_tab(void)
{
int i;
int j;
int k = 7;
uint_t s;
for (i = 0; i < NFSID_CACHE_ANCHORS; i++)
pkp_tab[i] = i;
for (j = 0; j < 4; j++)
for (i = 0; i < NFSID_CACHE_ANCHORS; i++) {
s = pkp_tab[i];
k = MOD2((k + s), NFSID_CACHE_ANCHORS);
pkp_tab[k] = s;
}
}
char *
{
int i;
for (i = 0; i < len; i++)
if (u8p[i] == c)
return (&u8p[i]);
return (NULL);
}