| OLD | NEW |
| 1 /* | 1 /* |
| 2 * Copyright (c) 2012 The Native Client Authors. All rights reserved. | 2 * Copyright (c) 2012 The Native Client Authors. All rights reserved. |
| 3 * Use of this source code is governed by a BSD-style license that can be | 3 * Use of this source code is governed by a BSD-style license that can be |
| 4 * found in the LICENSE file. | 4 * found in the LICENSE file. |
| 5 */ | 5 */ |
| 6 | 6 |
| 7 #include <string.h> | 7 #include <string.h> |
| 8 | 8 |
| 9 /* | 9 /* |
| 10 * NaCl Simple/secure ELF loader (NaCl SEL). | 10 * NaCl Simple/secure ELF loader (NaCl SEL). |
| (...skipping 1242 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1253 if (NULL != nap->secure_service) { | 1253 if (NULL != nap->secure_service) { |
| 1254 for (;;) { | 1254 for (;;) { |
| 1255 struct nacl_abi_timespec req; | 1255 struct nacl_abi_timespec req; |
| 1256 req.tv_sec = 1000; | 1256 req.tv_sec = 1000; |
| 1257 req.tv_nsec = 0; | 1257 req.tv_nsec = 0; |
| 1258 NaClNanosleep(&req, (struct nacl_abi_timespec *) NULL); | 1258 NaClNanosleep(&req, (struct nacl_abi_timespec *) NULL); |
| 1259 } | 1259 } |
| 1260 } | 1260 } |
| 1261 } | 1261 } |
| 1262 | 1262 |
| 1263 | |
| 1264 /* | |
| 1265 * Secure command channels. | |
| 1266 */ | |
| 1267 | |
| 1268 struct NaClSecureService { | |
| 1269 struct NaClSimpleService base; | |
| 1270 struct NaClApp *nap; | |
| 1271 }; | |
| 1272 | |
| 1273 struct NaClSimpleServiceVtbl const kNaClSecureServiceVtbl; | |
| 1274 | |
| 1275 | |
| 1276 struct NaClConnectionHandler { | |
| 1277 struct NaClConnectionHandler *next; | |
| 1278 | |
| 1279 /* used by NaClSimpleRevServiceClient's ClientCallback fn */ | |
| 1280 void (*handler)( | |
| 1281 void *state, | |
| 1282 struct NaClThreadInterface *tif, | |
| 1283 struct NaClDesc *conn); | |
| 1284 void *state; | |
| 1285 }; | |
| 1286 | |
| 1287 struct NaClSecureReverseClient { | |
| 1288 struct NaClSimpleRevClient base; | |
| 1289 struct NaClApp *nap; | |
| 1290 | |
| 1291 struct NaClMutex mu; | |
| 1292 | |
| 1293 struct NaClConnectionHandler *queue_head; | |
| 1294 struct NaClConnectionHandler **queue_insert; | |
| 1295 }; | |
| 1296 | |
| 1297 struct NaClSimpleRevClientVtbl const kNaClSecureReverseClientVtbl; | |
| 1298 | |
| 1299 | |
| 1300 int NaClSecureServiceCtor(struct NaClSecureService *self, | |
| 1301 struct NaClSrpcHandlerDesc const *srpc_handlers, | |
| 1302 struct NaClApp *nap) { | |
| 1303 NaClLog(4, | |
| 1304 "Entered NaClSecureServiceCtor: self 0x%"NACL_PRIxPTR"\n", | |
| 1305 (uintptr_t) self); | |
| 1306 if (NACL_FI_ERROR_COND( | |
| 1307 "NaClSecureServiceCtor__NaClSimpleServiceWithSocketCtor", | |
| 1308 !NaClSimpleServiceWithSocketCtor( | |
| 1309 &self->base, | |
| 1310 srpc_handlers, | |
| 1311 NaClThreadInterfaceThreadFactory, | |
| 1312 (void *) NULL, | |
| 1313 nap->service_port, | |
| 1314 nap->service_address))) { | |
| 1315 goto failure_simple_ctor; | |
| 1316 } | |
| 1317 self->nap = nap; | |
| 1318 | |
| 1319 NACL_VTBL(NaClRefCount, self) = | |
| 1320 (struct NaClRefCountVtbl *) &kNaClSecureServiceVtbl; | |
| 1321 return 1; | |
| 1322 failure_simple_ctor: | |
| 1323 return 0; | |
| 1324 } | |
| 1325 | |
| 1326 void NaClSecureServiceDtor(struct NaClRefCount *vself) { | |
| 1327 struct NaClSecureService *self = (struct NaClSecureService *) vself; | |
| 1328 | |
| 1329 NACL_VTBL(NaClRefCount, self) = (struct NaClRefCountVtbl const *) | |
| 1330 &kNaClSimpleServiceVtbl; | |
| 1331 (*NACL_VTBL(NaClRefCount, self)->Dtor)(vself); | |
| 1332 } | |
| 1333 | |
| 1334 /* | 1263 /* |
| 1335 * The first connection is performed by this callback handler. This | 1264 * The first connection is performed by this callback handler. This |
| 1336 * spawns a client thread that will bootstrap the other connections by | 1265 * spawns a client thread that will bootstrap the other connections by |
| 1337 * stashing the connection represented by |conn| to make reverse RPCs | 1266 * stashing the connection represented by |conn| to make reverse RPCs |
| 1338 * to ask the peer to connect to us. No thread is spawned; we just | 1267 * to ask the peer to connect to us. No thread is spawned; we just |
| 1339 * wrap access to the connection with a lock. | 1268 * wrap access to the connection with a lock. |
| 1340 * | 1269 * |
| 1341 * Subsequent connection callbacks will pass the connection to the | 1270 * Subsequent connection callbacks will pass the connection to the |
| 1342 * actual thread that made the connection request using |conn| | 1271 * actual thread that made the connection request using |conn| |
| 1343 * received in the first connection. | 1272 * received in the first connection. |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1383 NaClLog(LOG_FATAL, "Reverse quota interface Ctor failed\n"); | 1312 NaClLog(LOG_FATAL, "Reverse quota interface Ctor failed\n"); |
| 1384 } | 1313 } |
| 1385 nap->reverse_channel_initialization_state = NACL_REVERSE_CHANNEL_INITIALIZED; | 1314 nap->reverse_channel_initialization_state = NACL_REVERSE_CHANNEL_INITIALIZED; |
| 1386 | 1315 |
| 1387 NaClXCondVarBroadcast(&nap->cv); | 1316 NaClXCondVarBroadcast(&nap->cv); |
| 1388 NaClXMutexUnlock(&nap->mu); | 1317 NaClXMutexUnlock(&nap->mu); |
| 1389 | 1318 |
| 1390 NaClLog(4, "Leaving NaClSecureReverseClientCallback\n"); | 1319 NaClLog(4, "Leaving NaClSecureReverseClientCallback\n"); |
| 1391 } | 1320 } |
| 1392 | 1321 |
| 1393 /* fwd */ | |
| 1394 int NaClSecureReverseClientCtor( | |
| 1395 struct NaClSecureReverseClient *self, | |
| 1396 void (*client_callback)( | |
| 1397 void *, struct NaClThreadInterface *, struct NaClDesc *), | |
| 1398 void *state, | |
| 1399 struct NaClApp *nap); | |
| 1400 | |
| 1401 static void NaClSecureReverseClientSetup(struct NaClSrpcRpc *rpc, | 1322 static void NaClSecureReverseClientSetup(struct NaClSrpcRpc *rpc, |
| 1402 struct NaClSrpcArg **in_args, | 1323 struct NaClSrpcArg **in_args, |
| 1403 struct NaClSrpcArg **out_args, | 1324 struct NaClSrpcArg **out_args, |
| 1404 struct NaClSrpcClosure *done) { | 1325 struct NaClSrpcClosure *done) { |
| 1405 struct NaClApp *nap = | 1326 struct NaClApp *nap = |
| 1406 (struct NaClApp *) rpc->channel->server_instance_data; | 1327 (struct NaClApp *) rpc->channel->server_instance_data; |
| 1407 struct NaClSecureReverseClient *rev; | 1328 struct NaClSecureReverseClient *rev; |
| 1408 | 1329 |
| 1409 UNREFERENCED_PARAMETER(in_args); | 1330 UNREFERENCED_PARAMETER(in_args); |
| 1410 NaClLog(4, "Entered NaClSecureReverseClientSetup\n"); | 1331 NaClLog(4, "Entered NaClSecureReverseClientSetup\n"); |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1459 if (!NaClSimpleRevClientStartServiceThread(&rev->base)) { | 1380 if (!NaClSimpleRevClientStartServiceThread(&rev->base)) { |
| 1460 NaClLog(LOG_FATAL, "Could not start reverse service thread\n"); | 1381 NaClLog(LOG_FATAL, "Could not start reverse service thread\n"); |
| 1461 } | 1382 } |
| 1462 | 1383 |
| 1463 done: | 1384 done: |
| 1464 NaClXMutexUnlock(&nap->mu); | 1385 NaClXMutexUnlock(&nap->mu); |
| 1465 (*done->Run)(done); | 1386 (*done->Run)(done); |
| 1466 NaClLog(4, "Leaving NaClSecureReverseClientSetup\n"); | 1387 NaClLog(4, "Leaving NaClSecureReverseClientSetup\n"); |
| 1467 } | 1388 } |
| 1468 | 1389 |
| 1469 /* | |
| 1470 * Only called at startup and thereafter by the reverse secure | |
| 1471 * channel, with |self| locked. | |
| 1472 */ | |
| 1473 static | |
| 1474 int NaClSecureReverseClientInsertHandler_mu( | |
| 1475 struct NaClSecureReverseClient *self, | |
| 1476 void (*h)(void *, | |
| 1477 struct NaClThreadInterface *, | |
| 1478 struct NaClDesc *), | |
| 1479 void *d) { | |
| 1480 struct NaClConnectionHandler *entry; | |
| 1481 | |
| 1482 NaClLog(4, | |
| 1483 ("NaClSecureReverseClientInsertHandler_mu: h 0x%"NACL_PRIxPTR"," | |
| 1484 " d 0x%"NACL_PRIxPTR"\n"), | |
| 1485 (uintptr_t) h, | |
| 1486 (uintptr_t) d); | |
| 1487 entry = (struct NaClConnectionHandler *) malloc(sizeof *entry); | |
| 1488 if (NULL == entry) { | |
| 1489 return 0; | |
| 1490 } | |
| 1491 entry->handler = h; | |
| 1492 entry->state = d; | |
| 1493 entry->next = (struct NaClConnectionHandler *) NULL; | |
| 1494 *self->queue_insert = entry; | |
| 1495 self->queue_insert = &entry->next; | |
| 1496 | |
| 1497 return 1; | |
| 1498 } | |
| 1499 | |
| 1500 /* | |
| 1501 * Caller must set up handler before issuing connection request RPC on | |
| 1502 * nap->reverse_channel, since otherwise the connection handler queue | |
| 1503 * may be empty and the connect code would abort. Because the connect | |
| 1504 * doesn't wait for a handler, we don't need a condvar. | |
| 1505 * | |
| 1506 * We do not need to serialize on the handlers, since the | |
| 1507 * RPC-server/IMC-client implementation should not distinguish one | |
| 1508 * connection from another: it is okay for two handlers to be | |
| 1509 * inserted, and two connection request RPCs to be preformed | |
| 1510 * (sequentially, since they are over a single channel), and have the | |
| 1511 * server side spawn threads that asynchronously connect twice, in the | |
| 1512 * "incorrect" order, etc. | |
| 1513 */ | |
| 1514 int NaClSecureReverseClientInsertHandler( | |
| 1515 struct NaClSecureReverseClient *self, | |
| 1516 void (*handler)( | |
| 1517 void *handlr_state, | |
| 1518 struct NaClThreadInterface *thread_if, | |
| 1519 struct NaClDesc *new_conn), | |
| 1520 void *handler_state) { | |
| 1521 int retval; | |
| 1522 | |
| 1523 NaClXMutexLock(&self->mu); | |
| 1524 retval = NaClSecureReverseClientInsertHandler_mu(self, | |
| 1525 handler, handler_state); | |
| 1526 NaClXMutexUnlock(&self->mu); | |
| 1527 return retval; | |
| 1528 } | |
| 1529 | |
| 1530 static | |
| 1531 struct NaClConnectionHandler *NaClSecureReverseClientPopHandler( | |
| 1532 struct NaClSecureReverseClient *self) { | |
| 1533 struct NaClConnectionHandler *head; | |
| 1534 | |
| 1535 NaClLog(4, "Entered NaClSecureReverseClientPopHandler, acquiring lock\n"); | |
| 1536 NaClXMutexLock(&self->mu); | |
| 1537 NaClLog(4, "NaClSecureReverseClientPopHandler, got lock\n"); | |
| 1538 head = self->queue_head; | |
| 1539 if (NULL == head) { | |
| 1540 NaClLog(LOG_FATAL, | |
| 1541 "NaClSecureReverseClientPopHandler: empty handler queue\n"); | |
| 1542 } | |
| 1543 if (NULL == (self->queue_head = head->next)) { | |
| 1544 NaClLog(4, "NaClSecureReverseClientPopHandler, last elt patch up\n"); | |
| 1545 self->queue_insert = &self->queue_head; | |
| 1546 } | |
| 1547 NaClLog(4, "NaClSecureReverseClientPopHandler, unlocking\n"); | |
| 1548 NaClXMutexUnlock(&self->mu); | |
| 1549 | |
| 1550 head->next = NULL; | |
| 1551 NaClLog(4, | |
| 1552 ("Leaving NaClSecureReverseClientPopHandler:" | |
| 1553 " returning %"NACL_PRIxPTR"\n"), | |
| 1554 (uintptr_t) head); | |
| 1555 return head; | |
| 1556 } | |
| 1557 | |
| 1558 static | |
| 1559 void NaClSecureReverseClientInternalCallback( | |
| 1560 void *state, | |
| 1561 struct NaClThreadInterface *tif, | |
| 1562 struct NaClDesc *conn) { | |
| 1563 struct NaClSecureReverseClient *self = | |
| 1564 (struct NaClSecureReverseClient *) state; | |
| 1565 struct NaClConnectionHandler *hand_ptr; | |
| 1566 | |
| 1567 UNREFERENCED_PARAMETER(tif); | |
| 1568 NaClLog(4, "Entered NaClSecureReverseClientInternalCallback\n"); | |
| 1569 hand_ptr = NaClSecureReverseClientPopHandler(self); | |
| 1570 NaClLog(4, " got callback object %"NACL_PRIxPTR"\n", (uintptr_t) hand_ptr); | |
| 1571 NaClLog(4, | |
| 1572 " callback:0x%"NACL_PRIxPTR"(0x%"NACL_PRIxPTR",0x%"NACL_PRIxPTR")\n", | |
| 1573 (uintptr_t) hand_ptr->handler, | |
| 1574 (uintptr_t) hand_ptr->state, | |
| 1575 (uintptr_t) conn); | |
| 1576 (*hand_ptr->handler)(hand_ptr->state, tif, conn); | |
| 1577 NaClLog(4, "NaClSecureReverseClientInternalCallback: freeing memory\n"); | |
| 1578 free(hand_ptr); | |
| 1579 NaClLog(4, "Leaving NaClSecureReverseClientInternalCallback\n"); | |
| 1580 } | |
| 1581 | |
| 1582 /* | |
| 1583 * Require an initial connection handler in the Ctor, so that it's | |
| 1584 * obvious that a reverse client needs to accept an IMC connection | |
| 1585 * from the server to get things bootstrapped. | |
| 1586 */ | |
| 1587 int NaClSecureReverseClientCtor( | |
| 1588 struct NaClSecureReverseClient *self, | |
| 1589 void (*client_callback)( | |
| 1590 void *, struct NaClThreadInterface*, struct NaClDesc *), | |
| 1591 void *state, | |
| 1592 struct NaClApp *nap) { | |
| 1593 NaClLog(4, | |
| 1594 ("Entered NaClSecureReverseClientCtor, self 0x%"NACL_PRIxPTR"," | |
| 1595 " nap 0x%"NACL_PRIxPTR"\n"), | |
| 1596 (uintptr_t) self, | |
| 1597 (uintptr_t) nap); | |
| 1598 if (!NaClSimpleRevClientCtor(&self->base, | |
| 1599 NaClSecureReverseClientInternalCallback, | |
| 1600 (void *) self, | |
| 1601 NaClThreadInterfaceThreadFactory, | |
| 1602 (void *) NULL)) { | |
| 1603 goto failure_simple_ctor; | |
| 1604 } | |
| 1605 NaClLog(4, "NaClSecureReverseClientCtor: Mutex\n"); | |
| 1606 if (!NaClMutexCtor(&self->mu)) { | |
| 1607 goto failure_mutex_ctor; | |
| 1608 } | |
| 1609 self->nap = nap; | |
| 1610 self->queue_head = (struct NaClConnectionHandler *) NULL; | |
| 1611 self->queue_insert = &self->queue_head; | |
| 1612 | |
| 1613 NACL_VTBL(NaClRefCount, self) = | |
| 1614 (struct NaClRefCountVtbl *) &kNaClSecureReverseClientVtbl; | |
| 1615 | |
| 1616 NaClLog(4, "NaClSecureReverseClientCtor: InsertHandler\n"); | |
| 1617 if (!NaClSecureReverseClientInsertHandler(self, | |
| 1618 client_callback, | |
| 1619 state)) { | |
| 1620 goto failure_handler_insert; | |
| 1621 } | |
| 1622 | |
| 1623 NaClLog(4, "Leaving NaClSecureReverseClientCtor\n"); | |
| 1624 return 1; | |
| 1625 | |
| 1626 failure_handler_insert: | |
| 1627 NaClLog(4, "NaClSecureReverseClientCtor: InsertHandler failed\n"); | |
| 1628 NACL_VTBL(NaClRefCount, self) = | |
| 1629 (struct NaClRefCountVtbl *) &kNaClSimpleRevClientVtbl; | |
| 1630 | |
| 1631 self->nap = NULL; | |
| 1632 self->queue_insert = (struct NaClConnectionHandler **) NULL; | |
| 1633 NaClMutexDtor(&self->mu); | |
| 1634 | |
| 1635 failure_mutex_ctor: | |
| 1636 NaClLog(4, "NaClSecureReverseClientCtor: Mutex failed\n"); | |
| 1637 (*NACL_VTBL(NaClRefCount, self)->Dtor)((struct NaClRefCount *) self); | |
| 1638 failure_simple_ctor: | |
| 1639 NaClLog(4, "Leaving NaClSecureReverseClientCtor\n"); | |
| 1640 return 0; | |
| 1641 } | |
| 1642 | |
| 1643 void NaClSecureReverseClientDtor(struct NaClRefCount *vself) { | |
| 1644 struct NaClSecureReverseClient *self = | |
| 1645 (struct NaClSecureReverseClient *) vself; | |
| 1646 | |
| 1647 struct NaClConnectionHandler *entry; | |
| 1648 struct NaClConnectionHandler *next; | |
| 1649 | |
| 1650 for (entry = self->queue_head; NULL != entry; entry = next) { | |
| 1651 next = entry->next; | |
| 1652 free(entry); | |
| 1653 } | |
| 1654 NaClMutexDtor(&self->mu); | |
| 1655 | |
| 1656 NACL_VTBL(NaClRefCount, self) = (struct NaClRefCountVtbl const *) | |
| 1657 &kNaClSimpleRevClientVtbl; | |
| 1658 (*NACL_VTBL(NaClRefCount, self)->Dtor)(vself); | |
| 1659 } | |
| 1660 | |
| 1661 int NaClSecureServiceConnectionFactory( | |
| 1662 struct NaClSimpleService *vself, | |
| 1663 struct NaClDesc *conn, | |
| 1664 struct NaClSimpleServiceConnection **out) { | |
| 1665 struct NaClSecureService *self = | |
| 1666 (struct NaClSecureService *) vself; | |
| 1667 | |
| 1668 /* our instance_data is not connection specific */ | |
| 1669 return NaClSimpleServiceConnectionFactoryWithInstanceData( | |
| 1670 vself, conn, (void *) self->nap, out); | |
| 1671 } | |
| 1672 | |
| 1673 int NaClSecureServiceAcceptAndSpawnHandler(struct NaClSimpleService *vself) { | |
| 1674 int rv; | |
| 1675 | |
| 1676 NaClLog(4, | |
| 1677 "NaClSecureServiceAcceptAndSpawnHandler: invoking base class vfn\n"); | |
| 1678 rv = (*kNaClSimpleServiceVtbl.AcceptAndSpawnHandler)(vself); | |
| 1679 if (0 != rv) { | |
| 1680 NaClLog(LOG_FATAL, | |
| 1681 "Secure channel AcceptAndSpawnHandler returned %d\n", | |
| 1682 rv); | |
| 1683 } | |
| 1684 NaClThreadExit(0); | |
| 1685 /* | |
| 1686 * NOTREACHED The port is now to be used by untrusted code: all | |
| 1687 * subsequent connections are handled there. | |
| 1688 */ | |
| 1689 return rv; | |
| 1690 } | |
| 1691 | |
| 1692 void NaClSecureServiceRpcHandler(struct NaClSimpleService *vself, | |
| 1693 struct NaClSimpleServiceConnection *vconn) { | |
| 1694 | |
| 1695 NaClLog(4, "NaClSecureChannelThread started\n"); | |
| 1696 (*kNaClSimpleServiceVtbl.RpcHandler)(vself, vconn); | |
| 1697 NaClLog(4, "NaClSecureChannelThread: channel closed, exiting.\n"); | |
| 1698 NaClExit(0); | |
| 1699 } | |
| 1700 | |
| 1701 struct NaClSimpleServiceVtbl const kNaClSecureServiceVtbl = { | |
| 1702 { | |
| 1703 NaClSecureServiceDtor, | |
| 1704 }, | |
| 1705 NaClSecureServiceConnectionFactory, | |
| 1706 NaClSimpleServiceAcceptConnection, | |
| 1707 NaClSecureServiceAcceptAndSpawnHandler, | |
| 1708 NaClSecureServiceRpcHandler, | |
| 1709 }; | |
| 1710 | |
| 1711 struct NaClSimpleRevClientVtbl const kNaClSecureReverseClientVtbl = { | |
| 1712 { | |
| 1713 NaClSecureReverseClientDtor, | |
| 1714 }, | |
| 1715 }; | |
| 1716 | |
| 1717 | |
| 1718 void NaClSecureCommandChannel(struct NaClApp *nap) { | 1390 void NaClSecureCommandChannel(struct NaClApp *nap) { |
| 1719 struct NaClSecureService *secure_command_server; | 1391 struct NaClSecureService *secure_command_server; |
| 1720 | 1392 |
| 1721 static struct NaClSrpcHandlerDesc const secure_handlers[] = { | 1393 static struct NaClSrpcHandlerDesc const secure_handlers[] = { |
| 1722 { "hard_shutdown::", NaClSecureChannelShutdownRpc, }, | 1394 { "hard_shutdown::", NaClSecureChannelShutdownRpc, }, |
| 1723 { "start_module::i", NaClSecureChannelStartModuleRpc, }, | 1395 { "start_module::i", NaClSecureChannelStartModuleRpc, }, |
| 1724 { "log:is:", NaClSecureChannelLog, }, | 1396 { "log:is:", NaClSecureChannelLog, }, |
| 1725 { "load_module:hs:", NaClLoadModuleRpc, }, | 1397 { "load_module:hs:", NaClLoadModuleRpc, }, |
| 1726 { "load_irt:h:", NaClLoadIrtRpc, }, | 1398 { "load_irt:h:", NaClLoadIrtRpc, }, |
| 1727 { "reverse_setup::h", NaClSecureReverseClientSetup, }, | 1399 { "reverse_setup::h", NaClSecureReverseClientSetup, }, |
| (...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1829 nacl_global_xlate_base = mem_start; | 1501 nacl_global_xlate_base = mem_start; |
| 1830 | 1502 |
| 1831 NaClSandboxMemoryStartForValgrind(mem_start); | 1503 NaClSandboxMemoryStartForValgrind(mem_start); |
| 1832 | 1504 |
| 1833 _ovly_debug_event(); | 1505 _ovly_debug_event(); |
| 1834 } | 1506 } |
| 1835 | 1507 |
| 1836 void NaClGdbHook(struct NaClApp const *nap) { | 1508 void NaClGdbHook(struct NaClApp const *nap) { |
| 1837 StopForDebuggerInit(nap->mem_start); | 1509 StopForDebuggerInit(nap->mem_start); |
| 1838 } | 1510 } |
| OLD | NEW |