4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
22 * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
27 * There are well defined policies for mapping uid and gid values to and
28 * from utf8 strings, as specified in RFC 7530. The protocol ops that are
29 * most significantly affected by any changes in policy are GETATTR and
30 * SETATTR, as these have different behavior depending on whether the id
31 * mapping code is executing on the client or server. Thus, the following
32 * rules represents the latest incantation of the id mapping policies.
34 * 1) For the case in which the nfsmapid(1m) daemon has _never_ been
35 * started, the policy is to _always_ work with stringified uid's
38 * 2) For the case in which the nfsmapid(1m) daemon _was_ started but
39 * has either died or become unresponsive, the mapping policies are
43 * .-------------------------------.---------------------------------.
45 * | . Respond to req by replying | . If attr string does not have |
46 * | success and map the [u/g]id | '@' sign, attempt to decode |
47 * | into its literal id string | a stringified id; map to |
48 * | | *ID_NOBODY if not an encoded |
51 * GETATTR | | . If attr string _does_ have |
53 * | | Map to *ID_NOBODY on failure. |
55 * | nfs_idmap_*id_str | nfs_idmap_str_*id |
56 * +-------------------------------+---------------------------------+
58 * | . Respond to req by returning | . _Must_ map the user's passed |
59 * | ECOMM, which will be mapped | in [u/g]id into it's network |
60 * | to NFS4ERR_DELAY to clnt | attr string, so contact the |
61 * | | daemon, retrying forever if |
62 * | Server must not allow the | necessary, unless interrupted |
63 * SETATTR | mapping to *ID_NOBODY upon | |
64 * | lack of communication with | Client _should_ specify the |
65 * | the daemon, which could | correct attr string for a |
66 * | result in the file being | SETATTR operation, otherwise |
67 * | inadvertently given away ! | it can also result in the |
68 * | | file being inadvertently |
71 * | nfs_idmap_str_*id | nfs_idmap_*id_str |
72 * `-------------------------------'---------------------------------'
74 * 3) Lastly, in order to leverage better cache utilization whenever
75 * communication with nfsmapid(1m) is currently hindered, cache
76 * entry eviction is throttled whenever nfsidmap_daemon_dh == NULL.
79 * Server-side behavior for upcall communication errors
80 * ====================================================
82 * GETATTR - Server-side GETATTR *id to attr string conversion policies
83 * for unresponsive/dead nfsmapid(1m) daemon
85 * a) If the *id is *ID_NOBODY, the string "nobody" is returned
87 * b) If the *id is not *ID_NOBODY _and_ the nfsmapid(1m) daemon
88 * _is_ operational, the daemon is contacted to convert the
89 * [u/g]id into a string of type "[user/group]@domain"
91 * c) If the nfsmapid(1m) daemon has died or has become unresponsive,
92 * the server returns status == NFS4_OK for the GETATTR operation,
93 * and returns a strigified [u/g]id to let the client map it into
94 * the appropriate value.
96 * SETATTR - Server-side SETATTR attr string to *id conversion policies
97 * for unresponsive/dead nfsmapid(1m) daemon
99 * a) If the otw string is a stringified uid (ie. does _not_ contain
100 * an '@' sign and is of the form "12345") then the literal uid is
101 * decoded and it is used to perform the mapping.
103 * b) If, on the other hand, the otw string _is_ of the form
104 * "[user/group]@domain" and problems arise contacting nfsmapid(1m),
105 * the SETATTR operation _must_ fail w/NFS4ERR_DELAY, as the server
106 * cannot default to *ID_NOBODY, which would allow a file to be
107 * given away by setting it's owner or owner_group to "nobody".
109 #include <sys/param.h>
110 #include <sys/errno.h>
111 #include <sys/disp.h>
113 #include <sys/vnode.h>
114 #include <sys/cred.h>
115 #include <sys/cmn_err.h>
116 #include <sys/systm.h>
117 #include <sys/kmem.h>
118 #include <sys/pathname.h>
119 #include <sys/utsname.h>
120 #include <sys/debug.h>
121 #include <sys/sysmacros.h>
122 #include <sys/list.h>
123 #include <sys/sunddi.h>
124 #include <sys/dnlc.h>
126 #include <sys/pkp_hash.h>
127 #include <nfs/nfs4.h>
128 #include <nfs/rnode4.h>
129 #include <nfs/nfsid_map.h>
130 #include <nfs/nfs4_idmap_impl.h>
131 #include <nfs/nfssys.h>
134 * Truly global modular globals
136 zone_key_t nfsidmap_zone_key
;
137 static list_t nfsidmap_globals_list
;
138 static kmutex_t nfsidmap_globals_lock
;
139 static kmem_cache_t
*nfsidmap_cache
;
140 static int nfs4_idcache_tout
;
145 #define MOD2(a, pow_of_2) ((a) & ((pow_of_2) - 1))
146 #define _CACHE_TOUT (60*60) /* secs in 1 hour */
147 #define TIMEOUT(x) (gethrestime_sec() > \
148 ((x) + nfs4_idcache_tout))
150 * Max length of valid id string including the trailing null
152 #define _MAXIDSTRLEN 11
154 #define ID_HASH(id, hash) \
156 (hash) = MOD2(((id) ^ NFSID_CACHE_ANCHORS), NFSID_CACHE_ANCHORS); \
163 static void *nfs_idmap_init_zone(zoneid_t
);
164 static void nfs_idmap_fini_zone(zoneid_t
, void *);
166 static int is_stringified_id(utf8string
*);
167 static void nfs_idmap_i2s_literal(uid_t
, utf8string
*);
168 static int nfs_idmap_s2i_literal(utf8string
*, uid_t
*, int);
169 static void nfs_idmap_reclaim(void *);
170 static void nfs_idmap_cache_reclaim(idmap_cache_info_t
*);
171 static void nfs_idmap_cache_create(idmap_cache_info_t
*, const char *);
172 static void nfs_idmap_cache_destroy(idmap_cache_info_t
*);
173 static void nfs_idmap_cache_flush(idmap_cache_info_t
*);
175 static uint_t
nfs_idmap_cache_s2i_lkup(idmap_cache_info_t
*, utf8string
*,
178 static uint_t
nfs_idmap_cache_i2s_lkup(idmap_cache_info_t
*, uid_t
,
179 uint_t
*, utf8string
*);
181 static void nfs_idmap_cache_s2i_insert(idmap_cache_info_t
*, uid_t
,
182 utf8string
*, hash_stat
, uint_t
);
184 static void nfs_idmap_cache_i2s_insert(idmap_cache_info_t
*, uid_t
,
185 utf8string
*, hash_stat
, uint_t
);
187 static void nfs_idmap_cache_rment(nfsidmap_t
*);
190 * Initialization routine for NFSv4 id mapping
196 * Initialize the kmem cache
198 nfsidmap_cache
= kmem_cache_create("NFS_idmap_cache",
199 sizeof (nfsidmap_t
), 0, NULL
, NULL
, nfs_idmap_reclaim
, NULL
,
202 * If not set in "/etc/system", set to default value
204 if (!nfs4_idcache_tout
)
205 nfs4_idcache_tout
= _CACHE_TOUT
;
207 * Initialize the list of nfsidmap_globals
209 mutex_init(&nfsidmap_globals_lock
, NULL
, MUTEX_DEFAULT
, NULL
);
210 list_create(&nfsidmap_globals_list
, sizeof (struct nfsidmap_globals
),
211 offsetof(struct nfsidmap_globals
, nig_link
));
213 * Initialize the zone_key_t for per-zone idmaps
215 zone_key_create(&nfsidmap_zone_key
, nfs_idmap_init_zone
, NULL
,
216 nfs_idmap_fini_zone
);
220 * Called only when module was not loaded properly
225 (void) zone_key_delete(nfsidmap_zone_key
);
226 list_destroy(&nfsidmap_globals_list
);
227 mutex_destroy(&nfsidmap_globals_lock
);
228 kmem_cache_destroy(nfsidmap_cache
);
233 nfs_idmap_init_zone(zoneid_t zoneid
)
235 struct nfsidmap_globals
*nig
;
237 nig
= kmem_alloc(sizeof (*nig
), KM_SLEEP
);
238 nig
->nig_msg_done
= 0;
239 mutex_init(&nig
->nfsidmap_daemon_lock
, NULL
, MUTEX_DEFAULT
, NULL
);
242 * nfsidmap certainly isn't running.
244 nig
->nfsidmap_pid
= NOPID
;
245 nig
->nfsidmap_daemon_dh
= NULL
;
248 * Create the idmap caches
250 nfs_idmap_cache_create(&nig
->u2s_ci
, "u2s_cache");
251 nig
->u2s_ci
.nfsidmap_daemon_dh
= &nig
->nfsidmap_daemon_dh
;
252 nfs_idmap_cache_create(&nig
->s2u_ci
, "s2u_cache");
253 nig
->s2u_ci
.nfsidmap_daemon_dh
= &nig
->nfsidmap_daemon_dh
;
254 nfs_idmap_cache_create(&nig
->g2s_ci
, "g2s_cache");
255 nig
->g2s_ci
.nfsidmap_daemon_dh
= &nig
->nfsidmap_daemon_dh
;
256 nfs_idmap_cache_create(&nig
->s2g_ci
, "s2g_cache");
257 nig
->s2g_ci
.nfsidmap_daemon_dh
= &nig
->nfsidmap_daemon_dh
;
260 * Add to global list.
262 mutex_enter(&nfsidmap_globals_lock
);
263 list_insert_head(&nfsidmap_globals_list
, nig
);
264 mutex_exit(&nfsidmap_globals_lock
);
271 nfs_idmap_fini_zone(zoneid_t zoneid
, void *arg
)
273 struct nfsidmap_globals
*nig
= arg
;
278 mutex_enter(&nfsidmap_globals_lock
);
279 list_remove(&nfsidmap_globals_list
, nig
);
281 * Destroy the idmap caches
283 nfs_idmap_cache_destroy(&nig
->u2s_ci
);
284 nfs_idmap_cache_destroy(&nig
->s2u_ci
);
285 nfs_idmap_cache_destroy(&nig
->g2s_ci
);
286 nfs_idmap_cache_destroy(&nig
->s2g_ci
);
287 mutex_exit(&nfsidmap_globals_lock
);
291 if (nig
->nfsidmap_daemon_dh
)
292 door_ki_rele(nig
->nfsidmap_daemon_dh
);
293 mutex_destroy(&nig
->nfsidmap_daemon_lock
);
294 kmem_free(nig
, sizeof (*nig
));
298 * Convert a user utf-8 string identifier into its local uid.
301 nfs_idmap_str_uid(utf8string
*u8s
, uid_t
*uid
, bool_t isserver
)
305 const char *whoami
= "nfs_idmap_str_uid";
306 struct nfsidmap_globals
*nig
;
307 struct mapid_arg
*mapargp
;
308 struct mapid_res mapres
;
309 struct mapid_res
*mapresp
= &mapres
;
310 struct mapid_res
*resp
= mapresp
;
311 door_arg_t door_args
;
314 nig
= zone_getspecific(nfsidmap_zone_key
, nfs_zone());
317 if (!u8s
|| !u8s
->utf8string_val
|| u8s
->utf8string_len
== 0 ||
318 (u8s
->utf8string_val
[0] == '\0')) {
320 return (isserver
? EINVAL
: 0);
324 * If "nobody", just short circuit and bail
326 if (bcmp(u8s
->utf8string_val
, "nobody", 6) == 0) {
332 * Start-off with upcalls disabled, and once nfsmapid(1m) is up and
333 * running, we'll leverage it's first flush to let the kernel know
334 * when it's up and available to perform mappings. Also, on client
335 * only, be smarter about when to issue upcalls by checking the
336 * string for existence of an '@' sign. If no '@' sign, then we just
337 * make our best effort to decode the string ourselves.
340 mutex_enter(&nig
->nfsidmap_daemon_lock
);
341 dh
= nig
->nfsidmap_daemon_dh
;
344 mutex_exit(&nig
->nfsidmap_daemon_lock
);
346 if (dh
== NULL
|| nig
->nfsidmap_pid
== curproc
->p_pid
||
347 (!utf8_strchr(u8s
, '@') && !isserver
)) {
350 error
= nfs_idmap_s2i_literal(u8s
, uid
, isserver
);
352 * If we get a numeric value, but we only do so because
353 * we are nfsmapid, return ENOTSUP to indicate a valid
354 * response, but not to cache it.
356 if (!error
&& nig
->nfsidmap_pid
== curproc
->p_pid
)
362 if (nfs_idmap_cache_s2i_lkup(&nig
->s2u_ci
, u8s
, &hashno
, uid
)) {
368 mapargp
= kmem_alloc(MAPID_ARG_LEN(u8s
->utf8string_len
), KM_SLEEP
);
369 mapargp
->cmd
= NFSMAPID_STR_UID
;
370 mapargp
->u_arg
.len
= u8s
->utf8string_len
;
371 (void) bcopy(u8s
->utf8string_val
, mapargp
->str
, mapargp
->u_arg
.len
);
372 mapargp
->str
[mapargp
->u_arg
.len
] = '\0';
374 door_args
.data_ptr
= (char *)mapargp
;
375 door_args
.data_size
= MAPID_ARG_LEN(mapargp
->u_arg
.len
);
376 door_args
.desc_ptr
= NULL
;
377 door_args
.desc_num
= 0;
378 door_args
.rbuf
= (char *)mapresp
;
379 door_args
.rsize
= sizeof (struct mapid_res
);
381 error
= door_ki_upcall_limited(dh
, &door_args
, NULL
, SIZE_MAX
, 0);
383 resp
= (struct mapid_res
*)door_args
.rbuf
;
385 /* Should never provide daemon with bad args */
386 ASSERT(resp
->status
!= NFSMAPID_INVALID
);
388 switch (resp
->status
) {
391 * Valid mapping. Cache it.
393 *uid
= resp
->u_res
.uid
;
394 nfs_idmap_cache_s2i_insert(&nig
->s2u_ci
, *uid
,
395 u8s
, HQ_HASH_HINT
, hashno
);
398 case NFSMAPID_NUMSTR
:
400 * string came in as stringified id. Don't cache !
402 * nfsmapid(1m) semantics have changed in order to
403 * support diskless clients. Thus, for stringified
404 * id's that have passwd/group entries, we'll go
405 * ahead and map them, returning no error.
407 *uid
= resp
->u_res
.uid
;
410 case NFSMAPID_BADDOMAIN
:
412 * Make the offending "user@domain" string readily
413 * available to D scripts that enable the probe.
415 DTRACE_PROBE1(nfs4__str__uid
, char *, mapargp
->str
);
418 case NFSMAPID_INVALID
:
419 case NFSMAPID_UNMAPPABLE
:
420 case NFSMAPID_INTERNAL
:
422 case NFSMAPID_NOTFOUND
:
425 * For now, treat all of these errors as equal.
427 * Return error on the server side, then the
428 * server returns NFS4_BADOWNER to the client.
429 * On client side, just map to UID_NOBODY.
437 kmem_free(mapargp
, MAPID_ARG_LEN(u8s
->utf8string_len
));
439 kmem_free(door_args
.rbuf
, door_args
.rsize
);
444 kmem_free(mapargp
, MAPID_ARG_LEN(u8s
->utf8string_len
));
446 * We got some door error
451 * If we took an interrupt we have to bail out.
453 if (ttolwp(curthread
) && ISSIG(curthread
, JUSTLOOKING
)) {
459 * We may have gotten EINTR for other reasons like the
460 * door being revoked on us, instead of trying to
461 * extract this out of the door handle, sleep
462 * and try again, if still revoked we will get EBADF
466 case EAGAIN
: /* process may be forking */
473 default: /* Unknown must be fatal */
474 case EBADF
: /* Invalid door */
475 case EINVAL
: /* Not a door, wrong target */
477 * A fatal door error, if our failing door handle is the
478 * current door handle, clean up our state and
479 * mark the server dead.
481 mutex_enter(&nig
->nfsidmap_daemon_lock
);
482 if (dh
== nig
->nfsidmap_daemon_dh
) {
483 door_ki_rele(nig
->nfsidmap_daemon_dh
);
484 nig
->nfsidmap_daemon_dh
= NULL
;
486 mutex_exit(&nig
->nfsidmap_daemon_lock
);
493 * Note: We've already done optimizations above to check
494 * for '@' sign, so if we can't comm w/nfsmapid, we
495 * _know_ this _can't_ be a stringified uid.
497 if (!nig
->nig_msg_done
) {
498 zcmn_err(getzoneid(), CE_WARN
,
499 "!%s: Can't communicate with mapping daemon "
502 nig
->nig_msg_done
= 1;
511 * Convert a uid into its utf-8 string representation.
514 nfs_idmap_uid_str(uid_t uid
, utf8string
*u8s
, bool_t isserver
)
518 const char *whoami
= "nfs_idmap_uid_str";
519 struct nfsidmap_globals
*nig
;
520 struct mapid_arg maparg
;
521 struct mapid_res mapres
;
522 struct mapid_res
*mapresp
= &mapres
;
523 struct mapid_res
*resp
= mapresp
;
524 door_arg_t door_args
;
527 nig
= zone_getspecific(nfsidmap_zone_key
, nfs_zone());
531 * If the supplied uid is "nobody", then we don't look at the
532 * cache, since we DON'T cache it in the u2s_cache. We cannot
533 * tell two strings apart from caching the same uid.
535 if (uid
== UID_NOBODY
) {
536 (void) str_to_utf8("nobody", u8s
);
541 * Start-off with upcalls disabled, and once nfsmapid(1m) is
542 * up and running, we'll leverage it's first flush to let the
543 * kernel know when it's up and available to perform mappings.
544 * We fall back to answering with stringified uid's.
547 mutex_enter(&nig
->nfsidmap_daemon_lock
);
548 dh
= nig
->nfsidmap_daemon_dh
;
551 mutex_exit(&nig
->nfsidmap_daemon_lock
);
553 if (dh
== NULL
|| nig
->nfsidmap_pid
== curproc
->p_pid
) {
556 nfs_idmap_i2s_literal(uid
, u8s
);
561 if (nfs_idmap_cache_i2s_lkup(&nig
->u2s_ci
, uid
, &hashno
, u8s
)) {
567 maparg
.cmd
= NFSMAPID_UID_STR
;
568 maparg
.u_arg
.uid
= uid
;
570 door_args
.data_ptr
= (char *)&maparg
;
571 door_args
.data_size
= sizeof (struct mapid_arg
);
572 door_args
.desc_ptr
= NULL
;
573 door_args
.desc_num
= 0;
574 door_args
.rbuf
= (char *)mapresp
;
575 door_args
.rsize
= sizeof (struct mapid_res
);
577 error
= door_ki_upcall_limited(dh
, &door_args
, NULL
, SIZE_MAX
, 0);
579 resp
= (struct mapid_res
*)door_args
.rbuf
;
581 /* Should never provide daemon with bad args */
582 ASSERT(resp
->status
!= NFSMAPID_INVALID
);
584 switch (resp
->status
) {
587 * We now have a valid result from the
588 * user-land daemon, so cache the result (if need be).
589 * Load return value first then do the caches.
591 (void) str_to_utf8(resp
->str
, u8s
);
592 nfs_idmap_cache_i2s_insert(&nig
->u2s_ci
, uid
,
593 u8s
, HQ_HASH_HINT
, hashno
);
596 case NFSMAPID_INVALID
:
597 case NFSMAPID_UNMAPPABLE
:
598 case NFSMAPID_INTERNAL
:
599 case NFSMAPID_BADDOMAIN
:
601 case NFSMAPID_NOTFOUND
:
604 * For now, treat all of these errors as equal.
611 kmem_free(door_args
.rbuf
, door_args
.rsize
);
617 * We got some door error
622 * If we took an interrupt we have to bail out.
624 if (ttolwp(curthread
) && ISSIG(curthread
, JUSTLOOKING
)) {
630 * We may have gotten EINTR for other reasons like the
631 * door being revoked on us, instead of trying to
632 * extract this out of the door handle, sleep
633 * and try again, if still revoked we will get EBADF
637 case EAGAIN
: /* process may be forking */
644 default: /* Unknown must be fatal */
645 case EBADF
: /* Invalid door */
646 case EINVAL
: /* Not a door, wrong target */
648 * A fatal door error, if our failing door handle is the
649 * current door handle, clean up our state and
650 * mark the server dead.
652 mutex_enter(&nig
->nfsidmap_daemon_lock
);
653 if (dh
== nig
->nfsidmap_daemon_dh
) {
654 door_ki_rele(nig
->nfsidmap_daemon_dh
);
655 nig
->nfsidmap_daemon_dh
= NULL
;
657 mutex_exit(&nig
->nfsidmap_daemon_lock
);
661 * Log error on client-side only
663 if (!nig
->nig_msg_done
&& !isserver
) {
664 zcmn_err(getzoneid(), CE_WARN
,
665 "!%s: Can't communicate with mapping daemon "
668 nig
->nig_msg_done
= 1;
670 nfs_idmap_i2s_literal(uid
, u8s
);
677 * Convert a group utf-8 string identifier into its local gid.
680 nfs_idmap_str_gid(utf8string
*u8s
, gid_t
*gid
, bool_t isserver
)
684 const char *whoami
= "nfs_idmap_str_gid";
685 struct nfsidmap_globals
*nig
;
686 struct mapid_arg
*mapargp
;
687 struct mapid_res mapres
;
688 struct mapid_res
*mapresp
= &mapres
;
689 struct mapid_res
*resp
= mapresp
;
690 door_arg_t door_args
;
693 nig
= zone_getspecific(nfsidmap_zone_key
, nfs_zone());
696 if (!u8s
|| !u8s
->utf8string_val
|| u8s
->utf8string_len
== 0 ||
697 (u8s
->utf8string_val
[0] == '\0')) {
699 return (isserver
? EINVAL
: 0);
703 * If "nobody", just short circuit and bail
705 if (bcmp(u8s
->utf8string_val
, "nobody", 6) == 0) {
711 * Start-off with upcalls disabled, and once nfsmapid(1m) is up and
712 * running, we'll leverage it's first flush to let the kernel know
713 * when it's up and available to perform mappings. Also, on client
714 * only, be smarter about when to issue upcalls by checking the
715 * string for existence of an '@' sign. If no '@' sign, then we just
716 * make our best effort to decode the string ourselves.
719 mutex_enter(&nig
->nfsidmap_daemon_lock
);
720 dh
= nig
->nfsidmap_daemon_dh
;
723 mutex_exit(&nig
->nfsidmap_daemon_lock
);
725 if (dh
== NULL
|| nig
->nfsidmap_pid
== curproc
->p_pid
||
726 (!utf8_strchr(u8s
, '@') && !isserver
)) {
729 error
= nfs_idmap_s2i_literal(u8s
, gid
, isserver
);
731 * If we get a numeric value, but we only do so because
732 * we are nfsmapid, return ENOTSUP to indicate a valid
733 * response, but not to cache it.
735 if (!error
&& nig
->nfsidmap_pid
== curproc
->p_pid
)
741 if (nfs_idmap_cache_s2i_lkup(&nig
->s2g_ci
, u8s
, &hashno
, gid
)) {
747 mapargp
= kmem_alloc(MAPID_ARG_LEN(u8s
->utf8string_len
), KM_SLEEP
);
748 mapargp
->cmd
= NFSMAPID_STR_GID
;
749 mapargp
->u_arg
.len
= u8s
->utf8string_len
;
750 (void) bcopy(u8s
->utf8string_val
, mapargp
->str
, mapargp
->u_arg
.len
);
751 mapargp
->str
[mapargp
->u_arg
.len
] = '\0';
753 door_args
.data_ptr
= (char *)mapargp
;
754 door_args
.data_size
= MAPID_ARG_LEN(mapargp
->u_arg
.len
);
755 door_args
.desc_ptr
= NULL
;
756 door_args
.desc_num
= 0;
757 door_args
.rbuf
= (char *)mapresp
;
758 door_args
.rsize
= sizeof (struct mapid_res
);
760 error
= door_ki_upcall_limited(dh
, &door_args
, NULL
, SIZE_MAX
, 0);
762 resp
= (struct mapid_res
*)door_args
.rbuf
;
764 /* Should never provide daemon with bad args */
765 ASSERT(resp
->status
!= NFSMAPID_INVALID
);
767 switch (resp
->status
) {
770 * Valid mapping. Cache it.
772 *gid
= resp
->u_res
.gid
;
774 nfs_idmap_cache_s2i_insert(&nig
->s2g_ci
, *gid
,
775 u8s
, HQ_HASH_HINT
, hashno
);
778 case NFSMAPID_NUMSTR
:
780 * string came in as stringified id. Don't cache !
782 * nfsmapid(1m) semantics have changed in order to
783 * support diskless clients. Thus, for stringified
784 * id's that have passwd/group entries, we'll go
785 * ahead and map them, returning no error.
787 *gid
= resp
->u_res
.gid
;
790 case NFSMAPID_BADDOMAIN
:
792 * Make the offending "group@domain" string readily
793 * available to D scripts that enable the probe.
795 DTRACE_PROBE1(nfs4__str__gid
, char *, mapargp
->str
);
798 case NFSMAPID_INVALID
:
799 case NFSMAPID_UNMAPPABLE
:
800 case NFSMAPID_INTERNAL
:
802 case NFSMAPID_NOTFOUND
:
805 * For now, treat all of these errors as equal.
807 * Return error on the server side, then the
808 * server returns NFS4_BADOWNER to the client.
809 * On client side, just map to GID_NOBODY.
817 kmem_free(mapargp
, MAPID_ARG_LEN(u8s
->utf8string_len
));
819 kmem_free(door_args
.rbuf
, door_args
.rsize
);
824 kmem_free(mapargp
, MAPID_ARG_LEN(u8s
->utf8string_len
));
826 * We got some door error
831 * If we took an interrupt we have to bail out.
833 if (ttolwp(curthread
) && ISSIG(curthread
, JUSTLOOKING
)) {
839 * We may have gotten EINTR for other reasons like the
840 * door being revoked on us, instead of trying to
841 * extract this out of the door handle, sleep
842 * and try again, if still revoked we will get EBADF
846 case EAGAIN
: /* process may be forking */
853 default: /* Unknown must be fatal */
854 case EBADF
: /* Invalid door */
855 case EINVAL
: /* Not a door, wrong target */
857 * A fatal door error, clean up our state and
858 * mark the server dead.
861 mutex_enter(&nig
->nfsidmap_daemon_lock
);
862 if (dh
== nig
->nfsidmap_daemon_dh
) {
863 door_ki_rele(nig
->nfsidmap_daemon_dh
);
864 nig
->nfsidmap_daemon_dh
= NULL
;
866 mutex_exit(&nig
->nfsidmap_daemon_lock
);
873 * Note: We've already done optimizations above to check
874 * for '@' sign, so if we can't comm w/nfsmapid, we
875 * _know_ this _can't_ be a stringified gid.
877 if (!nig
->nig_msg_done
) {
878 zcmn_err(getzoneid(), CE_WARN
,
879 "!%s: Can't communicate with mapping daemon "
882 nig
->nig_msg_done
= 1;
891 * Convert a gid into its utf-8 string representation.
894 nfs_idmap_gid_str(gid_t gid
, utf8string
*u8s
, bool_t isserver
)
898 const char *whoami
= "nfs_idmap_gid_str";
899 struct nfsidmap_globals
*nig
;
900 struct mapid_arg maparg
;
901 struct mapid_res mapres
;
902 struct mapid_res
*mapresp
= &mapres
;
903 struct mapid_res
*resp
= mapresp
;
904 door_arg_t door_args
;
907 nig
= zone_getspecific(nfsidmap_zone_key
, nfs_zone());
911 * If the supplied gid is "nobody", then we don't look at the
912 * cache, since we DON'T cache it in the u2s_cache. We cannot
913 * tell two strings apart from caching the same gid.
915 if (gid
== GID_NOBODY
) {
916 (void) str_to_utf8("nobody", u8s
);
921 * Start-off with upcalls disabled, and once nfsmapid(1m) is
922 * up and running, we'll leverage it's first flush to let the
923 * kernel know when it's up and available to perform mappings.
924 * We fall back to answering with stringified gid's.
927 mutex_enter(&nig
->nfsidmap_daemon_lock
);
928 dh
= nig
->nfsidmap_daemon_dh
;
931 mutex_exit(&nig
->nfsidmap_daemon_lock
);
933 if (dh
== NULL
|| nig
->nfsidmap_pid
== curproc
->p_pid
) {
936 nfs_idmap_i2s_literal(gid
, u8s
);
941 if (nfs_idmap_cache_i2s_lkup(&nig
->g2s_ci
, gid
, &hashno
, u8s
)) {
947 maparg
.cmd
= NFSMAPID_GID_STR
;
948 maparg
.u_arg
.gid
= gid
;
950 door_args
.data_ptr
= (char *)&maparg
;
951 door_args
.data_size
= sizeof (struct mapid_arg
);
952 door_args
.desc_ptr
= NULL
;
953 door_args
.desc_num
= 0;
954 door_args
.rbuf
= (char *)mapresp
;
955 door_args
.rsize
= sizeof (struct mapid_res
);
957 error
= door_ki_upcall_limited(dh
, &door_args
, NULL
, SIZE_MAX
, 0);
959 resp
= (struct mapid_res
*)door_args
.rbuf
;
961 /* Should never provide daemon with bad args */
962 ASSERT(resp
->status
!= NFSMAPID_INVALID
);
964 switch (resp
->status
) {
967 * We now have a valid result from the
968 * user-land daemon, so cache the result (if need be).
969 * Load return value first then do the caches.
971 (void) str_to_utf8(resp
->str
, u8s
);
972 nfs_idmap_cache_i2s_insert(&nig
->g2s_ci
, gid
,
973 u8s
, HQ_HASH_HINT
, hashno
);
976 case NFSMAPID_INVALID
:
977 case NFSMAPID_UNMAPPABLE
:
978 case NFSMAPID_INTERNAL
:
979 case NFSMAPID_BADDOMAIN
:
981 case NFSMAPID_NOTFOUND
:
984 * For now, treat all of these errors as equal.
991 kmem_free(door_args
.rbuf
, door_args
.rsize
);
997 * We got some door error
1002 * If we took an interrupt we have to bail out.
1004 if (ttolwp(curthread
) && ISSIG(curthread
, JUSTLOOKING
)) {
1010 * We may have gotten EINTR for other reasons like the
1011 * door being revoked on us, instead of trying to
1012 * extract this out of the door handle, sleep
1013 * and try again, if still revoked we will get EBADF
1014 * next time through.
1017 case EAGAIN
: /* process may be forking */
1020 * Back off for a bit
1024 default: /* Unknown must be fatal */
1025 case EBADF
: /* Invalid door */
1026 case EINVAL
: /* Not a door, wrong target */
1028 * A fatal door error, if our failing door handle is the
1029 * current door handle, clean up our state and
1030 * mark the server dead.
1032 mutex_enter(&nig
->nfsidmap_daemon_lock
);
1033 if (dh
== nig
->nfsidmap_daemon_dh
) {
1034 door_ki_rele(nig
->nfsidmap_daemon_dh
);
1035 nig
->nfsidmap_daemon_dh
= NULL
;
1038 mutex_exit(&nig
->nfsidmap_daemon_lock
);
1041 * Log error on client-side only
1043 if (!nig
->nig_msg_done
&& !isserver
) {
1044 zcmn_err(getzoneid(), CE_WARN
,
1045 "!%s: Can't communicate with mapping daemon "
1046 "nfsmapid", whoami
);
1048 nig
->nig_msg_done
= 1;
1050 nfs_idmap_i2s_literal(gid
, u8s
);
1056 /* -- idmap cache management -- */
1059 * Cache creation and initialization routine
1062 nfs_idmap_cache_create(idmap_cache_info_t
*cip
, const char *name
)
1065 nfsidhq_t
*hq
= NULL
;
1067 cip
->table
= kmem_alloc((NFSID_CACHE_ANCHORS
* sizeof (nfsidhq_t
)),
1070 for (i
= 0, hq
= cip
->table
; i
< NFSID_CACHE_ANCHORS
; i
++, hq
++) {
1071 hq
->hq_que_forw
= hq
;
1072 hq
->hq_que_back
= hq
;
1073 mutex_init(&(hq
->hq_lock
), NULL
, MUTEX_DEFAULT
, NULL
);
1079 * Cache destruction routine
1081 * Ops per hash queue
1083 * - dequeue cache entries
1084 * - release string storage per entry
1085 * - release cache entry storage
1086 * - destroy HQ lock when HQ is empty
1087 * - once all HQ's empty, release HQ storage
1090 nfs_idmap_cache_destroy(idmap_cache_info_t
*cip
)
1095 ASSERT(MUTEX_HELD(&nfsidmap_globals_lock
));
1096 nfs_idmap_cache_flush(cip
);
1098 * We can safely destroy per-queue locks since the only
1099 * other entity that could be mucking with this table is the
1100 * kmem reaper thread which does everything under
1101 * nfsidmap_globals_lock (which we're holding).
1103 for (i
= 0, hq
= cip
->table
; i
< NFSID_CACHE_ANCHORS
; i
++, hq
++)
1104 mutex_destroy(&(hq
->hq_lock
));
1105 kmem_free(cip
->table
, NFSID_CACHE_ANCHORS
* sizeof (nfsidhq_t
));
1109 nfs_idmap_args(struct nfsidmap_args
*idmp
)
1111 struct nfsidmap_globals
*nig
;
1113 nig
= zone_getspecific(nfsidmap_zone_key
, nfs_zone());
1114 ASSERT(nig
!= NULL
);
1116 nfs_idmap_cache_flush(&nig
->u2s_ci
);
1117 nfs_idmap_cache_flush(&nig
->s2u_ci
);
1118 nfs_idmap_cache_flush(&nig
->g2s_ci
);
1119 nfs_idmap_cache_flush(&nig
->s2g_ci
);
1122 * nfsmapid(1m) up and running; enable upcalls
1124 * 0 Just flush caches
1125 * 1 Re-establish door knob
1129 * When reestablishing the nfsmapid we need to
1130 * not only purge the idmap cache but also
1131 * the dnlc as it will have cached uid/gid's.
1132 * While heavyweight, this should almost never happen
1137 * Invalidate the attrs of all rnodes to force new uid and gids
1139 nfs4_rnode_invalidate(NULL
);
1141 mutex_enter(&nig
->nfsidmap_daemon_lock
);
1142 if (nig
->nfsidmap_daemon_dh
)
1143 door_ki_rele(nig
->nfsidmap_daemon_dh
);
1144 nig
->nfsidmap_daemon_dh
= door_ki_lookup(idmp
->did
);
1145 nig
->nfsidmap_pid
= curproc
->p_pid
;
1146 nig
->nig_msg_done
= 0;
1147 mutex_exit(&nig
->nfsidmap_daemon_lock
);
1152 * Cache flush routine
1154 * The only serialization required it to hold the hash chain lock
1155 * when destroying cache entries. There is no need to prevent access
1156 * to all hash chains while flushing. It is possible that (valid)
1157 * entries could be cached in later hash chains after we start flushing.
1158 * It is unfortunate that the entry will be instantly destroyed, but
1159 * it isn't a major concern. This is only a cache. It'll be repopulated.
1161 * Ops per hash queue
1163 * - dequeue cache entries
1164 * - release string storage per entry
1165 * - release cache entry storage
1168 nfs_idmap_cache_flush(idmap_cache_info_t
*cip
)
1171 nfsidmap_t
*p
, *next
;
1174 for (i
= 0, hq
= cip
->table
; i
< NFSID_CACHE_ANCHORS
; i
++, hq
++) {
1176 mutex_enter(&(hq
->hq_lock
));
1179 * remove list from hash header so we can release
1182 p
= hq
->hq_lru_forw
;
1183 hq
->hq_que_forw
= hq
;
1184 hq
->hq_que_back
= hq
;
1186 mutex_exit(&(hq
->hq_lock
));
1189 * Iterate over the orphan'd list and free all elements.
1190 * There's no need to bother with remque since we're
1191 * freeing the entire list.
1193 while (p
!= (nfsidmap_t
*)hq
) {
1196 kmem_free(p
->id_val
, p
->id_len
);
1197 kmem_cache_free(nfsidmap_cache
, p
);
1205 nfs_idmap_cache_reclaim(idmap_cache_info_t
*cip
)
1208 nfsidmap_t
*pprev
= NULL
;
1212 ASSERT(cip
!= NULL
&& cip
->table
!= NULL
);
1215 * If the daemon is down, do not flush anything
1217 if ((*cip
->nfsidmap_daemon_dh
) == NULL
)
1220 for (i
= 0, hq
= cip
->table
; i
< NFSID_CACHE_ANCHORS
; i
++, hq
++) {
1221 if (!mutex_tryenter(&(hq
->hq_lock
)))
1225 * Start at end of list and work backwards since LRU
1227 for (p
= hq
->hq_lru_back
; p
!= (nfsidmap_t
*)hq
; p
= pprev
) {
1231 * List is LRU. If trailing end does not
1232 * contain stale entries, then no need to
1235 if (!TIMEOUT(p
->id_time
))
1238 nfs_idmap_cache_rment(p
);
1240 mutex_exit(&(hq
->hq_lock
));
1245 * Callback reclaim function for VM. We reap timed-out entries from all hash
1246 * tables in all zones.
1250 nfs_idmap_reclaim(void *arg
)
1252 struct nfsidmap_globals
*nig
;
1254 mutex_enter(&nfsidmap_globals_lock
);
1255 for (nig
= list_head(&nfsidmap_globals_list
); nig
!= NULL
;
1256 nig
= list_next(&nfsidmap_globals_list
, nig
)) {
1257 nfs_idmap_cache_reclaim(&nig
->u2s_ci
);
1258 nfs_idmap_cache_reclaim(&nig
->s2u_ci
);
1259 nfs_idmap_cache_reclaim(&nig
->g2s_ci
);
1260 nfs_idmap_cache_reclaim(&nig
->s2g_ci
);
1262 mutex_exit(&nfsidmap_globals_lock
);
1266 * Search the specified cache for the existence of the specified utf-8
1267 * string. If found, the corresponding mapping is returned in id_buf and
1268 * the cache entry is updated to the head of the LRU list. The computed
1269 * hash queue number, is returned in hashno.
1271 * cip - cache info ptr
1272 * u8s - utf8 string to resolve
1273 * hashno - hash number, retval
1274 * id_buf - if found, id for u8s
1277 nfs_idmap_cache_s2i_lkup(idmap_cache_info_t
*cip
, utf8string
*u8s
,
1278 uint_t
*hashno
, uid_t
*id_buf
)
1285 uint_t found_stat
= 0;
1287 if ((rqst_c_str
= utf8_to_str(u8s
, &rqst_len
, NULL
)) == NULL
) {
1289 * Illegal string, return not found.
1295 * Compute hash queue
1297 *hashno
= pkp_tab_hash(rqst_c_str
, rqst_len
- 1);
1298 hq
= &cip
->table
[*hashno
];
1301 * Look for the entry in the HQ
1303 mutex_enter(&(hq
->hq_lock
));
1304 for (p
= hq
->hq_lru_forw
; p
!= (nfsidmap_t
*)hq
; p
= pnext
) {
1309 * Check entry for staleness first, as user's id
1310 * may have changed and may need to be remapped.
1311 * Note that we don't evict entries from the cache
1312 * if we're having trouble contacting nfsmapid(1m)
1314 if (TIMEOUT(p
->id_time
) && (*cip
->nfsidmap_daemon_dh
) != NULL
) {
1315 nfs_idmap_cache_rment(p
);
1320 * Compare equal length strings
1322 if (p
->id_len
== (rqst_len
- 1)) {
1323 if (bcmp(p
->id_val
, rqst_c_str
, (rqst_len
- 1)) == 0) {
1325 * Found it. Update it and load return value.
1330 p
->id_time
= gethrestime_sec();
1337 mutex_exit(&(hq
->hq_lock
));
1339 if (rqst_c_str
!= NULL
)
1340 kmem_free(rqst_c_str
, rqst_len
);
1342 return (found_stat
);
1346 * Search the specified cache for the existence of the specified utf8
1347 * string, as it may have been inserted before this instance got a chance
1348 * to do it. If NOT found, then a new entry is allocated for the specified
1349 * cache, and inserted. The hash queue number is obtained from hash_number
1350 * if the behavior is HQ_HASH_HINT, or computed otherwise.
1352 * cip - cache info ptr
1353 * id - id result from upcall
1354 * u8s - utf8 string to resolve
1355 * behavior - hash algorithm behavior
1356 * hash_number - hash number iff hint
1359 nfs_idmap_cache_s2i_insert(idmap_cache_info_t
*cip
, uid_t id
, utf8string
*u8s
,
1360 hash_stat behavior
, uint_t hash_number
)
1371 * This shouldn't fail, since already successful at lkup.
1372 * So, if it does happen, just drop the request-to-insert
1375 if ((c_str
= utf8_to_str(u8s
, &c_len
, NULL
)) == NULL
)
1379 * Obtain correct hash queue to insert new entry in
1383 hashno
= hash_number
;
1388 hashno
= pkp_tab_hash(c_str
, c_len
- 1);
1391 hq
= &cip
->table
[hashno
];
1395 * Look for an existing entry in the cache. If one exists
1396 * update it, and return. Otherwise, allocate a new cache
1397 * entry, initialize it and insert it.
1399 mutex_enter(&(hq
->hq_lock
));
1400 for (p
= hq
->hq_lru_forw
; p
!= (nfsidmap_t
*)hq
; p
= pnext
) {
1405 * Check entry for staleness first, as user's id
1406 * may have changed and may need to be remapped.
1407 * Note that we don't evict entries from the cache
1408 * if we're having trouble contacting nfsmapid(1m)
1410 if (TIMEOUT(p
->id_time
) && (*cip
->nfsidmap_daemon_dh
) != NULL
) {
1411 nfs_idmap_cache_rment(p
);
1416 * Compare equal length strings
1418 if (p
->id_len
== (c_len
- 1)) {
1419 if (bcmp(p
->id_val
, c_str
, (c_len
- 1)) == 0) {
1421 * Move to front, and update time.
1425 p
->id_time
= gethrestime_sec();
1427 mutex_exit(&(hq
->hq_lock
));
1428 kmem_free(c_str
, c_len
);
1435 * Not found ! Alloc, init and insert new entry
1437 newp
= kmem_cache_alloc(nfsidmap_cache
, KM_SLEEP
);
1438 newp
->id_len
= u8s
->utf8string_len
;
1439 newp
->id_val
= kmem_alloc(u8s
->utf8string_len
, KM_SLEEP
);
1440 bcopy(u8s
->utf8string_val
, newp
->id_val
, u8s
->utf8string_len
);
1442 newp
->id_time
= gethrestime_sec();
1445 mutex_exit(&(hq
->hq_lock
));
1446 kmem_free(c_str
, c_len
);
1450 * Search the specified cache for the existence of the specified id.
1451 * If found, the corresponding mapping is returned in u8s and the
1452 * cache entry is updated to the head of the LRU list. The computed
1453 * hash queue number, is returned in hashno.
1455 * cip - cache info ptr
1456 * id - id to resolve
1457 * hashno - hash number, retval
1458 * u8s - if found, utf8 str for id
1461 nfs_idmap_cache_i2s_lkup(idmap_cache_info_t
*cip
, uid_t id
, uint_t
*hashno
,
1464 uint_t found_stat
= 0;
1471 * Compute hash queue
1475 hq
= &cip
->table
[hash
];
1478 * Look for the entry in the HQ
1480 mutex_enter(&(hq
->hq_lock
));
1481 for (p
= hq
->hq_lru_forw
; p
!= (nfsidmap_t
*)hq
; p
= pnext
) {
1486 * Check entry for staleness first, as user's id
1487 * may have changed and may need to be remapped.
1488 * Note that we don't evict entries from the cache
1489 * if we're having trouble contacting nfsmapid(1m)
1491 if (TIMEOUT(p
->id_time
) && (*cip
->nfsidmap_daemon_dh
) != NULL
) {
1492 nfs_idmap_cache_rment(p
);
1496 if (p
->id_no
== id
) {
1499 * Found it. Load return value and move to head
1501 ASSERT(u8s
->utf8string_val
== NULL
);
1502 u8s
->utf8string_len
= p
->id_len
;
1503 u8s
->utf8string_val
= kmem_alloc(p
->id_len
, KM_SLEEP
);
1504 bcopy(p
->id_val
, u8s
->utf8string_val
, p
->id_len
);
1508 p
->id_time
= gethrestime_sec();
1514 mutex_exit(&(hq
->hq_lock
));
1516 return (found_stat
);
1520 * Search the specified cache for the existence of the specified id,
1521 * as it may have been inserted before this instance got a chance to
1522 * do it. If NOT found, then a new entry is allocated for the specified
1523 * cache, and inserted. The hash queue number is obtained from hash_number
1524 * if the behavior is HQ_HASH_HINT, or computed otherwise.
1526 * cip - cache info ptr
1527 * id - id to resolve
1528 * u8s - utf8 result from upcall
1529 * behavior - has algorithm behavior
1530 * hash_number - hash number iff hint
1533 nfs_idmap_cache_i2s_insert(idmap_cache_info_t
*cip
, uid_t id
, utf8string
*u8s
,
1534 hash_stat behavior
, uint_t hash_number
)
1544 * Obtain correct hash queue to insert new entry in
1548 hashno
= hash_number
;
1553 ID_HASH(id
, hashno
);
1556 hq
= &cip
->table
[hashno
];
1560 * Look for an existing entry in the cache. If one exists
1561 * update it, and return. Otherwise, allocate a new cache
1562 * entry, initialize and insert it.
1564 mutex_enter(&(hq
->hq_lock
));
1565 for (p
= hq
->hq_lru_forw
; p
!= (nfsidmap_t
*)hq
; p
= pnext
) {
1570 * Check entry for staleness first, as user's id
1571 * may have changed and may need to be remapped.
1572 * Note that we don't evict entries from the cache
1573 * if we're having trouble contacting nfsmapid(1m)
1575 if (TIMEOUT(p
->id_time
) && (*cip
->nfsidmap_daemon_dh
) != NULL
) {
1576 nfs_idmap_cache_rment(p
);
1581 if ((p
->id_no
== id
) && (p
->id_len
== u8s
->utf8string_len
)) {
1583 * Found It ! Move to front, and update time.
1587 p
->id_time
= gethrestime_sec();
1589 mutex_exit(&(hq
->hq_lock
));
1595 * Not found ! Alloc, init and insert new entry
1597 newp
= kmem_cache_alloc(nfsidmap_cache
, KM_SLEEP
);
1598 newp
->id_len
= u8s
->utf8string_len
;
1599 newp
->id_val
= kmem_alloc(u8s
->utf8string_len
, KM_SLEEP
);
1600 bcopy(u8s
->utf8string_val
, newp
->id_val
, u8s
->utf8string_len
);
1602 newp
->id_time
= gethrestime_sec();
1605 mutex_exit(&(hq
->hq_lock
));
1609 * Remove and free one cache entry
1612 nfs_idmap_cache_rment(nfsidmap_t
*p
)
1616 kmem_free(p
->id_val
, p
->id_len
);
1617 kmem_cache_free(nfsidmap_cache
, p
);
1621 #define UID_MAX 2147483647 /* see limits.h */
1625 #define isdigit(c) ((c) >= '0' && (c) <= '9')
1629 is_stringified_id(utf8string
*u8s
)
1633 for (i
= 0; i
< u8s
->utf8string_len
; i
++)
1634 if (!isdigit(u8s
->utf8string_val
[i
]))
1640 nfs_idmap_s2i_literal(utf8string
*u8s
, uid_t
*id
, int isserver
)
1644 char ids
[_MAXIDSTRLEN
];
1647 * "nobody" unless we can actually decode it.
1652 * We're here because it has already been determined that the
1653 * string contains no '@' _or_ the nfsmapid daemon has yet to
1656 if (!is_stringified_id(u8s
))
1660 * If utf8string_len is greater than _MAXIDSTRLEN-1, then the id
1661 * is going to be greater than UID_MAX. Return id of "nobody"
1664 if (u8s
->utf8string_len
>= _MAXIDSTRLEN
)
1665 return (isserver
? EPERM
: 0);
1668 * Make sure we pass a NULL terminated 'C' string to ddi_strtol
1670 bcopy(u8s
->utf8string_val
, ids
, u8s
->utf8string_len
);
1671 ids
[u8s
->utf8string_len
] = '\0';
1672 convd
= ddi_strtol(ids
, NULL
, 10, &tmp
);
1673 if (convd
== 0 && tmp
>= 0 && tmp
<= UID_MAX
) {
1677 return (isserver
? EPERM
: 0);
1681 nfs_idmap_i2s_literal(uid_t id
, utf8string
*u8s
)
1683 char ids
[_MAXIDSTRLEN
];
1685 (void) snprintf(ids
, _MAXIDSTRLEN
, "%d", id
);
1686 (void) str_to_utf8(ids
, u8s
);
1689 /* -- Utility functions -- */
1692 utf8_strchr(utf8string
*u8s
, const char c
)
1695 char *u8p
= u8s
->utf8string_val
;
1696 int len
= u8s
->utf8string_len
;
1698 for (i
= 0; i
< len
; i
++)