Update the sockets applications
[mirror_edk2.git] / AppPkg / Applications / Sockets / WebServer / WebServer.c
1 /*++
2 This file contains an 'Intel UEFI Application' and is
3 licensed for Intel CPUs and chipsets under the terms of your
4 license agreement with Intel or your vendor. This file may
5 be modified by the user, subject to additional terms of the
6 license agreement
7 --*/
8 /*++
9
10 Copyright (c) 2011 Intel Corporation. All rights reserved
11 This software and associated documentation (if any) is furnished
12 under a license and may only be used or copied in accordance
13 with the terms of the license. Except as permitted by such
14 license, no part of this software or documentation may be
15 reproduced, stored in a retrieval system, or transmitted in any
16 form or by any means without the express written consent of
17 Intel Corporation.
18
19 --*/
20
21 /** @file
22 This is a simple shell application
23
24 This should be executed with "/Param2 Val1" and "/Param1" as the 2 command line options!
25
26 **/
27
28 #include <WebServer.h>
29
30 DT_WEB_SERVER mWebServer; ///< Web server's control structure
31
32
33 /**
34 Add a port to the list of ports to be polled.
35
36 @param [in] pWebServer The web server control structure address.
37
38 @param [in] SocketFD The socket's file descriptor to add to the list.
39
40 @retval EFI_SUCCESS The port was successfully added
41 @retval EFI_NO_RESOURCES Insufficient memory to add the port
42
43 **/
44 EFI_STATUS
45 PortAdd (
46 IN DT_WEB_SERVER * pWebServer,
47 IN int SocketFD
48 )
49 {
50 nfds_t Index;
51 size_t LengthInBytes;
52 nfds_t MaxEntries;
53 nfds_t MaxEntriesNew;
54 struct pollfd * pFdList;
55 struct pollfd * pFdListNew;
56 WSDT_PORT ** ppPortListNew;
57 WSDT_PORT * pPort;
58 EFI_STATUS Status;
59
60 DBG_ENTER ( );
61
62 //
63 // Use for/break instead of goto
64 //
65 for ( ; ; ) {
66 //
67 // Assume success
68 //
69 Status = EFI_SUCCESS;
70
71 //
72 // Create a new list if necessary
73 //
74 pFdList = pWebServer->pFdList;
75 MaxEntries = pWebServer->MaxEntries;
76 if ( pWebServer->Entries >= MaxEntries ) {
77 MaxEntriesNew = 16 + MaxEntries;
78
79 //
80 // The current FD list is full
81 // Allocate a new FD list
82 //
83 LengthInBytes = sizeof ( *pFdList ) * MaxEntriesNew;
84 Status = gBS->AllocatePool ( EfiRuntimeServicesData,
85 LengthInBytes,
86 (VOID **)&pFdListNew );
87 if ( EFI_ERROR ( Status )) {
88 DEBUG (( DEBUG_ERROR | DEBUG_POOL,
89 "ERROR - Failed to allocate the FD list, Status: %r\r\n",
90 Status ));
91 break;
92 }
93
94 //
95 // Allocate a new port list
96 //
97 LengthInBytes = sizeof ( *ppPortListNew ) * MaxEntriesNew;
98 Status = gBS->AllocatePool ( EfiRuntimeServicesData,
99 LengthInBytes,
100 (VOID **) &ppPortListNew );
101 if ( EFI_ERROR ( Status )) {
102 DEBUG (( DEBUG_ERROR | DEBUG_POOL,
103 "ERROR - Failed to allocate the port list, Status: %r\r\n",
104 Status ));
105
106 //
107 // Free the new FD list
108 //
109 gBS->FreePool ( pFdListNew );
110 break;
111 }
112
113 //
114 // Duplicate the FD list
115 //
116 Index = MaxEntries;
117 if ( NULL != pFdList ) {
118 CopyMem ( pFdListNew,
119 pFdList,
120 Index * sizeof ( *pFdList ));
121 }
122
123 //
124 // Initialize the new entries in the FD list
125 //
126 for ( ; MaxEntriesNew > Index; Index++ ) {
127 pFdListNew[ Index ].fd = -1;
128 pFdListNew[ Index ].events = 0;
129 pFdListNew[ Index ].revents = 0;
130 }
131
132 //
133 // Free the old FD list
134 //
135 if ( NULL != pFdList ) {
136 gBS->FreePool ( pFdList );
137 }
138
139 //
140 // Switch to the new FD list
141 //
142 pWebServer->pFdList = pFdListNew;
143 pFdList = pWebServer->pFdList;
144
145 //
146 // Duplicate the port list
147 //
148 Index = MaxEntries;
149 if ( NULL != pWebServer->ppPortList ) {
150 CopyMem ( ppPortListNew,
151 pWebServer->ppPortList,
152 Index * sizeof ( *ppPortListNew ));
153 }
154
155 //
156 // Initialize the new entries in the port list
157 //
158 for ( ; MaxEntriesNew > Index; Index++ ) {
159 ppPortListNew[ Index ] = NULL;
160 }
161
162 //
163 // Free the old port list
164 //
165 if ( NULL != pWebServer->ppPortList ) {
166 gBS->FreePool ( pWebServer->ppPortList );
167 }
168
169 //
170 // Switch to the new port list
171 //
172 pWebServer->ppPortList = ppPortListNew;
173
174 //
175 // Update the list size
176 //
177 pWebServer->MaxEntries = MaxEntriesNew;
178 }
179
180 //
181 // Allocate a new port
182 //
183 LengthInBytes = sizeof ( *pPort );
184 Status = gBS->AllocatePool ( EfiRuntimeServicesData,
185 LengthInBytes,
186 (VOID **)&pPort );
187 if ( EFI_ERROR ( Status )) {
188 DEBUG (( DEBUG_ERROR | DEBUG_POOL,
189 "ERROR - Failed to allocate the port, Status: %r\r\n",
190 Status ));
191 break;
192 }
193
194 //
195 // Initialize the port
196 //
197 pPort->RequestLength = 0;
198 pPort->TxBytes = 0;
199
200 //
201 // Add the socket to the FD list
202 //
203 pFdList[ pWebServer->Entries ].fd = SocketFD;
204 pFdList[ pWebServer->Entries ].events = POLLRDNORM
205 | POLLHUP;
206 pFdList[ pWebServer->Entries ].revents = 0;
207
208 //
209 // Add the port to the port list
210 //
211 pWebServer->ppPortList[ pWebServer->Entries ] = pPort;
212
213 //
214 // Account for the new entry
215 //
216 pWebServer->Entries += 1;
217 DEBUG (( DEBUG_PORT_WORK | DEBUG_INFO,
218 "WebServer handling %d ports\r\n",
219 pWebServer->Entries ));
220
221 //
222 // All done
223 //
224 break;
225 }
226
227 //
228 // Return the operation status
229 //
230 DBG_EXIT_STATUS ( Status );
231 return Status;
232 }
233
234
235 /**
236 Remove a port from the list of ports to be polled.
237
238 @param [in] pWebServer The web server control structure address.
239
240 @param [in] SocketFD The socket's file descriptor to add to the list.
241
242 **/
243 VOID
244 PortRemove (
245 IN DT_WEB_SERVER * pWebServer,
246 IN int SocketFD
247 )
248 {
249 nfds_t Entries;
250 nfds_t Index;
251 struct pollfd * pFdList;
252 WSDT_PORT ** ppPortList;
253
254 DBG_ENTER ( );
255
256 //
257 // Attempt to remove the entry from the list
258 //
259 Entries = pWebServer->Entries;
260 pFdList = pWebServer->pFdList;
261 ppPortList = pWebServer->ppPortList;
262 for ( Index = 0; Entries > Index; Index++ ) {
263 //
264 // Locate the specified socket file descriptor
265 //
266 if ( SocketFD == pFdList[ Index ].fd ) {
267 //
268 // Determine if this is the listen port
269 //
270 if ( SocketFD == pWebServer->HttpListenPort ) {
271 pWebServer->HttpListenPort = -1;
272 }
273
274 //
275 // Close the socket
276 //
277 close ( SocketFD );
278
279 //
280 // Free the port structure
281 //
282 gBS->FreePool ( ppPortList[ Index ]);
283
284 //
285 // Remove this port from the list by copying
286 // the rest of the list down one entry
287 //
288 Entries -= 1;
289 for ( ; Entries > Index; Index++ ) {
290 pFdList[ Index ] = pFdList[ Index + 1 ];
291 ppPortList[ Index ] = ppPortList[ Index + 1 ];
292 }
293 pFdList[ Index ].fd = -1;
294 pFdList[ Index ].events = 0;
295 pFdList[ Index ].revents = 0;
296 ppPortList[ Index ] = NULL;
297
298 //
299 // Update the number of entries in the list
300 //
301 pWebServer->Entries = Entries;
302 DEBUG (( DEBUG_PORT_WORK | DEBUG_INFO,
303 "WebServer handling %d ports\r\n",
304 pWebServer->Entries ));
305 break;
306 }
307 }
308
309 DBG_EXIT ( );
310 }
311
312
313 /**
314 Process the work for the sockets.
315
316 @param [in] pWebServer The web server control structure address.
317
318 @param [in] SocketFD The socket's file descriptor to add to the list.
319
320 @param [in] events everts is a bitmask of the work to be done
321
322 @param [in] pPort The address of a WSDT_PORT structure
323
324 @retval EFI_SUCCESS The operation was successful
325 @retval EFI_DEVICE_ERROR Error, close the port
326
327 **/
328 EFI_STATUS
329 PortWork (
330 IN DT_WEB_SERVER * pWebServer,
331 IN int SocketFD,
332 IN INTN events,
333 IN WSDT_PORT * pPort
334 )
335 {
336 BOOLEAN bDone;
337 size_t LengthInBytes;
338 int NewSocket;
339 EFI_STATUS OpStatus;
340 struct sockaddr RemoteAddress;
341 socklen_t RemoteAddressLength;
342 EFI_STATUS Status;
343
344 DEBUG (( DEBUG_PORT_WORK, "Entering PortWork\r\n" ));
345
346 //
347 // Assume success
348 //
349 OpStatus = EFI_SUCCESS;
350
351 //
352 // Handle input events
353 //
354 if ( 0 != ( events & POLLRDNORM )) {
355 //
356 // Determine if this is a connection attempt
357 //
358 if ( SocketFD == pWebServer->HttpListenPort ) {
359 //
360 // Handle connection attempts
361 // Accepts arrive as read events
362 //
363 RemoteAddressLength = sizeof ( RemoteAddress );
364 NewSocket = accept ( SocketFD,
365 &RemoteAddress,
366 &RemoteAddressLength );
367 if ( -1 != NewSocket ) {
368 if ( 0 != NewSocket ) {
369 //
370 // Add this port to the list monitored by the web server
371 //
372 Status = PortAdd ( pWebServer, NewSocket );
373 if ( EFI_ERROR ( Status )) {
374 DEBUG (( DEBUG_ERROR,
375 "ERROR - Failed to add the port 0x%08x, Status: %r\r\n",
376 NewSocket,
377 Status ));
378
379 //
380 // Done with the new socket
381 //
382 close ( NewSocket );
383 }
384 }
385 else {
386 DEBUG (( DEBUG_ERROR,
387 "ERROR - Socket not available!\r\n" ));
388 }
389
390 //
391 // Leave the listen port open
392 //
393 }
394 else {
395 //
396 // Listen port error
397 // Close the listen port by returning error status
398 //
399 OpStatus = EFI_DEVICE_ERROR;
400 DEBUG (( DEBUG_ERROR,
401 "ERROR - Failed to accept new connection, errno: 0x%08x\r\n",
402 errno ));
403 }
404 }
405 else {
406 //
407 // Handle the data received event
408 //
409 if ( 0 == pPort->RequestLength ) {
410 //
411 // Receive the page request
412 //
413 pPort->RequestLength = recv ( SocketFD,
414 &pPort->Request[0],
415 DIM ( pPort->Request ),
416 0 );
417 if ( -1 == pPort->RequestLength ) {
418 //
419 // Receive error detected
420 // Close the port
421 //
422 OpStatus = EFI_DEVICE_ERROR;
423 }
424 else {
425 DEBUG (( DEBUG_REQUEST,
426 "0x%08x: Socket - Received %d bytes of HTTP request\r\n",
427 SocketFD,
428 pPort->RequestLength ));
429
430 //
431 // Process the request
432 //
433 OpStatus = HttpRequest ( SocketFD, pPort, &bDone );
434 if ( bDone ) {
435 //
436 // Notify the upper layer to close the socket
437 //
438 OpStatus = EFI_DEVICE_ERROR;
439 }
440 }
441 }
442 else {
443 //
444 // Receive the file data
445 //
446 LengthInBytes = recv ( SocketFD,
447 &pPort->RxBuffer[0],
448 DIM ( pPort->RxBuffer ),
449 0 );
450 if ( -1 == LengthInBytes ) {
451 //
452 // Receive error detected
453 // Close the port
454 //
455 OpStatus = EFI_DEVICE_ERROR;
456 }
457 else {
458 DEBUG (( DEBUG_REQUEST,
459 "0x%08x: Socket - Received %d bytes of file data\r\n",
460 SocketFD,
461 LengthInBytes ));
462
463 //
464 // TODO: Process the file data
465 //
466 }
467 }
468 }
469 }
470
471 //
472 // Handle the close event
473 //
474 if ( 0 != ( events & POLLHUP )) {
475 //
476 // Close the port
477 //
478 OpStatus = EFI_DEVICE_ERROR;
479 }
480
481 //
482 // Return the operation status
483 //
484 DEBUG (( DEBUG_PORT_WORK,
485 "Exiting PortWork, Status: %r\r\n",
486 OpStatus ));
487 return OpStatus;
488 }
489
490
491 /**
492 Scan the list of sockets and process any pending work
493
494 @param [in] pWebServer The web server control structure address.
495
496 **/
497 VOID
498 SocketPoll (
499 IN DT_WEB_SERVER * pWebServer
500 )
501 {
502 int FDCount;
503 struct pollfd * pPoll;
504 WSDT_PORT ** ppPort;
505 EFI_STATUS Status;
506
507 DEBUG (( DEBUG_SOCKET_POLL, "Entering SocketPoll\r\n" ));
508
509 //
510 // Determine if any ports are active
511 //
512 FDCount = poll ( pWebServer->pFdList,
513 pWebServer->Entries,
514 CLIENT_POLL_DELAY );
515 if ( -1 == FDCount ) {
516 DEBUG (( DEBUG_ERROR | DEBUG_SOCKET_POLL,
517 "ERROR - errno: %d\r\n",
518 errno ));
519 }
520
521 pPoll = pWebServer->pFdList;
522 ppPort = pWebServer->ppPortList;
523 while ( 0 < FDCount ) {
524 //
525 // Walk the list of ports to determine what work needs to be done
526 //
527 if ( 0 != pPoll->revents ) {
528 //
529 // Process this port
530 //
531 Status = PortWork ( pWebServer,
532 pPoll->fd,
533 pPoll->revents,
534 *ppPort );
535 pPoll->revents = 0;
536
537 //
538 // Close the port if necessary
539 //
540 if ( EFI_ERROR ( Status )) {
541 PortRemove ( pWebServer, pPoll->fd );
542 pPoll -= 1;
543 ppPort -= 1;
544 }
545
546 //
547 // Account for this file descriptor
548 //
549 FDCount -= 1;
550 }
551
552 //
553 // Set the next port
554 //
555 pPoll += 1;
556 ppPort += 1;
557 }
558
559 DEBUG (( DEBUG_SOCKET_POLL, "Exiting SocketPoll\r\n" ));
560 }
561
562
563 /**
564 Create the HTTP port for the web server
565
566 This routine polls the network layer to create the HTTP port for the
567 web server. More than one attempt may be necessary since it may take
568 some time to get the IP address and initialize the upper layers of
569 the network stack.
570
571 After the HTTP port is created, the socket layer will manage the
572 coming and going of the network connections until the last network
573 connection is broken.
574
575 @param [in] pWebServer The web server control structure address.
576
577 **/
578 VOID
579 WebServerTimer (
580 IN DT_WEB_SERVER * pWebServer
581 )
582 {
583 UINT16 HttpPort;
584 struct sockaddr_in WebServerAddress;
585 int SocketStatus;
586 EFI_STATUS Status;
587
588 DEBUG (( DEBUG_SERVER_TIMER, "Entering WebServerTimer\r\n" ));
589
590 //
591 // Open the HTTP port on the server
592 //
593 do {
594 do {
595 //
596 // Complete the client operations
597 //
598 SocketPoll ( pWebServer );
599
600 //
601 // Wait for a while
602 //
603 Status = gBS->CheckEvent ( pWebServer->TimerEvent );
604 } while ( EFI_SUCCESS != Status );
605
606 //
607 // Attempt to create the socket for the web server
608 //
609 pWebServer->HttpListenPort = socket ( AF_INET,
610 SOCK_STREAM,
611 IPPROTO_TCP );
612 if ( -1 != pWebServer->HttpListenPort ) {
613 //
614 // Set the socket address
615 //
616 ZeroMem ( &WebServerAddress, sizeof ( WebServerAddress ));
617 HttpPort = PcdGet16 ( WebServer_HttpPort );
618 DEBUG (( DEBUG_HTTP_PORT,
619 "HTTP Port: %d\r\n",
620 HttpPort ));
621 WebServerAddress.sin_len = sizeof ( WebServerAddress );
622 WebServerAddress.sin_family = AF_INET;
623 WebServerAddress.sin_addr.s_addr = INADDR_ANY;
624 WebServerAddress.sin_port = htons ( HttpPort );
625
626 //
627 // Bind the socket to the HTTP port
628 //
629 SocketStatus = bind ( pWebServer->HttpListenPort,
630 (struct sockaddr *) &WebServerAddress,
631 WebServerAddress.sin_len );
632 if ( -1 != SocketStatus ) {
633 //
634 // Enable connections to the HTTP port
635 //
636 SocketStatus = listen ( pWebServer->HttpListenPort,
637 SOMAXCONN );
638 }
639
640 //
641 // Release the socket if necessary
642 //
643 if ( -1 == SocketStatus ) {
644 close ( pWebServer->HttpListenPort );
645 pWebServer->HttpListenPort = -1;
646 }
647 }
648
649 //
650 // Wait until the socket is open
651 //
652 }while ( -1 == pWebServer->HttpListenPort );
653
654 DEBUG (( DEBUG_SERVER_TIMER, "Exiting WebServerTimer\r\n" ));
655 }
656
657
658 /**
659 Start the web server port creation timer
660
661 @param [in] pWebServer The web server control structure address.
662
663 @retval EFI_SUCCESS The timer was successfully started.
664 @retval EFI_ALREADY_STARTED The timer is already running.
665 @retval Other The timer failed to start.
666
667 **/
668 EFI_STATUS
669 WebServerTimerStart (
670 IN DT_WEB_SERVER * pWebServer
671 )
672 {
673 EFI_STATUS Status;
674 UINT64 TriggerTime;
675
676 DBG_ENTER ( );
677
678 //
679 // Assume the timer is already running
680 //
681 Status = EFI_ALREADY_STARTED;
682 if ( !pWebServer->bTimerRunning ) {
683 //
684 // Compute the poll interval
685 //
686 TriggerTime = HTTP_PORT_POLL_DELAY * ( 1000 * 10 );
687 Status = gBS->SetTimer ( pWebServer->TimerEvent,
688 TimerPeriodic,
689 TriggerTime );
690 if ( !EFI_ERROR ( Status )) {
691 DEBUG (( DEBUG_HTTP_PORT, "HTTP port timer started\r\n" ));
692
693 //
694 // Mark the timer running
695 //
696 pWebServer->bTimerRunning = TRUE;
697 }
698 else {
699 DEBUG (( DEBUG_ERROR | DEBUG_HTTP_PORT,
700 "ERROR - Failed to start HTTP port timer, Status: %r\r\n",
701 Status ));
702 }
703 }
704
705 //
706 // Return the operation status
707 //
708 DBG_EXIT_STATUS ( Status );
709 return Status;
710 }
711
712
713 /**
714 Stop the web server port creation timer
715
716 @param [in] pWebServer The web server control structure address.
717
718 @retval EFI_SUCCESS The HTTP port timer is stopped
719 @retval Other Failed to stop the HTTP port timer
720
721 **/
722 EFI_STATUS
723 WebServerTimerStop (
724 IN DT_WEB_SERVER * pWebServer
725 )
726 {
727 EFI_STATUS Status;
728
729 DBG_ENTER ( );
730
731 //
732 // Assume the timer is stopped
733 //
734 Status = EFI_SUCCESS;
735 if ( pWebServer->bTimerRunning ) {
736 //
737 // Stop the port creation polling
738 //
739 Status = gBS->SetTimer ( pWebServer->TimerEvent,
740 TimerCancel,
741 0 );
742 if ( !EFI_ERROR ( Status )) {
743 DEBUG (( DEBUG_HTTP_PORT, "HTTP port timer stopped\r\n" ));
744
745 //
746 // Mark the timer stopped
747 //
748 pWebServer->bTimerRunning = FALSE;
749 }
750 else {
751 DEBUG (( DEBUG_ERROR | DEBUG_HTTP_PORT,
752 "ERROR - Failed to stop HTTP port timer, Status: %r\r\n",
753 Status ));
754 }
755 }
756
757 //
758 // Return the operation status
759 //
760 DBG_EXIT_STATUS ( Status );
761 return Status;
762 }
763
764 /**
765 Entry point for the web server application.
766
767 @param [in] Argc The number of arguments
768 @param [in] Argv The argument value array
769
770 @retval 0 The application exited normally.
771 @retval Other An error occurred.
772 **/
773 int
774 main (
775 IN int Argc,
776 IN char **Argv
777 )
778 {
779 DT_WEB_SERVER * pWebServer;
780 EFI_STATUS Status;
781
782 //
783 // Create a timer event to start HTTP port
784 //
785 pWebServer = &mWebServer;
786 Status = gBS->CreateEvent ( EVT_TIMER,
787 TPL_WEB_SERVER,
788 NULL,
789 NULL,
790 &pWebServer->TimerEvent );
791 if ( !EFI_ERROR ( Status )) {
792 Status = WebServerTimerStart ( pWebServer );
793 if ( !EFI_ERROR ( Status )) {
794 //
795 // Run the web server forever
796 //
797 for ( ; ; ) {
798 //
799 // Poll the network layer to create the HTTP port
800 // for the web server. More than one attempt may
801 // be necessary since it may take some time to get
802 // the IP address and initialize the upper layers
803 // of the network stack.
804 //
805 WebServerTimer ( pWebServer );
806
807 //
808 // Add the HTTP port to the list of ports
809 //
810 Status = PortAdd ( pWebServer, pWebServer->HttpListenPort );
811 if ( !EFI_ERROR ( Status )) {
812 //
813 // Poll the sockets for activity
814 //
815 do {
816 SocketPoll ( pWebServer );
817 } while ( -1 != pWebServer->HttpListenPort );
818
819 //
820 // The HTTP port failed the accept and was closed
821 //
822 }
823
824 //
825 // Close the HTTP port if necessary
826 //
827 if ( -1 != pWebServer->HttpListenPort ) {
828 close ( pWebServer->HttpListenPort );
829 pWebServer->HttpListenPort = -1;
830 }
831 //
832 // TODO: Remove the following test code
833 // Exit when the network connection is broken
834 //
835 break;
836 }
837
838 //
839 // Done with the timer event
840 //
841 WebServerTimerStop ( pWebServer );
842 Status = gBS->CloseEvent ( pWebServer->TimerEvent );
843 }
844 }
845
846 //
847 // Return the final status
848 //
849 DBG_EXIT_STATUS ( Status );
850 return Status;
851 }