]> git.saurik.com Git - apple/system_cmds.git/blame - lsmp.tproj/port_details.c
system_cmds-671.10.3.tar.gz
[apple/system_cmds.git] / lsmp.tproj / port_details.c
CommitLineData
45bc9d15 1/*
1a7e3f61 2 * Copyright (c) 2002-20014 Apple Inc. All rights reserved.
45bc9d15
A
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * The contents of this file constitute Original Code as defined in and
7 * are subject to the Apple Public Source License Version 1.1 (the
8 * "License"). You may not use this file except in compliance with the
9 * License. Please obtain a copy of the License at
10 * http://www.apple.com/publicsource and read it before using this file.
11 *
12 * This Original Code and all software distributed under the License are
13 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
14 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
15 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the
17 * License for the specific language governing rights and limitations
18 * under the License.
19 *
20 * @APPLE_LICENSE_HEADER_END@
21 */
1a7e3f61 22
45bc9d15 23#include <stdio.h>
1a7e3f61 24#include <unistd.h>
45bc9d15
A
25#include <stdlib.h>
26#include <libproc.h>
1a7e3f61
A
27#include <mach/mach.h>
28#include <mach/mach_voucher.h>
29#include "common.h"
45bc9d15 30
1a7e3f61 31const char * kobject_name(natural_t kotype)
45bc9d15
A
32{
33 switch (kotype) {
34 case IKOT_NONE: return "message-queue";
35 case IKOT_THREAD: return "THREAD";
36 case IKOT_TASK: return "TASK";
37 case IKOT_HOST: return "HOST";
38 case IKOT_HOST_PRIV: return "HOST-PRIV";
39 case IKOT_PROCESSOR: return "PROCESSOR";
40 case IKOT_PSET: return "PROCESSOR-SET";
41 case IKOT_PSET_NAME: return "PROCESSOR-SET-NAME";
42 case IKOT_TIMER: return "TIMER";
43 case IKOT_PAGING_REQUEST: return "PAGER-REQUEST";
44 case IKOT_MIG: return "MIG";
45 case IKOT_MEMORY_OBJECT: return "MEMORY-OBJECT";
46 case IKOT_XMM_PAGER: return "XMM-PAGER";
47 case IKOT_XMM_KERNEL: return "XMM-KERNEL";
48 case IKOT_XMM_REPLY: return "XMM-REPLY";
49 case IKOT_UND_REPLY: return "UND-REPLY";
50 case IKOT_HOST_NOTIFY: return "message-queue";
51 case IKOT_HOST_SECURITY: return "HOST-SECURITY";
52 case IKOT_LEDGER: return "LEDGER";
53 case IKOT_MASTER_DEVICE: return "MASTER-DEVICE";
54 case IKOT_TASK_NAME: return "TASK-NAME";
55 case IKOT_SUBSYSTEM: return "SUBSYSTEM";
56 case IKOT_IO_DONE_QUEUE: return "IO-QUEUE-DONE";
57 case IKOT_SEMAPHORE: return "SEMAPHORE";
58 case IKOT_LOCK_SET: return "LOCK-SET";
59 case IKOT_CLOCK: return "CLOCK";
60 case IKOT_CLOCK_CTRL: return "CLOCK-CONTROL";
61 case IKOT_IOKIT_SPARE: return "IOKIT-SPARE";
62 case IKOT_NAMED_ENTRY: return "NAMED-MEMORY";
63 case IKOT_IOKIT_CONNECT: return "IOKIT-CONNECT";
64 case IKOT_IOKIT_OBJECT: return "IOKIT-OBJECT";
65 case IKOT_UPL: return "UPL";
66 case IKOT_MEM_OBJ_CONTROL: return "XMM-CONTROL";
67 case IKOT_AU_SESSIONPORT: return "SESSIONPORT";
68 case IKOT_FILEPORT: return "FILEPORT";
69 case IKOT_LABELH: return "MACF-LABEL";
1a7e3f61
A
70 case IKOT_TASK_RESUME: return "TASK_RESUME";
71 case IKOT_VOUCHER: return "VOUCHER";
72 case IKOT_VOUCHER_ATTR_CONTROL: return "VOUCHER_ATTR_CONTROL";
45bc9d15
A
73 case IKOT_UNKNOWN:
74 default: return "UNKNOWN";
75 }
76}
77
1a7e3f61
A
78#define VOUCHER_DETAIL_PREFIX " "
79
80static const unsigned int voucher_contents_size = 8192;
81static uint8_t voucher_contents[voucher_contents_size];
82
83
ac27e6b4
A
84static uint32_t safesize (int len){
85 return (len > 0)? len : 0;
86}
87
88void show_recipe_detail(mach_voucher_attr_recipe_t recipe, char *voucher_outstr, uint32_t maxlen) {
89 uint32_t len = 0;
90 len += safesize(snprintf(&voucher_outstr[len], maxlen - len, VOUCHER_DETAIL_PREFIX "Key: %u, ", recipe->key));
91 len += safesize(snprintf(&voucher_outstr[len], maxlen - len, "Command: %u, ", recipe->command));
92 len += safesize(snprintf(&voucher_outstr[len], maxlen - len, "Previous voucher: 0x%x, ", recipe->previous_voucher));
93 len += safesize(snprintf(&voucher_outstr[len], maxlen - len, "Content size: %u\n", recipe->content_size));
1a7e3f61
A
94 switch (recipe->key) {
95 case MACH_VOUCHER_ATTR_KEY_ATM:
ac27e6b4 96 len += safesize(snprintf(&voucher_outstr[len], maxlen - len, VOUCHER_DETAIL_PREFIX "ATM ID: %llu\n", *(uint64_t *)(uintptr_t)recipe->content));
1a7e3f61
A
97 break;
98 case MACH_VOUCHER_ATTR_KEY_IMPORTANCE:
ac27e6b4 99 len += safesize(snprintf(&voucher_outstr[len], maxlen - len, VOUCHER_DETAIL_PREFIX "IMPORTANCE INFO: %s\n", (char *)recipe->content));
1a7e3f61
A
100 break;
101 case MACH_VOUCHER_ATTR_KEY_BANK:
ac27e6b4 102 len += safesize(snprintf(&voucher_outstr[len], maxlen - len, VOUCHER_DETAIL_PREFIX "RESOURCE ACCOUNTING INFO: %s\n", (char *)recipe->content));
1a7e3f61
A
103 break;
104 default:
ac27e6b4 105 print_hex_data(&voucher_outstr[len], maxlen - len, VOUCHER_DETAIL_PREFIX, "Recipe Contents", (void *)recipe->content, MIN(recipe->content_size, lsmp_config.voucher_detail_length));
1a7e3f61
A
106 break;
107 }
ac27e6b4 108
1a7e3f61
A
109}
110
ac27e6b4
A
111
112char * copy_voucher_detail(mach_port_t task, mach_port_name_t voucher) {
1a7e3f61
A
113 unsigned int recipe_size = voucher_contents_size;
114 kern_return_t kr = KERN_SUCCESS;
115 bzero((void *)&voucher_contents[0], sizeof(voucher_contents));
116 unsigned v_kobject = 0;
117 unsigned v_kotype = 0;
ac27e6b4
A
118 uint32_t detail_maxlen = VOUCHER_DETAIL_MAXLEN;
119 char * voucher_outstr = (char *)malloc(detail_maxlen);
120 voucher_outstr[0] = '\0';
121 uint32_t plen = 0;
122
1a7e3f61
A
123 kr = mach_port_kernel_object( task,
124 voucher,
125 &v_kotype, (unsigned *)&v_kobject);
126 if (kr == KERN_SUCCESS && v_kotype == IKOT_VOUCHER ) {
ac27e6b4 127
1a7e3f61
A
128 kr = mach_voucher_debug_info(task, voucher,
129 (mach_voucher_attr_raw_recipe_array_t)&voucher_contents[0],
130 &recipe_size);
131 if (kr != KERN_SUCCESS && kr != KERN_NOT_SUPPORTED) {
ac27e6b4
A
132 plen += safesize(snprintf(&voucher_outstr[plen], detail_maxlen - plen, VOUCHER_DETAIL_PREFIX "Voucher: 0x%x Failed to get contents %s\n", v_kobject, mach_error_string(kr)));
133 return voucher_outstr;
1a7e3f61 134 }
ac27e6b4 135
1a7e3f61 136 if (recipe_size == 0) {
ac27e6b4
A
137 plen += safesize(snprintf(&voucher_outstr[plen], detail_maxlen - plen, VOUCHER_DETAIL_PREFIX "Voucher: 0x%x has no contents\n", v_kobject));
138 return voucher_outstr;
1a7e3f61 139 }
ac27e6b4
A
140
141 plen += safesize(snprintf(&voucher_outstr[plen], detail_maxlen - plen, VOUCHER_DETAIL_PREFIX "Voucher: 0x%x\n", v_kobject));
1a7e3f61
A
142 unsigned int used_size = 0;
143 mach_voucher_attr_recipe_t recipe = NULL;
144 while (recipe_size > used_size) {
145 recipe = (mach_voucher_attr_recipe_t)&voucher_contents[used_size];
146 if (recipe->key) {
ac27e6b4 147 show_recipe_detail(recipe, &voucher_outstr[plen], detail_maxlen - plen);
1a7e3f61
A
148 }
149 used_size += sizeof(mach_voucher_attr_recipe_data_t) + recipe->content_size;
150 }
151 } else {
ac27e6b4 152 plen += safesize(snprintf(&voucher_outstr[plen], detail_maxlen - plen, VOUCHER_DETAIL_PREFIX "Invalid voucher: 0x%x\n", voucher));
45bc9d15 153 }
ac27e6b4
A
154
155 return voucher_outstr;
45bc9d15
A
156}
157
1a7e3f61
A
158void get_receive_port_context(task_t taskp, mach_port_name_t portname, mach_port_context_t *context) {
159 if (context == NULL) {
160 return;
161 }
162
163 kern_return_t ret;
164 ret = mach_port_get_context(taskp, portname, context);
165 if (ret != KERN_SUCCESS) {
166 fprintf(stderr, "mach_port_get_context(0x%08x) failed: %s\n",
167 portname,
168 mach_error_string(ret));
169 *context = (mach_port_context_t)0;
170 }
171 return;
172}
173
174int get_recieve_port_status(task_t taskp, mach_port_name_t portname, mach_port_info_ext_t *info){
45bc9d15
A
175 if (info == NULL) {
176 return -1;
177 }
178 mach_msg_type_number_t statusCnt;
179 kern_return_t ret;
180 statusCnt = MACH_PORT_INFO_EXT_COUNT;
181 ret = mach_port_get_attributes(taskp,
182 portname,
183 MACH_PORT_INFO_EXT,
184 (mach_port_info_t)info,
185 &statusCnt);
186 if (ret != KERN_SUCCESS) {
187 fprintf(stderr, "mach_port_get_attributes(0x%08x) failed: %s\n",
188 portname,
189 mach_error_string(ret));
190 return -1;
191 }
1a7e3f61 192
45bc9d15
A
193 return 0;
194}
195
1a7e3f61
A
196void show_task_mach_ports(my_per_task_info_t *taskinfo, uint32_t taskCount, my_per_task_info_t *allTaskInfos)
197{
198 int i, emptycount = 0, portsetcount = 0, sendcount = 0, receivecount = 0, sendoncecount = 0, deadcount = 0, dncount = 0, vouchercount = 0, pid;
45bc9d15
A
199 kern_return_t ret;
200 pid_for_task(taskinfo->task, &pid);
201
202 printf(" name ipc-object rights flags boost reqs recv send sonce oref qlimit msgcount context identifier type\n");
203 printf("--------- ---------- ---------- -------- ----- ---- ----- ----- ----- ---- ------ -------- ------------------ ----------- ------------\n");
204 for (i = 0; i < taskinfo->tableCount; i++) {
205 int j, k;
45bc9d15
A
206 boolean_t send = FALSE;
207 boolean_t sendonce = FALSE;
208 boolean_t dnreq = FALSE;
209 int sendrights = 0;
210 unsigned int kotype = 0;
211 vm_offset_t kobject = (vm_offset_t)0;
212
213 /* skip empty slots in the table */
214 if ((taskinfo->table[i].iin_type & MACH_PORT_TYPE_ALL_RIGHTS) == 0) {
215 emptycount++;
216 continue;
217 }
218
219
220 if (taskinfo->table[i].iin_type == MACH_PORT_TYPE_PORT_SET) {
221 mach_port_name_array_t members;
222 mach_msg_type_number_t membersCnt;
223
224 ret = mach_port_get_set_status(taskinfo->task,
225 taskinfo->table[i].iin_name,
226 &members, &membersCnt);
227 if (ret != KERN_SUCCESS) {
228 fprintf(stderr, "mach_port_get_set_status(0x%08x) failed: %s\n",
229 taskinfo->table[i].iin_name,
230 mach_error_string(ret));
231 continue;
232 }
1a7e3f61 233 printf("0x%08x 0x%08x port-set -------- --- 1 %d members\n",
45bc9d15
A
234 taskinfo->table[i].iin_name,
235 taskinfo->table[i].iin_object,
236 membersCnt);
237 /* get some info for each portset member */
238 for (j = 0; j < membersCnt; j++) {
239 for (k = 0; k < taskinfo->tableCount; k++) {
240 if (taskinfo->table[k].iin_name == members[j]) {
241 mach_port_info_ext_t info;
1a7e3f61
A
242 mach_port_status_t port_status;
243 mach_port_context_t port_context = (mach_port_context_t)0;
45bc9d15
A
244 if (0 != get_recieve_port_status(taskinfo->task, taskinfo->table[k].iin_name, &info)) {
245 bzero((void *)&info, sizeof(info));
246 }
1a7e3f61
A
247 port_status = info.mpie_status;
248 get_receive_port_context(taskinfo->task, taskinfo->table[k].iin_name, &port_context);
249 printf(" - 0x%08x %s --%s%s%s%s%s%s %5d %s%s%s %5d %5.0d %5.0d %s %6d %8d 0x%016llx 0x%08x (%d) %s\n",
45bc9d15
A
250 taskinfo->table[k].iin_object,
251 (taskinfo->table[k].iin_type & MACH_PORT_TYPE_SEND) ? "recv,send ":"recv ",
1a7e3f61
A
252 SHOW_PORT_STATUS_FLAGS(port_status.mps_flags),
253 info.mpie_boost_cnt,
45bc9d15
A
254 (taskinfo->table[k].iin_type & MACH_PORT_TYPE_DNREQUEST) ? "D" : "-",
255 (port_status.mps_nsrequest) ? "N" : "-",
256 (port_status.mps_pdrequest) ? "P" : "-",
257 1,
258 taskinfo->table[k].iin_urefs,
259 port_status.mps_sorights,
260 (port_status.mps_srights) ? "Y" : "N",
261 port_status.mps_qlimit,
262 port_status.mps_msgcount,
1a7e3f61 263 (uint64_t)port_context,
45bc9d15
A
264 taskinfo->table[k].iin_name,
265 pid,
266 taskinfo->processName);
267 break;
268 }
269 }
270 }
271
272 ret = vm_deallocate(mach_task_self(), (vm_address_t)members,
273 membersCnt * sizeof(mach_port_name_t));
274 if (ret != KERN_SUCCESS) {
275 fprintf(stderr, "vm_deallocate() failed: %s\n",
276 mach_error_string(ret));
277 exit(1);
278 }
279 portsetcount++;
280 continue;
281 }
282
283 if (taskinfo->table[i].iin_type & MACH_PORT_TYPE_SEND) {
284 send = TRUE;
285 sendrights = taskinfo->table[i].iin_urefs;
286 sendcount++;
287 }
288
289 if (taskinfo->table[i].iin_type & MACH_PORT_TYPE_DNREQUEST) {
290 dnreq = TRUE;
291 dncount++;
292 }
293
294 if (taskinfo->table[i].iin_type & MACH_PORT_TYPE_RECEIVE) {
295 mach_port_status_t status;
296 mach_port_info_ext_t info;
297 mach_port_context_t context = (mach_port_context_t)0;
298 ret = get_recieve_port_status(taskinfo->task, taskinfo->table[i].iin_name, &info);
1a7e3f61 299 get_receive_port_context(taskinfo->task, taskinfo->table[i].iin_name, &context);
45bc9d15
A
300 /* its ok to fail in fetching attributes */
301 if (ret < 0) {
302 continue;
303 }
1a7e3f61 304 status = info.mpie_status;
45bc9d15
A
305 printf("0x%08x 0x%08x %s --%s%s%s%s%s%s %5d %s%s%s %5d %5.0d %5.0d %s %6d %8d 0x%016llx \n",
306 taskinfo->table[i].iin_name,
307 taskinfo->table[i].iin_object,
308 (send) ? "recv,send ":"recv ",
309 SHOW_PORT_STATUS_FLAGS(status.mps_flags),
310 info.mpie_boost_cnt,
311 (dnreq) ? "D":"-",
312 (status.mps_nsrequest) ? "N":"-",
313 (status.mps_pdrequest) ? "P":"-",
314 1,
315 sendrights,
316 status.mps_sorights,
317 (status.mps_srights) ? "Y":"N",
318 status.mps_qlimit,
319 status.mps_msgcount,
320 (uint64_t)context);
321 receivecount++;
322
1a7e3f61 323
45bc9d15
A
324 /* show other rights (in this and other tasks) for the port */
325 for (j = 0; j < taskCount; j++) {
9726c137 326 for (k = 0; k < allTaskInfos[j].tableCount; k++) {
1a7e3f61
A
327 if (allTaskInfos[j].valid == FALSE ||
328 &allTaskInfos[j].table[k] == &taskinfo->table[i] ||
329 allTaskInfos[j].table[k].iin_object != taskinfo->table[i].iin_object)
45bc9d15
A
330 continue;
331
1a7e3f61
A
332 printf(" + %s -------- %s%s%s %5d <- 0x%08x (%d) %s\n",
333 (allTaskInfos[j].table[k].iin_type & MACH_PORT_TYPE_SEND_ONCE) ?
45bc9d15 334 "send-once " : "send ",
1a7e3f61 335 (allTaskInfos[j].table[k].iin_type & MACH_PORT_TYPE_DNREQUEST) ? "D" : "-",
45bc9d15
A
336 "-",
337 "-",
1a7e3f61
A
338 allTaskInfos[j].table[k].iin_urefs,
339 allTaskInfos[j].table[k].iin_name,
340 allTaskInfos[j].pid,
341 allTaskInfos[j].processName);
45bc9d15
A
342 }
343 }
344 continue;
345 }
346 else if (taskinfo->table[i].iin_type & MACH_PORT_TYPE_DEAD_NAME)
347 {
1a7e3f61 348 printf("0x%08x 0x%08x dead-name -------- --- %5d \n",
45bc9d15
A
349 taskinfo->table[i].iin_name,
350 taskinfo->table[i].iin_object,
351 taskinfo->table[i].iin_urefs);
352 deadcount++;
353 continue;
354 }
355
356 if (taskinfo->table[i].iin_type & MACH_PORT_TYPE_SEND_ONCE) {
357 sendonce = TRUE;
358 sendoncecount++;
359 }
360
1a7e3f61 361 printf("0x%08x 0x%08x %s -------- %s%s%s %5.0d ",
45bc9d15
A
362 taskinfo->table[i].iin_name,
363 taskinfo->table[i].iin_object,
364 (send) ? "send ":"send-once ",
365 (dnreq) ? "D":"-",
366 "-",
367 "-",
368 (send) ? sendrights : 0);
369
370 /* converting to kobjects is not always supported */
371 ret = mach_port_kernel_object(taskinfo->task,
372 taskinfo->table[i].iin_name,
373 &kotype, (unsigned *)&kobject);
374 if (ret == KERN_SUCCESS && kotype != 0) {
1a7e3f61
A
375 printf(" 0x%08x %s", (natural_t)kobject, kobject_name(kotype));
376 if ((kotype == IKOT_TASK_RESUME) || (kotype == IKOT_TASK) || (kotype == IKOT_TASK_NAME)) {
377 if (taskinfo->task_kobject == kobject) {
378 /* neat little optimization since in most cases tasks have themselves in their ipc space */
379 printf(" SELF (%d) %s", taskinfo->pid, taskinfo->processName);
380 } else {
381 my_per_task_info_t * _found_task = get_taskinfo_by_kobject((natural_t)kobject);
382 printf(" (%d) %s", _found_task->pid, _found_task->processName);
383 }
384 }
385
386 printf("\n");
387 if (kotype == IKOT_VOUCHER) {
388 vouchercount++;
389 if (lsmp_config.show_voucher_details) {
ac27e6b4
A
390 char * detail = copy_voucher_detail(taskinfo->task, taskinfo->table[i].iin_name);
391 printf("%s\n", detail);
392 free(detail);
1a7e3f61
A
393 }
394 }
45bc9d15
A
395 continue;
396 }
397
1a7e3f61
A
398 /* not kobject - find the receive right holder */
399 my_per_task_info_t *recv_holder_taskinfo;
400 mach_port_name_t recv_name = MACH_PORT_NULL;
401 if (KERN_SUCCESS == get_taskinfo_of_receiver_by_send_right(&taskinfo->table[i], &recv_holder_taskinfo, &recv_name)) {
402 mach_port_status_t port_status;
403 mach_port_info_ext_t info;
404 mach_port_context_t port_context = (mach_port_context_t)0;
405 if (0 != get_recieve_port_status(recv_holder_taskinfo->task, recv_name, &info)) {
406 bzero((void *)&port_status, sizeof(port_status));
407 }
408 port_status = info.mpie_status;
409 get_receive_port_context(recv_holder_taskinfo->task, recv_name, &port_context);
410 printf(" -> %6d %8d 0x%016llx 0x%08x (%d) %s\n",
411 port_status.mps_qlimit,
412 port_status.mps_msgcount,
413 (uint64_t)port_context,
414 recv_name,
415 recv_holder_taskinfo->pid,
416 recv_holder_taskinfo->processName);
417
418 } else
45bc9d15 419 printf(" 0x00000000 (-) Unknown Process\n");
1a7e3f61 420
45bc9d15
A
421 }
422 printf("total = %d\n", taskinfo->tableCount + taskinfo->treeCount - emptycount);
423 printf("SEND = %d\n", sendcount);
424 printf("RECEIVE = %d\n", receivecount);
425 printf("SEND_ONCE = %d\n", sendoncecount);
426 printf("PORT_SET = %d\n", portsetcount);
427 printf("DEAD_NAME = %d\n", deadcount);
428 printf("DNREQUEST = %d\n", dncount);
1a7e3f61
A
429 printf("VOUCHERS = %d\n", vouchercount);
430
45bc9d15
A
431}
432
ac27e6b4 433void print_hex_data(char *outstr, size_t maxlen, char *prefix, char *desc, void *addr, int len) {
1a7e3f61
A
434 int i;
435 unsigned char buff[17];
436 unsigned char *pc = addr;
ac27e6b4 437 uint32_t plen = 0;
45bc9d15 438
1a7e3f61 439 if (desc != NULL)
ac27e6b4 440 plen += safesize(snprintf(&outstr[len], maxlen - plen, "%s%s:\n", prefix, desc));
45bc9d15 441
1a7e3f61 442 for (i = 0; i < len; i++) {
45bc9d15 443
1a7e3f61
A
444 if ((i % 16) == 0) {
445 if (i != 0)
ac27e6b4 446 plen += safesize(snprintf(&outstr[len], maxlen - plen, " %s\n", buff));
1a7e3f61 447
ac27e6b4 448 plen += safesize(snprintf(&outstr[len], maxlen - plen, "%s %04x ", prefix, i));
1a7e3f61 449 }
45bc9d15 450
ac27e6b4 451 plen += safesize(snprintf(&outstr[len], maxlen - plen, " %02x", pc[i]));
45bc9d15 452
1a7e3f61
A
453 if ((pc[i] < 0x20) || (pc[i] > 0x7e))
454 buff[i % 16] = '.';
455 else
456 buff[i % 16] = pc[i];
457 buff[(i % 16) + 1] = '\0';
45bc9d15
A
458 }
459
1a7e3f61 460 while ((i % 16) != 0) {
ac27e6b4 461 plen += safesize(snprintf(&outstr[len], maxlen - plen, " "));
1a7e3f61 462 i++;
45bc9d15
A
463 }
464
ac27e6b4 465 plen += safesize(snprintf(&outstr[len], maxlen - plen, " %s\n", buff));
45bc9d15 466}