diff options
author | ng0 <ng0@n0.is> | 2019-09-10 16:59:32 +0000 |
---|---|---|
committer | ng0 <ng0@n0.is> | 2019-09-10 16:59:32 +0000 |
commit | 04b6df21cd281e8cd540139f8d9ae85defc1961c (patch) | |
tree | 6357199445df8d5c0c631bc8f10aef838b1f9f1e /src/util/network.c | |
parent | 483b0139a218a5f8a8311bda3eb23bcd88f57688 (diff) | |
download | gnunet-04b6df21cd281e8cd540139f8d9ae85defc1961c.tar.gz gnunet-04b6df21cd281e8cd540139f8d9ae85defc1961c.zip |
remove CYGWIN codeblocks, drop vendored Windows openvpn, drop win32 specific files.
configures and builds okay.
testsuite wasn't checked, will be checked.
diff including the plibc removal is now around 14370 lines of code less.
Diffstat (limited to 'src/util/network.c')
-rw-r--r-- | src/util/network.c | 866 |
1 files changed, 2 insertions, 864 deletions
diff --git a/src/util/network.c b/src/util/network.c index ec76424eb..6f8a8ff5a 100644 --- a/src/util/network.c +++ b/src/util/network.c | |||
@@ -759,10 +759,6 @@ GNUNET_NETWORK_socket_recvfrom(const struct GNUNET_NETWORK_Handle *desc, | |||
759 | flags, | 759 | flags, |
760 | src_addr, | 760 | src_addr, |
761 | addrlen); | 761 | addrlen); |
762 | #ifdef MINGW | ||
763 | if (SOCKET_ERROR == ret) | ||
764 | SetErrnoFromWinsockError(WSAGetLastError()); | ||
765 | #endif | ||
766 | return ret; | 762 | return ret; |
767 | } | 763 | } |
768 | 764 | ||
@@ -792,10 +788,6 @@ GNUNET_NETWORK_socket_recv(const struct GNUNET_NETWORK_Handle *desc, | |||
792 | buffer, | 788 | buffer, |
793 | length, | 789 | length, |
794 | flags); | 790 | flags); |
795 | #ifdef MINGW | ||
796 | if (SOCKET_ERROR == ret) | ||
797 | SetErrnoFromWinsockError(WSAGetLastError()); | ||
798 | #endif | ||
799 | return ret; | 791 | return ret; |
800 | } | 792 | } |
801 | 793 | ||
@@ -827,10 +819,6 @@ GNUNET_NETWORK_socket_send(const struct GNUNET_NETWORK_Handle *desc, | |||
827 | buffer, | 819 | buffer, |
828 | length, | 820 | length, |
829 | flags); | 821 | flags); |
830 | #ifdef MINGW | ||
831 | if (SOCKET_ERROR == ret) | ||
832 | SetErrnoFromWinsockError(WSAGetLastError()); | ||
833 | #endif | ||
834 | return ret; | 822 | return ret; |
835 | } | 823 | } |
836 | 824 | ||
@@ -865,10 +853,6 @@ GNUNET_NETWORK_socket_sendto(const struct GNUNET_NETWORK_Handle *desc, | |||
865 | flags |= MSG_NOSIGNAL; | 853 | flags |= MSG_NOSIGNAL; |
866 | #endif | 854 | #endif |
867 | ret = sendto(desc->fd, message, length, flags, dest_addr, dest_len); | 855 | ret = sendto(desc->fd, message, length, flags, dest_addr, dest_len); |
868 | #ifdef MINGW | ||
869 | if (SOCKET_ERROR == ret) | ||
870 | SetErrnoFromWinsockError(WSAGetLastError()); | ||
871 | #endif | ||
872 | return ret; | 856 | return ret; |
873 | } | 857 | } |
874 | 858 | ||
@@ -897,10 +881,7 @@ GNUNET_NETWORK_socket_setsockopt(struct GNUNET_NETWORK_Handle *fd, | |||
897 | option_name, | 881 | option_name, |
898 | option_value, | 882 | option_value, |
899 | option_len); | 883 | option_len); |
900 | #ifdef MINGW | 884 | |
901 | if (SOCKET_ERROR == ret) | ||
902 | SetErrnoFromWinsockError(WSAGetLastError()); | ||
903 | #endif | ||
904 | return ret == 0 ? GNUNET_OK : GNUNET_SYSERR; | 885 | return ret == 0 ? GNUNET_OK : GNUNET_SYSERR; |
905 | } | 886 | } |
906 | 887 | ||
@@ -950,10 +931,7 @@ GNUNET_NETWORK_socket_shutdown(struct GNUNET_NETWORK_Handle *desc, | |||
950 | int ret; | 931 | int ret; |
951 | 932 | ||
952 | ret = shutdown(desc->fd, how); | 933 | ret = shutdown(desc->fd, how); |
953 | #ifdef MINGW | 934 | |
954 | if (0 != ret) | ||
955 | SetErrnoFromWinsockError(WSAGetLastError()); | ||
956 | #endif | ||
957 | return (0 == ret) ? GNUNET_OK : GNUNET_SYSERR; | 935 | return (0 == ret) ? GNUNET_OK : GNUNET_SYSERR; |
958 | } | 936 | } |
959 | 937 | ||
@@ -1054,7 +1032,6 @@ void | |||
1054 | GNUNET_NETWORK_fdset_add(struct GNUNET_NETWORK_FDSet *dst, | 1032 | GNUNET_NETWORK_fdset_add(struct GNUNET_NETWORK_FDSet *dst, |
1055 | const struct GNUNET_NETWORK_FDSet *src) | 1033 | const struct GNUNET_NETWORK_FDSet *src) |
1056 | { | 1034 | { |
1057 | #ifndef MINGW | ||
1058 | int nfds; | 1035 | int nfds; |
1059 | 1036 | ||
1060 | for (nfds = src->nsds; nfds >= 0; nfds--) | 1037 | for (nfds = src->nsds; nfds >= 0; nfds--) |
@@ -1062,31 +1039,6 @@ GNUNET_NETWORK_fdset_add(struct GNUNET_NETWORK_FDSet *dst, | |||
1062 | FD_SET(nfds, &dst->sds); | 1039 | FD_SET(nfds, &dst->sds); |
1063 | dst->nsds = GNUNET_MAX(dst->nsds, | 1040 | dst->nsds = GNUNET_MAX(dst->nsds, |
1064 | src->nsds); | 1041 | src->nsds); |
1065 | #else | ||
1066 | /* This is MinGW32-specific implementation that relies on the code that | ||
1067 | * winsock2.h defines for FD_SET. Namely, it relies on FD_SET checking | ||
1068 | * that fd being added is not already in the set. | ||
1069 | * Also relies on us knowing what's inside fd_set (fd_count and fd_array). | ||
1070 | * | ||
1071 | * NOTE: I don't understand why the UNIX-logic wouldn't work | ||
1072 | * for the first part here as well. -CG | ||
1073 | */ | ||
1074 | unsigned int i; | ||
1075 | |||
1076 | for (i = 0; i < src->sds.fd_count; i++) | ||
1077 | FD_SET(src->sds.fd_array[i], | ||
1078 | &dst->sds); | ||
1079 | dst->nsds = GNUNET_MAX(src->nsds, | ||
1080 | dst->nsds); | ||
1081 | |||
1082 | /* also copy over `struct GNUNET_DISK_FileHandle` array */ | ||
1083 | if (dst->handles_pos + src->handles_pos > dst->handles_size) | ||
1084 | GNUNET_array_grow(dst->handles, | ||
1085 | dst->handles_size, | ||
1086 | ((dst->handles_pos + src->handles_pos) << 1)); | ||
1087 | for (i = 0; i < src->handles_pos; i++) | ||
1088 | dst->handles[dst->handles_pos++] = src->handles[i]; | ||
1089 | #endif | ||
1090 | } | 1042 | } |
1091 | 1043 | ||
1092 | 1044 | ||
@@ -1103,16 +1055,6 @@ GNUNET_NETWORK_fdset_copy(struct GNUNET_NETWORK_FDSet *to, | |||
1103 | FD_COPY(&from->sds, | 1055 | FD_COPY(&from->sds, |
1104 | &to->sds); | 1056 | &to->sds); |
1105 | to->nsds = from->nsds; | 1057 | to->nsds = from->nsds; |
1106 | #ifdef MINGW | ||
1107 | if (from->handles_pos > to->handles_size) | ||
1108 | GNUNET_array_grow(to->handles, | ||
1109 | to->handles_size, | ||
1110 | from->handles_pos * 2); | ||
1111 | GNUNET_memcpy(to->handles, | ||
1112 | from->handles, | ||
1113 | from->handles_pos * sizeof(struct GNUNET_NETWORK_Handle *)); | ||
1114 | to->handles_pos = from->handles_pos; | ||
1115 | #endif | ||
1116 | } | 1058 | } |
1117 | 1059 | ||
1118 | 1060 | ||
@@ -1217,13 +1159,6 @@ void | |||
1217 | GNUNET_NETWORK_fdset_handle_set(struct GNUNET_NETWORK_FDSet *fds, | 1159 | GNUNET_NETWORK_fdset_handle_set(struct GNUNET_NETWORK_FDSet *fds, |
1218 | const struct GNUNET_DISK_FileHandle *h) | 1160 | const struct GNUNET_DISK_FileHandle *h) |
1219 | { | 1161 | { |
1220 | #ifdef MINGW | ||
1221 | if (fds->handles_pos == fds->handles_size) | ||
1222 | GNUNET_array_grow(fds->handles, | ||
1223 | fds->handles_size, | ||
1224 | fds->handles_size * 2 + 2); | ||
1225 | fds->handles[fds->handles_pos++] = h; | ||
1226 | #else | ||
1227 | int fd; | 1162 | int fd; |
1228 | 1163 | ||
1229 | GNUNET_assert(GNUNET_OK == | 1164 | GNUNET_assert(GNUNET_OK == |
@@ -1234,7 +1169,6 @@ GNUNET_NETWORK_fdset_handle_set(struct GNUNET_NETWORK_FDSet *fds, | |||
1234 | &fds->sds); | 1169 | &fds->sds); |
1235 | fds->nsds = GNUNET_MAX(fd + 1, | 1170 | fds->nsds = GNUNET_MAX(fd + 1, |
1236 | fds->nsds); | 1171 | fds->nsds); |
1237 | #endif | ||
1238 | } | 1172 | } |
1239 | 1173 | ||
1240 | 1174 | ||
@@ -1247,22 +1181,7 @@ void | |||
1247 | GNUNET_NETWORK_fdset_handle_set_first(struct GNUNET_NETWORK_FDSet *fds, | 1181 | GNUNET_NETWORK_fdset_handle_set_first(struct GNUNET_NETWORK_FDSet *fds, |
1248 | const struct GNUNET_DISK_FileHandle *h) | 1182 | const struct GNUNET_DISK_FileHandle *h) |
1249 | { | 1183 | { |
1250 | #ifdef MINGW | ||
1251 | if (fds->handles_pos == fds->handles_size) | ||
1252 | GNUNET_array_grow(fds->handles, | ||
1253 | fds->handles_size, | ||
1254 | fds->handles_size * 2 + 2); | ||
1255 | fds->handles[fds->handles_pos] = h; | ||
1256 | if (fds->handles[0] != h) | ||
1257 | { | ||
1258 | const struct GNUNET_DISK_FileHandle *bak = fds->handles[0]; | ||
1259 | fds->handles[0] = h; | ||
1260 | fds->handles[fds->handles_pos] = bak; | ||
1261 | } | ||
1262 | fds->handles_pos++; | ||
1263 | #else | ||
1264 | GNUNET_NETWORK_fdset_handle_set(fds, h); | 1184 | GNUNET_NETWORK_fdset_handle_set(fds, h); |
1265 | #endif | ||
1266 | } | 1185 | } |
1267 | 1186 | ||
1268 | 1187 | ||
@@ -1277,42 +1196,11 @@ int | |||
1277 | GNUNET_NETWORK_fdset_handle_isset(const struct GNUNET_NETWORK_FDSet *fds, | 1196 | GNUNET_NETWORK_fdset_handle_isset(const struct GNUNET_NETWORK_FDSet *fds, |
1278 | const struct GNUNET_DISK_FileHandle *h) | 1197 | const struct GNUNET_DISK_FileHandle *h) |
1279 | { | 1198 | { |
1280 | #ifdef MINGW | ||
1281 | unsigned int i; | ||
1282 | |||
1283 | for (i = 0; i < fds->handles_pos; i++) | ||
1284 | if (fds->handles[i] == h) | ||
1285 | return GNUNET_YES; | ||
1286 | return GNUNET_NO; | ||
1287 | #else | ||
1288 | return FD_ISSET(h->fd, | 1199 | return FD_ISSET(h->fd, |
1289 | &fds->sds); | 1200 | &fds->sds); |
1290 | #endif | ||
1291 | } | 1201 | } |
1292 | 1202 | ||
1293 | 1203 | ||
1294 | #ifdef MINGW | ||
1295 | /** | ||
1296 | * Numerically compare pointers to sort them. | ||
1297 | * Used to test for overlap in the arrays. | ||
1298 | * | ||
1299 | * @param p1 a pointer | ||
1300 | * @param p2 a pointer | ||
1301 | * @return -1, 0 or 1, if the p1 < p2, p1==p2 or p1 > p2. | ||
1302 | */ | ||
1303 | static int | ||
1304 | ptr_cmp(const void *p1, | ||
1305 | const void *p2) | ||
1306 | { | ||
1307 | if (p1 == p2) | ||
1308 | return 0; | ||
1309 | if ((intptr_t)p1 < (intptr_t)p2) | ||
1310 | return -1; | ||
1311 | return 1; | ||
1312 | } | ||
1313 | #endif | ||
1314 | |||
1315 | |||
1316 | /** | 1204 | /** |
1317 | * Checks if two fd sets overlap | 1205 | * Checks if two fd sets overlap |
1318 | * | 1206 | * |
@@ -1324,7 +1212,6 @@ int | |||
1324 | GNUNET_NETWORK_fdset_overlap(const struct GNUNET_NETWORK_FDSet *fds1, | 1212 | GNUNET_NETWORK_fdset_overlap(const struct GNUNET_NETWORK_FDSet *fds1, |
1325 | const struct GNUNET_NETWORK_FDSet *fds2) | 1213 | const struct GNUNET_NETWORK_FDSet *fds2) |
1326 | { | 1214 | { |
1327 | #ifndef MINGW | ||
1328 | int nfds; | 1215 | int nfds; |
1329 | 1216 | ||
1330 | nfds = GNUNET_MIN(fds1->nsds, | 1217 | nfds = GNUNET_MIN(fds1->nsds, |
@@ -1339,53 +1226,6 @@ GNUNET_NETWORK_fdset_overlap(const struct GNUNET_NETWORK_FDSet *fds1, | |||
1339 | return GNUNET_YES; | 1226 | return GNUNET_YES; |
1340 | } | 1227 | } |
1341 | return GNUNET_NO; | 1228 | return GNUNET_NO; |
1342 | #else | ||
1343 | unsigned int i; | ||
1344 | unsigned int j; | ||
1345 | |||
1346 | /* This code is somewhat hacky, we are not supposed to know what's | ||
1347 | * inside of fd_set; also the O(n^2) is really bad... */ | ||
1348 | for (i = 0; i < fds1->sds.fd_count; i++) | ||
1349 | for (j = 0; j < fds2->sds.fd_count; j++) | ||
1350 | if (fds1->sds.fd_array[i] == fds2->sds.fd_array[j]) | ||
1351 | return GNUNET_YES; | ||
1352 | |||
1353 | /* take a short cut if possible */ | ||
1354 | if ((0 == fds1->handles_pos) || | ||
1355 | (0 == fds2->handles_pos)) | ||
1356 | return GNUNET_NO; | ||
1357 | |||
1358 | /* Sort file handles array to avoid quadratic complexity when | ||
1359 | checking for overlap */ | ||
1360 | qsort(fds1->handles, | ||
1361 | fds1->handles_pos, | ||
1362 | sizeof(void *), | ||
1363 | &ptr_cmp); | ||
1364 | qsort(fds2->handles, | ||
1365 | fds2->handles_pos, | ||
1366 | sizeof(void *), | ||
1367 | &ptr_cmp); | ||
1368 | i = 0; | ||
1369 | j = 0; | ||
1370 | while ((i < fds1->handles_pos) && | ||
1371 | (j < fds2->handles_pos)) | ||
1372 | { | ||
1373 | switch (ptr_cmp(fds1->handles[i], | ||
1374 | fds2->handles[j])) | ||
1375 | { | ||
1376 | case -1: | ||
1377 | i++; | ||
1378 | break; | ||
1379 | |||
1380 | case 0: | ||
1381 | return GNUNET_YES; | ||
1382 | |||
1383 | case 1: | ||
1384 | j++; | ||
1385 | } | ||
1386 | } | ||
1387 | return GNUNET_NO; | ||
1388 | #endif | ||
1389 | } | 1229 | } |
1390 | 1230 | ||
1391 | 1231 | ||
@@ -1413,192 +1253,10 @@ GNUNET_NETWORK_fdset_create() | |||
1413 | void | 1253 | void |
1414 | GNUNET_NETWORK_fdset_destroy(struct GNUNET_NETWORK_FDSet *fds) | 1254 | GNUNET_NETWORK_fdset_destroy(struct GNUNET_NETWORK_FDSet *fds) |
1415 | { | 1255 | { |
1416 | #ifdef MINGW | ||
1417 | GNUNET_array_grow(fds->handles, | ||
1418 | fds->handles_size, | ||
1419 | 0); | ||
1420 | #endif | ||
1421 | GNUNET_free(fds); | 1256 | GNUNET_free(fds); |
1422 | } | 1257 | } |
1423 | 1258 | ||
1424 | 1259 | ||
1425 | #if MINGW | ||
1426 | /** | ||
1427 | * FIXME. | ||
1428 | */ | ||
1429 | struct _select_params { | ||
1430 | /** | ||
1431 | * Read set. | ||
1432 | */ | ||
1433 | fd_set *r; | ||
1434 | |||
1435 | /** | ||
1436 | * Write set. | ||
1437 | */ | ||
1438 | fd_set *w; | ||
1439 | |||
1440 | /** | ||
1441 | * Except set. | ||
1442 | */ | ||
1443 | fd_set *e; | ||
1444 | |||
1445 | /** | ||
1446 | * Timeout for select(). | ||
1447 | */ | ||
1448 | struct timeval *tv; | ||
1449 | |||
1450 | /** | ||
1451 | * FIXME. | ||
1452 | */ | ||
1453 | HANDLE wakeup; | ||
1454 | |||
1455 | /** | ||
1456 | * FIXME. | ||
1457 | */ | ||
1458 | HANDLE standby; | ||
1459 | |||
1460 | /** | ||
1461 | * FIXME. | ||
1462 | */ | ||
1463 | _win_socket wakeup_socket; | ||
1464 | |||
1465 | /** | ||
1466 | * Set to return value from select. | ||
1467 | */ | ||
1468 | int status; | ||
1469 | }; | ||
1470 | |||
1471 | |||
1472 | /** | ||
1473 | * FIXME. | ||
1474 | */ | ||
1475 | static DWORD WINAPI | ||
1476 | _selector(LPVOID p) | ||
1477 | { | ||
1478 | struct _select_params *sp = p; | ||
1479 | |||
1480 | while (1) | ||
1481 | { | ||
1482 | WaitForSingleObject(sp->standby, | ||
1483 | INFINITE); | ||
1484 | ResetEvent(sp->standby); | ||
1485 | sp->status = select(1, | ||
1486 | sp->r, | ||
1487 | sp->w, | ||
1488 | sp->e, | ||
1489 | sp->tv); | ||
1490 | if (FD_ISSET(sp->wakeup_socket, | ||
1491 | sp->r)) | ||
1492 | { | ||
1493 | FD_CLR(sp->wakeup_socket, | ||
1494 | sp->r); | ||
1495 | sp->status -= 1; | ||
1496 | } | ||
1497 | SetEvent(sp->wakeup); | ||
1498 | } | ||
1499 | return 0; | ||
1500 | } | ||
1501 | |||
1502 | |||
1503 | static HANDLE hEventPipeWrite; | ||
1504 | |||
1505 | static HANDLE hEventReadReady; | ||
1506 | |||
1507 | static struct _select_params sp; | ||
1508 | |||
1509 | static HANDLE select_thread; | ||
1510 | |||
1511 | static HANDLE select_finished_event; | ||
1512 | |||
1513 | static HANDLE select_standby_event; | ||
1514 | |||
1515 | static _win_socket select_wakeup_socket = -1; | ||
1516 | |||
1517 | static _win_socket select_send_socket = -1; | ||
1518 | |||
1519 | static struct timeval select_timeout; | ||
1520 | |||
1521 | |||
1522 | /** | ||
1523 | * On W32, we actually use a thread to help with the | ||
1524 | * event loop due to W32-API limitations. This function | ||
1525 | * initializes that thread. | ||
1526 | */ | ||
1527 | static void | ||
1528 | initialize_select_thread() | ||
1529 | { | ||
1530 | _win_socket select_listening_socket = -1; | ||
1531 | struct sockaddr_in s_in; | ||
1532 | int alen; | ||
1533 | int res; | ||
1534 | unsigned long p; | ||
1535 | |||
1536 | select_standby_event = CreateEvent(NULL, TRUE, FALSE, NULL); | ||
1537 | select_finished_event = CreateEvent(NULL, TRUE, FALSE, NULL); | ||
1538 | |||
1539 | select_wakeup_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); | ||
1540 | |||
1541 | select_listening_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); | ||
1542 | |||
1543 | p = 1; | ||
1544 | res = ioctlsocket(select_wakeup_socket, FIONBIO, &p); | ||
1545 | LOG(GNUNET_ERROR_TYPE_DEBUG, | ||
1546 | "Select thread initialization: ioctlsocket() returns %d\n", | ||
1547 | res); | ||
1548 | |||
1549 | alen = sizeof(s_in); | ||
1550 | s_in.sin_family = AF_INET; | ||
1551 | s_in.sin_port = 0; | ||
1552 | s_in.sin_addr.S_un.S_un_b.s_b1 = 127; | ||
1553 | s_in.sin_addr.S_un.S_un_b.s_b2 = 0; | ||
1554 | s_in.sin_addr.S_un.S_un_b.s_b3 = 0; | ||
1555 | s_in.sin_addr.S_un.S_un_b.s_b4 = 1; | ||
1556 | res = bind(select_listening_socket, | ||
1557 | (const struct sockaddr *)&s_in, | ||
1558 | sizeof(s_in)); | ||
1559 | LOG(GNUNET_ERROR_TYPE_DEBUG, | ||
1560 | "Select thread initialization: bind() returns %d\n", | ||
1561 | res); | ||
1562 | |||
1563 | res = getsockname(select_listening_socket, | ||
1564 | (struct sockaddr *)&s_in, | ||
1565 | &alen); | ||
1566 | LOG(GNUNET_ERROR_TYPE_DEBUG, | ||
1567 | "Select thread initialization: getsockname() returns %d\n", | ||
1568 | res); | ||
1569 | |||
1570 | res = listen(select_listening_socket, | ||
1571 | SOMAXCONN); | ||
1572 | LOG(GNUNET_ERROR_TYPE_DEBUG, | ||
1573 | "Select thread initialization: listen() returns %d\n", | ||
1574 | res); | ||
1575 | res = connect(select_wakeup_socket, | ||
1576 | (const struct sockaddr *)&s_in, | ||
1577 | sizeof(s_in)); | ||
1578 | LOG(GNUNET_ERROR_TYPE_DEBUG, | ||
1579 | "Select thread initialization: connect() returns %d\n", | ||
1580 | res); | ||
1581 | |||
1582 | select_send_socket = accept(select_listening_socket, | ||
1583 | (struct sockaddr *)&s_in, | ||
1584 | &alen); | ||
1585 | |||
1586 | closesocket(select_listening_socket); | ||
1587 | |||
1588 | sp.wakeup = select_finished_event; | ||
1589 | sp.standby = select_standby_event; | ||
1590 | sp.wakeup_socket = select_wakeup_socket; | ||
1591 | |||
1592 | select_thread = CreateThread(NULL, | ||
1593 | 0, | ||
1594 | _selector, | ||
1595 | &sp, | ||
1596 | 0, NULL); | ||
1597 | } | ||
1598 | |||
1599 | |||
1600 | #endif | ||
1601 | |||
1602 | /** | 1260 | /** |
1603 | * Test if the given @a port is available. | 1261 | * Test if the given @a port is available. |
1604 | * | 1262 | * |
@@ -1657,7 +1315,6 @@ GNUNET_NETWORK_test_port_free(int ipproto, | |||
1657 | } | 1315 | } |
1658 | 1316 | ||
1659 | 1317 | ||
1660 | #ifndef MINGW | ||
1661 | /** | 1318 | /** |
1662 | * Check if sockets or pipes meet certain conditions | 1319 | * Check if sockets or pipes meet certain conditions |
1663 | * | 1320 | * |
@@ -1714,523 +1371,4 @@ GNUNET_NETWORK_socket_select(struct GNUNET_NETWORK_FDSet *rfds, | |||
1714 | GNUNET_TIME_UNIT_FOREVER_REL.rel_value_us) ? NULL : &tv); | 1371 | GNUNET_TIME_UNIT_FOREVER_REL.rel_value_us) ? NULL : &tv); |
1715 | } | 1372 | } |
1716 | 1373 | ||
1717 | |||
1718 | #else | ||
1719 | /* MINGW */ | ||
1720 | |||
1721 | |||
1722 | /** | ||
1723 | * Non-blocking test if a pipe is ready for reading. | ||
1724 | * | ||
1725 | * @param fh pipe handle | ||
1726 | * @return #GNUNET_YES if the pipe is ready for reading | ||
1727 | */ | ||
1728 | static int | ||
1729 | pipe_read_ready(const struct GNUNET_DISK_FileHandle *fh) | ||
1730 | { | ||
1731 | DWORD error; | ||
1732 | BOOL bret; | ||
1733 | DWORD waitstatus = 0; | ||
1734 | |||
1735 | SetLastError(0); | ||
1736 | bret = PeekNamedPipe(fh->h, NULL, 0, NULL, &waitstatus, NULL); | ||
1737 | error = GetLastError(); | ||
1738 | if (0 == bret) | ||
1739 | { | ||
1740 | /* TODO: either add more errors to this condition, or eliminate it | ||
1741 | * entirely (failed to peek -> pipe is in serious trouble, should | ||
1742 | * be selected as readable). | ||
1743 | */ | ||
1744 | if ((error != ERROR_BROKEN_PIPE) && | ||
1745 | (error != ERROR_INVALID_HANDLE)) | ||
1746 | return GNUNET_NO; | ||
1747 | } | ||
1748 | else if (waitstatus <= 0) | ||
1749 | return GNUNET_NO; | ||
1750 | return GNUNET_YES; | ||
1751 | } | ||
1752 | |||
1753 | |||
1754 | /** | ||
1755 | * Non-blocking test if a pipe is having an IO exception. | ||
1756 | * | ||
1757 | * @param fh pipe handle | ||
1758 | * @return #GNUNET_YES if the pipe is having an IO exception. | ||
1759 | */ | ||
1760 | static int | ||
1761 | pipe_except_ready(const struct GNUNET_DISK_FileHandle *fh) | ||
1762 | { | ||
1763 | DWORD dwBytes; | ||
1764 | |||
1765 | if (PeekNamedPipe(fh->h, NULL, 0, NULL, &dwBytes, NULL)) | ||
1766 | return GNUNET_NO; | ||
1767 | return GNUNET_YES; | ||
1768 | } | ||
1769 | |||
1770 | |||
1771 | /** | ||
1772 | * Iterate over handles in fds, destructively rewrite the | ||
1773 | * handles array contents of fds so that it starts with the | ||
1774 | * handles that are ready, and update handles_pos accordingly. | ||
1775 | * | ||
1776 | * @param fds set of handles (usually pipes) to be checked for readiness | ||
1777 | * @param except GNUNET_NO if fds should be checked for readiness to read, | ||
1778 | * GNUNET_YES if fds should be checked for exceptions | ||
1779 | * (there is no way to check for write-readiness - pipes are always write-ready) | ||
1780 | * @param set_for_sure a HANDLE that is known to be set already, | ||
1781 | * because WaitForMultipleObjects() returned its index. | ||
1782 | * @return number of ready handles | ||
1783 | */ | ||
1784 | static int | ||
1785 | check_handles_status(struct GNUNET_NETWORK_FDSet *fds, | ||
1786 | int except, | ||
1787 | HANDLE set_for_sure) | ||
1788 | { | ||
1789 | const struct GNUNET_DISK_FileHandle *fh; | ||
1790 | unsigned int roff; | ||
1791 | unsigned int woff; | ||
1792 | |||
1793 | for (woff = 0, roff = 0; roff < fds->handles_pos; roff++) | ||
1794 | { | ||
1795 | fh = fds->handles[roff]; | ||
1796 | if (fh == set_for_sure) | ||
1797 | { | ||
1798 | fds->handles[woff++] = fh; | ||
1799 | } | ||
1800 | else if (fh->type == GNUNET_DISK_HANLDE_TYPE_PIPE) | ||
1801 | { | ||
1802 | if ((except && pipe_except_ready(fh)) || | ||
1803 | (!except && pipe_read_ready(fh))) | ||
1804 | fds->handles[woff++] = fh; | ||
1805 | } | ||
1806 | else if (fh->type == GNUNET_DISK_HANLDE_TYPE_FILE) | ||
1807 | { | ||
1808 | if (!except) | ||
1809 | fds->handles[woff++] = fh; | ||
1810 | } | ||
1811 | else | ||
1812 | { | ||
1813 | if (WAIT_OBJECT_0 == WaitForSingleObject(fh->h, 0)) | ||
1814 | fds->handles[woff++] = fh; | ||
1815 | } | ||
1816 | } | ||
1817 | fds->handles_pos = woff; | ||
1818 | return woff; | ||
1819 | } | ||
1820 | |||
1821 | |||
1822 | /** | ||
1823 | * Check if sockets or pipes meet certain conditions, version for W32. | ||
1824 | * | ||
1825 | * @param rfds set of sockets or pipes to be checked for readability | ||
1826 | * @param wfds set of sockets or pipes to be checked for writability | ||
1827 | * @param efds set of sockets or pipes to be checked for exceptions | ||
1828 | * @param timeout relative value when to return | ||
1829 | * @return number of selected sockets or pipes, #GNUNET_SYSERR on error | ||
1830 | */ | ||
1831 | int | ||
1832 | GNUNET_NETWORK_socket_select(struct GNUNET_NETWORK_FDSet *rfds, | ||
1833 | struct GNUNET_NETWORK_FDSet *wfds, | ||
1834 | struct GNUNET_NETWORK_FDSet *efds, | ||
1835 | const struct GNUNET_TIME_Relative timeout) | ||
1836 | { | ||
1837 | const struct GNUNET_DISK_FileHandle *fh; | ||
1838 | int nfds; | ||
1839 | int handles; | ||
1840 | unsigned int i; | ||
1841 | int retcode; | ||
1842 | uint64_t mcs_total; | ||
1843 | DWORD ms_rounded; | ||
1844 | int nhandles = 0; | ||
1845 | int read_pipes_off; | ||
1846 | HANDLE handle_array[FD_SETSIZE + 2]; | ||
1847 | int returncode; | ||
1848 | int returnedpos = 0; | ||
1849 | int selectret; | ||
1850 | fd_set aread; | ||
1851 | fd_set awrite; | ||
1852 | fd_set aexcept; | ||
1853 | |||
1854 | nfds = 0; | ||
1855 | handles = 0; | ||
1856 | if (NULL != rfds) | ||
1857 | { | ||
1858 | nfds = GNUNET_MAX(nfds, rfds->nsds); | ||
1859 | handles += rfds->handles_pos; | ||
1860 | } | ||
1861 | if (NULL != wfds) | ||
1862 | { | ||
1863 | nfds = GNUNET_MAX(nfds, wfds->nsds); | ||
1864 | handles += wfds->handles_pos; | ||
1865 | } | ||
1866 | if (NULL != efds) | ||
1867 | { | ||
1868 | nfds = GNUNET_MAX(nfds, efds->nsds); | ||
1869 | handles += efds->handles_pos; | ||
1870 | } | ||
1871 | |||
1872 | if ((0 == nfds) && | ||
1873 | (GNUNET_TIME_UNIT_FOREVER_REL.rel_value_us == timeout.rel_value_us) && | ||
1874 | (0 == handles)) | ||
1875 | { | ||
1876 | GNUNET_break(0); | ||
1877 | LOG(GNUNET_ERROR_TYPE_ERROR, | ||
1878 | _("Fatal internal logic error, process hangs in `%s' (abort with CTRL-C)!\n"), | ||
1879 | "select"); | ||
1880 | } | ||
1881 | #define SAFE_FD_ISSET(fd, set) (set != NULL && FD_ISSET(fd, set)) | ||
1882 | /* calculate how long we need to wait in microseconds */ | ||
1883 | if (timeout.rel_value_us == GNUNET_TIME_UNIT_FOREVER_REL.rel_value_us) | ||
1884 | { | ||
1885 | mcs_total = INFINITE; | ||
1886 | ms_rounded = INFINITE; | ||
1887 | } | ||
1888 | else | ||
1889 | { | ||
1890 | mcs_total = timeout.rel_value_us / GNUNET_TIME_UNIT_MICROSECONDS.rel_value_us; | ||
1891 | ms_rounded = (DWORD)(mcs_total / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us); | ||
1892 | if (mcs_total > 0 && ms_rounded == 0) | ||
1893 | ms_rounded = 1; | ||
1894 | } | ||
1895 | /* select() may be used as a portable way to sleep */ | ||
1896 | if (!(rfds || wfds || efds)) | ||
1897 | { | ||
1898 | Sleep(ms_rounded); | ||
1899 | return 0; | ||
1900 | } | ||
1901 | |||
1902 | if (NULL == select_thread) | ||
1903 | initialize_select_thread(); | ||
1904 | |||
1905 | FD_ZERO(&aread); | ||
1906 | FD_ZERO(&awrite); | ||
1907 | FD_ZERO(&aexcept); | ||
1908 | if (rfds) | ||
1909 | FD_COPY(&rfds->sds, &aread); | ||
1910 | if (wfds) | ||
1911 | FD_COPY(&wfds->sds, &awrite); | ||
1912 | if (efds) | ||
1913 | FD_COPY(&efds->sds, &aexcept); | ||
1914 | |||
1915 | /* Start by doing a fast check on sockets and pipes (without | ||
1916 | waiting). It is cheap, and is sufficient most of the time. By | ||
1917 | profiling we detected that to be true in 90% of the cases. | ||
1918 | */ | ||
1919 | |||
1920 | /* Do the select now */ | ||
1921 | select_timeout.tv_sec = 0; | ||
1922 | select_timeout.tv_usec = 0; | ||
1923 | |||
1924 | /* Copy all the writes to the except, so we can detect connect() errors */ | ||
1925 | for (i = 0; i < awrite.fd_count; i++) | ||
1926 | FD_SET(awrite.fd_array[i], | ||
1927 | &aexcept); | ||
1928 | if ((aread.fd_count > 0) || | ||
1929 | (awrite.fd_count > 0) || | ||
1930 | (aexcept.fd_count > 0)) | ||
1931 | selectret = select(1, | ||
1932 | (NULL != rfds) ? &aread : NULL, | ||
1933 | (NULL != wfds) ? &awrite : NULL, | ||
1934 | &aexcept, | ||
1935 | &select_timeout); | ||
1936 | else | ||
1937 | selectret = 0; | ||
1938 | if (-1 == selectret) | ||
1939 | { | ||
1940 | /* Throw an error early on, while we still have the context. */ | ||
1941 | LOG(GNUNET_ERROR_TYPE_ERROR, | ||
1942 | "W32 select(%d, %d, %d) failed: %lu\n", | ||
1943 | rfds ? aread.fd_count : 0, | ||
1944 | wfds ? awrite.fd_count : 0, | ||
1945 | aexcept.fd_count, | ||
1946 | GetLastError()); | ||
1947 | GNUNET_assert(0); | ||
1948 | } | ||
1949 | |||
1950 | /* Check aexcept, if something is in there and we copied that | ||
1951 | FD before to detect connect() errors, add it back to the | ||
1952 | write set to report errors. */ | ||
1953 | if (NULL != wfds) | ||
1954 | for (i = 0; i < aexcept.fd_count; i++) | ||
1955 | if (FD_ISSET(aexcept.fd_array[i], | ||
1956 | &wfds->sds)) | ||
1957 | FD_SET(aexcept.fd_array[i], | ||
1958 | &awrite); | ||
1959 | |||
1960 | |||
1961 | /* If our select returned something or is a 0-timed request, then | ||
1962 | also check the pipes and get out of here! */ | ||
1963 | /* Sadly, it means code duplication :( */ | ||
1964 | if ((selectret > 0) || (0 == mcs_total)) | ||
1965 | { | ||
1966 | retcode = 0; | ||
1967 | |||
1968 | /* Read Pipes */ | ||
1969 | if (rfds && (rfds->handles_pos > 0)) | ||
1970 | retcode += check_handles_status(rfds, GNUNET_NO, NULL); | ||
1971 | |||
1972 | /* wfds handles remain untouched, on W32 | ||
1973 | we pretend our pipes are "always" write-ready */ | ||
1974 | |||
1975 | /* except pipes */ | ||
1976 | if (efds && (efds->handles_pos > 0)) | ||
1977 | retcode += check_handles_status(efds, GNUNET_YES, NULL); | ||
1978 | |||
1979 | if (rfds) | ||
1980 | { | ||
1981 | GNUNET_NETWORK_fdset_zero(rfds); | ||
1982 | if (selectret != -1) | ||
1983 | GNUNET_NETWORK_fdset_copy_native(rfds, &aread, selectret); | ||
1984 | } | ||
1985 | if (wfds) | ||
1986 | { | ||
1987 | GNUNET_NETWORK_fdset_zero(wfds); | ||
1988 | if (selectret != -1) | ||
1989 | GNUNET_NETWORK_fdset_copy_native(wfds, &awrite, selectret); | ||
1990 | } | ||
1991 | if (efds) | ||
1992 | { | ||
1993 | GNUNET_NETWORK_fdset_zero(efds); | ||
1994 | if (selectret != -1) | ||
1995 | GNUNET_NETWORK_fdset_copy_native(efds, &aexcept, selectret); | ||
1996 | } | ||
1997 | if (-1 == selectret) | ||
1998 | return -1; | ||
1999 | /* Add our select() FDs to the total return value */ | ||
2000 | retcode += selectret; | ||
2001 | return retcode; | ||
2002 | } | ||
2003 | |||
2004 | /* If we got this far, use slower implementation that is able to do a waiting select | ||
2005 | on both sockets and pipes simultaneously */ | ||
2006 | |||
2007 | /* Events for pipes */ | ||
2008 | if (!hEventReadReady) | ||
2009 | hEventReadReady = CreateEvent(NULL, TRUE, TRUE, NULL); | ||
2010 | if (!hEventPipeWrite) | ||
2011 | hEventPipeWrite = CreateEvent(NULL, TRUE, TRUE, NULL); | ||
2012 | retcode = 0; | ||
2013 | |||
2014 | FD_ZERO(&aread); | ||
2015 | FD_ZERO(&awrite); | ||
2016 | FD_ZERO(&aexcept); | ||
2017 | if (rfds) | ||
2018 | FD_COPY(&rfds->sds, &aread); | ||
2019 | if (wfds) | ||
2020 | FD_COPY(&wfds->sds, &awrite); | ||
2021 | if (efds) | ||
2022 | FD_COPY(&efds->sds, &aexcept); | ||
2023 | /* We will first Add the PIPES to the events */ | ||
2024 | /* Track how far in `handle_array` the read pipes go, | ||
2025 | so we may by-pass them quickly if none of them | ||
2026 | are selected. */ | ||
2027 | read_pipes_off = 0; | ||
2028 | if (rfds && (rfds->handles_pos > 0)) | ||
2029 | { | ||
2030 | for (i = 0; i < rfds->handles_pos; i++) | ||
2031 | { | ||
2032 | fh = rfds->handles[i]; | ||
2033 | if (fh->type == GNUNET_DISK_HANLDE_TYPE_EVENT) | ||
2034 | { | ||
2035 | handle_array[nhandles++] = fh->h; | ||
2036 | continue; | ||
2037 | } | ||
2038 | if (fh->type != GNUNET_DISK_HANLDE_TYPE_PIPE) | ||
2039 | continue; | ||
2040 | /* Read zero bytes to check the status of the pipe */ | ||
2041 | if (!ReadFile(fh->h, NULL, 0, NULL, fh->oOverlapRead)) | ||
2042 | { | ||
2043 | DWORD error_code = GetLastError(); | ||
2044 | |||
2045 | if (error_code == ERROR_IO_PENDING) | ||
2046 | { | ||
2047 | /* add as unready */ | ||
2048 | handle_array[nhandles++] = fh->oOverlapRead->hEvent; | ||
2049 | read_pipes_off++; | ||
2050 | } | ||
2051 | else | ||
2052 | { | ||
2053 | /* add as ready */ | ||
2054 | handle_array[nhandles++] = hEventReadReady; | ||
2055 | read_pipes_off++; | ||
2056 | } | ||
2057 | } | ||
2058 | else | ||
2059 | { | ||
2060 | /* error also counts as ready */ | ||
2061 | handle_array[nhandles++] = hEventReadReady; | ||
2062 | read_pipes_off++; | ||
2063 | } | ||
2064 | } | ||
2065 | } | ||
2066 | |||
2067 | if (wfds && (wfds->handles_pos > 0)) | ||
2068 | { | ||
2069 | LOG(GNUNET_ERROR_TYPE_DEBUG, | ||
2070 | "Adding the write ready event to the array as %d\n", | ||
2071 | nhandles); | ||
2072 | handle_array[nhandles++] = hEventPipeWrite; | ||
2073 | } | ||
2074 | |||
2075 | sp.status = 0; | ||
2076 | if (nfds > 0) | ||
2077 | { | ||
2078 | LOG(GNUNET_ERROR_TYPE_DEBUG, | ||
2079 | "Adding the socket event to the array as %d\n", | ||
2080 | nhandles); | ||
2081 | handle_array[nhandles++] = select_finished_event; | ||
2082 | if (timeout.rel_value_us == GNUNET_TIME_UNIT_FOREVER_REL.rel_value_us) | ||
2083 | { | ||
2084 | sp.tv = NULL; | ||
2085 | } | ||
2086 | else | ||
2087 | { | ||
2088 | select_timeout.tv_sec = timeout.rel_value_us / GNUNET_TIME_UNIT_SECONDS.rel_value_us; | ||
2089 | select_timeout.tv_usec = (timeout.rel_value_us - | ||
2090 | (select_timeout.tv_sec * | ||
2091 | GNUNET_TIME_UNIT_SECONDS.rel_value_us)); | ||
2092 | sp.tv = &select_timeout; | ||
2093 | } | ||
2094 | FD_SET(select_wakeup_socket, &aread); | ||
2095 | do | ||
2096 | { | ||
2097 | i = recv(select_wakeup_socket, | ||
2098 | (char *)&returnedpos, | ||
2099 | 1, | ||
2100 | 0); | ||
2101 | } | ||
2102 | while (i == 1); | ||
2103 | sp.r = &aread; | ||
2104 | sp.w = &awrite; | ||
2105 | sp.e = &aexcept; | ||
2106 | /* Failed connections cause sockets to be set in errorfds on W32, | ||
2107 | * but on POSIX it should set them in writefds. | ||
2108 | * First copy all awrite sockets to aexcept, later we'll | ||
2109 | * check aexcept and set its contents in awrite as well | ||
2110 | * Sockets are also set in errorfds when OOB data is available, | ||
2111 | * but we don't use OOB data. | ||
2112 | */ | ||
2113 | for (i = 0; i < awrite.fd_count; i++) | ||
2114 | FD_SET(awrite.fd_array[i], | ||
2115 | &aexcept); | ||
2116 | ResetEvent(select_finished_event); | ||
2117 | SetEvent(select_standby_event); | ||
2118 | } | ||
2119 | |||
2120 | /* NULL-terminate array */ | ||
2121 | handle_array[nhandles] = NULL; | ||
2122 | LOG(GNUNET_ERROR_TYPE_DEBUG, | ||
2123 | "nfds: %d, handles: %d, will wait: %llu mcs\n", | ||
2124 | nfds, | ||
2125 | nhandles, | ||
2126 | mcs_total); | ||
2127 | if (nhandles) | ||
2128 | { | ||
2129 | returncode | ||
2130 | = WaitForMultipleObjects(nhandles, | ||
2131 | handle_array, | ||
2132 | FALSE, | ||
2133 | ms_rounded); | ||
2134 | LOG(GNUNET_ERROR_TYPE_DEBUG, | ||
2135 | "WaitForMultipleObjects Returned: %d\n", | ||
2136 | returncode); | ||
2137 | } | ||
2138 | else if (nfds > 0) | ||
2139 | { | ||
2140 | GNUNET_break(0); /* This branch shouldn't actually be executed...*/ | ||
2141 | i = (int)WaitForSingleObject(select_finished_event, | ||
2142 | INFINITE); | ||
2143 | returncode = WAIT_TIMEOUT; | ||
2144 | } | ||
2145 | else | ||
2146 | { | ||
2147 | /* Shouldn't come this far. If it does - investigate. */ | ||
2148 | GNUNET_assert(0); | ||
2149 | } | ||
2150 | |||
2151 | if (nfds > 0) | ||
2152 | { | ||
2153 | /* Don't wake up select-thread when delay is 0, it should return immediately | ||
2154 | * and wake up by itself. | ||
2155 | */ | ||
2156 | if (0 != mcs_total) | ||
2157 | i = send(select_send_socket, | ||
2158 | (const char *)&returnedpos, | ||
2159 | 1, | ||
2160 | 0); | ||
2161 | i = (int)WaitForSingleObject(select_finished_event, | ||
2162 | INFINITE); | ||
2163 | LOG(GNUNET_ERROR_TYPE_DEBUG, | ||
2164 | "Finished waiting for the select thread: %d %d\n", | ||
2165 | i, | ||
2166 | sp.status); | ||
2167 | if (0 != mcs_total) | ||
2168 | { | ||
2169 | do | ||
2170 | { | ||
2171 | i = recv(select_wakeup_socket, | ||
2172 | (char *)&returnedpos, | ||
2173 | 1, 0); | ||
2174 | } | ||
2175 | while (1 == i); | ||
2176 | } | ||
2177 | /* Check aexcept, add its contents to awrite */ | ||
2178 | for (i = 0; i < aexcept.fd_count; i++) | ||
2179 | FD_SET(aexcept.fd_array[i], &awrite); | ||
2180 | } | ||
2181 | |||
2182 | returnedpos = returncode - WAIT_OBJECT_0; | ||
2183 | LOG(GNUNET_ERROR_TYPE_DEBUG, | ||
2184 | "return pos is: %d\n", | ||
2185 | returnedpos); | ||
2186 | |||
2187 | if (rfds) | ||
2188 | { | ||
2189 | /* We queued a zero-long read on each pipe to check | ||
2190 | * its state, now we must cancel these read operations. | ||
2191 | * This must be done while rfds->handles_pos is still | ||
2192 | * intact and matches the number of read handles that we | ||
2193 | * got from the caller. | ||
2194 | */ | ||
2195 | for (i = 0; i < rfds->handles_pos; i++) | ||
2196 | { | ||
2197 | fh = rfds->handles[i]; | ||
2198 | if (GNUNET_DISK_HANLDE_TYPE_PIPE == fh->type) | ||
2199 | CancelIo(fh->h); | ||
2200 | } | ||
2201 | |||
2202 | /* We may have some pipes ready for reading. */ | ||
2203 | if (returnedpos < read_pipes_off) | ||
2204 | retcode += check_handles_status(rfds, GNUNET_NO, handle_array[returnedpos]); | ||
2205 | else | ||
2206 | rfds->handles_pos = 0; | ||
2207 | |||
2208 | if (-1 != sp.status) | ||
2209 | GNUNET_NETWORK_fdset_copy_native(rfds, &aread, retcode); | ||
2210 | } | ||
2211 | if (wfds) | ||
2212 | { | ||
2213 | retcode += wfds->handles_pos; | ||
2214 | /* wfds handles remain untouched */ | ||
2215 | if (-1 != sp.status) | ||
2216 | GNUNET_NETWORK_fdset_copy_native(wfds, &awrite, retcode); | ||
2217 | } | ||
2218 | if (efds) | ||
2219 | { | ||
2220 | retcode += check_handles_status(rfds, | ||
2221 | GNUNET_YES, | ||
2222 | returnedpos < nhandles ? handle_array[returnedpos] : NULL); | ||
2223 | if (-1 != sp.status) | ||
2224 | GNUNET_NETWORK_fdset_copy_native(efds, &aexcept, retcode); | ||
2225 | } | ||
2226 | |||
2227 | if (sp.status > 0) | ||
2228 | retcode += sp.status; | ||
2229 | |||
2230 | return retcode; | ||
2231 | } | ||
2232 | |||
2233 | /* MINGW */ | ||
2234 | #endif | ||
2235 | |||
2236 | /* end of network.c */ | 1374 | /* end of network.c */ |