This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
integrate cfgperl contents into mainline
[perl5.git] / win32 / win32sck.c
CommitLineData
326b05e3 1/* win32sck.c
68dc0745 2 *
3 * (c) 1995 Microsoft Corporation. All rights reserved.
4 * Developed by hip communications inc., http://info.hip.com/info/
5 * Portions (c) 1993 Intergraph Corporation. All rights reserved.
6 *
7 * You may distribute under the terms of either the GNU General Public
8 * License or the Artistic License, as specified in the README file.
9 */
0a753a76 10
390b85e7 11#define WIN32IO_IS_STDIO
55d25626 12#define WIN32SCK_IS_STDSCK
0a753a76 13#define WIN32_LEAN_AND_MEAN
a835ef8a
NIS
14#ifdef __GNUC__
15#define Win32_Winsock
16#endif
390b85e7 17#include <windows.h>
0a753a76 18#include "EXTERN.h"
19#include "perl.h"
c69f6586
GS
20
21#if defined(PERL_OBJECT)
22#define NO_XSLOCKS
c69f6586
GS
23#include "XSUB.h"
24#endif
25
58a50f62 26#include "Win32iop.h"
0a753a76 27#include <sys/socket.h>
28#include <fcntl.h>
29#include <sys/stat.h>
30#include <assert.h>
390b85e7 31#include <io.h>
0a753a76 32
68dc0745 33/* thanks to Beverly Brown (beverly@datacube.com) */
326b05e3 34#ifdef USE_SOCKETS_AS_HANDLES
58a50f62 35# define OPEN_SOCKET(x) win32_open_osfhandle(x,O_RDWR|O_BINARY)
326b05e3 36# define TO_SOCKET(x) _get_osfhandle(x)
0a753a76 37#else
0a753a76 38# define OPEN_SOCKET(x) (x)
39# define TO_SOCKET(x) (x)
68dc0745 40#endif /* USE_SOCKETS_AS_HANDLES */
0a753a76 41
401ef382 42#ifdef USE_THREADS
326b05e3
GS
43#define StartSockets() \
44 STMT_START { \
45 if (!wsock_started) \
46 start_sockets(); \
401ef382 47 set_socktype(); \
326b05e3 48 } STMT_END
401ef382
SN
49#else
50#define StartSockets() \
51 STMT_START { \
52 if (!wsock_started) { \
53 start_sockets(); \
54 set_socktype(); \
55 } \
56 } STMT_END
57#endif
326b05e3
GS
58
59#define EndSockets() \
60 STMT_START { \
61 if (wsock_started) \
62 WSACleanup(); \
63 } STMT_END
64
65#define SOCKET_TEST(x, y) \
66 STMT_START { \
67 StartSockets(); \
68 if((x) == (y)) \
69 errno = WSAGetLastError(); \
70 } STMT_END
71
72#define SOCKET_TEST_ERROR(x) SOCKET_TEST(x, SOCKET_ERROR)
73
137443ea 74static struct servent* win32_savecopyservent(struct servent*d,
75 struct servent*s,
76 const char *proto);
0a753a76 77
c53bd28a
NIS
78#ifdef USE_THREADS
79#ifdef USE_DECLSPEC_THREAD
0a753a76 80__declspec(thread) struct servent myservent;
401ef382 81__declspec(thread) int init_socktype;
c53bd28a
NIS
82#else
83#define myservent (thr->i.Wservent)
401ef382 84#define init_socktype (thr->i.Winit_socktype)
c53bd28a
NIS
85#endif
86#else
87static struct servent myservent;
88#endif
89
326b05e3 90static int wsock_started = 0;
68dc0745 91
92void
326b05e3 93start_sockets(void)
68dc0745 94{
c5be433b 95 dTHXo;
68dc0745 96 unsigned short version;
97 WSADATA retdata;
98 int ret;
68dc0745 99
68dc0745 100 /*
101 * initalize the winsock interface and insure that it is
102 * cleaned up at exit.
103 */
104 version = 0x101;
326b05e3 105 if(ret = WSAStartup(version, &retdata))
4f63d024 106 Perl_croak_nocontext("Unable to locate winsock library!\n");
68dc0745 107 if(retdata.wVersion != version)
4f63d024 108 Perl_croak_nocontext("Could not find version 1.1 of winsock dll\n");
68dc0745 109
110 /* atexit((void (*)(void)) EndSockets); */
401ef382
SN
111 wsock_started = 1;
112}
0a753a76 113
401ef382
SN
114void
115set_socktype(void)
116{
0a753a76 117#ifdef USE_SOCKETS_AS_HANDLES
401ef382 118#ifdef USE_THREADS
4f63d024 119 dTHX;
401ef382
SN
120 if(!init_socktype) {
121#endif
122 int iSockOpt = SO_SYNCHRONOUS_NONALERT;
68dc0745 123 /*
124 * Enable the use of sockets as filehandles
125 */
326b05e3 126 setsockopt(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE,
68dc0745 127 (char *)&iSockOpt, sizeof(iSockOpt));
401ef382
SN
128#ifdef USE_THREADS
129 init_socktype = 1;
130 }
131#endif
68dc0745 132#endif /* USE_SOCKETS_AS_HANDLES */
0a753a76 133}
134
135
136#ifndef USE_SOCKETS_AS_HANDLES
f3986ebb 137#undef fdopen
68dc0745 138FILE *
f3986ebb 139my_fdopen(int fd, char *mode)
0a753a76 140{
68dc0745 141 FILE *fp;
142 char sockbuf[256];
143 int optlen = sizeof(sockbuf);
144 int retval;
0a753a76 145
326b05e3 146 if (!wsock_started)
fb73857a 147 return(fdopen(fd, mode));
0a753a76 148
326b05e3
GS
149 retval = getsockopt((SOCKET)fd, SOL_SOCKET, SO_TYPE, sockbuf, &optlen);
150 if(retval == SOCKET_ERROR && WSAGetLastError() == WSAENOTSOCK) {
fb73857a 151 return(fdopen(fd, mode));
68dc0745 152 }
0a753a76 153
68dc0745 154 /*
155 * If we get here, then fd is actually a socket.
156 */
fc36a67e 157 Newz(1310, fp, 1, FILE);
68dc0745 158 if(fp == NULL) {
159 errno = ENOMEM;
160 return NULL;
161 }
0a753a76 162
68dc0745 163 fp->_file = fd;
164 if(*mode == 'r')
165 fp->_flag = _IOREAD;
166 else
167 fp->_flag = _IOWRT;
168
169 return fp;
0a753a76 170}
68dc0745 171#endif /* USE_SOCKETS_AS_HANDLES */
0a753a76 172
173
68dc0745 174u_long
175win32_htonl(u_long hostlong)
0a753a76 176{
326b05e3
GS
177 StartSockets();
178 return htonl(hostlong);
0a753a76 179}
180
68dc0745 181u_short
182win32_htons(u_short hostshort)
0a753a76 183{
326b05e3
GS
184 StartSockets();
185 return htons(hostshort);
0a753a76 186}
187
68dc0745 188u_long
189win32_ntohl(u_long netlong)
0a753a76 190{
326b05e3
GS
191 StartSockets();
192 return ntohl(netlong);
0a753a76 193}
194
68dc0745 195u_short
196win32_ntohs(u_short netshort)
0a753a76 197{
326b05e3
GS
198 StartSockets();
199 return ntohs(netshort);
0a753a76 200}
201
202
0a753a76 203
68dc0745 204SOCKET
205win32_accept(SOCKET s, struct sockaddr *addr, int *addrlen)
0a753a76 206{
68dc0745 207 SOCKET r;
0a753a76 208
326b05e3 209 SOCKET_TEST((r = accept(TO_SOCKET(s), addr, addrlen)), INVALID_SOCKET);
68dc0745 210 return OPEN_SOCKET(r);
0a753a76 211}
212
68dc0745 213int
214win32_bind(SOCKET s, const struct sockaddr *addr, int addrlen)
0a753a76 215{
68dc0745 216 int r;
0a753a76 217
326b05e3 218 SOCKET_TEST_ERROR(r = bind(TO_SOCKET(s), addr, addrlen));
68dc0745 219 return r;
0a753a76 220}
221
68dc0745 222int
223win32_connect(SOCKET s, const struct sockaddr *addr, int addrlen)
0a753a76 224{
68dc0745 225 int r;
0a753a76 226
326b05e3 227 SOCKET_TEST_ERROR(r = connect(TO_SOCKET(s), addr, addrlen));
68dc0745 228 return r;
0a753a76 229}
230
231
68dc0745 232int
233win32_getpeername(SOCKET s, struct sockaddr *addr, int *addrlen)
0a753a76 234{
68dc0745 235 int r;
0a753a76 236
326b05e3 237 SOCKET_TEST_ERROR(r = getpeername(TO_SOCKET(s), addr, addrlen));
68dc0745 238 return r;
0a753a76 239}
240
68dc0745 241int
242win32_getsockname(SOCKET s, struct sockaddr *addr, int *addrlen)
0a753a76 243{
68dc0745 244 int r;
0a753a76 245
326b05e3 246 SOCKET_TEST_ERROR(r = getsockname(TO_SOCKET(s), addr, addrlen));
68dc0745 247 return r;
0a753a76 248}
249
68dc0745 250int
251win32_getsockopt(SOCKET s, int level, int optname, char *optval, int *optlen)
0a753a76 252{
68dc0745 253 int r;
0a753a76 254
326b05e3 255 SOCKET_TEST_ERROR(r = getsockopt(TO_SOCKET(s), level, optname, optval, optlen));
68dc0745 256 return r;
0a753a76 257}
258
2d7a9237 259int
68dc0745 260win32_ioctlsocket(SOCKET s, long cmd, u_long *argp)
0a753a76 261{
68dc0745 262 int r;
0a753a76 263
326b05e3 264 SOCKET_TEST_ERROR(r = ioctlsocket(TO_SOCKET(s), cmd, argp));
68dc0745 265 return r;
0a753a76 266}
267
68dc0745 268int
269win32_listen(SOCKET s, int backlog)
0a753a76 270{
68dc0745 271 int r;
0a753a76 272
326b05e3 273 SOCKET_TEST_ERROR(r = listen(TO_SOCKET(s), backlog));
68dc0745 274 return r;
0a753a76 275}
276
68dc0745 277int
278win32_recv(SOCKET s, char *buf, int len, int flags)
0a753a76 279{
68dc0745 280 int r;
0a753a76 281
326b05e3 282 SOCKET_TEST_ERROR(r = recv(TO_SOCKET(s), buf, len, flags));
68dc0745 283 return r;
0a753a76 284}
285
68dc0745 286int
287win32_recvfrom(SOCKET s, char *buf, int len, int flags, struct sockaddr *from, int *fromlen)
0a753a76 288{
68dc0745 289 int r;
e4449fe1 290 int frombufsize = *fromlen;
0a753a76 291
326b05e3 292 SOCKET_TEST_ERROR(r = recvfrom(TO_SOCKET(s), buf, len, flags, from, fromlen));
e4449fe1
GS
293 /* Winsock's recvfrom() only returns a valid 'from' when the socket
294 * is connectionless. Perl expects a valid 'from' for all types
295 * of sockets, so go the extra mile.
296 */
297 if (r != SOCKET_ERROR && frombufsize == *fromlen)
298 (void)win32_getpeername(s, from, fromlen);
68dc0745 299 return r;
0a753a76 300}
301
68dc0745 302/* select contributed by Vincent R. Slyngstad (vrs@ibeam.intel.com) */
303int
55d25626 304win32_select(int nfds, Perl_fd_set* rd, Perl_fd_set* wr, Perl_fd_set* ex, const struct timeval* timeout)
0a753a76 305{
55d25626
GS
306 int r;
307#ifdef USE_SOCKETS_AS_HANDLES
308 Perl_fd_set dummy;
68dc0745 309 int i, fd, bit, offset;
55d25626 310 FD_SET nrd, nwr, nex, *prd, *pwr, *pex;
0a753a76 311
7e3be867
GS
312 /* winsock seems incapable of dealing with all three null fd_sets,
313 * so do the (millisecond) sleep as a special case
314 */
315 if (!(rd || wr || ex)) {
87c2f9c4
BZ
316 if (timeout)
317 Sleep(timeout->tv_sec * 1000 +
318 timeout->tv_usec / 1000); /* do the best we can */
319 else
320 Sleep(UINT_MAX);
7e3be867
GS
321 return 0;
322 }
f2bb5751 323 StartSockets();
55d25626 324 PERL_FD_ZERO(&dummy);
68dc0745 325 if (!rd)
326 rd = &dummy, prd = NULL;
327 else
328 prd = &nrd;
329 if (!wr)
330 wr = &dummy, pwr = NULL;
331 else
332 pwr = &nwr;
333 if (!ex)
334 ex = &dummy, pex = NULL;
335 else
336 pex = &nex;
0a753a76 337
68dc0745 338 FD_ZERO(&nrd);
339 FD_ZERO(&nwr);
340 FD_ZERO(&nex);
341 for (i = 0; i < nfds; i++) {
342 fd = TO_SOCKET(i);
55d25626 343 if (PERL_FD_ISSET(i,rd))
68dc0745 344 FD_SET(fd, &nrd);
55d25626 345 if (PERL_FD_ISSET(i,wr))
68dc0745 346 FD_SET(fd, &nwr);
55d25626 347 if (PERL_FD_ISSET(i,ex))
68dc0745 348 FD_SET(fd, &nex);
349 }
350
326b05e3 351 SOCKET_TEST_ERROR(r = select(nfds, prd, pwr, pex, timeout));
0a753a76 352
68dc0745 353 for (i = 0; i < nfds; i++) {
354 fd = TO_SOCKET(i);
55d25626
GS
355 if (PERL_FD_ISSET(i,rd) && !FD_ISSET(fd, &nrd))
356 PERL_FD_CLR(i,rd);
357 if (PERL_FD_ISSET(i,wr) && !FD_ISSET(fd, &nwr))
358 PERL_FD_CLR(i,wr);
359 if (PERL_FD_ISSET(i,ex) && !FD_ISSET(fd, &nex))
360 PERL_FD_CLR(i,ex);
68dc0745 361 }
55d25626
GS
362#else
363 SOCKET_TEST_ERROR(r = select(nfds, rd, wr, ex, timeout));
364#endif
68dc0745 365 return r;
0a753a76 366}
367
68dc0745 368int
369win32_send(SOCKET s, const char *buf, int len, int flags)
0a753a76 370{
68dc0745 371 int r;
0a753a76 372
326b05e3 373 SOCKET_TEST_ERROR(r = send(TO_SOCKET(s), buf, len, flags));
68dc0745 374 return r;
0a753a76 375}
376
68dc0745 377int
378win32_sendto(SOCKET s, const char *buf, int len, int flags,
379 const struct sockaddr *to, int tolen)
0a753a76 380{
68dc0745 381 int r;
0a753a76 382
326b05e3 383 SOCKET_TEST_ERROR(r = sendto(TO_SOCKET(s), buf, len, flags, to, tolen));
68dc0745 384 return r;
0a753a76 385}
386
68dc0745 387int
388win32_setsockopt(SOCKET s, int level, int optname, const char *optval, int optlen)
0a753a76 389{
68dc0745 390 int r;
0a753a76 391
326b05e3 392 SOCKET_TEST_ERROR(r = setsockopt(TO_SOCKET(s), level, optname, optval, optlen));
68dc0745 393 return r;
0a753a76 394}
395
68dc0745 396int
397win32_shutdown(SOCKET s, int how)
0a753a76 398{
68dc0745 399 int r;
0a753a76 400
326b05e3 401 SOCKET_TEST_ERROR(r = shutdown(TO_SOCKET(s), how));
68dc0745 402 return r;
0a753a76 403}
404
3a25acb4
GS
405int
406win32_closesocket(SOCKET s)
407{
408 int r;
409
410 SOCKET_TEST_ERROR(r = closesocket(TO_SOCKET(s)));
411 return r;
412}
413
68dc0745 414SOCKET
415win32_socket(int af, int type, int protocol)
0a753a76 416{
68dc0745 417 SOCKET s;
0a753a76 418
419#ifndef USE_SOCKETS_AS_HANDLES
326b05e3 420 SOCKET_TEST(s = socket(af, type, protocol), INVALID_SOCKET);
0a753a76 421#else
3a25acb4 422 StartSockets();
326b05e3
GS
423 if((s = socket(af, type, protocol)) == INVALID_SOCKET)
424 errno = WSAGetLastError();
68dc0745 425 else
426 s = OPEN_SOCKET(s);
427#endif /* USE_SOCKETS_AS_HANDLES */
0a753a76 428
68dc0745 429 return s;
0a753a76 430}
431
fb73857a 432#undef fclose
433int
434my_fclose (FILE *pf)
435{
326b05e3
GS
436 int osf, retval;
437 if (!wsock_started) /* No WinSock? */
438 return(fclose(pf)); /* Then not a socket. */
439 osf = TO_SOCKET(fileno(pf));/* Get it now before it's gone! */
440 retval = fclose(pf); /* Must fclose() before closesocket() */
441 if (osf != -1
442 && closesocket(osf) == SOCKET_ERROR
443 && WSAGetLastError() != WSAENOTSOCK)
444 {
445 return EOF;
446 }
447 return retval;
fb73857a 448}
449
68dc0745 450struct hostent *
451win32_gethostbyaddr(const char *addr, int len, int type)
0a753a76 452{
68dc0745 453 struct hostent *r;
0a753a76 454
326b05e3 455 SOCKET_TEST(r = gethostbyaddr(addr, len, type), NULL);
68dc0745 456 return r;
0a753a76 457}
458
68dc0745 459struct hostent *
460win32_gethostbyname(const char *name)
0a753a76 461{
68dc0745 462 struct hostent *r;
0a753a76 463
326b05e3 464 SOCKET_TEST(r = gethostbyname(name), NULL);
68dc0745 465 return r;
0a753a76 466}
467
68dc0745 468int
469win32_gethostname(char *name, int len)
0a753a76 470{
68dc0745 471 int r;
0a753a76 472
326b05e3 473 SOCKET_TEST_ERROR(r = gethostname(name, len));
68dc0745 474 return r;
0a753a76 475}
476
68dc0745 477struct protoent *
478win32_getprotobyname(const char *name)
0a753a76 479{
68dc0745 480 struct protoent *r;
0a753a76 481
326b05e3 482 SOCKET_TEST(r = getprotobyname(name), NULL);
68dc0745 483 return r;
0a753a76 484}
485
68dc0745 486struct protoent *
487win32_getprotobynumber(int num)
0a753a76 488{
68dc0745 489 struct protoent *r;
0a753a76 490
326b05e3 491 SOCKET_TEST(r = getprotobynumber(num), NULL);
68dc0745 492 return r;
0a753a76 493}
494
68dc0745 495struct servent *
496win32_getservbyname(const char *name, const char *proto)
0a753a76 497{
c5be433b 498 dTHXo;
68dc0745 499 struct servent *r;
c53bd28a 500
326b05e3 501 SOCKET_TEST(r = getservbyname(name, proto), NULL);
68dc0745 502 if (r) {
503 r = win32_savecopyservent(&myservent, r, proto);
504 }
505 return r;
0a753a76 506}
507
68dc0745 508struct servent *
509win32_getservbyport(int port, const char *proto)
0a753a76 510{
c5be433b 511 dTHXo;
68dc0745 512 struct servent *r;
0a753a76 513
326b05e3 514 SOCKET_TEST(r = getservbyport(port, proto), NULL);
68dc0745 515 if (r) {
516 r = win32_savecopyservent(&myservent, r, proto);
517 }
518 return r;
0a753a76 519}
520
f998180f
GS
521int
522win32_ioctl(int i, unsigned int u, char *data)
523{
c5be433b 524 dTHXo;
f998180f
GS
525 u_long argp = (u_long)data;
526 int retval;
527
528 if (!wsock_started) {
4f63d024 529 Perl_croak_nocontext("ioctl implemented only on sockets");
f998180f
GS
530 /* NOTREACHED */
531 }
532
533 retval = ioctlsocket(TO_SOCKET(i), (long)u, &argp);
534 if (retval == SOCKET_ERROR) {
535 if (WSAGetLastError() == WSAENOTSOCK) {
4f63d024 536 Perl_croak_nocontext("ioctl implemented only on sockets");
f998180f
GS
537 /* NOTREACHED */
538 }
539 errno = WSAGetLastError();
540 }
541 return retval;
542}
543
68dc0745 544char FAR *
545win32_inet_ntoa(struct in_addr in)
0a753a76 546{
326b05e3
GS
547 StartSockets();
548 return inet_ntoa(in);
0a753a76 549}
550
68dc0745 551unsigned long
552win32_inet_addr(const char FAR *cp)
0a753a76 553{
326b05e3
GS
554 StartSockets();
555 return inet_addr(cp);
0a753a76 556}
68dc0745 557
558/*
559 * Networking stubs
560 */
0a753a76 561
68dc0745 562void
563win32_endhostent()
0a753a76 564{
c5be433b 565 dTHXo;
4f63d024 566 Perl_croak_nocontext("endhostent not implemented!\n");
0a753a76 567}
568
68dc0745 569void
570win32_endnetent()
0a753a76 571{
c5be433b 572 dTHXo;
4f63d024 573 Perl_croak_nocontext("endnetent not implemented!\n");
0a753a76 574}
575
68dc0745 576void
577win32_endprotoent()
0a753a76 578{
c5be433b 579 dTHXo;
4f63d024 580 Perl_croak_nocontext("endprotoent not implemented!\n");
0a753a76 581}
582
68dc0745 583void
584win32_endservent()
0a753a76 585{
c5be433b 586 dTHXo;
4f63d024 587 Perl_croak_nocontext("endservent not implemented!\n");
0a753a76 588}
589
590
68dc0745 591struct netent *
592win32_getnetent(void)
0a753a76 593{
c5be433b 594 dTHXo;
4f63d024 595 Perl_croak_nocontext("getnetent not implemented!\n");
68dc0745 596 return (struct netent *) NULL;
0a753a76 597}
598
68dc0745 599struct netent *
600win32_getnetbyname(char *name)
0a753a76 601{
c5be433b 602 dTHXo;
4f63d024 603 Perl_croak_nocontext("getnetbyname not implemented!\n");
68dc0745 604 return (struct netent *)NULL;
0a753a76 605}
606
68dc0745 607struct netent *
608win32_getnetbyaddr(long net, int type)
0a753a76 609{
c5be433b 610 dTHXo;
4f63d024 611 Perl_croak_nocontext("getnetbyaddr not implemented!\n");
68dc0745 612 return (struct netent *)NULL;
0a753a76 613}
614
68dc0745 615struct protoent *
616win32_getprotoent(void)
0a753a76 617{
c5be433b 618 dTHXo;
4f63d024 619 Perl_croak_nocontext("getprotoent not implemented!\n");
68dc0745 620 return (struct protoent *) NULL;
0a753a76 621}
622
68dc0745 623struct servent *
624win32_getservent(void)
0a753a76 625{
c5be433b 626 dTHXo;
4f63d024 627 Perl_croak_nocontext("getservent not implemented!\n");
68dc0745 628 return (struct servent *) NULL;
0a753a76 629}
630
68dc0745 631void
632win32_sethostent(int stayopen)
0a753a76 633{
c5be433b 634 dTHXo;
4f63d024 635 Perl_croak_nocontext("sethostent not implemented!\n");
0a753a76 636}
637
638
68dc0745 639void
640win32_setnetent(int stayopen)
0a753a76 641{
c5be433b 642 dTHXo;
4f63d024 643 Perl_croak_nocontext("setnetent not implemented!\n");
0a753a76 644}
645
646
68dc0745 647void
648win32_setprotoent(int stayopen)
0a753a76 649{
c5be433b 650 dTHXo;
4f63d024 651 Perl_croak_nocontext("setprotoent not implemented!\n");
0a753a76 652}
653
654
68dc0745 655void
656win32_setservent(int stayopen)
0a753a76 657{
c5be433b 658 dTHXo;
4f63d024 659 Perl_croak_nocontext("setservent not implemented!\n");
0a753a76 660}
661
137443ea 662static struct servent*
663win32_savecopyservent(struct servent*d, struct servent*s, const char *proto)
664{
665 d->s_name = s->s_name;
666 d->s_aliases = s->s_aliases;
667 d->s_port = s->s_port;
0af56dfe 668#ifndef __BORLANDC__ /* Buggy on Win95 and WinNT-with-Borland-WSOCK */
137443ea 669 if (!IsWin95() && s->s_proto && strlen(s->s_proto))
670 d->s_proto = s->s_proto;
0af56dfe
GS
671 else
672#endif
c69f6586 673 if (proto && strlen(proto))
137443ea 674 d->s_proto = (char *)proto;
675 else
676 d->s_proto = "tcp";
677
678 return d;
679}
680
0a753a76 681