]> git.saurik.com Git - apple/libc.git/blob - mach/mach_msg.c
9af534c5c6e330c857ee9fbaf0636517df54d9d0
[apple/libc.git] / mach / mach_msg.c
1 /*
2 * Copyright (c) 1999-2002 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved.
7 *
8 * This file contains Original Code and/or Modifications of Original Code
9 * as defined in and that are subject to the Apple Public Source License
10 * Version 2.0 (the 'License'). You may not use this file except in
11 * compliance with the License. Please obtain a copy of the License at
12 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * file.
14 *
15 * The Original Code and all software distributed under the License are
16 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
17 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
18 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
20 * Please see the License for the specific language governing rights and
21 * limitations under the License.
22 *
23 * @APPLE_LICENSE_HEADER_END@
24 */
25 /*
26 * Mach Operating System
27 * Copyright (c) 1991,1990,1989 Carnegie Mellon University
28 * All Rights Reserved.
29 *
30 * Permission to use, copy, modify and distribute this software and its
31 * documentation is hereby granted, provided that both the copyright
32 * notice and this permission notice appear in all copies of the
33 * software, derivative works or modified versions, and any portions
34 * thereof, and that both notices appear in supporting documentation.
35 *
36 * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
37 * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
38 * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
39 *
40 * Carnegie Mellon requests users of this software to return to
41 *
42 * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU
43 * School of Computer Science
44 * Carnegie Mellon University
45 * Pittsburgh PA 15213-3890
46 *
47 * any improvements or extensions that they make and grant Carnegie Mellon
48 * the rights to redistribute these changes.
49 */
50
51 #include <stdlib.h>
52 #include <mach/mach.h>
53 #include <mach/boolean.h>
54 #include <mach/kern_return.h>
55 #include <mach/message.h>
56 #include <mach/mig_errors.h>
57 #include <mach/vm_statistics.h>
58
59 #define MACH_MSG_TRAP(msg, opt, ssize, rsize, rname, to, not) \
60 mach_msg_trap((msg), (opt), (ssize), (rsize), (rname), (to), (not))
61
62 #define LIBMACH_OPTIONS (MACH_SEND_INTERRUPT|MACH_RCV_INTERRUPT)
63
64 /*
65 * Routine: mach_msg
66 * Purpose:
67 * Send and/or receive a message. If the message operation
68 * is interrupted, and the user did not request an indication
69 * of that fact, then restart the appropriate parts of the
70 * operation.
71 */
72 mach_msg_return_t
73 mach_msg(msg, option, send_size, rcv_size, rcv_name, timeout, notify)
74 mach_msg_header_t *msg;
75 mach_msg_option_t option;
76 mach_msg_size_t send_size;
77 mach_msg_size_t rcv_size;
78 mach_port_t rcv_name;
79 mach_msg_timeout_t timeout;
80 mach_port_t notify;
81 {
82 mach_msg_return_t mr;
83
84 /*
85 * Consider the following cases:
86 * 1) Errors in pseudo-receive (eg, MACH_SEND_INTERRUPTED
87 * plus special bits).
88 * 2) Use of MACH_SEND_INTERRUPT/MACH_RCV_INTERRUPT options.
89 * 3) RPC calls with interruptions in one/both halves.
90 *
91 * We refrain from passing the option bits that we implement
92 * to the kernel. This prevents their presence from inhibiting
93 * the kernel's fast paths (when it checks the option value).
94 */
95
96 mr = MACH_MSG_TRAP(msg, option &~ LIBMACH_OPTIONS,
97 send_size, rcv_size, rcv_name,
98 timeout, notify);
99 if (mr == MACH_MSG_SUCCESS)
100 return MACH_MSG_SUCCESS;
101
102 if ((option & MACH_SEND_INTERRUPT) == 0)
103 while (mr == MACH_SEND_INTERRUPTED)
104 mr = MACH_MSG_TRAP(msg,
105 option &~ LIBMACH_OPTIONS,
106 send_size, rcv_size, rcv_name,
107 timeout, notify);
108
109 if ((option & MACH_RCV_INTERRUPT) == 0)
110 while (mr == MACH_RCV_INTERRUPTED)
111 mr = MACH_MSG_TRAP(msg,
112 option &~ (LIBMACH_OPTIONS|MACH_SEND_MSG),
113 0, rcv_size, rcv_name,
114 timeout, notify);
115
116 return mr;
117 }
118
119 /*
120 * Routine: mach_msg_overwrite
121 * Purpose:
122 * Send and/or receive a message. If the message operation
123 * is interrupted, and the user did not request an indication
124 * of that fact, then restart the appropriate parts of the
125 * operation.
126 *
127 * Distinct send and receive buffers may be specified. If
128 * no separate receive buffer is specified, the msg parameter
129 * will be used for both send and receive operations.
130 *
131 * In addition to a distinct receive buffer, that buffer may
132 * already contain scatter control information to direct the
133 * receiving of the message.
134 */
135 mach_msg_return_t
136 mach_msg_overwrite(msg, option, send_size, rcv_limit, rcv_name, timeout,
137 notify, rcv_msg, rcv_scatter_size)
138 mach_msg_header_t *msg;
139 mach_msg_option_t option;
140 mach_msg_size_t send_size;
141 mach_msg_size_t rcv_limit;
142 mach_port_t rcv_name;
143 mach_msg_timeout_t timeout;
144 mach_port_t notify;
145 mach_msg_header_t *rcv_msg;
146 mach_msg_size_t rcv_scatter_size;
147 {
148 mach_msg_return_t mr;
149
150 /*
151 * Consider the following cases:
152 * 1) Errors in pseudo-receive (eg, MACH_SEND_INTERRUPTED
153 * plus special bits).
154 * 2) Use of MACH_SEND_INTERRUPT/MACH_RCV_INTERRUPT options.
155 * 3) RPC calls with interruptions in one/both halves.
156 *
157 * We refrain from passing the option bits that we implement
158 * to the kernel. This prevents their presence from inhibiting
159 * the kernel's fast paths (when it checks the option value).
160 */
161
162 mr = mach_msg_overwrite_trap(msg, option &~ LIBMACH_OPTIONS,
163 send_size, rcv_limit, rcv_name,
164 timeout, notify, rcv_msg, rcv_scatter_size);
165 if (mr == MACH_MSG_SUCCESS)
166 return MACH_MSG_SUCCESS;
167
168 if ((option & MACH_SEND_INTERRUPT) == 0)
169 while (mr == MACH_SEND_INTERRUPTED)
170 mr = mach_msg_overwrite_trap(msg,
171 option &~ LIBMACH_OPTIONS,
172 send_size, rcv_limit, rcv_name,
173 timeout, notify, rcv_msg, rcv_scatter_size);
174
175 if ((option & MACH_RCV_INTERRUPT) == 0)
176 while (mr == MACH_RCV_INTERRUPTED)
177 mr = mach_msg_overwrite_trap(msg,
178 option &~ (LIBMACH_OPTIONS|MACH_SEND_MSG),
179 0, rcv_limit, rcv_name,
180 timeout, notify, rcv_msg, rcv_scatter_size);
181
182 return mr;
183 }
184
185
186 mach_msg_return_t
187 mach_msg_send(mach_msg_header_t *msg)
188 {
189 return mach_msg(msg, MACH_SEND_MSG,
190 msg->msgh_size, 0, MACH_PORT_NULL,
191 MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
192 }
193
194 mach_msg_return_t
195 mach_msg_receive(mach_msg_header_t *msg)
196 {
197 return mach_msg(msg, MACH_RCV_MSG,
198 0, msg->msgh_size, msg->msgh_local_port,
199 MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
200 }
201
202
203 static void
204 mach_msg_destroy_port(mach_port_t port, mach_msg_type_name_t type)
205 {
206 if (MACH_PORT_VALID(port)) switch (type) {
207 case MACH_MSG_TYPE_MOVE_SEND:
208 case MACH_MSG_TYPE_MOVE_SEND_ONCE:
209 /* destroy the send/send-once right */
210 (void) mach_port_deallocate(mach_task_self(), port);
211 break;
212
213 case MACH_MSG_TYPE_MOVE_RECEIVE:
214 /* destroy the receive right */
215 (void) mach_port_mod_refs(mach_task_self(), port,
216 MACH_PORT_RIGHT_RECEIVE, -1);
217 break;
218
219 case MACH_MSG_TYPE_MAKE_SEND:
220 /* create a send right and then destroy it */
221 (void) mach_port_insert_right(mach_task_self(), port,
222 port, MACH_MSG_TYPE_MAKE_SEND);
223 (void) mach_port_deallocate(mach_task_self(), port);
224 break;
225
226 case MACH_MSG_TYPE_MAKE_SEND_ONCE:
227 /* create a send-once right and then destroy it */
228 (void) mach_port_extract_right(mach_task_self(), port,
229 MACH_MSG_TYPE_MAKE_SEND_ONCE,
230 &port, &type);
231 (void) mach_port_deallocate(mach_task_self(), port);
232 break;
233 }
234 }
235
236 static void
237 mach_msg_destroy_memory(vm_offset_t addr, vm_size_t size)
238 {
239 if (size != 0)
240 (void) vm_deallocate(mach_task_self(), addr, size);
241 }
242
243
244 /*
245 * Routine: mach_msg_destroy
246 * Purpose:
247 * mach_msg_destroy is useful in two contexts.
248 *
249 * First, it can deallocate all port rights and
250 * out-of-line memory in a received message.
251 * When a server receives a request it doesn't want,
252 * it needs this functionality.
253 *
254 * Second, it can mimic the side-effects of a msg-send
255 * operation. The effect is as if the message were sent
256 * and then destroyed inside the kernel. When a server
257 * can't send a reply (because the client died),
258 * it needs this functionality.
259 */
260 void
261 mach_msg_destroy(mach_msg_header_t *msg)
262 {
263 mach_msg_bits_t mbits = msg->msgh_bits;
264
265 /*
266 * The msgh_local_port field doesn't hold a port right.
267 * The receive operation consumes the destination port right.
268 */
269
270 mach_msg_destroy_port(msg->msgh_remote_port, MACH_MSGH_BITS_REMOTE(mbits));
271
272 if (mbits & MACH_MSGH_BITS_COMPLEX) {
273 mach_msg_body_t *body;
274 mach_msg_descriptor_t *saddr, *eaddr;
275
276 body = (mach_msg_body_t *) (msg + 1);
277 saddr = (mach_msg_descriptor_t *)
278 ((mach_msg_base_t *) msg + 1);
279 eaddr = saddr + body->msgh_descriptor_count;
280
281 for ( ; saddr < eaddr; saddr++) {
282 switch (saddr->type.type) {
283
284 case MACH_MSG_PORT_DESCRIPTOR: {
285 mach_msg_port_descriptor_t *dsc;
286
287 /*
288 * Destroy port rights carried in the message
289 */
290 dsc = &saddr->port;
291 mach_msg_destroy_port(dsc->name, dsc->disposition);
292 break;
293 }
294
295 case MACH_MSG_OOL_DESCRIPTOR : {
296 mach_msg_ool_descriptor_t *dsc;
297
298 /*
299 * Destroy memory carried in the message
300 */
301 dsc = &saddr->out_of_line;
302 if (dsc->deallocate) {
303 mach_msg_destroy_memory((vm_offset_t)dsc->address,
304 dsc->size);
305 }
306 break;
307 }
308
309 case MACH_MSG_OOL_PORTS_DESCRIPTOR : {
310 mach_port_t *ports;
311 mach_msg_ool_ports_descriptor_t *dsc;
312 mach_msg_type_number_t j;
313
314 /*
315 * Destroy port rights carried in the message
316 */
317 dsc = &saddr->ool_ports;
318 ports = (mach_port_t *) dsc->address;
319 for (j = 0; j < dsc->count; j++, ports++) {
320 mach_msg_destroy_port(*ports, dsc->disposition);
321 }
322
323 /*
324 * Destroy memory carried in the message
325 */
326 if (dsc->deallocate) {
327 mach_msg_destroy_memory((vm_offset_t)dsc->address,
328 dsc->count * sizeof(mach_port_t));
329 }
330 break;
331 }
332 }
333 }
334 }
335 }
336
337 /*
338 * Routine: mach_msg_server_once
339 * Purpose:
340 * A simple generic server function. It allows more flexibility
341 * than mach_msg_server by processing only one message request
342 * and then returning to the user. Note that more in the way
343 * of error codes are returned to the user; specifically, any
344 * failing error from mach_msg calls will be returned
345 * (though errors from the demux routine or the routine it
346 * calls will not be).
347 */
348 mach_msg_return_t
349 mach_msg_server_once(
350 boolean_t (*demux)(mach_msg_header_t *, mach_msg_header_t *),
351 mach_msg_size_t max_size,
352 mach_port_t rcv_name,
353 mach_msg_options_t options)
354 {
355 mig_reply_error_t *bufRequest, *bufReply;
356 mach_msg_size_t request_size;
357 mach_msg_size_t request_alloc;
358 mach_msg_size_t trailer_alloc;
359 mach_msg_size_t reply_alloc;
360 mach_msg_return_t mr;
361 kern_return_t kr;
362 mach_port_t self = mach_task_self();
363
364 options &= ~(MACH_SEND_MSG|MACH_RCV_MSG);
365
366 trailer_alloc = REQUESTED_TRAILER_SIZE(options);
367 request_alloc = round_page(max_size + trailer_alloc);
368
369 request_size = (options & MACH_RCV_LARGE) ?
370 request_alloc : max_size + trailer_alloc;
371
372 reply_alloc = round_page((options & MACH_SEND_TRAILER) ?
373 (max_size + MAX_TRAILER_SIZE) :
374 max_size);
375
376 kr = vm_allocate(self,
377 (vm_address_t *)&bufReply,
378 reply_alloc,
379 VM_MAKE_TAG(VM_MEMORY_MACH_MSG)|TRUE);
380 if (kr != KERN_SUCCESS)
381 return kr;
382
383 for (;;) {
384 mach_msg_size_t new_request_alloc;
385
386 kr = vm_allocate(self,
387 (vm_address_t *)&bufRequest,
388 request_alloc,
389 VM_MAKE_TAG(VM_MEMORY_MACH_MSG)|TRUE);
390 if (kr != KERN_SUCCESS) {
391 vm_deallocate(self,
392 (vm_address_t)bufReply,
393 reply_alloc);
394 return kr;
395 }
396
397 mr = mach_msg(&bufRequest->Head, MACH_RCV_MSG|options,
398 0, request_size, rcv_name,
399 MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
400
401 if (!((mr == MACH_RCV_TOO_LARGE) && (options & MACH_RCV_LARGE)))
402 break;
403
404 new_request_alloc = round_page(bufRequest->Head.msgh_size +
405 trailer_alloc);
406 vm_deallocate(self,
407 (vm_address_t) bufRequest,
408 request_alloc);
409 request_size = request_alloc = new_request_alloc;
410 }
411
412 if (mr == MACH_MSG_SUCCESS) {
413 /* we have a request message */
414
415 (void) (*demux)(&bufRequest->Head, &bufReply->Head);
416
417 if (!(bufReply->Head.msgh_bits & MACH_MSGH_BITS_COMPLEX)) {
418 if (bufReply->RetCode == MIG_NO_REPLY)
419 bufReply->Head.msgh_remote_port = MACH_PORT_NULL;
420 else if ((bufReply->RetCode != KERN_SUCCESS) &&
421 (bufRequest->Head.msgh_bits & MACH_MSGH_BITS_COMPLEX)) {
422 /* destroy the request - but not the reply port */
423 bufRequest->Head.msgh_remote_port = MACH_PORT_NULL;
424 mach_msg_destroy(&bufRequest->Head);
425 }
426 }
427
428 /*
429 * We don't want to block indefinitely because the client
430 * isn't receiving messages from the reply port.
431 * If we have a send-once right for the reply port, then
432 * this isn't a concern because the send won't block.
433 * If we have a send right, we need to use MACH_SEND_TIMEOUT.
434 * To avoid falling off the kernel's fast RPC path unnecessarily,
435 * we only supply MACH_SEND_TIMEOUT when absolutely necessary.
436 */
437 if (bufReply->Head.msgh_remote_port != MACH_PORT_NULL) {
438
439 mr = mach_msg(&bufReply->Head,
440 (MACH_MSGH_BITS_REMOTE(bufReply->Head.msgh_bits) ==
441 MACH_MSG_TYPE_MOVE_SEND_ONCE) ?
442 MACH_SEND_MSG|options :
443 MACH_SEND_MSG|MACH_SEND_TIMEOUT|options,
444 bufReply->Head.msgh_size, 0, MACH_PORT_NULL,
445 MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
446
447 if ((mr != MACH_SEND_INVALID_DEST) &&
448 (mr != MACH_SEND_TIMED_OUT))
449 goto done_once;
450 mr = MACH_MSG_SUCCESS;
451 }
452 if (bufReply->Head.msgh_bits & MACH_MSGH_BITS_COMPLEX)
453 mach_msg_destroy(&bufReply->Head);
454 }
455
456 done_once:
457 (void)vm_deallocate(self,
458 (vm_address_t) bufRequest,
459 request_alloc);
460 (void)vm_deallocate(self,
461 (vm_address_t) bufReply,
462 reply_alloc);
463 return mr;
464 }
465
466 /*
467 * Routine: mach_msg_server
468 * Purpose:
469 * A simple generic server function. Note that changes here
470 * should be considered for duplication above.
471 */
472 mach_msg_return_t
473 mach_msg_server(
474 boolean_t (*demux)(mach_msg_header_t *, mach_msg_header_t *),
475 mach_msg_size_t max_size,
476 mach_port_t rcv_name,
477 mach_msg_options_t options)
478 {
479 mig_reply_error_t *bufRequest, *bufReply;
480 mach_msg_size_t request_size;
481 mach_msg_size_t new_request_alloc;
482 mach_msg_size_t request_alloc;
483 mach_msg_size_t trailer_alloc;
484 mach_msg_size_t reply_alloc;
485 mach_msg_return_t mr;
486 kern_return_t kr;
487 mach_port_t self = mach_task_self();
488
489 options &= ~(MACH_SEND_MSG|MACH_RCV_MSG|MACH_RCV_OVERWRITE);
490
491 reply_alloc = round_page((options & MACH_SEND_TRAILER) ?
492 (max_size + MAX_TRAILER_SIZE) : max_size);
493
494 kr = vm_allocate(self,
495 (vm_address_t *)&bufReply,
496 reply_alloc,
497 VM_MAKE_TAG(VM_MEMORY_MACH_MSG)|TRUE);
498 if (kr != KERN_SUCCESS)
499 return kr;
500
501 request_alloc = 0;
502 trailer_alloc = REQUESTED_TRAILER_SIZE(options);
503 new_request_alloc = round_page(max_size + trailer_alloc);
504
505 request_size = (options & MACH_RCV_LARGE) ?
506 new_request_alloc : max_size + trailer_alloc;
507
508 for (;;) {
509 if (request_alloc < new_request_alloc) {
510 request_alloc = new_request_alloc;
511 kr = vm_allocate(self,
512 (vm_address_t *)&bufRequest,
513 request_alloc,
514 VM_MAKE_TAG(VM_MEMORY_MACH_MSG)|TRUE);
515 if (kr != KERN_SUCCESS) {
516 vm_deallocate(self,
517 (vm_address_t)bufReply,
518 reply_alloc);
519 return kr;
520 }
521 }
522
523 mr = mach_msg(&bufRequest->Head, MACH_RCV_MSG|options,
524 0, request_size, rcv_name,
525 MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
526
527 while (mr == MACH_MSG_SUCCESS) {
528 /* we have another request message */
529
530 (void) (*demux)(&bufRequest->Head, &bufReply->Head);
531
532 if (!(bufReply->Head.msgh_bits & MACH_MSGH_BITS_COMPLEX)) {
533 if (bufReply->RetCode == MIG_NO_REPLY)
534 bufReply->Head.msgh_remote_port = MACH_PORT_NULL;
535 else if ((bufReply->RetCode != KERN_SUCCESS) &&
536 (bufRequest->Head.msgh_bits & MACH_MSGH_BITS_COMPLEX)) {
537 /* destroy the request - but not the reply port */
538 bufRequest->Head.msgh_remote_port = MACH_PORT_NULL;
539 mach_msg_destroy(&bufRequest->Head);
540 }
541 }
542
543 /*
544 * We don't want to block indefinitely because the client
545 * isn't receiving messages from the reply port.
546 * If we have a send-once right for the reply port, then
547 * this isn't a concern because the send won't block.
548 * If we have a send right, we need to use MACH_SEND_TIMEOUT.
549 * To avoid falling off the kernel's fast RPC path,
550 * we only supply MACH_SEND_TIMEOUT when absolutely necessary.
551 */
552 if (bufReply->Head.msgh_remote_port != MACH_PORT_NULL) {
553 if (request_alloc == reply_alloc) {
554 mig_reply_error_t *bufTemp;
555
556 mr = mach_msg(
557 &bufReply->Head,
558 (MACH_MSGH_BITS_REMOTE(bufReply->Head.msgh_bits) ==
559 MACH_MSG_TYPE_MOVE_SEND_ONCE) ?
560 MACH_SEND_MSG|MACH_RCV_MSG|options :
561 MACH_SEND_MSG|MACH_RCV_MSG|MACH_SEND_TIMEOUT|options,
562 bufReply->Head.msgh_size, request_size, rcv_name,
563 MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
564
565 /* swap request and reply */
566 bufTemp = bufRequest;
567 bufRequest = bufReply;
568 bufReply = bufTemp;
569
570 } else {
571 mr = mach_msg_overwrite(
572 &bufReply->Head,
573 (MACH_MSGH_BITS_REMOTE(bufReply->Head.msgh_bits) ==
574 MACH_MSG_TYPE_MOVE_SEND_ONCE) ?
575 MACH_SEND_MSG|MACH_RCV_MSG|options :
576 MACH_SEND_MSG|MACH_RCV_MSG|MACH_SEND_TIMEOUT|options,
577 bufReply->Head.msgh_size, request_size, rcv_name,
578 MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL,
579 &bufRequest->Head, 0);
580 }
581
582 if ((mr != MACH_SEND_INVALID_DEST) &&
583 (mr != MACH_SEND_TIMED_OUT))
584 continue;
585 }
586 if (bufReply->Head.msgh_bits & MACH_MSGH_BITS_COMPLEX)
587 mach_msg_destroy(&bufReply->Head);
588
589 mr = mach_msg(&bufRequest->Head, MACH_RCV_MSG|options,
590 0, request_size, rcv_name,
591 MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
592
593 } /* while (mr == MACH_MSG_SUCCESS) */
594
595 if ((mr == MACH_RCV_TOO_LARGE) && (options & MACH_RCV_LARGE)) {
596 new_request_alloc = round_page(bufRequest->Head.msgh_size +
597 trailer_alloc);
598 request_size = new_request_alloc;
599 vm_deallocate(self,
600 (vm_address_t) bufRequest,
601 request_alloc);
602 continue;
603 }
604
605 break;
606
607 } /* for(;;) */
608
609 (void)vm_deallocate(self,
610 (vm_address_t) bufRequest,
611 request_alloc);
612 (void)vm_deallocate(self,
613 (vm_address_t) bufReply,
614 reply_alloc);
615 return mr;
616 }