Portability fixes.
[wine] / dlls / wsock32 / socket.c
1 /*
2  * WSOCK32 specific functions
3  *
4  * Copyright (C) 1993,1994,1996,1997 John Brezak, Erik Bos, Alex Korobka.
5  */
6
7 #include "config.h"
8
9 #include <sys/types.h>
10 #include "windef.h"
11 #include "debugtools.h"
12 #include "winsock2.h"
13 #include "winnt.h"
14 #include "wscontrol.h"
15 #include <ctype.h>
16 #include <sys/ioctl.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <unistd.h>
20 #ifdef HAVE_SYS_SOCKIO_H
21 # include <sys/sockio.h>
22 #endif
23 #ifdef HAVE_NET_IF_H
24 # include <net/if.h>
25 #endif
26
27 DEFAULT_DEBUG_CHANNEL(winsock);
28
29
30 /***********************************************************************
31  *      WsControl()
32  *
33  * WsControl seems to be an undocumented Win95 function. A lot of 
34  * discussion about WsControl can be found on the net, e.g.
35  * Subject:      Re: WSOCK32.DLL WsControl Exported Function
36  * From:         "Peter Rindfuss" <rindfuss-s@medea.wz-berlin.de>
37  * Date:         1997/08/17
38  *
39  * WSCNTL_TCPIP_QUERY_INFO option is partially implemeted based
40  * on observing the behaviour of WsControl with an app in 
41  * Windows 98.  It is not fully implemented, and there could
42  * be (are?) errors due to incorrect assumptions made.
43  *
44  *
45  * WsControl returns WSCTL_SUCCESS on success.
46  * STATUS_BUFFER_TOO_SMALL is returned if the output buffer length
47  * (*pcbResponseInfoLen) is too small, otherwise errors return -1.
48  *
49  * It doesn't seem to generate errors that can be retrieved by 
50  * WSAGetLastError().
51  *
52  */
53
54 DWORD WINAPI WsControl(DWORD protocoll,
55                        DWORD action,
56                        LPVOID pRequestInfo,
57                        LPDWORD pcbRequestInfoLen,
58                        LPVOID pResponseInfo,
59                        LPDWORD pcbResponseInfoLen) 
60 {
61    /* Get the command structure into a pointer we can use,
62       rather than void */
63    TDIObjectID *pcommand = (TDIObjectID *)pRequestInfo;
64   
65    TRACE ("   WsControl TOI_ID=>0x%lx<, {TEI_ENTITY=0x%lx, TEI_INSTANCE=0x%lx}, TOI_CLASS=0x%lx, TOI_TYPE=0x%lx\n",
66           pcommand->toi_id, pcommand->toi_entity.tei_entity, pcommand->toi_entity.tei_instance,
67           pcommand->toi_class, pcommand->toi_type );
68   
69
70
71    switch (action) 
72    {
73       case WSCNTL_TCPIP_QUERY_INFO: 
74       {
75          switch (pcommand->toi_id)
76          {
77             /* 
78                ENTITY_LIST_ID seems to get number of adapters in the system.
79                (almost like an index to be used when calling other WsControl options)
80             */
81             case ENTITY_LIST_ID: 
82             {
83                TDIEntityID *baseptr = pResponseInfo;
84                int numInt = 0, i;
85
86                if (pcommand->toi_class != INFO_CLASS_GENERIC &&
87                    pcommand->toi_type != INFO_TYPE_PROVIDER) 
88                { 
89                   FIXME ("Unexpected Option for ENTITY_LIST_ID request -> toi_class=0x%lx, toi_type=0x%lx\n",
90                        pcommand->toi_class, pcommand->toi_type);
91                   return (WSAEOPNOTSUPP); 
92                }
93            
94                numInt = WSCNTL_GetInterfaceCount(); 
95                if (numInt < 0)
96                {
97                   ERR ("Unable to open /proc filesystem to determine number of network interfaces!\n");
98                   return (-1); 
99                }
100
101                if (*pcbResponseInfoLen < sizeof(TDIEntityID)*(numInt*2) ) 
102                {
103                   return (STATUS_BUFFER_TOO_SMALL);
104                }
105            
106                /* 0 it out first */
107                memset(baseptr, 0, sizeof(TDIEntityID)*(numInt*2)); 
108                
109                for (i=0; i<numInt; i++)
110                {
111                   /* tei_instance is an network interface identifier.
112                      I'm not quite sure what the difference is between tei_entity values of 
113                      CL_NL_ENTITY and IF_ENTITY */
114                   baseptr->tei_entity = CL_NL_ENTITY;  baseptr->tei_instance = i; baseptr++;
115                   baseptr->tei_entity = IF_ENTITY;     baseptr->tei_instance = i; baseptr++; 
116                }
117
118                /* Calculate size of out buffer */
119                *pcbResponseInfoLen = sizeof(TDIEntityID)*(numInt*2);
120             
121                break;
122             }
123           
124           
125             /* ENTITY_TYPE_ID is used to obtain simple information about a 
126                network card, such as MAC Address, description, interface type,
127                number of network addresses, etc. */
128             case ENTITY_TYPE_ID:  /* ALSO: IP_MIB_STATS_ID */
129             {
130                if (pcommand->toi_class == INFO_CLASS_GENERIC && pcommand->toi_type == INFO_TYPE_PROVIDER) 
131                {
132                   if (pcommand->toi_entity.tei_entity == IF_ENTITY)
133                   {
134                      * ((ULONG *)pResponseInfo) = IF_MIB; 
135
136                      /* Calculate size of out buffer */
137                      *pcbResponseInfoLen = sizeof (ULONG);
138
139                   }
140                   else if (pcommand->toi_entity.tei_entity == CL_NL_ENTITY) 
141                   {
142                      * ((ULONG *)pResponseInfo) = CL_NL_IP;  
143
144                      /* Calculate size of out buffer */
145                      *pcbResponseInfoLen = sizeof (ULONG); 
146                   }
147                }
148                else if (pcommand->toi_class == INFO_CLASS_PROTOCOL &&
149                         pcommand->toi_type == INFO_TYPE_PROVIDER)
150                {
151                   if (pcommand->toi_entity.tei_entity == IF_ENTITY)
152                   {
153                      /* In this case, we are requesting specific information about a 
154                         a particular network adapter. (MAC Address, speed, data transmitted/received,
155                         etc.)
156                      */ 
157                      IFEntry *IntInfo = (IFEntry *) pResponseInfo;
158                      char ifName[512];
159                      struct ifreq ifInfo;
160                      int sock;
161
162                      
163                      if (!WSCNTL_GetInterfaceName(pcommand->toi_entity.tei_instance, ifName))
164                      {
165                         ERR ("Unable to parse /proc filesystem!\n");
166                         return (-1);
167                      }
168                
169                      /* Get a socket so that we can use ioctl */
170                      if ( (sock = socket (AF_INET, SOCK_DGRAM, 0)) < 0)
171                      {
172                         ERR ("Error creating socket!\n");
173                         return (-1);
174                      }
175                    
176                      /* 0 out return structure first */
177                      memset (IntInfo, 0, sizeof(IFEntry));
178                      
179                      /* Interface ID */
180                      IntInfo->if_index = pcommand->toi_entity.tei_instance;
181                      
182                      /* MAC Address - Let's try to do this in a cross-platform way... */
183                      #if defined(SIOCGIFHWADDR) /* Linux */
184                         strcpy(ifInfo.ifr_name, ifName);
185                         if (ioctl(sock, SIOCGIFHWADDR, &ifInfo) < 0)
186                         {
187                            ERR ("Error obtaining MAC Address!\n");
188                            close(sock);
189                            return (-1);
190                         }
191                         else
192                         {
193                            /* FIXME: Is it correct to assume size of 6? */
194                            memcpy(IntInfo->if_physaddr, ifInfo.ifr_hwaddr.sa_data, 6);
195                            IntInfo->if_physaddrlen=6;
196                         }
197                      #elif defined(SIOCGENADDR) /* Solaris */
198                         if (ioctl(sock, SIOCGENADDR, &ifInfo) < 0)
199                         {
200                            ERR ("Error obtaining MAC Address!\n");
201                            close(sock);
202                            return (-1);
203                         }
204                         else
205                         {
206                            /* FIXME: Is it correct to assume size of 6? */
207                            memcpy(IntInfo->if_physaddr, ifInfo.ifr_enaddr, 6);
208                            IntInfo->if_physaddrlen=6;
209                         }
210                      #else
211                         memset (IntInfo->if_physaddr, 0, 6);
212                         ERR ("Unable to determine MAC Address on your platform!\n");
213                      #endif
214
215                      
216                      /* Interface name and length */
217                      strcpy (IntInfo->if_descr, ifName);
218                      IntInfo->if_descrlen= strlen (IntInfo->if_descr);
219                      
220                      /* Obtain bytes transmitted/received for interface */
221                      if ( (WSCNTL_GetTransRecvStat(pcommand->toi_entity.tei_instance, 
222                            &IntInfo->if_inoctets, &IntInfo->if_outoctets)) < 0)
223                      {
224                         ERR ("Error obtaining transmit/receive stats for the network interface!\n");
225                         close(sock);
226                         return (-1);
227                      }
228                      
229                      
230                      /* FIXME: How should the below be properly calculated? ******************/
231                      IntInfo->if_type =  0x6; /* Ethernet (?) */
232                      IntInfo->if_speed = 1000000; /* Speed of interface (bits per second?) */
233                      /************************************************************************/
234
235                      close(sock);
236                      *pcbResponseInfoLen = sizeof (IFEntry) + IntInfo->if_descrlen; 
237                   }
238                   else if (pcommand->toi_entity.tei_entity == CL_NL_ENTITY) 
239                   {
240                      IPSNMPInfo *infoStruc = (IPSNMPInfo *) pResponseInfo;
241                      int numInt;
242                      
243                      /* This case is used to obtain general statistics about the 
244                         network */
245                      
246                      if (*pcbResponseInfoLen < sizeof(IPSNMPInfo) )
247                      {
248                         return (STATUS_BUFFER_TOO_SMALL);
249                      }
250                      else
251                      {
252                         /* 0 it out first */
253                         memset(infoStruc, 0, sizeof(IPSNMPInfo));
254             
255                         /* Get the number of interfaces */
256                         numInt = WSCNTL_GetInterfaceCount(); 
257                         if (numInt < 0)
258                         {
259                            ERR ("Unable to open /proc filesystem to determine number of network interfaces!\n");
260                            return (-1); 
261                         }
262
263                         infoStruc->ipsi_numif           = numInt; /* # of interfaces */
264                         infoStruc->ipsi_numaddr         = numInt; /* # of addresses */
265                         infoStruc->ipsi_numroutes       = numInt; /* # of routes ~ FIXME - Is this right? */
266
267                         /* FIXME: How should the below be properly calculated? ******************/
268                         infoStruc->ipsi_forwarding      = 0x0;
269                         infoStruc->ipsi_defaultttl      = 0x0;
270                         infoStruc->ipsi_inreceives      = 0x0;
271                         infoStruc->ipsi_inhdrerrors     = 0x0;
272                         infoStruc->ipsi_inaddrerrors    = 0x0;
273                         infoStruc->ipsi_forwdatagrams   = 0x0;
274                         infoStruc->ipsi_inunknownprotos = 0x0;
275                         infoStruc->ipsi_indiscards      = 0x0;
276                         infoStruc->ipsi_indelivers      = 0x0;
277                         infoStruc->ipsi_outrequests     = 0x0;
278                         infoStruc->ipsi_routingdiscards = 0x0;
279                         infoStruc->ipsi_outdiscards     = 0x0;
280                         infoStruc->ipsi_outnoroutes     = 0x0;
281                         infoStruc->ipsi_reasmtimeout    = 0x0;
282                         infoStruc->ipsi_reasmreqds      = 0x0;
283                         infoStruc->ipsi_reasmoks        = 0x0;
284                         infoStruc->ipsi_reasmfails      = 0x0;
285                         infoStruc->ipsi_fragoks         = 0x0;
286                         infoStruc->ipsi_fragfails       = 0x0;
287                         infoStruc->ipsi_fragcreates     = 0x0;
288                         /************************************************************************/
289                       
290                         /* Calculate size of out buffer */
291                         *pcbResponseInfoLen = sizeof(IPSNMPInfo);
292                      }
293                   }
294                }
295                else
296                {
297                   FIXME ("Unexpected Option for ENTITY_TYPE_ID request -> toi_class=0x%lx, toi_type=0x%lx\n",
298                        pcommand->toi_class, pcommand->toi_type);
299                   
300                   return (WSAEOPNOTSUPP); 
301                }
302
303                break;
304             }
305
306
307             /* IP_MIB_ADDRTABLE_ENTRY_ID is used to obtain more detailed information about a 
308                particular network adapter */
309             case IP_MIB_ADDRTABLE_ENTRY_ID: 
310             {
311                IPAddrEntry *baseIPInfo = (IPAddrEntry *) pResponseInfo;
312                char ifName[512];
313                struct ifreq ifInfo;
314                int sock;
315
316                if (*pcbResponseInfoLen < sizeof(IPAddrEntry))
317                {
318                   return (STATUS_BUFFER_TOO_SMALL); 
319                }
320                
321                if (!WSCNTL_GetInterfaceName(pcommand->toi_entity.tei_instance, ifName))
322                {
323                   ERR ("Unable to parse /proc filesystem!\n");
324                   return (-1);
325                }
326                
327                
328                /* Get a socket so we can use ioctl */
329                if ( (sock = socket (AF_INET, SOCK_DGRAM, 0)) < 0)
330                {
331                   ERR ("Error creating socket!\n");
332                   return (-1);
333                }
334                
335                /* 0 it out first */
336                memset(baseIPInfo, 0, sizeof(IPAddrEntry) );
337                
338                /* Interface Id */
339                baseIPInfo->iae_index     = pcommand->toi_entity.tei_instance;
340                
341                /* IP Address */
342                strcpy (ifInfo.ifr_name, ifName);
343                ifInfo.ifr_addr.sa_family = AF_INET;
344                if (ioctl(sock, SIOCGIFADDR, &ifInfo) < 0) 
345                {
346                   baseIPInfo->iae_addr = 0x0;
347                }
348                else
349                {
350                   struct ws_sockaddr_in *ipTemp = (struct ws_sockaddr_in *)&ifInfo.ifr_addr;
351                   baseIPInfo->iae_addr = ipTemp->sin_addr.S_un.S_addr;
352                }
353                
354                /* Broadcast Address */
355                strcpy (ifInfo.ifr_name, ifName);
356                if (ioctl(sock, SIOCGIFBRDADDR, &ifInfo) < 0)
357                {
358                   baseIPInfo->iae_bcastaddr = 0x0;
359                }
360                else
361                {
362                   struct ws_sockaddr_in *ipTemp = (struct ws_sockaddr_in *)&ifInfo.ifr_broadaddr;
363                   baseIPInfo->iae_bcastaddr = ipTemp->sin_addr.S_un.S_addr; 
364                }
365
366                /* Subnet Mask */
367                strcpy(ifInfo.ifr_name, ifName);
368                if (ioctl(sock, SIOCGIFNETMASK, &ifInfo) < 0)
369                {
370                   baseIPInfo->iae_mask = 0x0;
371                }
372                else
373                {
374                   /* Trying to avoid some compile problems across platforms.
375                      (Linux, FreeBSD, Solaris...) */
376                   #ifndef ifr_netmask
377                      #ifndef ifr_addr
378                         baseIPInfo->iae_mask = 0;
379                         ERR ("Unable to determine Netmask on your platform!\n");
380                      #else
381                         struct ws_sockaddr_in *ipTemp = (struct ws_sockaddr_in *)&ifInfo.ifr_addr;
382                         baseIPInfo->iae_mask = ipTemp->sin_addr.S_un.S_addr; 
383                      #endif
384                   #else  
385                      struct ws_sockaddr_in *ipTemp = (struct ws_sockaddr_in *)&ifInfo.ifr_netmask;
386                      baseIPInfo->iae_mask = ipTemp->sin_addr.S_un.S_addr; 
387                   #endif
388                }
389
390                /* FIXME: How should the below be properly calculated? ******************/
391                baseIPInfo->iae_reasmsize = 0x0;
392                baseIPInfo->iae_context   = 0x0;
393                baseIPInfo->iae_pad       = 0x0;
394                /************************************************************************/
395              
396                /* Calculate size of out buffer */
397                *pcbResponseInfoLen = sizeof(IPAddrEntry);
398                close(sock);
399                break;
400             }
401
402
403             default: 
404             {
405                FIXME ("Command ID Not Supported -> toi_id=0x%lx, toi_entity={tei_entity=0x%lx, tei_instance=0x%lx}, toi_class=0x%lx, toi_type=0x%lx\n",
406                        pcommand->toi_id, pcommand->toi_entity.tei_entity, pcommand->toi_entity.tei_instance, 
407                        pcommand->toi_class, pcommand->toi_type);
408               
409                return (WSAEOPNOTSUPP); 
410             }
411          }
412       
413          break;
414       }
415     
416       case WSCNTL_TCPIP_ICMP_ECHO:
417       {
418          unsigned int addr = *(unsigned int*)pRequestInfo;
419          #if 0
420             int timeout= *(unsigned int*)(inbuf+4);
421             short x1 = *(unsigned short*)(inbuf+8);
422             short sendbufsize = *(unsigned short*)(inbuf+10);
423             char x2 = *(unsigned char*)(inbuf+12);
424             char ttl = *(unsigned char*)(inbuf+13);
425             char service = *(unsigned char*)(inbuf+14);
426             char type= *(unsigned char*)(inbuf+15); /* 0x2: don't fragment*/
427          #endif      
428       
429          FIXME("(ICMP_ECHO) to 0x%08x stub \n", addr);
430          break;
431       }
432
433       default:
434       {
435          FIXME("Protocoll Not Supported -> protocoll=0x%lx, action=0x%lx, Request=%p, RequestLen=%p, Response=%p, ResponseLen=%p\n",
436                protocoll, action, pRequestInfo, pcbRequestInfoLen, pResponseInfo, pcbResponseInfoLen);
437       
438          return (WSAEOPNOTSUPP); 
439       }
440    }
441    
442    
443    return (WSCTL_SUCCESS); 
444 }
445
446
447
448 /* 
449   Helper function for WsControl - Get count of the number of interfaces
450   by parsing /proc filesystem.
451 */
452 int WSCNTL_GetInterfaceCount(void)
453 {
454    FILE *procfs;
455    char buf[512];  /* Size doesn't matter, something big */
456    int  intcnt=0;
457  
458  
459    /* Open /proc filesystem file for network devices */ 
460    procfs = fopen(PROCFS_NETDEV_FILE, "r");
461    if (!procfs) 
462    {
463       /* If we can't open the file, return an error */
464       return (-1);
465    }
466    
467    /* Omit first two lines, they are only headers */
468    fgets(buf, sizeof buf, procfs);      
469    fgets(buf, sizeof buf, procfs);
470
471    while (fgets(buf, sizeof buf, procfs)) 
472    {
473       /* Each line in the file represents a network interface */
474       intcnt++;
475    }
476
477    fclose(procfs);
478    return(intcnt);
479 }
480
481
482 /*
483    Helper function for WsControl - Get name of device from interface number
484    by parsing /proc filesystem.
485 */
486 int WSCNTL_GetInterfaceName(int intNumber, char *intName)
487 {
488    FILE *procfs;
489    char buf[512]; /* Size doesn't matter, something big */
490    int  i;
491
492    /* Open /proc filesystem file for network devices */ 
493    procfs = fopen(PROCFS_NETDEV_FILE, "r");
494    if (!procfs) 
495    {
496       /* If we can't open the file, return an error */
497       return (-1);
498    }
499    
500    /* Omit first two lines, they are only headers */
501    fgets(buf, sizeof(buf), procfs);     
502    fgets(buf, sizeof(buf), procfs);
503
504    for (i=0; i<intNumber; i++)
505    {
506       /* Skip the lines that don't interest us. */
507       fgets(buf, sizeof(buf), procfs);
508    }
509    fgets(buf, sizeof(buf), procfs); /* This is the line we want */
510
511    
512    /* Parse out the line, grabbing only the name of the device
513       to the intName variable 
514       
515       The Line comes in like this: (we only care about the device name)
516       lo:   21970 377 0 0 0 0 0 0 21970 377 0 0 0 0 0 0
517    */
518    i=0; 
519    while (isspace(buf[i])) /* Skip initial space(s) */
520    {
521       i++;
522    }
523
524    while (buf[i]) 
525    {
526       if (isspace(buf[i]))
527       {
528          break;
529       }
530       
531       if (buf[i] == ':')  /* FIXME: Not sure if this block (alias detection) works properly */
532       {
533          /* This interface could be an alias... */
534          int hold = i;
535          char *dotname = intName;
536          *intName++ = buf[i++];
537          
538          while (isdigit(buf[i]))
539          {
540             *intName++ = buf[i++];
541          }
542          
543          if (buf[i] != ':') 
544          {
545             /* ... It wasn't, so back up */
546             i = hold;
547             intName = dotname;
548          }
549  
550          if (buf[i] == '\0')
551          {
552             fclose(procfs);
553             return(FALSE);
554          }
555          
556          i++;
557          break;
558       }
559       
560       *intName++ = buf[i++];
561    }
562    *intName++ = '\0';
563
564    fclose(procfs);
565    return(TRUE);
566 }
567
568
569 /*
570    Helper function for WsControl - This function returns the bytes (octets) transmitted
571    and received for the supplied interface number from the /proc fs. 
572 */
573 int WSCNTL_GetTransRecvStat(int intNumber, unsigned long *transBytes, unsigned long *recvBytes)
574 {
575    FILE *procfs;
576    char buf[512], result[512]; /* Size doesn't matter, something big */
577    int  i, bufPos, resultPos;
578
579    /* Open /proc filesystem file for network devices */ 
580    procfs = fopen(PROCFS_NETDEV_FILE, "r");
581    if (!procfs) 
582    {
583       /* If we can't open the file, return an error */
584       return (-1);
585    }
586    
587    /* Omit first two lines, they are only headers */
588    fgets(buf, sizeof(buf), procfs);     
589    fgets(buf, sizeof(buf), procfs);
590
591    for (i=0; i<intNumber; i++)
592    {
593       /* Skip the lines that don't interest us. */
594       fgets(buf, sizeof(buf), procfs);
595    }
596    fgets(buf, sizeof(buf), procfs); /* This is the line we want */
597
598
599
600    /* Parse out the line, grabbing the number of bytes transmitted
601       and received on the interface.
602       
603       The Line comes in like this: (we care about columns 2 and 10)
604       lo:   21970 377 0 0 0 0 0 0 21970 377 0 0 0 0 0 0
605    */
606
607    /* Start at character 0 in the buffer */
608    bufPos=0;
609    
610    /* Skip initial space(s) */ 
611    while (isspace(buf[bufPos])) 
612       bufPos++;
613
614
615    /* Skip the name and its trailing spaces (if any) */
616    while (buf[bufPos]) 
617    {
618       if (isspace(buf[bufPos]))
619          break;
620  
621       if (buf[bufPos] == ':') /* Could be an alias */
622       {
623          int hold = bufPos;
624
625          while(isdigit (buf[bufPos]))
626             bufPos++;
627          if (buf[bufPos] != ':')
628             bufPos = hold;
629          if (buf[bufPos] == '\0')
630          {
631             fclose(procfs);
632             return(FALSE);
633          }
634          
635          bufPos++;
636          break;
637       }
638
639       bufPos++;
640    }
641    while (isspace(buf[bufPos]))
642       bufPos++;
643
644
645    /* This column (#2) is the number of bytes received. */
646    resultPos = 0;
647    while (!isspace(buf[bufPos]))
648    {
649       result[resultPos] = buf[bufPos];
650       result[resultPos+1]='\0';
651       resultPos++; bufPos++;
652    }
653    *recvBytes = strtoul (result, NULL, 10); /* convert string to unsigned long, using base 10 */
654
655    
656    /* Skip columns #3 to #9 (Don't need them) */
657    for  (i=0; i<7; i++)
658    {
659       while (isspace(buf[bufPos]))
660          bufPos++;
661       while (!isspace(buf[bufPos])) 
662          bufPos++;
663    }
664
665
666    /* This column (#10) is the number of bytes transmitted */
667    while (isspace(buf[bufPos]))
668        bufPos++;
669
670    resultPos = 0;
671    while (!isspace(buf[bufPos]))
672    {
673       result[resultPos] = buf[bufPos];
674       result[resultPos+1]='\0';
675       resultPos++; bufPos++;
676    }
677    *transBytes = strtoul (result, NULL, 10); /* convert string to unsigned long, using base 10 */
678
679
680    fclose(procfs);
681    return(TRUE);
682 }
683
684
685
686
687 /***********************************************************************
688  *       WS_s_perror         (WSOCK32.1108)
689  */
690 void WINAPI WS_s_perror(LPCSTR message)
691 {
692     FIXME("(%s): stub\n",message);
693     return;
694 }