Add Socket Library 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 //
445 // Receive the file data
446 //
447 LengthInBytes = recv ( SocketFD,
448 &pPort->RxBuffer[0],
449 DIM ( pPort->RxBuffer ),
450 0 );
451 if ( -1 == LengthInBytes ) {
452 //
453 // Receive error detected
454 // Close the port
455 //
456 OpStatus = EFI_DEVICE_ERROR;
457 }
458 else {
459 DEBUG (( DEBUG_REQUEST,
460 "0x%08x: Socket - Received %d bytes of file data\r\n",
461 SocketFD,
462 LengthInBytes ));
463
464 //
465 // TODO: Process the file data
466 //
467 }
468 }
469 }
470 }
471
472 //
473 // Handle the close event
474 //
475 if ( 0 != ( events & POLLHUP )) {
476 //
477 // Close the port
478 //
479 OpStatus = EFI_DEVICE_ERROR;
480 }
481
482 //
483 // Return the operation status
484 //
485 DEBUG (( DEBUG_PORT_WORK,
486 "Exiting PortWork, Status: %r\r\n",
487 OpStatus ));
488 return OpStatus;
489 }
490
491
492 /**
493 Scan the list of sockets and process any pending work
494
495 @param [in] pWebServer The web server control structure address.
496
497 **/
498 VOID
499 SocketPoll (
500 IN DT_WEB_SERVER * pWebServer
501 )
502 {
503 int FDCount;
504 struct pollfd * pPoll;
505 WSDT_PORT ** ppPort;
506 EFI_STATUS Status;
507
508 DEBUG (( DEBUG_SOCKET_POLL, "Entering SocketPoll\r\n" ));
509
510 //
511 // Determine if any ports are active
512 //
513 FDCount = poll ( pWebServer->pFdList,
514 pWebServer->Entries,
515 CLIENT_POLL_DELAY );
516 if ( -1 == FDCount ) {
517 DEBUG (( DEBUG_ERROR | DEBUG_SOCKET_POLL,
518 "ERROR - errno: %d\r\n",
519 errno ));
520 }
521
522 pPoll = pWebServer->pFdList;
523 ppPort = pWebServer->ppPortList;
524 while ( 0 < FDCount ) {
525 //
526 // Walk the list of ports to determine what work needs to be done
527 //
528 if ( 0 != pPoll->revents ) {
529 //
530 // Process this port
531 //
532 Status = PortWork ( pWebServer,
533 pPoll->fd,
534 pPoll->revents,
535 *ppPort );
536 pPoll->revents = 0;
537
538 //
539 // Close the port if necessary
540 //
541 if ( EFI_ERROR ( Status )) {
542 PortRemove ( pWebServer, pPoll->fd );
543 pPoll -= 1;
544 ppPort -= 1;
545 }
546
547 //
548 // Account for this file descriptor
549 //
550 FDCount -= 1;
551 }
552
553 //
554 // Set the next port
555 //
556 pPoll += 1;
557 ppPort += 1;
558 }
559
560 DEBUG (( DEBUG_SOCKET_POLL, "Exiting SocketPoll\r\n" ));
561 }
562
563
564 /**
565 Create the HTTP port for the web server
566
567 This routine polls the network layer to create the HTTP port for the
568 web server. More than one attempt may be necessary since it may take
569 some time to get the IP address and initialize the upper layers of
570 the network stack.
571
572 After the HTTP port is created, the socket layer will manage the
573 coming and going of the network connections until the last network
574 connection is broken.
575
576 @param [in] pWebServer The web server control structure address.
577
578 **/
579 VOID
580 WebServerTimer (
581 IN DT_WEB_SERVER * pWebServer
582 )
583 {
584 UINT16 HttpPort;
585 struct sockaddr_in WebServerAddress;
586 int SocketStatus;
587 EFI_STATUS Status;
588
589 DEBUG (( DEBUG_SERVER_TIMER, "Entering WebServerTimer\r\n" ));
590
591 //
592 // Open the HTTP port on the server
593 //
594 do {
595 do {
596 //
597 // Complete the client operations
598 //
599 SocketPoll ( pWebServer );
600
601 //
602 // Wait for a while
603 //
604 Status = gBS->CheckEvent ( pWebServer->TimerEvent );
605 } while ( EFI_SUCCESS != Status );
606
607 //
608 // Attempt to create the socket for the web server
609 //
610 pWebServer->HttpListenPort = socket ( AF_INET,
611 SOCK_STREAM,
612 IPPROTO_TCP );
613 if ( -1 != pWebServer->HttpListenPort )
614 {
615 //
616 // Set the socket address
617 //
618 ZeroMem ( &WebServerAddress, sizeof ( WebServerAddress ));
619 HttpPort = PcdGet16 ( WebServer_HttpPort );
620 DEBUG (( DEBUG_HTTP_PORT,
621 "HTTP Port: %d\r\n",
622 HttpPort ));
623 WebServerAddress.sin_len = sizeof ( WebServerAddress );
624 WebServerAddress.sin_family = AF_INET;
625 WebServerAddress.sin_addr.s_addr = INADDR_ANY;
626 WebServerAddress.sin_port = htons ( HttpPort );
627
628 //
629 // Bind the socket to the HTTP port
630 //
631 SocketStatus = bind ( pWebServer->HttpListenPort,
632 (struct sockaddr *) &WebServerAddress,
633 WebServerAddress.sin_len );
634 if ( -1 != SocketStatus ) {
635 //
636 // Enable connections to the HTTP port
637 //
638 SocketStatus = listen ( pWebServer->HttpListenPort,
639 SOMAXCONN );
640 }
641
642 //
643 // Release the socket if necessary
644 //
645 if ( -1 == SocketStatus ) {
646 close ( pWebServer->HttpListenPort );
647 pWebServer->HttpListenPort = -1;
648 }
649 }
650
651 //
652 // Wait until the socket is open
653 //
654 }while ( -1 == pWebServer->HttpListenPort );
655
656 DEBUG (( DEBUG_SERVER_TIMER, "Exiting WebServerTimer\r\n" ));
657 }
658
659
660 /**
661 Start the web server port creation timer
662
663 @param [in] pWebServer The web server control structure address.
664
665 @retval EFI_SUCCESS The timer was successfully started.
666 @retval EFI_ALREADY_STARTED The timer is already running.
667 @retval Other The timer failed to start.
668
669 **/
670 EFI_STATUS
671 WebServerTimerStart (
672 IN DT_WEB_SERVER * pWebServer
673 )
674 {
675 EFI_STATUS Status;
676 UINT64 TriggerTime;
677
678 DBG_ENTER ( );
679
680 //
681 // Assume the timer is already running
682 //
683 Status = EFI_ALREADY_STARTED;
684 if ( !pWebServer->bTimerRunning ) {
685 //
686 // Compute the poll interval
687 //
688 TriggerTime = HTTP_PORT_POLL_DELAY * ( 1000 * 10 );
689 Status = gBS->SetTimer ( pWebServer->TimerEvent,
690 TimerPeriodic,
691 TriggerTime );
692 if ( !EFI_ERROR ( Status )) {
693 DEBUG (( DEBUG_HTTP_PORT, "HTTP port timer started\r\n" ));
694
695 //
696 // Mark the timer running
697 //
698 pWebServer->bTimerRunning = TRUE;
699 }
700 else {
701 DEBUG (( DEBUG_ERROR | DEBUG_HTTP_PORT,
702 "ERROR - Failed to start HTTP port timer, Status: %r\r\n",
703 Status ));
704 }
705 }
706
707 //
708 // Return the operation status
709 //
710 DBG_EXIT_STATUS ( Status );
711 return Status;
712 }
713
714
715 /**
716 Stop the web server port creation timer
717
718 @param [in] pWebServer The web server control structure address.
719
720 @retval EFI_SUCCESS The HTTP port timer is stopped
721 @retval Other Failed to stop the HTTP port timer
722
723 **/
724 EFI_STATUS
725 WebServerTimerStop (
726 IN DT_WEB_SERVER * pWebServer
727 )
728 {
729 EFI_STATUS Status;
730
731 DBG_ENTER ( );
732
733 //
734 // Assume the timer is stopped
735 //
736 Status = EFI_SUCCESS;
737 if ( pWebServer->bTimerRunning ) {
738 //
739 // Stop the port creation polling
740 //
741 Status = gBS->SetTimer ( pWebServer->TimerEvent,
742 TimerCancel,
743 0 );
744 if ( !EFI_ERROR ( Status )) {
745 DEBUG (( DEBUG_HTTP_PORT, "HTTP port timer stopped\r\n" ));
746
747 //
748 // Mark the timer stopped
749 //
750 pWebServer->bTimerRunning = FALSE;
751 }
752 else {
753 DEBUG (( DEBUG_ERROR | DEBUG_HTTP_PORT,
754 "ERROR - Failed to stop HTTP port timer, Status: %r\r\n",
755 Status ));
756 }
757 }
758
759 //
760 // Return the operation status
761 //
762 DBG_EXIT_STATUS ( Status );
763 return Status;
764 }
765
766 /**
767 Entry point for the web server application.
768
769 @param [in] Argc The number of arguments
770 @param [in] Argv The argument value array
771
772 @retval 0 The application exited normally.
773 @retval Other An error occurred.
774 **/
775 int
776 main (
777 IN int Argc,
778 IN char **Argv
779 )
780 {
781 DT_WEB_SERVER * pWebServer;
782 EFI_STATUS Status;
783
784 //
785 // Create a timer event to start HTTP port
786 //
787 pWebServer = &mWebServer;
788 Status = gBS->CreateEvent ( EVT_TIMER,
789 TPL_WEB_SERVER,
790 NULL,
791 NULL,
792 &pWebServer->TimerEvent );
793 if ( !EFI_ERROR ( Status )) {
794 Status = WebServerTimerStart ( pWebServer );
795 if ( !EFI_ERROR ( Status )) {
796 //
797 // Run the web server forever
798 //
799 for ( ; ; ) {
800 //
801 // Poll the network layer to create the HTTP port
802 // for the web server. More than one attempt may
803 // be necessary since it may take some time to get
804 // the IP address and initialize the upper layers
805 // of the network stack.
806 //
807 WebServerTimer ( pWebServer );
808
809 //
810 // Add the HTTP port to the list of ports
811 //
812 Status = PortAdd ( pWebServer, pWebServer->HttpListenPort );
813 if ( !EFI_ERROR ( Status )) {
814 //
815 // Poll the sockets for activity
816 //
817 do {
818 SocketPoll ( pWebServer );
819 } while ( -1 != pWebServer->HttpListenPort );
820
821 //
822 // The HTTP port failed the accept and was closed
823 //
824 }
825
826 //
827 // Close the HTTP port if necessary
828 //
829 if ( -1 != pWebServer->HttpListenPort ) {
830 close ( pWebServer->HttpListenPort );
831 pWebServer->HttpListenPort = -1;
832 }
833 //
834 // TODO: Remove the following test code
835 // Exit when the network connection is broken
836 //
837 break;
838 }
839
840 //
841 // Done with the timer event
842 //
843 WebServerTimerStop ( pWebServer );
844 Status = gBS->CloseEvent ( pWebServer->TimerEvent );
845 }
846 }
847
848 //
849 // Return the final status
850 //
851 DBG_EXIT_STATUS ( Status );
852 return Status;
853 }