1 | /* |
2 | * svc.c, Server-side remote procedure call interface. |
3 | * |
4 | * There are two sets of procedures here. The xprt routines are |
5 | * for handling transport handles. The svc routines handle the |
6 | * list of service routines. |
7 | * Copyright (C) 2002-2017 Free Software Foundation, Inc. |
8 | * This file is part of the GNU C Library. |
9 | * Contributed by Ulrich Drepper <drepper@redhat.com>, 2002. |
10 | * |
11 | * The GNU C Library is free software; you can redistribute it and/or |
12 | * modify it under the terms of the GNU Lesser General Public |
13 | * License as published by the Free Software Foundation; either |
14 | * version 2.1 of the License, or (at your option) any later version. |
15 | * |
16 | * The GNU C Library is distributed in the hope that it will be useful, |
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
19 | * Lesser General Public License for more details. |
20 | * |
21 | * You should have received a copy of the GNU Lesser General Public |
22 | * License along with the GNU C Library; if not, see |
23 | * <http://www.gnu.org/licenses/>. |
24 | * |
25 | * Copyright (c) 2010, Oracle America, Inc. |
26 | * |
27 | * Redistribution and use in source and binary forms, with or without |
28 | * modification, are permitted provided that the following conditions are |
29 | * met: |
30 | * |
31 | * * Redistributions of source code must retain the above copyright |
32 | * notice, this list of conditions and the following disclaimer. |
33 | * * Redistributions in binary form must reproduce the above |
34 | * copyright notice, this list of conditions and the following |
35 | * disclaimer in the documentation and/or other materials |
36 | * provided with the distribution. |
37 | * * Neither the name of the "Oracle America, Inc." nor the names of its |
38 | * contributors may be used to endorse or promote products derived |
39 | * from this software without specific prior written permission. |
40 | * |
41 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
42 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
43 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
44 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
45 | * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, |
46 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
47 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE |
48 | * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
49 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
50 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
51 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
52 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
53 | */ |
54 | |
55 | #include <errno.h> |
56 | #include <unistd.h> |
57 | #include <rpc/rpc.h> |
58 | #include <rpc/svc.h> |
59 | #include <rpc/pmap_clnt.h> |
60 | #include <sys/poll.h> |
61 | #include <time.h> |
62 | #include <shlib-compat.h> |
63 | |
64 | #ifdef _RPC_THREAD_SAFE_ |
65 | #define xports RPC_THREAD_VARIABLE(svc_xports_s) |
66 | #else |
67 | static SVCXPRT **xports; |
68 | #endif |
69 | |
70 | #define NULL_SVC ((struct svc_callout *)0) |
71 | #define RQCRED_SIZE 400 /* this size is excessive */ |
72 | |
73 | /* The services list |
74 | Each entry represents a set of procedures (an rpc program). |
75 | The dispatch routine takes request structs and runs the |
76 | appropriate procedure. */ |
77 | struct svc_callout { |
78 | struct svc_callout *sc_next; |
79 | rpcprog_t sc_prog; |
80 | rpcvers_t sc_vers; |
81 | void (*sc_dispatch) (struct svc_req *, SVCXPRT *); |
82 | bool_t sc_mapped; |
83 | }; |
84 | #ifdef _RPC_THREAD_SAFE_ |
85 | #define svc_head RPC_THREAD_VARIABLE(svc_head_s) |
86 | #else |
87 | static struct svc_callout *svc_head; |
88 | #endif |
89 | |
90 | /* *************** SVCXPRT related stuff **************** */ |
91 | |
92 | /* Activate a transport handle. */ |
93 | void |
94 | xprt_register (SVCXPRT *xprt) |
95 | { |
96 | register int sock = xprt->xp_sock; |
97 | register int i; |
98 | |
99 | if (xports == NULL) |
100 | { |
101 | xports = (SVCXPRT **) calloc (_rpc_dtablesize (), sizeof (SVCXPRT *)); |
102 | if (xports == NULL) /* Don't add handle */ |
103 | return; |
104 | } |
105 | |
106 | if (sock < _rpc_dtablesize ()) |
107 | { |
108 | struct pollfd *new_svc_pollfd; |
109 | |
110 | xports[sock] = xprt; |
111 | if (sock < FD_SETSIZE) |
112 | FD_SET (sock, &svc_fdset); |
113 | |
114 | /* Check if we have an empty slot */ |
115 | for (i = 0; i < svc_max_pollfd; ++i) |
116 | if (svc_pollfd[i].fd == -1) |
117 | { |
118 | svc_pollfd[i].fd = sock; |
119 | svc_pollfd[i].events = (POLLIN | POLLPRI | |
120 | POLLRDNORM | POLLRDBAND); |
121 | return; |
122 | } |
123 | |
124 | new_svc_pollfd = (struct pollfd *) realloc (svc_pollfd, |
125 | sizeof (struct pollfd) |
126 | * (svc_max_pollfd + 1)); |
127 | if (new_svc_pollfd == NULL) /* Out of memory */ |
128 | return; |
129 | svc_pollfd = new_svc_pollfd; |
130 | ++svc_max_pollfd; |
131 | |
132 | svc_pollfd[svc_max_pollfd - 1].fd = sock; |
133 | svc_pollfd[svc_max_pollfd - 1].events = (POLLIN | POLLPRI | |
134 | POLLRDNORM | POLLRDBAND); |
135 | } |
136 | } |
137 | libc_hidden_nolink_sunrpc (xprt_register, GLIBC_2_0) |
138 | |
139 | /* De-activate a transport handle. */ |
140 | void |
141 | xprt_unregister (SVCXPRT *xprt) |
142 | { |
143 | register int sock = xprt->xp_sock; |
144 | register int i; |
145 | |
146 | if ((sock < _rpc_dtablesize ()) && (xports[sock] == xprt)) |
147 | { |
148 | xports[sock] = (SVCXPRT *) 0; |
149 | |
150 | if (sock < FD_SETSIZE) |
151 | FD_CLR (sock, &svc_fdset); |
152 | |
153 | for (i = 0; i < svc_max_pollfd; ++i) |
154 | if (svc_pollfd[i].fd == sock) |
155 | svc_pollfd[i].fd = -1; |
156 | } |
157 | } |
158 | #ifdef EXPORT_RPC_SYMBOLS |
159 | libc_hidden_def (xprt_unregister) |
160 | #else |
161 | libc_hidden_nolink_sunrpc (xprt_unregister, GLIBC_2_0) |
162 | #endif |
163 | |
164 | |
165 | /* ********************** CALLOUT list related stuff ************* */ |
166 | |
167 | /* Search the callout list for a program number, return the callout |
168 | struct. */ |
169 | static struct svc_callout * |
170 | svc_find (rpcprog_t prog, rpcvers_t vers, struct svc_callout **prev) |
171 | { |
172 | register struct svc_callout *s, *p; |
173 | |
174 | p = NULL_SVC; |
175 | for (s = svc_head; s != NULL_SVC; s = s->sc_next) |
176 | { |
177 | if ((s->sc_prog == prog) && (s->sc_vers == vers)) |
178 | goto done; |
179 | p = s; |
180 | } |
181 | done: |
182 | *prev = p; |
183 | return s; |
184 | } |
185 | |
186 | /* Add a service program to the callout list. |
187 | The dispatch routine will be called when a rpc request for this |
188 | program number comes in. */ |
189 | bool_t |
190 | svc_register (SVCXPRT * xprt, rpcprog_t prog, rpcvers_t vers, |
191 | void (*dispatch) (struct svc_req *, SVCXPRT *), |
192 | rpcproc_t protocol) |
193 | { |
194 | struct svc_callout *prev; |
195 | register struct svc_callout *s; |
196 | |
197 | if ((s = svc_find (prog, vers, &prev)) != NULL_SVC) |
198 | { |
199 | if (s->sc_dispatch == dispatch) |
200 | goto pmap_it; /* he is registering another xptr */ |
201 | return FALSE; |
202 | } |
203 | s = (struct svc_callout *) mem_alloc (sizeof (struct svc_callout)); |
204 | if (s == (struct svc_callout *) 0) |
205 | return FALSE; |
206 | |
207 | s->sc_prog = prog; |
208 | s->sc_vers = vers; |
209 | s->sc_dispatch = dispatch; |
210 | s->sc_next = svc_head; |
211 | s->sc_mapped = FALSE; |
212 | svc_head = s; |
213 | |
214 | pmap_it: |
215 | /* now register the information with the local binder service */ |
216 | if (protocol) |
217 | { |
218 | if (! pmap_set (prog, vers, protocol, xprt->xp_port)) |
219 | return FALSE; |
220 | |
221 | s->sc_mapped = TRUE; |
222 | } |
223 | |
224 | return TRUE; |
225 | } |
226 | #ifdef EXPORT_RPC_SYMBOLS |
227 | libc_hidden_def (svc_register) |
228 | #else |
229 | libc_hidden_nolink_sunrpc (svc_register, GLIBC_2_0) |
230 | #endif |
231 | |
232 | /* Remove a service program from the callout list. */ |
233 | void |
234 | svc_unregister (rpcprog_t prog, rpcvers_t vers) |
235 | { |
236 | struct svc_callout *prev; |
237 | register struct svc_callout *s; |
238 | |
239 | if ((s = svc_find (prog, vers, &prev)) == NULL_SVC) |
240 | return; |
241 | bool is_mapped = s->sc_mapped; |
242 | |
243 | if (prev == NULL_SVC) |
244 | svc_head = s->sc_next; |
245 | else |
246 | prev->sc_next = s->sc_next; |
247 | |
248 | s->sc_next = NULL_SVC; |
249 | mem_free ((char *) s, (u_int) sizeof (struct svc_callout)); |
250 | /* now unregister the information with the local binder service */ |
251 | if (is_mapped) |
252 | pmap_unset (prog, vers); |
253 | } |
254 | libc_hidden_nolink_sunrpc (svc_unregister, GLIBC_2_0) |
255 | |
256 | /* ******************* REPLY GENERATION ROUTINES ************ */ |
257 | |
258 | /* Send a reply to an rpc request */ |
259 | bool_t |
260 | svc_sendreply (register SVCXPRT *xprt, xdrproc_t xdr_results, |
261 | caddr_t xdr_location) |
262 | { |
263 | struct rpc_msg rply; |
264 | |
265 | rply.rm_direction = REPLY; |
266 | rply.rm_reply.rp_stat = MSG_ACCEPTED; |
267 | rply.acpted_rply.ar_verf = xprt->xp_verf; |
268 | rply.acpted_rply.ar_stat = SUCCESS; |
269 | rply.acpted_rply.ar_results.where = xdr_location; |
270 | rply.acpted_rply.ar_results.proc = xdr_results; |
271 | return SVC_REPLY (xprt, &rply); |
272 | } |
273 | #ifdef EXPORT_RPC_SYMBOLS |
274 | libc_hidden_def (svc_sendreply) |
275 | #else |
276 | libc_hidden_nolink_sunrpc (svc_sendreply, GLIBC_2_0) |
277 | #endif |
278 | |
279 | /* No procedure error reply */ |
280 | void |
281 | svcerr_noproc (register SVCXPRT *xprt) |
282 | { |
283 | struct rpc_msg rply; |
284 | |
285 | rply.rm_direction = REPLY; |
286 | rply.rm_reply.rp_stat = MSG_ACCEPTED; |
287 | rply.acpted_rply.ar_verf = xprt->xp_verf; |
288 | rply.acpted_rply.ar_stat = PROC_UNAVAIL; |
289 | SVC_REPLY (xprt, &rply); |
290 | } |
291 | #ifdef EXPORT_RPC_SYMBOLS |
292 | libc_hidden_def (svcerr_noproc) |
293 | #else |
294 | libc_hidden_nolink_sunrpc (svcerr_noproc, GLIBC_2_0) |
295 | #endif |
296 | |
297 | /* Can't decode args error reply */ |
298 | void |
299 | svcerr_decode (register SVCXPRT *xprt) |
300 | { |
301 | struct rpc_msg rply; |
302 | |
303 | rply.rm_direction = REPLY; |
304 | rply.rm_reply.rp_stat = MSG_ACCEPTED; |
305 | rply.acpted_rply.ar_verf = xprt->xp_verf; |
306 | rply.acpted_rply.ar_stat = GARBAGE_ARGS; |
307 | SVC_REPLY (xprt, &rply); |
308 | } |
309 | #ifdef EXPORT_RPC_SYMBOLS |
310 | libc_hidden_def (svcerr_decode) |
311 | #else |
312 | libc_hidden_nolink_sunrpc (svcerr_decode, GLIBC_2_0) |
313 | #endif |
314 | |
315 | /* Some system error */ |
316 | void |
317 | svcerr_systemerr (register SVCXPRT *xprt) |
318 | { |
319 | struct rpc_msg rply; |
320 | |
321 | rply.rm_direction = REPLY; |
322 | rply.rm_reply.rp_stat = MSG_ACCEPTED; |
323 | rply.acpted_rply.ar_verf = xprt->xp_verf; |
324 | rply.acpted_rply.ar_stat = SYSTEM_ERR; |
325 | SVC_REPLY (xprt, &rply); |
326 | } |
327 | #ifdef EXPORT_RPC_SYMBOLS |
328 | libc_hidden_def (svcerr_systemerr) |
329 | #else |
330 | libc_hidden_nolink_sunrpc (svcerr_systemerr, GLIBC_2_0) |
331 | #endif |
332 | |
333 | /* Authentication error reply */ |
334 | void |
335 | svcerr_auth (SVCXPRT *xprt, enum auth_stat why) |
336 | { |
337 | struct rpc_msg rply; |
338 | |
339 | rply.rm_direction = REPLY; |
340 | rply.rm_reply.rp_stat = MSG_DENIED; |
341 | rply.rjcted_rply.rj_stat = AUTH_ERROR; |
342 | rply.rjcted_rply.rj_why = why; |
343 | SVC_REPLY (xprt, &rply); |
344 | } |
345 | libc_hidden_nolink_sunrpc (svcerr_auth, GLIBC_2_0) |
346 | |
347 | /* Auth too weak error reply */ |
348 | void |
349 | svcerr_weakauth (SVCXPRT *xprt) |
350 | { |
351 | svcerr_auth (xprt, AUTH_TOOWEAK); |
352 | } |
353 | libc_hidden_nolink_sunrpc (svcerr_weakauth, GLIBC_2_0) |
354 | |
355 | /* Program unavailable error reply */ |
356 | void |
357 | svcerr_noprog (register SVCXPRT *xprt) |
358 | { |
359 | struct rpc_msg rply; |
360 | |
361 | rply.rm_direction = REPLY; |
362 | rply.rm_reply.rp_stat = MSG_ACCEPTED; |
363 | rply.acpted_rply.ar_verf = xprt->xp_verf; |
364 | rply.acpted_rply.ar_stat = PROG_UNAVAIL; |
365 | SVC_REPLY (xprt, &rply); |
366 | } |
367 | libc_hidden_nolink_sunrpc (svcerr_noprog, GLIBC_2_0) |
368 | |
369 | /* Program version mismatch error reply */ |
370 | void |
371 | svcerr_progvers (register SVCXPRT *xprt, rpcvers_t low_vers, |
372 | rpcvers_t high_vers) |
373 | { |
374 | struct rpc_msg rply; |
375 | |
376 | rply.rm_direction = REPLY; |
377 | rply.rm_reply.rp_stat = MSG_ACCEPTED; |
378 | rply.acpted_rply.ar_verf = xprt->xp_verf; |
379 | rply.acpted_rply.ar_stat = PROG_MISMATCH; |
380 | rply.acpted_rply.ar_vers.low = low_vers; |
381 | rply.acpted_rply.ar_vers.high = high_vers; |
382 | SVC_REPLY (xprt, &rply); |
383 | } |
384 | libc_hidden_nolink_sunrpc (svcerr_progvers, GLIBC_2_0) |
385 | |
386 | /* ******************* SERVER INPUT STUFF ******************* */ |
387 | |
388 | /* |
389 | * Get server side input from some transport. |
390 | * |
391 | * Statement of authentication parameters management: |
392 | * This function owns and manages all authentication parameters, specifically |
393 | * the "raw" parameters (msg.rm_call.cb_cred and msg.rm_call.cb_verf) and |
394 | * the "cooked" credentials (rqst->rq_clntcred). |
395 | * However, this function does not know the structure of the cooked |
396 | * credentials, so it make the following assumptions: |
397 | * a) the structure is contiguous (no pointers), and |
398 | * b) the cred structure size does not exceed RQCRED_SIZE bytes. |
399 | * In all events, all three parameters are freed upon exit from this routine. |
400 | * The storage is trivially management on the call stack in user land, but |
401 | * is mallocated in kernel land. |
402 | */ |
403 | |
404 | void |
405 | svc_getreq (int rdfds) |
406 | { |
407 | fd_set readfds; |
408 | |
409 | FD_ZERO (&readfds); |
410 | readfds.fds_bits[0] = rdfds; |
411 | svc_getreqset (&readfds); |
412 | } |
413 | libc_hidden_nolink_sunrpc (svc_getreq, GLIBC_2_0) |
414 | |
415 | void |
416 | svc_getreqset (fd_set *readfds) |
417 | { |
418 | register fd_mask mask; |
419 | register fd_mask *maskp; |
420 | register int setsize; |
421 | register int sock; |
422 | register int bit; |
423 | |
424 | setsize = _rpc_dtablesize (); |
425 | if (setsize > FD_SETSIZE) |
426 | setsize = FD_SETSIZE; |
427 | maskp = readfds->fds_bits; |
428 | for (sock = 0; sock < setsize; sock += NFDBITS) |
429 | for (mask = *maskp++; (bit = ffsl (mask)); mask ^= (1L << (bit - 1))) |
430 | svc_getreq_common (sock + bit - 1); |
431 | } |
432 | libc_hidden_nolink_sunrpc (svc_getreqset, GLIBC_2_0) |
433 | |
434 | void |
435 | svc_getreq_poll (struct pollfd *pfdp, int pollretval) |
436 | { |
437 | if (pollretval == 0) |
438 | return; |
439 | |
440 | register int fds_found; |
441 | for (int i = fds_found = 0; i < svc_max_pollfd; ++i) |
442 | { |
443 | register struct pollfd *p = &pfdp[i]; |
444 | |
445 | if (p->fd != -1 && p->revents) |
446 | { |
447 | /* fd has input waiting */ |
448 | if (p->revents & POLLNVAL) |
449 | xprt_unregister (xports[p->fd]); |
450 | else |
451 | svc_getreq_common (p->fd); |
452 | |
453 | if (++fds_found >= pollretval) |
454 | break; |
455 | } |
456 | } |
457 | } |
458 | #ifdef EXPORT_RPC_SYMBOLS |
459 | libc_hidden_def (svc_getreq_poll) |
460 | #else |
461 | libc_hidden_nolink_sunrpc (svc_getreq_poll, GLIBC_2_2) |
462 | #endif |
463 | |
464 | |
465 | void |
466 | svc_getreq_common (const int fd) |
467 | { |
468 | enum xprt_stat stat; |
469 | struct rpc_msg msg; |
470 | register SVCXPRT *xprt; |
471 | char cred_area[2 * MAX_AUTH_BYTES + RQCRED_SIZE]; |
472 | msg.rm_call.cb_cred.oa_base = cred_area; |
473 | msg.rm_call.cb_verf.oa_base = &(cred_area[MAX_AUTH_BYTES]); |
474 | |
475 | xprt = xports[fd]; |
476 | /* Do we control fd? */ |
477 | if (xprt == NULL) |
478 | return; |
479 | |
480 | /* now receive msgs from xprtprt (support batch calls) */ |
481 | do |
482 | { |
483 | if (SVC_RECV (xprt, &msg)) |
484 | { |
485 | /* now find the exported program and call it */ |
486 | struct svc_callout *s; |
487 | struct svc_req r; |
488 | enum auth_stat why; |
489 | rpcvers_t low_vers; |
490 | rpcvers_t high_vers; |
491 | int prog_found; |
492 | |
493 | r.rq_clntcred = &(cred_area[2 * MAX_AUTH_BYTES]); |
494 | r.rq_xprt = xprt; |
495 | r.rq_prog = msg.rm_call.cb_prog; |
496 | r.rq_vers = msg.rm_call.cb_vers; |
497 | r.rq_proc = msg.rm_call.cb_proc; |
498 | r.rq_cred = msg.rm_call.cb_cred; |
499 | |
500 | /* first authenticate the message */ |
501 | /* Check for null flavor and bypass these calls if possible */ |
502 | |
503 | if (msg.rm_call.cb_cred.oa_flavor == AUTH_NULL) |
504 | { |
505 | r.rq_xprt->xp_verf.oa_flavor = _null_auth.oa_flavor; |
506 | r.rq_xprt->xp_verf.oa_length = 0; |
507 | } |
508 | else if ((why = _authenticate (&r, &msg)) != AUTH_OK) |
509 | { |
510 | svcerr_auth (xprt, why); |
511 | goto call_done; |
512 | } |
513 | |
514 | /* now match message with a registered service */ |
515 | prog_found = FALSE; |
516 | low_vers = 0 - 1; |
517 | high_vers = 0; |
518 | |
519 | for (s = svc_head; s != NULL_SVC; s = s->sc_next) |
520 | { |
521 | if (s->sc_prog == r.rq_prog) |
522 | { |
523 | if (s->sc_vers == r.rq_vers) |
524 | { |
525 | (*s->sc_dispatch) (&r, xprt); |
526 | goto call_done; |
527 | } |
528 | /* found correct version */ |
529 | prog_found = TRUE; |
530 | if (s->sc_vers < low_vers) |
531 | low_vers = s->sc_vers; |
532 | if (s->sc_vers > high_vers) |
533 | high_vers = s->sc_vers; |
534 | } |
535 | /* found correct program */ |
536 | } |
537 | /* if we got here, the program or version |
538 | is not served ... */ |
539 | if (prog_found) |
540 | svcerr_progvers (xprt, low_vers, high_vers); |
541 | else |
542 | svcerr_noprog (xprt); |
543 | /* Fall through to ... */ |
544 | } |
545 | call_done: |
546 | if ((stat = SVC_STAT (xprt)) == XPRT_DIED) |
547 | { |
548 | SVC_DESTROY (xprt); |
549 | break; |
550 | } |
551 | } |
552 | while (stat == XPRT_MOREREQS); |
553 | } |
554 | libc_hidden_nolink_sunrpc (svc_getreq_common, GLIBC_2_2) |
555 | |
556 | /* If there are no file descriptors available, then accept will fail. |
557 | We want to delay here so the connection request can be dequeued; |
558 | otherwise we can bounce between polling and accepting, never giving the |
559 | request a chance to dequeue and eating an enormous amount of cpu time |
560 | in svc_run if we're polling on many file descriptors. */ |
561 | void |
562 | __svc_accept_failed (void) |
563 | { |
564 | if (errno == EMFILE) |
565 | { |
566 | struct timespec ts = { .tv_sec = 0, .tv_nsec = 50000000 }; |
567 | __nanosleep (&ts, NULL); |
568 | } |
569 | } |
570 | |
571 | #ifdef _RPC_THREAD_SAFE_ |
572 | |
573 | void |
574 | __rpc_thread_svc_cleanup (void) |
575 | { |
576 | struct svc_callout *svcp; |
577 | |
578 | while ((svcp = svc_head) != NULL) |
579 | svc_unregister (svcp->sc_prog, svcp->sc_vers); |
580 | } |
581 | |
582 | #endif /* _RPC_THREAD_SAFE_ */ |
583 | |