1 #include <rpc/rpc.h>
2 #include <arpa/inet.h>
3 #include <rpc/rpc_router_ioctl.h>
4 #include <debug.h>
5 #include <pthread.h>
6 #include <sys/select.h>
7
8 #include <stdarg.h>
9 #include <stdio.h>
10 #include <string.h>
11 #include <errno.h>
12
13 struct CLIENT {
14 xdr_s_type *xdr;
15 struct CLIENT *next;
16 /* common attribute struct for setting up recursive mutexes */
17 pthread_mutexattr_t lock_attr;
18
19 /* We insist that there is only one outstanding call for a client at any
20 time, and we use this mutex to enforce the rule. When we start
21 supporting multiple outstanding RPCs on a client, we will have to
22 maintain a queue of them, and match incoming replies (by the XID of the
23 incoming packet). For now, we just block until we get that reply.
24 */
25 pthread_mutex_t lock;
26
27 pthread_mutex_t wait_reply_lock;
28 pthread_cond_t wait_reply;
29
30 pthread_mutex_t input_xdr_lock;
31 pthread_cond_t input_xdr_wait;
32 volatile int input_xdr_busy;
33
34 pthread_mutex_t wait_cb_lock;
35 pthread_cond_t wait_cb;
36 pthread_t cb_thread;
37 volatile int got_cb;
38 volatile int cb_stop;
39 };
40
41 extern void* svc_find(void *xprt, rpcprog_t prog, rpcvers_t vers);
42 extern void svc_dispatch(void *svc, void *xprt);
43 extern int r_open();
44 extern void r_close();
45 extern xdr_s_type *xdr_init_common(const char *name, int is_client);
46 extern xdr_s_type *xdr_clone(xdr_s_type *);
47 extern void xdr_destroy_common(xdr_s_type *xdr);
48 extern bool_t xdr_recv_reply_header (xdr_s_type *xdr, rpc_reply_header *reply);
49 extern void *the_xprt;
50
51
52 static pthread_mutex_t rx_mutex = PTHREAD_MUTEX_INITIALIZER;
53 static pthread_t rx_thread;
54 static volatile unsigned int num_clients;
55 static volatile fd_set rx_fdset;
56 static volatile int max_rxfd;
57 static volatile struct CLIENT *clients;
58
59 /* There's one of these for each RPC client which has received an RPC call. */
cb_context(void * __u)60 static void *cb_context(void *__u)
61 {
62 CLIENT *client = (CLIENT *)__u;
63 D("RPC-callback thread for %08x:%08x starting.\n",
64 (client->xdr->x_prog | 0x01000000),
65 client->xdr->x_vers);
66 pthread_mutex_lock(&client->wait_cb_lock);
67 while (client->cb_stop == 0) {
68 if (!client->got_cb)
69 pthread_cond_wait(&client->wait_cb,
70 &client->wait_cb_lock);
71 /* We tell the thread it's time to exit by setting cb_stop to nonzero
72 and signalling the conditional variable. When there's no data, we
73 skip to the top of the loop and exit.
74 */
75 if (!client->got_cb) {
76 D("RPC-callback thread for %08x:%08x: signalled but no data.\n",
77 (client->xdr->x_prog | 0x01000000),
78 client->xdr->x_vers);
79 continue;
80 }
81 client->got_cb = 0;
82
83 /* We dispatch the message to the server representing the callback
84 * client.
85 */
86 if (the_xprt) {
87 void *svc;
88 rpcprog_t prog =
89 ntohl(((uint32 *)(client->xdr->in_msg))[RPC_OFFSET+3]);
90 rpcvers_t vers =
91 ntohl(((uint32 *)(client->xdr->in_msg))[RPC_OFFSET+4]);
92
93 svc = svc_find(the_xprt, prog, vers);
94 if (svc) {
95 XDR **svc_xdr = (XDR **)svc;
96 D("%08x:%08x dispatching RPC call (XID %d, xdr %p) for "
97 "callback client %08x:%08x.\n",
98 client->xdr->x_prog,
99 client->xdr->x_vers,
100 ntohl(((uint32 *)(client->xdr->in_msg))[RPC_OFFSET]),
101 client->xdr,
102 (uint32_t)prog, (int)vers);
103 /* We transplant the xdr of the client into the entry
104 representing the callback client in the list of servers.
105 Note that since we hold the wait_cb_lock for this client,
106 if another call for this callback client arrives before
107 we've finished processing this call, that will block until
108 we're done with this one. If this happens, it would be
109 most likely a bug in the arm9 rpc router.
110 */
111 if (*svc_xdr) {
112 D("%08x:%08x expecting XDR == NULL"
113 "callback client %08x:%08x!\n",
114 client->xdr->x_prog,
115 client->xdr->x_vers,
116 (uint32_t)prog, (int)vers);
117 xdr_destroy_common(*svc_xdr);
118 }
119
120 D("%08x:%08x cloning XDR for "
121 "callback client %08x:%08x.\n",
122 client->xdr->x_prog,
123 client->xdr->x_vers,
124 (uint32_t)prog, (int)vers);
125 *svc_xdr = xdr_clone(client->xdr);
126
127 (*svc_xdr)->x_prog = prog;
128 (*svc_xdr)->x_vers = vers;
129 memcpy((*svc_xdr)->in_msg,
130 client->xdr->in_msg, client->xdr->in_len);
131 memcpy((*svc_xdr)->out_msg,
132 client->xdr->out_msg, client->xdr->out_next);
133 (*svc_xdr)->in_len = client->xdr->in_len;
134 (*svc_xdr)->out_next = client->xdr->out_next;
135
136 pthread_mutex_lock(&client->input_xdr_lock);
137 D("%08x:%08x marking input buffer as free.\n",
138 client->xdr->x_prog, client->xdr->x_vers);
139 client->input_xdr_busy = 0;
140 pthread_cond_signal(&client->input_xdr_wait);
141 pthread_mutex_unlock(&client->input_xdr_lock);
142
143 svc_dispatch(svc, the_xprt);
144 xdr_destroy_common(*svc_xdr);
145 *svc_xdr = NULL;
146 }
147 else E("%08x:%08x call packet arrived, but there's no "
148 "RPC server registered for %08x:%08x.\n",
149 client->xdr->x_prog,
150 client->xdr->x_vers,
151 (uint32_t)prog, (int)vers);
152 }
153 else E("%08x:%08x call packet arrived, but there's "
154 "no RPC transport!\n",
155 client->xdr->x_prog,
156 client->xdr->x_vers);
157 }
158 pthread_mutex_unlock(&client->wait_cb_lock);
159
160
161 D("RPC-callback thread for %08x:%08x terminating.\n",
162 (client->xdr->x_prog | 0x01000000),
163 client->xdr->x_vers);
164 return NULL;
165 }
166
rx_context(void * __u)167 static void *rx_context(void *__u __attribute__((unused)))
168 {
169 int n;
170 struct timeval tv;
171 fd_set rfds;
172 while(num_clients) {
173 pthread_mutex_lock(&rx_mutex);
174 rfds = rx_fdset;
175 pthread_mutex_unlock(&rx_mutex);
176 tv.tv_sec = 0; tv.tv_usec = 500 * 1000;
177 n = select(max_rxfd + 1, (fd_set *)&rfds, NULL, NULL, &tv);
178 if (n < 0) {
179 E("select() error %s (%d)\n", strerror(errno), errno);
180 continue;
181 }
182
183 if (n) {
184 pthread_mutex_lock(&rx_mutex); /* sync access to the client list */
185 CLIENT *client = (CLIENT *)clients;
186 for (; client; client = client->next) {
187 if (FD_ISSET(client->xdr->fd, &rfds)) {
188
189 /* We need to make sure that the XDR's in_buf is not in
190 use before we read into it. The in_buf may be in use
191 in a race between processing an incoming call and
192 receiving a reply to an outstanding call, or processing
193 an incoming reply and receiving a call.
194 */
195
196 pthread_mutex_lock(&client->input_xdr_lock);
197 while (client->input_xdr_busy) {
198 D("%08x:%08x waiting for XDR input buffer "
199 "to be consumed.\n",
200 client->xdr->x_prog, client->xdr->x_vers);
201 pthread_cond_wait(
202 &client->input_xdr_wait,
203 &client->input_xdr_lock);
204 }
205 D("%08x:%08x reading data.\n",
206 client->xdr->x_prog, client->xdr->x_vers);
207 client->xdr->xops->read(client->xdr);
208 client->input_xdr_busy = 1;
209 pthread_mutex_unlock(&client->input_xdr_lock);
210
211 if (((uint32 *)(client->xdr->in_msg))[RPC_OFFSET+1] ==
212 htonl(RPC_MSG_REPLY)) {
213 /* Wake up the RPC client to receive its data. */
214 D("%08x:%08x received REPLY (XID %d), "
215 "grabbing mutex to wake up client.\n",
216 client->xdr->x_prog,
217 client->xdr->x_vers,
218 ntohl(((uint32 *)client->xdr->in_msg)[RPC_OFFSET]));
219 pthread_mutex_lock(&client->wait_reply_lock);
220 D("%08x:%08x got mutex, waking up client.\n",
221 client->xdr->x_prog,
222 client->xdr->x_vers);
223 pthread_cond_signal(&client->wait_reply);
224 pthread_mutex_unlock(&client->wait_reply_lock);
225 }
226 else {
227 pthread_mutex_lock(&client->wait_cb_lock);
228 D("%08x:%08x received CALL.\n",
229 client->xdr->x_prog,
230 client->xdr->x_vers);
231 client->got_cb = 1;
232 if (client->cb_stop < 0) {
233 D("%08x:%08x starting callback thread.\n",
234 client->xdr->x_prog,
235 client->xdr->x_vers);
236 client->cb_stop = 0;
237 pthread_create(&client->cb_thread,
238 NULL,
239 cb_context, client);
240 }
241 D("%08x:%08x waking up callback thread.\n",
242 client->xdr->x_prog,
243 client->xdr->x_vers);
244 pthread_cond_signal(&client->wait_cb);
245 pthread_mutex_unlock(&client->wait_cb_lock);
246 }
247 }
248 }
249 pthread_mutex_unlock(&rx_mutex);
250 }
251 else {
252 V("rx thread timeout (%d clients):\n", num_clients);
253 #if 0
254 {
255 CLIENT *trav = (CLIENT *)clients;
256 for(; trav; trav = trav->next) {
257 if (trav->xdr)
258 V("\t%08x:%08x fd %02d\n",
259 trav->xdr->x_prog,
260 trav->xdr->x_vers,
261 trav->xdr->fd);
262 else V("\t(unknown)\n");
263 }
264 }
265 #endif
266 }
267 }
268 D("RPC-client RX thread exiting!\n");
269 return NULL;
270 }
271
272 enum clnt_stat
clnt_call(CLIENT * client,u_long proc,xdrproc_t xdr_args,caddr_t args_ptr,xdrproc_t xdr_results,caddr_t rets_ptr,struct timeval timeout)273 clnt_call(
274 CLIENT * client,
275 u_long proc,
276 xdrproc_t xdr_args,
277 caddr_t args_ptr,
278 xdrproc_t xdr_results,
279 caddr_t rets_ptr,
280 struct timeval timeout)
281 {
282 opaque_auth cred;
283 opaque_auth verf;
284 rpc_reply_header reply_header;
285 enum clnt_stat ret = RPC_SUCCESS;
286
287 xdr_s_type *xdr = client->xdr;
288
289 pthread_mutex_lock(&client->lock);
290
291
292 cred.oa_flavor = AUTH_NONE;
293 cred.oa_length = 0;
294 verf.oa_flavor = AUTH_NONE;
295 verf.oa_length = 0;
296
297 xdr->x_op = XDR_ENCODE;
298
299 /* Send message header */
300
301 if (!xdr_call_msg_start (xdr, xdr->x_prog, xdr->x_vers,
302 proc, &cred, &verf)) {
303 XDR_MSG_ABORT (xdr);
304 ret = RPC_CANTENCODEARGS;
305 E("%08x:%08x error in xdr_call_msg_start()\n",
306 client->xdr->x_prog,
307 client->xdr->x_vers);
308 goto out;
309 }
310
311 /* Send arguments */
312
313 if (!xdr_args (xdr, args_ptr)) {
314 XDR_MSG_ABORT(xdr);
315 ret = RPC_CANTENCODEARGS;
316 E("%08x:%08x error in xdr_args()\n",
317 client->xdr->x_prog,
318 client->xdr->x_vers);
319 goto out;
320 }
321
322 /* Finish message - blocking */
323 pthread_mutex_lock(&client->wait_reply_lock);
324 D("%08x:%08x sending call (XID %d).\n",
325 client->xdr->x_prog, client->xdr->x_vers, client->xdr->xid);
326 if (!XDR_MSG_SEND(xdr)) {
327 ret = RPC_CANTSEND;
328 E("error in XDR_MSG_SEND\n");
329 goto out_unlock;
330 }
331
332 D("%08x:%08x waiting for reply.\n",
333 client->xdr->x_prog, client->xdr->x_vers);
334 pthread_cond_wait(&client->wait_reply, &client->wait_reply_lock);
335 D("%08x:%08x received reply.\n", client->xdr->x_prog, client->xdr->x_vers);
336
337 if (((uint32 *)xdr->out_msg)[RPC_OFFSET] !=
338 ((uint32 *)xdr->in_msg)[RPC_OFFSET]) {
339 E("%08x:%08x XID mismatch: got %d, expecting %d.\n",
340 client->xdr->x_prog, client->xdr->x_vers,
341 ntohl(((uint32 *)xdr->in_msg)[RPC_OFFSET]),
342 ntohl(((uint32 *)xdr->out_msg)[RPC_OFFSET]));
343 ret = RPC_CANTRECV;
344 goto out_unlock;
345 }
346
347 D("%08x:%08x decoding reply header.\n",
348 client->xdr->x_prog, client->xdr->x_vers);
349 if (!xdr_recv_reply_header (client->xdr, &reply_header)) {
350 E("%08x:%08x error reading reply header.\n",
351 client->xdr->x_prog, client->xdr->x_vers);
352 ret = RPC_CANTRECV;
353 goto out_unlock;
354 }
355
356 /* Check that other side accepted and responded */
357 if (reply_header.stat != RPC_MSG_ACCEPTED) {
358 /* Offset to map returned error into clnt_stat */
359 ret = reply_header.u.dr.stat + RPC_VERSMISMATCH;
360 E("%08x:%08x call was not accepted.\n",
361 (uint32_t)client->xdr->x_prog, client->xdr->x_vers);
362 goto out_unlock;
363 } else if (reply_header.u.ar.stat != RPC_ACCEPT_SUCCESS) {
364 /* Offset to map returned error into clnt_stat */
365 ret = reply_header.u.ar.stat + RPC_AUTHERROR;
366 E("%08x:%08x call failed with an authentication error.\n",
367 (uint32_t)client->xdr->x_prog, client->xdr->x_vers);
368 goto out_unlock;
369 }
370
371 xdr->x_op = XDR_DECODE;
372 /* Decode results */
373 if (!xdr_results(xdr, rets_ptr) || ! XDR_MSG_DONE(xdr)) {
374 ret = RPC_CANTDECODERES;
375 E("%08x:%08x error decoding results.\n",
376 client->xdr->x_prog, client->xdr->x_vers);
377 goto out_unlock;
378 }
379
380 D("%08x:%08x call success.\n",
381 client->xdr->x_prog, client->xdr->x_vers);
382
383 out_unlock:
384 pthread_mutex_lock(&client->input_xdr_lock);
385 D("%08x:%08x marking input buffer as free.\n",
386 client->xdr->x_prog, client->xdr->x_vers);
387 client->input_xdr_busy = 0;
388 pthread_cond_signal(&client->input_xdr_wait);
389 pthread_mutex_unlock(&client->input_xdr_lock);
390
391 pthread_mutex_unlock(&client->wait_reply_lock);
392 out:
393 pthread_mutex_unlock(&client->lock);
394 return ret;
395 } /* clnt_call */
396
xdr_recv_auth(xdr_s_type * xdr,opaque_auth * auth)397 bool_t xdr_recv_auth (xdr_s_type *xdr, opaque_auth *auth)
398 {
399 switch(sizeof(auth->oa_flavor)) {
400 case 1:
401 if(!XDR_RECV_INT8(xdr, (int8_t *)&(auth->oa_flavor))) return FALSE;
402 break;
403 case 2:
404 if(!XDR_RECV_INT16(xdr, (int16_t *)&(auth->oa_flavor))) return FALSE;
405 break;
406 case 4:
407 if(!XDR_RECV_INT32(xdr, (int32_t *)&(auth->oa_flavor))) return FALSE;
408 break;
409 }
410 if (!XDR_RECV_UINT (xdr, (unsigned *)&(auth->oa_length))) {
411 return FALSE;
412 }
413
414 if (auth->oa_length != 0) {
415 /* We throw away the auth stuff--it's always the default. */
416 auth->oa_base = NULL;
417 if (!XDR_RECV_BYTES (xdr, NULL, auth->oa_length))
418 return FALSE;
419 else
420 return FALSE;
421 }
422
423 return TRUE;
424 } /* xdr_recv_auth */
425
426 static bool_t
xdr_recv_accepted_reply_header(xdr_s_type * xdr,struct rpc_accepted_reply_header * accreply)427 xdr_recv_accepted_reply_header(xdr_s_type *xdr,
428 struct rpc_accepted_reply_header *accreply)
429 {
430 if (!xdr_recv_auth(xdr, &accreply->verf)) {
431 return FALSE;
432 }
433
434 if (!XDR_RECV_ENUM(xdr, &accreply->stat)) {
435 return FALSE;
436 }
437
438 switch ((*accreply).stat) {
439 case RPC_PROG_MISMATCH:
440 if (!XDR_RECV_UINT32(xdr, &accreply->u.versions.low)) {
441 return FALSE;
442 }
443
444 if (!XDR_RECV_UINT32(xdr, &accreply->u.versions.high)) {
445 return FALSE;
446 }
447 break;
448
449 case RPC_ACCEPT_SUCCESS:
450 case RPC_PROG_UNAVAIL:
451 case RPC_PROC_UNAVAIL:
452 case RPC_GARBAGE_ARGS:
453 case RPC_SYSTEM_ERR:
454 case RPC_PROG_LOCKED:
455 // case ignored
456 break;
457
458 default:
459 return FALSE;
460 }
461
462 return TRUE;
463 } /* xdr_recv_accepted_reply_header */
464
xdr_recv_denied_reply(xdr_s_type * xdr,struct rpc_denied_reply * rejreply)465 static bool_t xdr_recv_denied_reply(xdr_s_type *xdr,
466 struct rpc_denied_reply *rejreply)
467 {
468 if (!XDR_RECV_ENUM (xdr, &rejreply->stat))
469 return FALSE;
470
471 switch ((*rejreply).stat) {
472 case RPC_MISMATCH:
473 if (!XDR_RECV_UINT32(xdr, &rejreply->u.versions.low))
474 return FALSE;
475 if (!XDR_RECV_UINT32(xdr, &rejreply->u.versions.high))
476 return FALSE;
477 break;
478 case RPC_AUTH_ERROR:
479 if (!XDR_RECV_ENUM (xdr, &rejreply->u.why))
480 return FALSE;
481 break;
482 default:
483 return FALSE;
484 }
485
486 return TRUE;
487 } /* xdr_recv_denied_reply */
488
xdr_recv_reply_header(xdr_s_type * xdr,rpc_reply_header * reply)489 bool_t xdr_recv_reply_header (xdr_s_type *xdr, rpc_reply_header *reply)
490 {
491 if (!XDR_RECV_ENUM(xdr, &reply->stat)) {
492 return FALSE;
493 }
494
495 switch ((*reply).stat) {
496 case RPC_MSG_ACCEPTED:
497 if (!xdr_recv_accepted_reply_header(xdr, &reply->u.ar)) {
498 return FALSE;
499 }
500 break;
501 case RPC_MSG_DENIED:
502 if (!xdr_recv_denied_reply(xdr, &reply->u.dr)) {
503 return FALSE;
504 }
505 break;
506 default:
507 return FALSE;
508 }
509
510 return TRUE;
511 } /* xdr_recv_reply_header */
512
clnt_create(char * host,uint32 prog,uint32 vers,char * proto)513 CLIENT *clnt_create(
514 char * host,
515 uint32 prog,
516 uint32 vers,
517 char * proto)
518 {
519 CLIENT *client = calloc(1, sizeof(CLIENT));
520 if (client) {
521 char name[256];
522
523 /* for versions like 0x00010001, only compare against major version */
524 if ((vers & 0xFFF00000) == 0)
525 vers &= 0xFFFF0000;
526
527 pthread_mutex_lock(&rx_mutex);
528
529 snprintf(name, sizeof(name), "/dev/oncrpc/%08x:%08x",
530 (uint32_t)prog, (int)vers);
531 client->xdr = xdr_init_common(name, 1 /* client XDR */);
532 if (!client->xdr) {
533 E("failed to initialize client (permissions?)!\n");
534 free(client);
535 pthread_mutex_unlock(&rx_mutex);
536 return NULL;
537 }
538 client->xdr->x_prog = prog;
539 client->xdr->x_vers = vers;
540 client->cb_stop = -1; /* callback thread has not been started */
541
542 if (!num_clients) {
543 FD_ZERO(&rx_fdset);
544 max_rxfd = 0;
545 }
546
547 FD_SET(client->xdr->fd, &rx_fdset);
548 if (max_rxfd < client->xdr->fd)
549 max_rxfd = client->xdr->fd;
550 client->next = (CLIENT *)clients;
551 clients = client;
552 if (!num_clients++) {
553 D("launching RX thread.\n");
554 pthread_create(&rx_thread, NULL, rx_context, NULL);
555 }
556
557 pthread_mutexattr_init(&client->lock_attr);
558 // pthread_mutexattr_settype(&client->lock_attr, PTHREAD_MUTEX_RECURSIVE);
559 pthread_mutex_init(&client->lock, &client->lock_attr);
560 pthread_mutex_init(&client->wait_reply_lock, &client->lock_attr);
561 pthread_cond_init(&client->wait_reply, NULL);
562 pthread_mutex_init(&client->wait_cb_lock, &client->lock_attr);
563 pthread_cond_init(&client->wait_cb, NULL);
564 pthread_mutex_init(&client->input_xdr_lock, &client->lock_attr);
565 pthread_cond_init(&client->input_xdr_wait, NULL);
566
567 pthread_mutex_unlock(&rx_mutex);
568 }
569
570 return client;
571 }
572
clnt_destroy(CLIENT * client)573 void clnt_destroy(CLIENT *client) {
574 if (client) {
575 pthread_mutex_lock(&client->lock);
576 D("%08x:%08x destroying client\n",
577 client->xdr->x_prog,
578 client->xdr->x_vers);
579
580
581 if (!client->cb_stop) {
582 /* The callback thread is running, we need to stop it */
583 client->cb_stop = 1;
584 D("%08x:%08x stopping callback thread\n",
585 client->xdr->x_prog,
586 client->xdr->x_vers);
587 pthread_mutex_lock(&client->wait_cb_lock);
588 pthread_cond_signal(&client->wait_cb);
589 pthread_mutex_unlock(&client->wait_cb_lock);
590 D("%08x:%08x joining callback thread\n",
591 client->xdr->x_prog,
592 client->xdr->x_vers);
593 pthread_join(client->cb_thread, NULL);
594 }
595
596 pthread_mutex_lock(&rx_mutex); /* sync access to the client list */
597 {
598 CLIENT *trav = (CLIENT *)clients, *prev = NULL;
599 for(; trav; trav = trav->next) {
600 if (trav == client) {
601 D("%08x:%08x removing from client list\n",
602 client->xdr->x_prog,
603 client->xdr->x_vers);
604 if (prev)
605 prev->next = trav->next;
606 else
607 clients = trav->next;
608 num_clients--;
609 FD_CLR(client->xdr->fd, &rx_fdset);
610 break;
611 }
612 prev = trav;
613 }
614 }
615 if (!num_clients) {
616 D("stopping rx thread!\n");
617 pthread_join(rx_thread, NULL);
618 D("stopped rx thread\n");
619 }
620 pthread_mutex_unlock(&rx_mutex); /* sync access to the client list */
621
622 pthread_mutex_destroy(&client->input_xdr_lock);
623 pthread_cond_destroy(&client->input_xdr_wait);
624
625 pthread_mutex_destroy(&client->wait_reply_lock);
626 pthread_cond_destroy(&client->wait_reply);
627 xdr_destroy_common(client->xdr);
628
629 // FIXME: what happens when we lock the client while destroying it,
630 // and another thread locks the mutex in clnt_call, and then we
631 // call pthread_mutex_destroy? Does destroy automatically unlock and
632 // then cause the lock in clnt_call() to return an error? When we
633 // unlock the mutex here there can be a context switch to the other
634 // thread, which will cause it to obtain the mutex on the destroyed
635 // client (and probably crash), and then we get to the destroy call
636 // here... will that call fail?
637 pthread_mutex_unlock(&client->lock);
638 pthread_mutex_destroy(&client->lock);
639 pthread_mutexattr_destroy(&client->lock_attr);
640 D("client destroy done\n");
641 free(client);
642 }
643 }
644