/** * FILE OPEN * * @v file_open Pointer to a struct s_PXENV_FILE_OPEN * @v s_PXENV_FILE_OPEN::FileName URL of file to open * @ret #PXENV_EXIT_SUCCESS File was opened * @ret #PXENV_EXIT_FAILURE File was not opened * @ret s_PXENV_FILE_OPEN::Status PXE status code * @ret s_PXENV_FILE_OPEN::FileHandle Handle of opened file * */ PXENV_EXIT_t pxenv_file_open ( struct s_PXENV_FILE_OPEN *file_open ) { userptr_t filename; size_t filename_len; int fd; DBG ( "PXENV_FILE_OPEN" ); /* Copy name from external program, and open it */ filename = real_to_user ( file_open->FileName.segment, file_open->FileName.offset ); filename_len = strlen_user ( filename, 0 ); { char uri_string[ filename_len + 1 ]; copy_from_user ( uri_string, filename, 0, sizeof ( uri_string ) ); DBG ( " %s", uri_string ); fd = open ( uri_string ); } if ( fd < 0 ) { file_open->Status = PXENV_STATUS ( fd ); return PXENV_EXIT_FAILURE; } DBG ( " as file %d", fd ); file_open->FileHandle = fd; file_open->Status = PXENV_STATUS_SUCCESS; return PXENV_EXIT_SUCCESS; }
/** * Deallocate space on the real-mode stack, optionally copying back * data to a user buffer. * * @v data User buffer * @v size Size of stack data */ void remove_user_from_rm_stack ( userptr_t data, size_t size ) { if ( data ) { userptr_t rm_stack = real_to_user ( rm_ss, rm_sp ); memcpy_user ( rm_stack, 0, data, 0, size ); } rm_sp += size; };
/** * FILE EXEC * * @v file_exec Pointer to a struct s_PXENV_FILE_EXEC * @v s_PXENV_FILE_EXEC::Command Command to execute * @ret #PXENV_EXIT_SUCCESS Command was executed successfully * @ret #PXENV_EXIT_FAILURE Command was not executed successfully * @ret s_PXENV_FILE_EXEC::Status PXE status code * */ PXENV_EXIT_t pxenv_file_exec ( struct s_PXENV_FILE_EXEC *file_exec ) { userptr_t command; size_t command_len; int rc; DBG ( "PXENV_FILE_EXEC" ); /* Copy name from external program, and exec it */ command = real_to_user ( file_exec->Command.segment, file_exec->Command.offset ); command_len = strlen_user ( command, 0 ); { char command_string[ command_len + 1 ]; copy_from_user ( command_string, command, 0, sizeof ( command_string ) ); DBG ( " %s", command_string ); if ( ( rc = system ( command_string ) ) != 0 ) { file_exec->Status = PXENV_STATUS ( rc ); return PXENV_EXIT_FAILURE; } } file_exec->Status = PXENV_STATUS_SUCCESS; return PXENV_EXIT_SUCCESS; }
/** * Perform a series of memory copies from a list in low memory */ static void shuffle ( unsigned int list_segment, unsigned int list_offset, unsigned int count ) { comboot_shuffle_descriptor shuf[COMBOOT_MAX_SHUFFLE_DESCRIPTORS]; unsigned int i; /* Copy shuffle descriptor list so it doesn't get overwritten */ copy_from_user ( shuf, real_to_user ( list_segment, list_offset ), 0, count * sizeof( comboot_shuffle_descriptor ) ); /* Do the copies */ for ( i = 0; i < count; i++ ) { userptr_t src_u = phys_to_user ( shuf[ i ].src ); userptr_t dest_u = phys_to_user ( shuf[ i ].dest ); if ( shuf[ i ].src == 0xFFFFFFFF ) { /* Fill with 0 instead of copying */ memset_user ( dest_u, 0, 0, shuf[ i ].len ); } else if ( shuf[ i ].dest == 0xFFFFFFFF ) { /* Copy new list of descriptors */ count = shuf[ i ].len / sizeof( comboot_shuffle_descriptor ); assert ( count <= COMBOOT_MAX_SHUFFLE_DESCRIPTORS ); copy_from_user ( shuf, src_u, 0, shuf[ i ].len ); i = -1; } else { /* Regular copy */ memmove_user ( dest_u, 0, src_u, 0, shuf[ i ].len ); } } }
/** * Allocate space on the real-mode stack and copy data there from a * user buffer * * @v data User buffer * @v size Size of stack data * @ret sp New value of real-mode stack pointer */ uint16_t copy_user_to_rm_stack ( userptr_t data, size_t size ) { userptr_t rm_stack; rm_sp -= size; rm_stack = real_to_user ( rm_ss, rm_sp ); memcpy_user ( rm_stack, 0, data, 0, size ); return rm_sp; };
/** * Execute PXE image * * @v image PXE image * @ret rc Return status code */ static int pxe_exec ( struct image *image ) { userptr_t buffer = real_to_user ( 0, 0x7c00 ); struct net_device *netdev; int rc; /* Verify and prepare segment */ if ( ( rc = prep_segment ( buffer, image->len, image->len ) ) != 0 ) { DBGC ( image, "IMAGE %p could not prepare segment: %s\n", image, strerror ( rc ) ); return rc; } /* Copy image to segment */ memcpy_user ( buffer, 0, image->data, 0, image->len ); /* Arbitrarily pick the most recently opened network device */ if ( ( netdev = last_opened_netdev() ) == NULL ) { DBGC ( image, "IMAGE %p could not locate PXE net device\n", image ); return -ENODEV; } netdev_get ( netdev ); /* Activate PXE */ pxe_activate ( netdev ); /* Construct fake DHCP packets */ pxe_fake_cached_info(); /* Set PXE command line */ pxe_cmdline = image->cmdline; /* Reset console since PXE NBP will probably use it */ console_reset(); /* Disable IRQ, if applicable */ if ( netdev_irq_supported ( netdev ) && netdev->dev->desc.irq ) disable_irq ( netdev->dev->desc.irq ); /* Start PXE NBP */ rc = pxe_start_nbp(); /* Clear PXE command line */ pxe_cmdline = NULL; /* Deactivate PXE */ pxe_deactivate(); /* Try to reopen network device. Ignore errors, since the NBP * may have called PXENV_STOP_UNDI. */ netdev_open ( netdev ); netdev_put ( netdev ); return rc; }
/** * Print a string with a particular terminator */ static void print_user_string ( unsigned int segment, unsigned int offset, char terminator ) { int i = 0; char c; userptr_t str = real_to_user ( segment, offset ); for ( ; ; ) { copy_from_user ( &c, str, i, 1 ); if ( c == terminator ) break; putchar ( c ); i++; } }
/** * Receive PXE UDP data * * @v pxe_udp PXE UDP connection * @v iobuf I/O buffer * @v meta Data transfer metadata * @ret rc Return status code * * Receives a packet as part of the current pxenv_udp_read() * operation. */ static int pxe_udp_deliver ( struct pxe_udp_connection *pxe_udp, struct io_buffer *iobuf, struct xfer_metadata *meta ) { struct s_PXENV_UDP_READ *pxenv_udp_read = pxe_udp->pxenv_udp_read; struct sockaddr_in *sin_src; struct sockaddr_in *sin_dest; userptr_t buffer; size_t len; int rc = 0; if ( ! pxenv_udp_read ) { DBG ( "PXE discarded UDP packet\n" ); rc = -ENOBUFS; goto done; } /* Copy packet to buffer and record length */ buffer = real_to_user ( pxenv_udp_read->buffer.segment, pxenv_udp_read->buffer.offset ); len = iob_len ( iobuf ); if ( len > pxenv_udp_read->buffer_size ) len = pxenv_udp_read->buffer_size; copy_to_user ( buffer, 0, iobuf->data, len ); pxenv_udp_read->buffer_size = len; /* Fill in source/dest information */ assert ( meta ); sin_src = ( struct sockaddr_in * ) meta->src; assert ( sin_src ); assert ( sin_src->sin_family == AF_INET ); pxenv_udp_read->src_ip = sin_src->sin_addr.s_addr; pxenv_udp_read->s_port = sin_src->sin_port; sin_dest = ( struct sockaddr_in * ) meta->dest; assert ( sin_dest ); assert ( sin_dest->sin_family == AF_INET ); pxenv_udp_read->dest_ip = sin_dest->sin_addr.s_addr; pxenv_udp_read->d_port = sin_dest->sin_port; /* Mark as received */ pxe_udp->pxenv_udp_read = NULL; done: free_iob ( iobuf ); return rc; }
/** * FILE READ * * @v file_read Pointer to a struct s_PXENV_FILE_READ * @v s_PXENV_FILE_READ::FileHandle File handle * @v s_PXENV_FILE_READ::BufferSize Size of data buffer * @v s_PXENV_FILE_READ::Buffer Data buffer * @ret #PXENV_EXIT_SUCCESS Data has been read from file * @ret #PXENV_EXIT_FAILURE Data has not been read from file * @ret s_PXENV_FILE_READ::Status PXE status code * @ret s_PXENV_FILE_READ::Ready Indication of readiness * @ret s_PXENV_FILE_READ::BufferSize Length of data read * */ PXENV_EXIT_t pxenv_file_read ( struct s_PXENV_FILE_READ *file_read ) { userptr_t buffer; ssize_t len; DBG ( "PXENV_FILE_READ %d to %04x:%04x+%04x", file_read->FileHandle, file_read->Buffer.segment, file_read->Buffer.offset, file_read->BufferSize ); buffer = real_to_user ( file_read->Buffer.segment, file_read->Buffer.offset ); if ( ( len = read_user ( file_read->FileHandle, buffer, 0, file_read->BufferSize ) ) < 0 ) { file_read->Status = PXENV_STATUS ( len ); return PXENV_EXIT_FAILURE; } DBG ( " read %04zx", ( ( size_t ) len ) ); file_read->BufferSize = len; file_read->Status = PXENV_STATUS_SUCCESS; return PXENV_EXIT_SUCCESS; }
/** * FILE CMDLINE * * @v file_cmdline Pointer to a struct s_PXENV_FILE_CMDLINE * @v s_PXENV_FILE_CMDLINE::Buffer Buffer to contain command line * @v s_PXENV_FILE_CMDLINE::BufferSize Size of buffer * @ret #PXENV_EXIT_SUCCESS Command was executed successfully * @ret #PXENV_EXIT_FAILURE Command was not executed successfully * @ret s_PXENV_FILE_EXEC::Status PXE status code * @ret s_PXENV_FILE_EXEC::BufferSize Length of command line (including NUL) * */ static PXENV_EXIT_t pxenv_file_cmdline ( struct s_PXENV_FILE_CMDLINE *file_cmdline ) { userptr_t buffer; size_t max_len; size_t len; DBG ( "PXENV_FILE_CMDLINE to %04x:%04x+%04x \"%s\"\n", file_cmdline->Buffer.segment, file_cmdline->Buffer.offset, file_cmdline->BufferSize, pxe_cmdline ); buffer = real_to_user ( file_cmdline->Buffer.segment, file_cmdline->Buffer.offset ); len = file_cmdline->BufferSize; max_len = ( pxe_cmdline ? ( strlen ( pxe_cmdline ) + 1 /* NUL */ ) : 0 ); if ( len > max_len ) len = max_len; copy_to_user ( buffer, 0, pxe_cmdline, len ); file_cmdline->BufferSize = max_len; file_cmdline->Status = PXENV_STATUS_SUCCESS; return PXENV_EXIT_SUCCESS; }
/** * Load PXE image into memory * * @v image PXE file * @ret rc Return status code */ int pxe_load ( struct image *image ) { userptr_t buffer = real_to_user ( 0, 0x7c00 ); size_t filesz = image->len; size_t memsz = image->len; int rc; /* Images too large to fit in base memory cannot be PXE * images. We include this check to help prevent unrecognised * images from being marked as PXE images, since PXE images * have no signature we can check against. */ if ( filesz > ( 0xa0000 - 0x7c00 ) ) return -ENOEXEC; /* Rejecting zero-length images is also useful, since these * end up looking to the user like bugs in gPXE. */ if ( ! filesz ) return -ENOEXEC; /* There are no signature checks for PXE; we will accept anything */ if ( ! image->type ) image->type = &pxe_image_type; /* Verify and prepare segment */ if ( ( rc = prep_segment ( buffer, filesz, memsz ) ) != 0 ) { DBGC ( image, "IMAGE %p could not prepare segment: %s\n", image, strerror ( rc ) ); return rc; } /* Copy image to segment */ memcpy_user ( buffer, 0, image->data, 0, filesz ); return 0; }
/** * Read and verify El Torito Boot Record Volume Descriptor * * @v image El Torito file * @ret catalog_offset Offset of Boot Catalog * @ret rc Return status code */ static int eltorito_read_voldesc ( struct image *image, unsigned long *catalog_offset ) { static const struct eltorito_vol_desc vol_desc_signature = { .record_indicator = 0, .iso9660_id = "CD001", .version = 1, .system_indicator = "EL TORITO SPECIFICATION", }; struct eltorito_vol_desc vol_desc; /* Sanity check */ if ( image->len < ( ELTORITO_VOL_DESC_OFFSET + ISO9660_BLKSIZE ) ) { DBGC ( image, "ElTorito %p too short\n", image ); return -ENOEXEC; } /* Read and verify Boot Record Volume Descriptor */ copy_from_user ( &vol_desc, image->data, ELTORITO_VOL_DESC_OFFSET, sizeof ( vol_desc ) ); if ( memcmp ( &vol_desc, &vol_desc_signature, offsetof ( typeof ( vol_desc ), sector ) ) != 0 ) { DBGC ( image, "ElTorito %p invalid Boot Record Volume " "Descriptor\n", image ); return -ENOEXEC; } *catalog_offset = ( vol_desc.sector * ISO9660_BLKSIZE ); DBGC ( image, "ElTorito %p boot catalog at offset %#lx\n", image, *catalog_offset ); return 0; } /** * Read and verify El Torito Boot Catalog * * @v image El Torito file * @v catalog_offset Offset of Boot Catalog * @ret boot_entry El Torito boot entry * @ret rc Return status code */ static int eltorito_read_catalog ( struct image *image, unsigned long catalog_offset, struct eltorito_boot_entry *boot_entry ) { struct eltorito_validation_entry validation_entry; /* Sanity check */ if ( image->len < ( catalog_offset + ISO9660_BLKSIZE ) ) { DBGC ( image, "ElTorito %p bad boot catalog offset %#lx\n", image, catalog_offset ); return -ENOEXEC; } /* Read and verify the Validation Entry of the Boot Catalog */ copy_from_user ( &validation_entry, image->data, catalog_offset, sizeof ( validation_entry ) ); if ( word_checksum ( &validation_entry, sizeof ( validation_entry ) ) != 0 ) { DBGC ( image, "ElTorito %p bad Validation Entry checksum\n", image ); return -ENOEXEC; } /* Read and verify the Initial/Default entry */ copy_from_user ( boot_entry, image->data, ( catalog_offset + sizeof ( validation_entry ) ), sizeof ( *boot_entry ) ); if ( boot_entry->indicator != ELTORITO_BOOTABLE ) { DBGC ( image, "ElTorito %p not bootable\n", image ); return -ENOEXEC; } if ( boot_entry->media_type != ELTORITO_NO_EMULATION ) { DBGC ( image, "ElTorito %p cannot support media type %d\n", image, boot_entry->media_type ); return -ENOTSUP; } DBGC ( image, "ElTorito %p media type %d segment %04x\n", image, boot_entry->media_type, boot_entry->load_segment ); return 0; } /** * Load El Torito virtual disk image into memory * * @v image El Torito file * @v boot_entry El Torito boot entry * @ret rc Return status code */ static int eltorito_load_disk ( struct image *image, struct eltorito_boot_entry *boot_entry ) { unsigned long start = ( boot_entry->start * ISO9660_BLKSIZE ); unsigned long length = ( boot_entry->length * ISO9660_BLKSIZE ); unsigned int load_segment; userptr_t buffer; int rc; /* Sanity check */ if ( image->len < ( start + length ) ) { DBGC ( image, "ElTorito %p virtual disk lies outside image\n", image ); return -ENOEXEC; } DBGC ( image, "ElTorito %p virtual disk at %#lx+%#lx\n", image, start, length ); /* Calculate load address */ load_segment = boot_entry->load_segment; buffer = real_to_user ( load_segment, ( load_segment ? 0 : 0x7c00 ) ); /* Verify and prepare segment */ if ( ( rc = prep_segment ( buffer, length, length ) ) != 0 ) { DBGC ( image, "ElTorito %p could not prepare segment: %s\n", image, strerror ( rc ) ); return rc; } /* Copy image to segment */ memcpy_user ( buffer, 0, image->data, start, length ); return 0; }
/** * UDP WRITE * * @v pxenv_udp_write Pointer to a struct s_PXENV_UDP_WRITE * @v s_PXENV_UDP_WRITE::ip Destination IP address * @v s_PXENV_UDP_WRITE::gw Relay agent IP address, or 0.0.0.0 * @v s_PXENV_UDP_WRITE::src_port Source UDP port, or 0 * @v s_PXENV_UDP_WRITE::dst_port Destination UDP port * @v s_PXENV_UDP_WRITE::buffer_size Length of the UDP payload * @v s_PXENV_UDP_WRITE::buffer Address of the UDP payload * @ret #PXENV_EXIT_SUCCESS Packet was transmitted successfully * @ret #PXENV_EXIT_FAILURE Packet could not be transmitted * @ret s_PXENV_UDP_WRITE::Status PXE status code * @err #PXENV_STATUS_UDP_CLOSED UDP connection is not open * @err #PXENV_STATUS_UNDI_TRANSMIT_ERROR Could not transmit packet * * Transmits a single UDP packet. A valid IP and UDP header will be * prepended to the payload in s_PXENV_UDP_WRITE::buffer; the buffer * should not contain precomputed IP and UDP headers, nor should it * contain space allocated for these headers. The first byte of the * buffer will be transmitted as the first byte following the UDP * header. * * If s_PXENV_UDP_WRITE::gw is 0.0.0.0, normal IP routing will take * place. See the relevant @ref pxe_routing "implementation note" for * more details. * * If s_PXENV_UDP_WRITE::src_port is 0, port 2069 will be used. * * You must have opened a UDP connection with pxenv_udp_open() before * calling pxenv_udp_write(). * * On x86, you must set the s_PXE::StatusCallout field to a nonzero * value before calling this function in protected mode. You cannot * call this function with a 32-bit stack segment. (See the relevant * @ref pxe_x86_pmode16 "implementation note" for more details.) * * @note Etherboot currently ignores the s_PXENV_UDP_WRITE::gw * parameter. * */ PXENV_EXIT_t pxenv_udp_write ( struct s_PXENV_UDP_WRITE *pxenv_udp_write ) { struct sockaddr_in dest; struct xfer_metadata meta = { .src = ( struct sockaddr * ) &pxe_udp.local, .dest = ( struct sockaddr * ) &dest, .netdev = pxe_netdev, }; size_t len; struct io_buffer *iobuf; userptr_t buffer; int rc; DBG ( "PXENV_UDP_WRITE" ); /* Construct destination socket address */ memset ( &dest, 0, sizeof ( dest ) ); dest.sin_family = AF_INET; dest.sin_addr.s_addr = pxenv_udp_write->ip; dest.sin_port = pxenv_udp_write->dst_port; /* Set local (source) port. PXE spec says source port is 2069 * if not specified. Really, this ought to be set at UDP open * time but hey, we didn't design this API. */ pxe_udp.local.sin_port = pxenv_udp_write->src_port; if ( ! pxe_udp.local.sin_port ) pxe_udp.local.sin_port = htons ( 2069 ); /* FIXME: we ignore the gateway specified, since we're * confident of being able to do our own routing. We should * probably allow for multiple gateways. */ /* Allocate and fill data buffer */ len = pxenv_udp_write->buffer_size; iobuf = xfer_alloc_iob ( &pxe_udp.xfer, len ); if ( ! iobuf ) { DBG ( " out of memory\n" ); pxenv_udp_write->Status = PXENV_STATUS_OUT_OF_RESOURCES; return PXENV_EXIT_FAILURE; } buffer = real_to_user ( pxenv_udp_write->buffer.segment, pxenv_udp_write->buffer.offset ); copy_from_user ( iob_put ( iobuf, len ), buffer, 0, len ); DBG ( " %04x:%04x+%x %d->%s:%d\n", pxenv_udp_write->buffer.segment, pxenv_udp_write->buffer.offset, pxenv_udp_write->buffer_size, ntohs ( pxenv_udp_write->src_port ), inet_ntoa ( dest.sin_addr ), ntohs ( pxenv_udp_write->dst_port ) ); /* Transmit packet */ if ( ( rc = xfer_deliver ( &pxe_udp.xfer, iobuf, &meta ) ) != 0 ) { DBG ( "PXENV_UDP_WRITE could not transmit: %s\n", strerror ( rc ) ); pxenv_udp_write->Status = PXENV_STATUS ( rc ); return PXENV_EXIT_FAILURE; } pxenv_udp_write->Status = PXENV_STATUS_SUCCESS; return PXENV_EXIT_SUCCESS; } /** * UDP READ * * @v pxenv_udp_read Pointer to a struct s_PXENV_UDP_READ * @v s_PXENV_UDP_READ::dest_ip Destination IP address, or 0.0.0.0 * @v s_PXENV_UDP_READ::d_port Destination UDP port, or 0 * @v s_PXENV_UDP_READ::buffer_size Size of the UDP payload buffer * @v s_PXENV_UDP_READ::buffer Address of the UDP payload buffer * @ret #PXENV_EXIT_SUCCESS A packet has been received * @ret #PXENV_EXIT_FAILURE No packet has been received * @ret s_PXENV_UDP_READ::Status PXE status code * @ret s_PXENV_UDP_READ::src_ip Source IP address * @ret s_PXENV_UDP_READ::dest_ip Destination IP address * @ret s_PXENV_UDP_READ::s_port Source UDP port * @ret s_PXENV_UDP_READ::d_port Destination UDP port * @ret s_PXENV_UDP_READ::buffer_size Length of UDP payload * @err #PXENV_STATUS_UDP_CLOSED UDP connection is not open * @err #PXENV_STATUS_FAILURE No packet was ready to read * * Receive a single UDP packet. This is a non-blocking call; if no * packet is ready to read, the call will return instantly with * s_PXENV_UDP_READ::Status==PXENV_STATUS_FAILURE. * * If s_PXENV_UDP_READ::dest_ip is 0.0.0.0, UDP packets addressed to * any IP address will be accepted and may be returned to the caller. * * If s_PXENV_UDP_READ::d_port is 0, UDP packets addressed to any UDP * port will be accepted and may be returned to the caller. * * You must have opened a UDP connection with pxenv_udp_open() before * calling pxenv_udp_read(). * * On x86, you must set the s_PXE::StatusCallout field to a nonzero * value before calling this function in protected mode. You cannot * call this function with a 32-bit stack segment. (See the relevant * @ref pxe_x86_pmode16 "implementation note" for more details.) * * @note The PXE specification (version 2.1) does not state that we * should fill in s_PXENV_UDP_READ::dest_ip and * s_PXENV_UDP_READ::d_port, but Microsoft Windows' NTLDR program * expects us to do so, and will fail if we don't. * */ PXENV_EXIT_t pxenv_udp_read ( struct s_PXENV_UDP_READ *pxenv_udp_read ) { struct in_addr dest_ip_wanted = { .s_addr = pxenv_udp_read->dest_ip }; struct in_addr dest_ip; uint16_t d_port_wanted = pxenv_udp_read->d_port; uint16_t d_port; /* Try receiving a packet */ pxe_udp.pxenv_udp_read = pxenv_udp_read; step(); if ( pxe_udp.pxenv_udp_read ) { /* No packet received */ DBG2 ( "PXENV_UDP_READ\n" ); pxe_udp.pxenv_udp_read = NULL; goto no_packet; } dest_ip.s_addr = pxenv_udp_read->dest_ip; d_port = pxenv_udp_read->d_port; DBG ( "PXENV_UDP_READ" ); /* Filter on destination address and/or port */ if ( dest_ip_wanted.s_addr && ( dest_ip_wanted.s_addr != dest_ip.s_addr ) ) { DBG ( " wrong IP %s", inet_ntoa ( dest_ip ) ); DBG ( " (wanted %s)\n", inet_ntoa ( dest_ip_wanted ) ); goto no_packet; } if ( d_port_wanted && ( d_port_wanted != d_port ) ) { DBG ( " wrong port %d", htons ( d_port ) ); DBG ( " (wanted %d)\n", htons ( d_port_wanted ) ); goto no_packet; } DBG ( " %04x:%04x+%x %s:", pxenv_udp_read->buffer.segment, pxenv_udp_read->buffer.offset, pxenv_udp_read->buffer_size, inet_ntoa ( *( ( struct in_addr * ) &pxenv_udp_read->src_ip ) )); DBG ( "%d<-%s:%d\n", ntohs ( pxenv_udp_read->s_port ), inet_ntoa ( *( ( struct in_addr * ) &pxenv_udp_read->dest_ip ) ), ntohs ( pxenv_udp_read->d_port ) ); pxenv_udp_read->Status = PXENV_STATUS_SUCCESS; return PXENV_EXIT_SUCCESS; no_packet: pxenv_udp_read->Status = PXENV_STATUS_FAILURE; return PXENV_EXIT_FAILURE; }