]> icculus.org git repositories - taylor/freespace2.git/blob - src/inetfile/chttpget.cpp
nothing important
[taylor/freespace2.git] / src / inetfile / chttpget.cpp
1 /*
2  * Copyright (C) Volition, Inc. 1999.  All rights reserved.
3  *
4  * All source code herein is the property of Volition, Inc. You may not sell 
5  * or otherwise commercially exploit the source or things you created based on
6  * the source.
7  */
8
9 /*
10 * $Logfile: /Freespace2/code/Inetfile/Chttpget.cpp $
11 * $Revision$
12 * $Date$
13 * $Author$
14 *
15 * HTTP Client class (get only)
16 *
17 * $Log$
18 * Revision 1.8  2002/06/21 03:04:12  relnev
19 * nothing important
20 *
21 * Revision 1.7  2002/06/17 06:33:09  relnev
22 * ryan's struct patch for gcc 2.95
23 *
24 * Revision 1.6  2002/06/09 04:41:21  relnev
25 * added copyright header
26 *
27 * Revision 1.5  2002/06/02 04:26:34  relnev
28 * warning cleanup
29 *
30 * Revision 1.4  2002/05/26 20:32:24  theoddone33
31 * Fix some minor stuff
32 *
33 * Revision 1.3  2002/05/26 20:20:54  relnev
34 * unix.h: updated
35 *
36 * inetfile/: complete
37 *
38 * Revision 1.2  2002/05/07 03:16:45  theoddone33
39 * The Great Newline Fix
40 *
41 * Revision 1.1.1.1  2002/05/03 03:28:09  root
42 * Initial import.
43 *
44  * 
45  * 5     8/24/99 1:49a Dave
46  * Fixed client-side afterburner stuttering. Added checkbox for no version
47  * checking on PXO join. Made button info passing more friendly between
48  * client and server.
49  * 
50  * 4     8/22/99 1:19p Dave
51  * Fixed up http proxy code. Cleaned up scoring code. Reverse the order in
52  * which d3d cards are detected.
53  * 
54  * 21    8/21/99 6:33p Kevin
55  * Fixed Proxy Stuff
56  * 
57  * 20    8/21/99 6:48a Jeff
58  * Linux port
59  * 
60  * 19    8/20/99 3:01p Kevin
61  * Added support for Proxies (I hope!)
62  * 
63  * 18    8/15/99 6:38p Jeff
64  * fixed compile error
65  * 
66  * 17    8/15/99 6:26p Kevin
67  * 
68  * 16    4/14/99 1:20a Jeff
69  * fixed case mismatched #includes
70  * 
71  * 15    3/03/99 12:28a Nate
72  * sped up something or other when the connection is done
73  * 
74  * 14    2/03/99 4:20p Kevin
75  * Got multiplayer working with .mn3 files, and setup autodownloading
76  * 
77  * 13    1/27/99 5:49p Kevin
78  * 
79  * 12    1/27/99 5:38p Kevin
80  * 
81  * 11    12/30/98 12:15p Kevin
82  * Auto Mission Download system
83  * 
84  * 10    10/12/98 4:59p Kevin
85  * Added delay to thread when cancelled...
86  * 
87  * 9     10/12/98 4:49p Nate
88  * More fixes
89  * 
90  * 8     10/12/98 1:54p Nate
91  * Fixed bug
92  * 
93  * 7     10/12/98 11:30a Kevin
94  * More memory stuff
95  * 
96  * 6     10/08/98 12:59p Nate
97  * fixed cancel
98  * 
99  * 5     10/08/98 9:57a Kevin
100  * made transfer cancellable
101  * 
102  * 4     7/31/98 12:19p Nate
103  * Fixed http abort problem.
104  * 
105  * 3     7/31/98 11:57a Kevin
106  * Added new functions for getting state
107  * 
108  * 2     6/01/98 10:10a Kevin
109  * Added DLL connection interface and auto update DLL
110  * 
111  * 1     5/27/98 9:52a Kevin
112  * 
113  * 1     5/25/98 5:31p Kevin
114  * Initial version
115 *
116 * $NoKeywords: $
117 */
118
119 #ifndef PLAT_UNIX
120 // #define WIN32
121
122 // #ifdef WIN32
123 #include <windows.h>
124 #include <process.h>
125 // #endif
126 #else
127 #include <sys/types.h>
128 #include <sys/socket.h>
129 #include <netinet/in.h>
130 #include <arpa/inet.h>
131 #include <netdb.h>
132 #include <sys/ioctl.h>
133 #include <errno.h>
134 #include <sys/time.h>
135 #include <unistd.h>
136
137 #include "unix.h"
138
139 #endif
140
141 #include <string.h>
142 #include <stdio.h>
143 #include <stdlib.h>
144 #include <ctype.h>
145
146 #include "inetgetfile.h"
147 #include "chttpget.h"
148
149 #ifdef __LINUX__
150
151 inline void Sleep(int millis)
152 {
153         struct timeval tv;
154         tv.tv_sec = 0;
155         tv.tv_usec = millis*1000;
156         select(0,NULL,NULL,NULL,&tv);
157 }
158 #endif
159
160 #define NW_AGHBN_CANCEL         1
161 #define NW_AGHBN_LOOKUP         2
162 #define NW_AGHBN_READ           3
163
164 #ifndef __LINUX__
165 void __cdecl http_gethostbynameworker(void *parm);
166 #else
167 void *http_gethostbynameworker(void *parm);
168 #endif
169
170 int http_Asyncgethostbyname(unsigned int *ip,int command, char *hostname);
171
172 #ifndef __LINUX__
173 void HTTPObjThread( void * obj )
174 #else
175 void *HTTPObjThread( void * obj )
176 #endif
177 {
178         ((ChttpGet *)obj)->WorkerThread();
179         ((ChttpGet *)obj)->m_Aborted = true;
180         //OutputDebugString("http transfer exiting....\n");
181
182         #ifdef __LINUX__
183         return NULL;
184         #endif
185 }
186
187 void ChttpGet::AbortGet()
188 {
189 // #ifdef WIN32
190         OutputDebugString("Aborting....\n");
191 // #endif
192         m_Aborting = true;
193         while(!m_Aborted) Sleep(50); //Wait for the thread to end
194 // #ifdef WIN32
195         OutputDebugString("Aborted....\n");
196 // #endif
197 }
198
199 ChttpGet::ChttpGet(char *URL,char *localfile,char *proxyip,unsigned short proxyport)
200 {
201         m_ProxyEnabled = true;
202         m_ProxyIP = proxyip;
203         m_ProxyPort = proxyport;
204         GetFile(URL,localfile);
205 }
206
207 ChttpGet::ChttpGet(char *URL,char *localfile)
208 {
209         m_ProxyEnabled = false;
210         GetFile(URL,localfile);
211 }
212
213
214 void ChttpGet::GetFile(char *URL,char *localfile)
215 {
216         m_DataSock = INVALID_SOCKET;
217         m_iBytesIn = 0;
218         m_iBytesTotal = 0;
219         m_State = HTTP_STATE_STARTUP;;
220         m_Aborting = false;
221         m_Aborted = false;
222
223         strncpy(m_URL,URL,MAX_URL_LEN-1);
224         m_URL[MAX_URL_LEN-1] = 0;
225
226         LOCALFILE = fopen(localfile,"wb");
227         if(NULL == LOCALFILE)
228         {
229                 m_State = HTTP_STATE_CANT_WRITE_FILE;
230                 return;
231         }
232         m_DataSock = socket(AF_INET, SOCK_STREAM, 0);
233         if(INVALID_SOCKET == m_DataSock)
234         {
235                 m_State = HTTP_STATE_SOCKET_ERROR;
236                 return;
237         }
238         unsigned long arg;
239
240         arg = true;
241 //#ifndef __LINUX__
242 #ifndef PLAT_UNIX
243         ioctlsocket( m_DataSock, FIONBIO, &arg );
244 #else
245         ioctl( m_DataSock, FIONBIO, &arg );
246 #endif
247
248         char *pURL = URL;
249         if(strnicmp(URL,"http:",5)==0)
250         {
251                 pURL +=5;
252                 while(*pURL == '/')
253                 {
254                         pURL++;
255                 }
256         }
257         //There shouldn't be any : in this string
258         if(strchr(pURL,':'))
259         {
260                 m_State = HTTP_STATE_URL_PARSING_ERROR;
261                 return;
262         }
263         //read the filename by searching backwards for a /
264         //then keep reading until you find the first /
265         //when you found it, you have the host and dir
266         char *filestart = NULL;
267         char *dirstart = NULL;
268         for(int i = strlen(pURL);i>=0;i--)
269         {
270                 if(pURL[i]== '/')
271                 {
272                         if(!filestart)
273                         {
274                                 filestart = pURL+i+1;
275                                 dirstart = pURL+i+1;
276                                 strcpy(m_szFilename,filestart);
277                         }
278                         else
279                         {
280                                 dirstart = pURL+i+1;
281                         }
282                 }
283         }
284         if((dirstart==NULL) || (filestart==NULL))
285         {
286                 m_State = HTTP_STATE_URL_PARSING_ERROR;
287                 return;
288         }
289         else
290         {
291                 strcpy(m_szDir,dirstart);//,(filestart-dirstart));
292                 //m_szDir[(filestart-dirstart)] = NULL;
293                 strncpy(m_szHost,pURL,(dirstart-pURL));
294                 m_szHost[(dirstart-pURL)-1] = '\0';
295         }
296 // #ifdef WIN32
297         if(0==_beginthread(HTTPObjThread,0,this))
298         {
299                 m_State = HTTP_STATE_INTERNAL_ERROR;
300                 return;
301         }
302         /*
303 #elif defined(__LINUX__)
304         pthread_t thread;
305         if(!inet_LoadThreadLib())
306         {
307                 m_State = HTTP_STATE_INTERNAL_ERROR;
308                 return;
309         }
310         if(df_pthread_create(&thread,NULL,HTTPObjThread,this)!=0)
311         {
312                 m_State = HTTP_STATE_INTERNAL_ERROR;
313                 return;
314         }
315 #endif
316         */
317 }
318
319
320 ChttpGet::~ChttpGet()
321 {
322         if(m_DataSock != INVALID_SOCKET)
323         {
324                 shutdown(m_DataSock,2);
325 #ifndef __LINUX__
326                 closesocket(m_DataSock);
327 #else
328                 close(m_DataSock);
329 #endif
330         }
331 }
332
333 int ChttpGet::GetStatus()
334 {
335         return m_State;
336 }
337
338 unsigned int ChttpGet::GetBytesIn()
339 {
340         return m_iBytesIn;
341 }
342
343 unsigned int ChttpGet::GetTotalBytes()
344 {
345         return m_iBytesTotal;
346 }
347
348
349 void ChttpGet::WorkerThread()
350 {
351         char szCommand[1000];
352         char *p;
353         int irsp = 0;
354         ConnectSocket();
355         if(m_Aborting)
356         {
357                 fclose(LOCALFILE);
358                 return;
359         }
360         if(m_State != HTTP_STATE_CONNECTED)
361         {
362                 fclose(LOCALFILE);
363                 return;
364         }
365         sprintf(szCommand,"GET %s%s HTTP/1.1\nAccept: */*\nAccept-Encoding: deflate\nHost: %s\n\n\n",m_ProxyEnabled?"":"/",m_ProxyEnabled?m_URL:m_szDir,m_szHost);
366         send(m_DataSock,szCommand,strlen(szCommand),0);
367         p = GetHTTPLine();
368         if(strnicmp("HTTP/",p,5)==0)
369         {
370                 char *pcode;
371                 pcode = strchr(p,' ')+1;
372                 if(!pcode)
373                 {
374                         m_State = HTTP_STATE_UNKNOWN_ERROR;     
375                         fclose(LOCALFILE);
376                         return;
377
378                 }
379                 pcode[3] = '\0';
380                 irsp = atoi(pcode);
381
382                 if(irsp == 0)
383                 {
384                         m_State = HTTP_STATE_UNKNOWN_ERROR;     
385                         fclose(LOCALFILE);
386                         return;
387                 }
388                 if(irsp==200)
389                 {
390                         int idataready=0;
391                         do
392                         {
393                                 p = GetHTTPLine();
394                                 if(p==NULL)
395                                 {
396                                         m_State = HTTP_STATE_UNKNOWN_ERROR;     
397                                         fclose(LOCALFILE);
398                                         return;
399                                 }
400                                 if(*p=='\0')
401                                 {
402                                         idataready = 1;
403                                         break;
404                                 }
405                                 if(strnicmp(p,"Content-Length:",strlen("Content-Length:"))==0)
406                                 {
407                                         char *s = strchr(p,' ')+1;
408                                         p = s;
409                                         if(s)
410                                         {
411                                                 while(*s)
412                                                 {
413                                                         if(!isdigit(*s))
414                                                         {
415                                                                 *s='\0';
416                                                         }
417                                                         s++;
418                                                 };
419                                                 m_iBytesTotal = atoi(p);
420                                         }
421
422                                 }
423
424                                 Sleep(1);
425                         }while(!idataready);
426                 ReadDataChannel();
427                 return;
428                 }
429                 m_State = HTTP_STATE_FILE_NOT_FOUND;
430                 fclose(LOCALFILE);
431                 return;
432         }
433         else
434         {
435                 m_State = HTTP_STATE_UNKNOWN_ERROR;
436                 fclose(LOCALFILE);
437                 return;
438         }
439 }
440
441 int ChttpGet::ConnectSocket()
442 {
443         //HOSTENT *he;
444         unsigned int ip;
445         SERVENT *se;
446         SOCKADDR_IN hostaddr;
447         if(m_Aborting){
448                 return 0;
449         }
450         
451         ip = inet_addr((const char *)m_szHost);
452
453         int rcode = 0;
454         if(ip==INADDR_NONE)
455         {
456                 http_Asyncgethostbyname(&ip,NW_AGHBN_LOOKUP,m_szHost);          
457                 do
458                 {       
459                         if(m_Aborting)
460                         {
461                                 http_Asyncgethostbyname(&ip,NW_AGHBN_CANCEL,m_szHost);
462                                 return 0;
463                         }
464                         rcode = http_Asyncgethostbyname(&ip,NW_AGHBN_READ,m_szHost);
465
466                         Sleep(1);
467                 }while(rcode==0);
468         }
469         
470         if(rcode == -1)
471         {
472                 m_State = HTTP_STATE_HOST_NOT_FOUND;
473                 return 0;
474         }
475         //m_ControlSock
476         if(m_Aborting)
477                 return 0;
478         se = getservbyname("http", NULL);
479         if(m_Aborting)
480                 return 0;
481         if(se == NULL)
482         {
483                 hostaddr.sin_port = htons(80);
484         }
485         else
486         {
487                 hostaddr.sin_port = se->s_port;
488         }
489         hostaddr.sin_family = AF_INET;          
490         //ip = htonl(ip);
491         memcpy(&hostaddr.sin_addr,&ip,4);
492
493         if(m_ProxyEnabled)
494         {
495                 //This is on a proxy, so we need to make sure to connect to the proxy machine
496                 ip = inet_addr((const char *)m_ProxyIP);
497                                 
498                 if(ip==INADDR_NONE)
499                 {
500                         http_Asyncgethostbyname(&ip,NW_AGHBN_LOOKUP,m_ProxyIP);
501                         rcode = 0;
502                         do
503                         {       
504                                 if(m_Aborting)
505                                 {
506                                         http_Asyncgethostbyname(&ip,NW_AGHBN_CANCEL,m_ProxyIP);
507                                         return 0;
508                                 }
509                                 rcode = http_Asyncgethostbyname(&ip,NW_AGHBN_READ,m_ProxyIP);
510
511                                 Sleep(1);
512                         }while(rcode==0);
513                         
514                         
515                         if(rcode == -1)
516                         {
517                                 m_State = HTTP_STATE_HOST_NOT_FOUND;
518                                 return 0;
519                         }
520
521                 }
522                 //Use either the proxy port or 80 if none specified
523                 hostaddr.sin_port = htons((ushort)(m_ProxyPort ? m_ProxyPort : 80));
524                 //Copy the proxy address...
525                 memcpy(&hostaddr.sin_addr,&ip,4);
526
527         }
528         //Now we will connect to the host                                       
529         fd_set  wfds;
530
531         timeval timeout;
532         timeout.tv_sec = 0;
533         timeout.tv_usec = 0;
534         int serr = connect(m_DataSock, (SOCKADDR *)&hostaddr, sizeof(SOCKADDR));
535         int cerr = WSAGetLastError();
536         if(serr)
537         {
538                 while((cerr==WSAEALREADY)||(cerr==WSAEINVAL)||(cerr==WSAEWOULDBLOCK))
539                 {
540                         FD_ZERO(&wfds);
541                         FD_SET( m_DataSock, &wfds );
542                         if(select(0,NULL,&wfds,NULL,&timeout))
543                         {
544                                 serr = 0;
545                                 break;
546                         }
547                         if(m_Aborting)
548                                 return 0;
549                         serr = connect(m_DataSock, (SOCKADDR *)&hostaddr, sizeof(SOCKADDR));
550                         if(serr == 0)
551                                 break;
552                         cerr = WSAGetLastError();
553                         if(cerr==WSAEISCONN)
554                         {
555                                 serr = 0;
556                                 break;
557                         }
558
559                         Sleep(1);
560                 };
561         }
562         if(serr)
563         {
564                 m_State = HTTP_STATE_CANT_CONNECT;
565                 return 0;
566         }
567         m_State = HTTP_STATE_CONNECTED;
568         return 1;
569 }
570
571 char *ChttpGet::GetHTTPLine()
572 {
573         int iBytesRead;
574         char chunk[2];
575         unsigned int igotcrlf = 0;
576         memset(recv_buffer,0,1000);
577         do
578         {
579                 chunk[0]='\0';
580                 bool gotdata = false;
581                 do
582                 {
583                         iBytesRead = recv(m_DataSock,chunk,1,0);
584
585                         if(SOCKET_ERROR == iBytesRead)
586                         {       
587                                 int error = WSAGetLastError();
588                                 if(WSAEWOULDBLOCK==error)
589                                 {
590                                         gotdata = false;
591                                         continue;
592                                 }
593                                 else
594                                         return NULL;
595                         }
596                         else
597                         {
598                                 gotdata = true;
599                         }
600
601                         Sleep(1);
602                 }while(!gotdata);
603                 
604                 if(chunk[0]==0x0d)
605                 {
606                         //This should always read a 0x0a
607                         do
608                         {
609                                 iBytesRead = recv(m_DataSock,chunk,1,0);
610
611                                 if(SOCKET_ERROR == iBytesRead)
612                                 {       
613                                         int error = WSAGetLastError();
614                                         if(WSAEWOULDBLOCK==error)
615                                         {
616                                                 gotdata = false;
617                                                 continue;
618                                         }
619                                         else
620                                                 return NULL;
621                                 }
622                                 else
623                                 {
624                                         gotdata = true;
625                                 }
626
627                                 Sleep(1);
628                         }while(!gotdata);
629                         igotcrlf = 1;   
630                 }
631                 else
632                 {       chunk[1] = '\0';
633                         strcat(recv_buffer,chunk);
634                 }
635                 
636                 Sleep(1);
637         }while(igotcrlf==0);
638         return recv_buffer;     
639 }
640
641 unsigned int ChttpGet::ReadDataChannel()
642 {
643         char sDataBuffer[4096];         // Data-storage buffer for the data channel
644         int nBytesRecv = 0;                                             // Bytes received from the data channel
645
646         fd_set  wfds;
647
648         timeval timeout;
649         timeout.tv_sec = 0;
650         timeout.tv_usec = 500;
651
652         m_State = HTTP_STATE_RECEIVING;                 
653    do   
654    {
655                 FD_ZERO(&wfds);
656                 FD_SET( m_DataSock, &wfds );
657
658                 if((m_iBytesTotal)&&(m_iBytesIn==m_iBytesTotal))
659                 {
660                         break;
661                 }
662                 select(0,&wfds,NULL,NULL,&timeout);
663         if(m_Aborting)
664                 {
665                         fclose(LOCALFILE);
666                         return 0;               
667                 }
668                 nBytesRecv = recv(m_DataSock, (char *)&sDataBuffer,sizeof(sDataBuffer), 0);
669         if(m_Aborting)
670                 {
671                         fclose(LOCALFILE);
672                         return 0;
673                 }
674                 if(SOCKET_ERROR == nBytesRecv)
675                 {       
676                         int error = WSAGetLastError();
677                         if(WSAEWOULDBLOCK==error)
678                         {
679                                 nBytesRecv = 1;
680                                 continue;
681                         }
682                 }
683                 m_iBytesIn += nBytesRecv;
684                 if (nBytesRecv > 0 )
685                 {
686                         fwrite(sDataBuffer,nBytesRecv,1,LOCALFILE);
687                         //Write sDataBuffer, nBytesRecv
688         }
689                 
690                 Sleep(1);
691         }while (nBytesRecv > 0);
692         fclose(LOCALFILE);                                                      
693         // Close the file and check for error returns.
694         if (nBytesRecv == SOCKET_ERROR)
695         { 
696                 //Ok, we got a socket error -- xfer aborted?
697                 m_State = HTTP_STATE_RECV_FAILED;
698                 return 0;
699         }
700         else
701         {
702                 //OutputDebugString("HTTP File complete!\n");
703                 //done!
704                 m_State = HTTP_STATE_FILE_RECEIVED;
705                 return 1;
706         }
707 }       
708
709
710 typedef struct _async_dns_lookup
711 {
712         unsigned int ip;        //resolved host. Write only to worker thread.
713         char * host;//host name to resolve. read only to worker thread
714         bool done;      //write only to the worker thread. Signals that the operation is complete
715         bool error; //write only to worker thread. Thread sets this if the name doesn't resolve
716         bool abort;     //read only to worker thread. If this is set, don't fill in the struct.
717 }async_dns_lookup;
718
719 async_dns_lookup httpaslu;
720 async_dns_lookup *http_lastaslu = NULL;
721
722 #ifndef __LINUX__
723 void __cdecl http_gethostbynameworker(void *parm);
724 #else
725 void *http_gethostbynameworker(void *parm);
726 #endif
727
728 int http_Asyncgethostbyname(unsigned int *ip,int command, char *hostname)
729 {
730         
731         if(command==NW_AGHBN_LOOKUP)
732         {
733                 if(http_lastaslu)
734                         http_lastaslu->abort = true;
735
736                 async_dns_lookup *newaslu;
737                 newaslu = (async_dns_lookup *)malloc(sizeof(async_dns_lookup));
738                 memset(&newaslu->ip,0,sizeof(unsigned int));
739                 newaslu->host = hostname;
740                 newaslu->done = false;
741                 newaslu->error = false;
742                 newaslu->abort = false;
743                 http_lastaslu = newaslu;
744                 httpaslu.done = false;
745
746 // #ifdef WIN32
747                 _beginthread(http_gethostbynameworker,0,newaslu);
748         /*
749 #elif defined(__LINUX__)
750                 pthread_t thread;
751                 if(!inet_LoadThreadLib())
752                 {
753                         return 0;
754                 }
755
756                 df_pthread_create(&thread,NULL,http_gethostbynameworker,newaslu);
757 #endif
758                 */
759                 return 1;
760         }
761         else if(command==NW_AGHBN_CANCEL)
762         {
763                 if(http_lastaslu)
764                         http_lastaslu->abort = true;
765                 http_lastaslu = NULL;
766         }
767         else if(command==NW_AGHBN_READ)
768         {
769                 if(!http_lastaslu)
770                         return -1;
771                 if(httpaslu.done)
772                 {
773                         //free(http_lastaslu);
774                         http_lastaslu = NULL;
775                         memcpy(ip,&httpaslu.ip,sizeof(unsigned int));
776                         return 1;
777                 }
778                 else if(httpaslu.error)
779                 {
780                         free(http_lastaslu);
781                         http_lastaslu = NULL;
782                         return -1;
783                 }
784                 else return 0;
785         }
786         return -2;
787
788 }
789
790 // This is the worker thread which does the lookup.
791 #ifndef __LINUX__
792 void __cdecl http_gethostbynameworker(void *parm)
793 #else
794 void *http_gethostbynameworker(void *parm)
795 #endif
796 {
797 #ifdef __LINUX__
798         df_pthread_detach(df_pthread_self());
799 #endif
800         async_dns_lookup *lookup = (async_dns_lookup *)parm;
801         HOSTENT *he = gethostbyname(lookup->host);
802         if(he==NULL)
803         {
804                 lookup->error = true;
805                 #ifdef __LINUX__
806                 return NULL;
807                 #else
808                 return;
809                 #endif
810         }
811         else if(!lookup->abort)
812         {
813                 memcpy(&lookup->ip,he->h_addr_list[0],sizeof(unsigned int));
814                 lookup->done = true;
815                 memcpy(&httpaslu,lookup,sizeof(async_dns_lookup));
816         }
817         free(lookup);
818
819 #ifdef __LINUX__
820         return NULL;
821 #endif
822 }
823