diff --git a/src/core/async.c b/src/core/async.c index 8bd5d7c2..95bbc057 100644 --- a/src/core/async.c +++ b/src/core/async.c @@ -16,7 +16,9 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#include #include +#include #include #include @@ -27,26 +29,316 @@ */ /** - * Wait for asynchronous operation to complete + * Name signal * - * @v aop Asynchronous operation - * @ret rc Return status code - * - * Blocks until the specified asynchronous operation has completed. - * The return status is the return status of the asynchronous - * operation itself. + * @v signal Signal number + * @ret name Name of signal */ -int async_wait ( struct async_operation *aop ) { - int rc; - - /* Wait for operation to complete */ - do { - step(); - rc = async_status ( aop ); - } while ( rc == -EINPROGRESS ); - - /* Prepare for next call to async_wait() */ - async_set_status ( aop, -EINPROGRESS ); - - return rc; +static inline __attribute__ (( always_inline )) const char * +signal_name ( enum signal signal ) { + switch ( signal ) { + case SIGCHLD: return "SIGCHLD"; + case SIGKILL: return "SIGKILL"; + case SIGUPDATE: return "SIGUPDATE"; + default: return "SIG"; + } } + +/** + * Initialise an asynchronous operation + * + * @v async Asynchronous operation + * @v aop Asynchronous operation operations to use + * @v parent Parent asynchronous operation, or NULL + * @ret aid Asynchronous operation ID + * + * It is valid to create an asynchronous operation with no parent + * operation; see async_init_orphan(). + */ +aid_t async_init ( struct async *async, struct async_operations *aop, + struct async *parent ) { + static aid_t aid = 1; + + /* Assign identifier. Negative IDs are used to indicate + * errors, so avoid assigning them. + */ + ++aid; + aid &= ( ( ~( ( aid_t ) 0 ) ) >> 1 ); + + DBGC ( async, "ASYNC %p (type %p) initialising as", async, aop ); + if ( parent ) { + DBGC ( async, " child of ASYNC %p", parent ); + } else { + DBGC ( async, " orphan" ); + } + DBGC ( async, " with ID %ld\n", aid ); + + assert ( async != NULL ); + assert ( aop != NULL ); + + /* Add to hierarchy */ + if ( parent ) { + async->parent = parent; + list_add ( &async->siblings, &parent->children ); + } + INIT_LIST_HEAD ( &async->children ); + + /* Initialise fields */ + async->rc = -EINPROGRESS; + async->completed = 0; + async->total = 0; + async->aop = aop; + async->aid = aid; + + return async->aid; +} + +/** + * SIGCHLD 'ignore' handler + * + * @v async Asynchronous operation + * @v signal Signal received + */ +static void async_ignore_sigchld ( struct async *async, enum signal signal ) { + aid_t waited_aid; + + assert ( async != NULL ); + assert ( signal == SIGCHLD ); + + /* Reap the child */ + waited_aid = async_wait ( async, NULL, 0 ); + assert ( waited_aid >= 0 ); +} + +/** + * 'Ignore' signal handler + * + * @v async Asynchronous operation + * @v signal Signal received + */ +void async_ignore_signal ( struct async *async, enum signal signal ) { + + DBGC ( async, "ASYNC %p using ignore handler for %s\n", + async, signal_name ( signal ) ); + + assert ( async != NULL ); + + switch ( signal ) { + case SIGCHLD: + async_ignore_sigchld ( async, signal ); + break; + case SIGKILL: + case SIGUPDATE: + default: + /* Nothing to do */ + break; + } +} + +/** + * Default signal handler + * + * @v async Asynchronous operation + * @v signal Signal received + */ +static void async_default_signal ( struct async *async, enum signal signal ) { + + DBGC ( async, "ASYNC %p using default handler for %s\n", + async, signal_name ( signal ) ); + + assert ( async != NULL ); + + switch ( signal ) { + case SIGCHLD: + case SIGKILL: + case SIGUPDATE: + default: + /* Nothing to do */ + break; + } +} + +/** + * Send signal to asynchronous operation + * + * @v async Asynchronous operation + * @v signal Signal to send + */ +void async_signal ( struct async *async, enum signal signal ) { + signal_handler_t handler; + + DBGC ( async, "ASYNC %p receiving %s\n", + async, signal_name ( signal ) ); + + assert ( async != NULL ); + assert ( async->aop != NULL ); + assert ( signal < SIGMAX ); + + handler = async->aop->signal[signal]; + if ( handler ) { + /* Use the asynchronous operation's signal handler */ + handler ( async, signal ); + } else { + /* Use the default handler */ + async_default_signal ( async, signal ); + } +} + +/** + * Send signal to all child asynchronous operations + * + * @v async Asynchronous operation + * @v signal Signal to send + */ +void async_signal_children ( struct async *async, enum signal signal ) { + struct async *child; + struct async *tmp; + + assert ( async != NULL ); + + list_for_each_entry_safe ( child, tmp, &async->children, siblings ) { + async_signal ( child, signal ); + } +} + +/** + * Mark asynchronous operation as complete + * + * @v async Asynchronous operation + * @v rc Return status code + * + * An asynchronous operation should call this once it has completed. + * After calling async_done(), it must be prepared to be reaped by + * having its reap() method called. + */ +void async_done ( struct async *async, int rc ) { + + DBGC ( async, "ASYNC %p completing with status %d (%s)\n", + async, rc, strerror ( rc ) ); + + assert ( async != NULL ); + assert ( async->parent != NULL ); + assert ( rc != -EINPROGRESS ); + + /* Store return status code */ + async->rc = rc; + + /* Send SIGCHLD to parent. Guard against NULL pointer dereferences */ + if ( async->parent ) + async_signal ( async->parent, SIGCHLD ); +} + +/** + * Reap default handler + * + * @v async Asynchronous operation + */ +static void async_reap_default ( struct async *async ) { + + DBGC ( async, "ASYNC %p ignoring REAP\n", async ); + + assert ( async != NULL ); + + /* Nothing to do */ +} + +/** + * Wait for any child asynchronous operation to complete + * + * @v child Child asynchronous operation + * @v rc Child exit status to fill in, or NULL + * @v block Block waiting for child operation to complete + * @ret aid Asynchronous operation ID, or -1 on error + */ +aid_t async_wait ( struct async *async, int *rc, int block ) { + struct async *child; + aid_t child_aid; + int dummy_rc; + + DBGC ( async, "ASYNC %p performing %sblocking wait%s\n", async, + ( block ? "" : "non-" ), ( rc ? "" : " (ignoring status)" ) ); + + assert ( async != NULL ); + + /* Avoid multiple tests for "if ( rc )" */ + if ( ! rc ) + rc = &dummy_rc; + + while ( 1 ) { + + /* Return immediately if we have no children */ + if ( list_empty ( &async->children ) ) { + DBGC ( async, "ASYNC %p has no more children\n", + async ); + *rc = -ECHILD; + return -1; + } + + /* Look for a completed child */ + list_for_each_entry ( child, &async->children, siblings ) { + if ( child->rc == -EINPROGRESS ) + continue; + + /* Found a completed child */ + *rc = child->rc; + child_aid = child->aid; + + DBGC ( async, "ASYNC %p reaping child ASYNC %p (ID " + "%ld), exit status %d (%s)\n", async, child, + child_aid, child->rc, strerror ( child->rc ) ); + + /* Reap the child */ + assert ( child->aop != NULL ); + assert ( list_empty ( &child->children ) ); + + /* Unlink from operations hierarchy */ + list_del ( &child->siblings ); + child->parent = NULL; + + /* Release all resources */ + if ( child->aop->reap ) { + child->aop->reap ( child ); + } else { + async_reap_default ( child ); + } + + return child_aid; + } + + /* Return immediately if non-blocking */ + if ( ! block ) { + *rc = -EINPROGRESS; + return -1; + } + + /* Allow processes to run */ + step(); + } +} + +/** + * Default asynchronous operations + * + * The default is to ignore SIGCHLD (i.e. to automatically reap + * children) and to use the default handler (i.e. do nothing) for all + * other signals. + */ +struct async_operations default_async_operations = { + .signal = { + [SIGCHLD] = SIG_IGN, + }, +}; + +/** + * Default asynchronous operations for orphan asynchronous operations + * + * The default for orphan asynchronous operations is to do nothing for + * SIGCHLD (i.e. to not automatically reap children), on the + * assumption that you're probably creating the orphan solely in order + * to async_wait() on it. + */ +struct async_operations orphan_async_operations = { + .signal = { + [SIGCHLD] = SIG_DFL, + }, +}; diff --git a/src/drivers/ata/aoedev.c b/src/drivers/ata/aoedev.c index 9679fc1b..6658827c 100644 --- a/src/drivers/ata/aoedev.c +++ b/src/drivers/ata/aoedev.c @@ -36,9 +36,14 @@ static int aoe_command ( struct ata_device *ata, struct ata_command *command ) { struct aoe_device *aoedev = container_of ( ata, struct aoe_device, ata ); + struct async async; + int rc; - aoe_issue ( &aoedev->aoe, command ); - return async_wait ( &aoedev->aoe.aop ); + async_init_orphan ( &async ); + if ( ( rc = aoe_issue ( &aoedev->aoe, command, &async ) ) != 0 ) + return rc; + async_wait ( &async, &rc, 1 ); + return rc; } /** diff --git a/src/drivers/scsi/iscsidev.c b/src/drivers/scsi/iscsidev.c index b92d4bc9..75b857e7 100644 --- a/src/drivers/scsi/iscsidev.c +++ b/src/drivers/scsi/iscsidev.c @@ -36,8 +36,14 @@ static int iscsi_command ( struct scsi_device *scsi, struct scsi_command *command ) { struct iscsi_device *iscsidev = container_of ( scsi, struct iscsi_device, scsi ); + struct async async; + int rc; - return async_wait ( iscsi_issue ( &iscsidev->iscsi, command ) ); + async_init_orphan ( &async ); + if ( ( rc = iscsi_issue ( &iscsidev->iscsi, command, &async ) ) != 0 ) + return rc; + async_wait ( &async, &rc, 1 ); + return rc; } /** diff --git a/src/include/errno.h b/src/include/errno.h index dc4e4c19..3025fa96 100644 --- a/src/include/errno.h +++ b/src/include/errno.h @@ -125,6 +125,7 @@ #define EBUSY 0xd4 /**< Device or resource busy */ /** Operation cancelled */ #define ECANCELED PXENV_STATUS_BINL_CANCELED_BY_KEYSTROKE +#define ECHILD ENOENT /**< No child processes */ #define ECONNABORTED 0xd5 /**< Software caused connection abort */ #define ECONNREFUSED 0xd6 /**< Connection refused */ #define ECONNRESET 0xd7 /**< Connection reset by peer */ @@ -157,10 +158,10 @@ #define ENOSYS 0xee /**< Function not implemented */ #define ENOTCONN 0xef /**< Transport endpoint is not connected */ #define ENOTSOCK 0xf0 /**< Socket operation on non-socket */ -#define EOPNOTSUPP 0xf1 /**< Operation not supported */ -#define ENOTSUP EOPNOTSUPP /**< Not supported */ +#define ENOTSUP 0xf1 /**< Not supported */ #define ENOTTY 0xf2 /**< Inappropriate ioctl for device */ #define ENXIO ENODEV /**< No such device or address */ +#define EOPNOTSUPP ENOTSUP /**< Operation not supported */ #define EOVERFLOW 0xf3 /**< Result too large */ #define EPERM EACCES /**< Operation not permitted */ #define EPROTO 0xf4 /**< Protocol error */ diff --git a/src/include/gpxe/aoe.h b/src/include/gpxe/aoe.h index 8b3e0ca4..3c54b6bb 100644 --- a/src/include/gpxe/aoe.h +++ b/src/include/gpxe/aoe.h @@ -107,7 +107,7 @@ struct aoe_session { /** Byte offset within command's data buffer */ unsigned int command_offset; /** Asynchronous operation for this command */ - struct async_operation aop; + struct async async; /** Retransmission timer */ struct retry_timer timer; @@ -121,8 +121,9 @@ struct aoe_session { extern void aoe_open ( struct aoe_session *aoe ); extern void aoe_close ( struct aoe_session *aoe ); -extern struct async_operation * aoe_issue ( struct aoe_session *aoe, - struct ata_command *command ); +extern int aoe_issue ( struct aoe_session *aoe, + struct ata_command *command, + struct async *parent ); /** An AoE device */ struct aoe_device { diff --git a/src/include/gpxe/async.h b/src/include/gpxe/async.h index 8a681978..d3b075b9 100644 --- a/src/include/gpxe/async.h +++ b/src/include/gpxe/async.h @@ -7,56 +7,164 @@ * */ -#include -#include +#include -/** An asynchronous operation */ -struct async_operation { - /** Operation status +struct async; + +/** An asynchronous operation ID + * + * Only positive identifiers are valid; negative values are used to + * indicate errors. + */ +typedef long aid_t; + +/** Signals that can be delivered to asynchronous operations */ +enum signal { + /** A child asynchronous operation has completed * - * This is an error code as defined in errno.h, plus an offset - * of EINPROGRESS. This means that a status value of 0 - * corresponds to a return status code of -EINPROGRESS, - * i.e. that the default state of an asynchronous operation is - * "not yet completed". + * The parent should call async_wait() to reap the completed + * child. async_wait() will return the exit status and + * operation identifier of the child. + * + * The handler for this signal can be set to @c NULL; if it + * is, then the children will accumulate as zombies until + * async_wait() is called. + * + * The handler for this signal can also be set to @c SIG_IGN; + * if it is, then the children will automatically be reaped. + * Note that if you use @c SIG_IGN then you will not be able + * to retrieve the return status of the children; the call to + * async_wait() will simply return -ECHILD. */ - int status; + SIGCHLD = 0, + /** Cancel asynchronous operation + * + * This signal should trigger the asynchronous operation to + * cancel itself (including killing all its own children, if + * any), and then call async_done(). The asynchronous + * operation is allowed to not complete immediately. + * + * The handler for this signal can be set to @c NULL; if it + * is, then attempts to cancel the asynchronous operation will + * fail and the operation will complete normally. Anything + * waiting for the operation to cancel will block. + */ + SIGKILL, + /** Update progress of asynchronous operation + * + * This signal should cause the asynchronous operation to + * immediately update the @c completed and @c total fields. + * + * The handler for this signal can be set to @c NULL; if it + * is, then the asynchronous operation is expected to keep its + * @c completed and @c total fields up to date at all times. + */ + SIGUPDATE, + SIGMAX }; /** - * Set asynchronous operation status + * A signal handler * - * @v aop Asynchronous operation - * @v rc Return status code + * @v async Asynchronous operation + * @v signal Signal received */ -static inline __attribute__ (( always_inline )) void -async_set_status ( struct async_operation *aop, int rc ) { - aop->status = ( rc + EINPROGRESS ); -} +typedef void ( * signal_handler_t ) ( struct async *async, + enum signal signal ); + +/** Asynchronous operation operations */ +struct async_operations { + /** Reap asynchronous operation + * + * @v async Asynchronous operation + * + * Release all resources associated with the asynchronous + * operation. This will be called only after the asynchronous + * operation itself calls async_done(), so the only remaining + * resources will probably be the memory used by the struct + * async itself. + * + * This method can be set to @c NULL; if it is, then no + * resources will be freed. This may be suitable for + * asynchronous operations that consume no dynamically + * allocated memory. + */ + void ( * reap ) ( struct async *async ); + /** Handle signals */ + signal_handler_t signal[SIGMAX]; +}; + +/** An asynchronous operation */ +struct async { + /** Other asynchronous operations with the same parent */ + struct list_head siblings; + /** Child asynchronous operations */ + struct list_head children; + /** Parent asynchronous operation + * + * This field is optional; if left to NULL then the owner must + * never call async_done(). + */ + struct async *parent; + /** Asynchronous operation ID */ + aid_t aid; + /** Final return status code */ + int rc; + + /** Amount of operation completed so far + * + * The units for this quantity are arbitrary. @c completed + * divded by @total should give something which approximately + * represents the progress through the operation. For a + * download operation, using byte counts would make sense. + * + * This progress indicator should also incorporate the status + * of any child asynchronous operations. + */ + unsigned long completed; + /** Total operation size + * + * See @c completed. A zero value means "total size unknown" + * and is explcitly permitted; users should take this into + * account before calculating @c completed/total. + */ + unsigned long total; + + struct async_operations *aop; +}; + +extern struct async_operations default_async_operations; +extern struct async_operations orphan_async_operations; + +extern aid_t async_init ( struct async *async, struct async_operations *aop, + struct async *parent ); +extern void async_ignore_signal ( struct async *async, enum signal signal ); +extern void async_signal ( struct async *async, enum signal signal ); +extern void async_signal_children ( struct async *async, enum signal signal ); +extern void async_done ( struct async *async, int rc ); +extern aid_t async_wait ( struct async *async, int *rc, int block ); + +/** Default signal handler */ +#define SIG_DFL NULL + +/** Ignore signal */ +#define SIG_IGN async_ignore_signal /** - * Get asynchronous operation status + * Initialise orphan asynchronous operation * - * @v aop Asynchronous operation - * @ret rc Return status code - */ -static inline __attribute__ (( always_inline )) int -async_status ( struct async_operation *aop ) { - return ( aop->status - EINPROGRESS ); -} - -/** - * Flag asynchronous operation as complete + * @v async Asynchronous operation + * @ret aid Asynchronous operation ID * - * @v aop Asynchronous operation - * @v rc Return status code + * An orphan asynchronous operation can act as a context for child + * operations. However, you must not call async_done() on such an + * operation, since this would attempt to send a signal to its + * (non-existent) parent. Instead, simply free the structure (after + * calling async_wait() to ensure that any child operations have + * completed). */ -static inline __attribute__ (( always_inline )) void -async_done ( struct async_operation *aop, int rc ) { - assert ( rc != -EINPROGRESS ); - async_set_status ( aop, rc ); +static inline aid_t async_init_orphan ( struct async *async ) { + return async_init ( async, &orphan_async_operations, NULL ); } -extern int async_wait ( struct async_operation *aop ); - #endif /* _GPXE_ASYNC_H */ diff --git a/src/include/gpxe/dhcp.h b/src/include/gpxe/dhcp.h index 60991e7e..a3311d1a 100644 --- a/src/include/gpxe/dhcp.h +++ b/src/include/gpxe/dhcp.h @@ -466,7 +466,7 @@ struct dhcp_session { */ int state; /** Asynchronous operation for this DHCP session */ - struct async_operation aop; + struct async async; /** Retransmission timer */ struct retry_timer timer; }; @@ -504,6 +504,6 @@ extern int create_dhcp_packet ( struct net_device *netdev, uint8_t msgtype, struct dhcp_packet *dhcppkt ); extern int copy_dhcp_packet_options ( struct dhcp_packet *dhcppkt, struct dhcp_option_block *options ); -extern struct async_operation * start_dhcp ( struct dhcp_session *dhcp ); +extern int start_dhcp ( struct dhcp_session *dhcp, struct async *parent ); #endif /* _GPXE_DHCP_H */ diff --git a/src/include/gpxe/ftp.h b/src/include/gpxe/ftp.h index 2c51036a..06799d24 100644 --- a/src/include/gpxe/ftp.h +++ b/src/include/gpxe/ftp.h @@ -64,7 +64,7 @@ struct ftp_request { struct tcp_application tcp_data; /** Asynchronous operation for this FTP operation */ - struct async_operation aop; + struct async async; }; struct async_operation * ftp_get ( struct ftp_request *ftp ); diff --git a/src/include/gpxe/hello.h b/src/include/gpxe/hello.h index 18d0b378..31e035cd 100644 --- a/src/include/gpxe/hello.h +++ b/src/include/gpxe/hello.h @@ -44,7 +44,7 @@ struct hello_request { struct tcp_application tcp; /** Asynchronous operation */ - struct async_operation aop; + struct async async; }; extern struct async_operation * say_hello ( struct hello_request *hello ); diff --git a/src/include/gpxe/http.h b/src/include/gpxe/http.h index ca0afb94..49922e50 100644 --- a/src/include/gpxe/http.h +++ b/src/include/gpxe/http.h @@ -11,6 +11,7 @@ #include #include #include +#include /** HTTP default port */ #define HTTP_PORT 80 @@ -28,33 +29,29 @@ enum http_rx_state { * */ struct http_request { - /** Server address */ - struct sockaddr_tcpip server; - /** Server host name */ - const char *hostname; - /** Filename */ - const char *filename; + /** URI being fetched */ + struct uri *uri; /** Data buffer to fill */ struct buffer *buffer; + /** Asynchronous operation */ + struct async async; /** HTTP response code */ unsigned int response; /** HTTP Content-Length */ size_t content_length; + /** TCP application for this request */ + struct tcp_application tcp; /** Number of bytes already sent */ size_t tx_offset; /** RX state */ enum http_rx_state rx_state; /** Line buffer for received header lines */ struct line_buffer linebuf; - - /** TCP application for this request */ - struct tcp_application tcp; - /** Asynchronous operation */ - struct async_operation aop; }; -extern struct async_operation * http_get ( struct http_request *http ); +extern int http_get ( struct uri *uri, struct buffer *buffer, + struct async *parent ); #endif /* _GPXE_HTTP_H */ diff --git a/src/include/gpxe/iscsi.h b/src/include/gpxe/iscsi.h index bcb27376..74560649 100644 --- a/src/include/gpxe/iscsi.h +++ b/src/include/gpxe/iscsi.h @@ -591,7 +591,7 @@ struct iscsi_session { */ struct scsi_command *command; /** Asynchronous operation for the current iSCSI operation */ - struct async_operation aop; + struct async async; /** Instant return code * * Set to a non-zero value if all requests should return @@ -637,8 +637,9 @@ struct iscsi_session { /** Maximum number of retries at connecting */ #define ISCSI_MAX_RETRIES 2 -extern struct async_operation * iscsi_issue ( struct iscsi_session *iscsi, - struct scsi_command *command ); +extern int iscsi_issue ( struct iscsi_session *iscsi, + struct scsi_command *command, + struct async *parent ); extern void iscsi_shutdown ( struct iscsi_session *iscsi ); /** An iSCSI device */ diff --git a/src/include/gpxe/tftp.h b/src/include/gpxe/tftp.h index 2359dcc4..551a6fc9 100644 --- a/src/include/gpxe/tftp.h +++ b/src/include/gpxe/tftp.h @@ -135,7 +135,7 @@ struct tftp_session { int state; /** Asynchronous operation for this session */ - struct async_operation aop; + struct async async; /** Retransmission timer */ struct retry_timer timer; }; diff --git a/src/include/usr/fetch.h b/src/include/usr/fetch.h index 372f6f8c..85b85634 100644 --- a/src/include/usr/fetch.h +++ b/src/include/usr/fetch.h @@ -11,6 +11,6 @@ #include #include -extern int fetch ( const char *filename, userptr_t *data, size_t *len ); +extern int fetch ( const char *uri_string, userptr_t *data, size_t *len ); #endif /* _USR_FETCH_H */ diff --git a/src/net/aoe.c b/src/net/aoe.c index 2dc5ed40..1c3cd1d2 100644 --- a/src/net/aoe.c +++ b/src/net/aoe.c @@ -56,7 +56,7 @@ static void aoe_done ( struct aoe_session *aoe, int rc ) { aoe->command = NULL; /* Mark async operation as complete */ - async_done ( &aoe->aop, rc ); + async_done ( &aoe->async, rc ); } /** @@ -309,17 +309,19 @@ void aoe_close ( struct aoe_session *aoe ) { * * @v aoe AoE session * @v command ATA command - * @ret aop Asynchronous operation + * @v parent Parent asynchronous operation + * @ret rc Return status code * * Only one command may be issued concurrently per session. This call * is non-blocking; use async_wait() to wait for the command to * complete. */ -struct async_operation * aoe_issue ( struct aoe_session *aoe, - struct ata_command *command ) { +int aoe_issue ( struct aoe_session *aoe, struct ata_command *command, + struct async *parent ) { aoe->command = command; aoe->status = 0; aoe->command_offset = 0; aoe_send_command ( aoe ); - return &aoe->aop; + async_init ( &aoe->async, &default_async_operations, parent ); + return 0; } diff --git a/src/net/tcp/ftp.c b/src/net/tcp/ftp.c index a857f7b7..8a27f8cb 100644 --- a/src/net/tcp/ftp.c +++ b/src/net/tcp/ftp.c @@ -83,7 +83,7 @@ static void ftp_done ( struct ftp_request *ftp, int rc ) { tcp_close ( &ftp->tcp_data ); /* Mark asynchronous operation as complete */ - async_done ( &ftp->aop, rc ); + async_done ( &ftp->async, rc ); } /** @@ -379,5 +379,5 @@ struct async_operation * ftp_get ( struct ftp_request *ftp ) { if ( ( rc = tcp_connect ( &ftp->tcp, &ftp->server, 0 ) ) != 0 ) ftp_done ( ftp, rc ); - return &ftp->aop; + return &ftp->async; } diff --git a/src/net/tcp/hello.c b/src/net/tcp/hello.c index 4de7e872..25c07936 100644 --- a/src/net/tcp/hello.c +++ b/src/net/tcp/hello.c @@ -51,7 +51,7 @@ tcp_to_hello ( struct tcp_application *app ) { static void hello_closed ( struct tcp_application *app, int status ) { struct hello_request *hello = tcp_to_hello ( app ); - async_done ( &hello->aop, status ); + async_done ( &hello->async, status ); } static void hello_connected ( struct tcp_application *app ) { @@ -116,7 +116,7 @@ struct async_operation * say_hello ( struct hello_request *hello ) { hello->tcp.tcp_op = &hello_tcp_operations; if ( ( rc = tcp_connect ( &hello->tcp, &hello->server, 0 ) ) != 0 ) - async_done ( &hello->aop, rc ); + async_done ( &hello->async, rc ); - return &hello->aop; + return &hello->async; } diff --git a/src/net/tcp/http.c b/src/net/tcp/http.c index 22e24082..dfa39f53 100644 --- a/src/net/tcp/http.c +++ b/src/net/tcp/http.c @@ -27,9 +27,11 @@ #include #include #include -#include +#include #include +#include #include +#include #include #include @@ -67,7 +69,7 @@ static void http_done ( struct http_request *http, int rc ) { } /* Mark async operation as complete */ - async_done ( &http->aop, rc ); + async_done ( &http->async, rc ); } /** @@ -303,12 +305,21 @@ static void http_newdata ( struct tcp_application *app, static void http_senddata ( struct tcp_application *app, void *buf, size_t len ) { struct http_request *http = tcp_to_http ( app ); + const char *path = http->uri->path; + const char *host = http->uri->host; + + if ( ! path ) + path = "/"; + + if ( ! host ) + host = ""; len = snprintf ( buf, len, - "GET /%s HTTP/1.1\r\n" + "GET %s HTTP/1.1\r\n" "User-Agent: gPXE/" VERSION "\r\n" "Host: %s\r\n" - "\r\n", http->filename, http->hostname ); + "\r\n", path, host ); + tcp_send ( app, ( buf + http->tx_offset ), ( len - http->tx_offset ) ); } @@ -346,17 +357,72 @@ static struct tcp_operations http_tcp_operations = { .senddata = http_senddata, }; +/** + * Reap asynchronous operation + * + * @v async Asynchronous operation + */ +static void http_reap ( struct async *async ) { + struct http_request *http = + container_of ( async, struct http_request, async ); + + free_uri ( http->uri ); + free ( http ); +} + +/** HTTP asynchronous operations */ +static struct async_operations http_async_operations = { + .reap = http_reap, +}; + +#warning "Quick name resolution hack" +#include + /** * Initiate a HTTP connection * - * @v http a HTTP request + * @v uri Uniform Resource Identifier + * @v buffer Buffer into which to download file + * @v parent Parent asynchronous operation + * @ret rc Return status code + * + * If it returns success, this function takes ownership of the URI. */ -struct async_operation * http_get ( struct http_request *http ) { +int http_get ( struct uri *uri, struct buffer *buffer, struct async *parent ) { + struct http_request *http; int rc; + /* Allocate and populate HTTP structure */ + http = malloc ( sizeof ( *http ) ); + if ( ! http ) { + rc = -ENOMEM; + goto err; + } + memset ( http, 0, sizeof ( *http ) ); + http->uri = uri; + http->buffer = buffer; http->tcp.tcp_op = &http_tcp_operations; - if ( ( rc = tcp_connect ( &http->tcp, &http->server, 0 ) ) != 0 ) - async_done ( &http->aop, rc ); - return &http->aop; +#warning "Quick name resolution hack" + union { + struct sockaddr_tcpip st; + struct sockaddr_in sin; + } server; + server.sin.sin_port = htons ( HTTP_PORT ); + server.sin.sin_family = AF_INET; + if ( inet_aton ( uri->host, &server.sin.sin_addr ) == 0 ) { + rc = -EINVAL; + goto err; + } + + if ( ( rc = tcp_connect ( &http->tcp, &server.st, 0 ) ) != 0 ) + goto err; + + async_init ( &http->async, &http_async_operations, parent ); + return 0; + + err: + DBGC ( http, "HTTP %p could not create request: %s\n", + http, strerror ( rc ) ); + return rc; } diff --git a/src/net/tcp/iscsi.c b/src/net/tcp/iscsi.c index ae23cfe8..09edf697 100644 --- a/src/net/tcp/iscsi.c +++ b/src/net/tcp/iscsi.c @@ -121,7 +121,7 @@ static void iscsi_done ( struct iscsi_session *iscsi, int rc ) { iscsi->command = NULL; /* Mark asynchronous operation as complete */ - async_done ( &iscsi->aop, rc ); + async_done ( &iscsi->async, rc ); } /**************************************************************************** @@ -1208,10 +1208,11 @@ static struct tcp_operations iscsi_tcp_operations = { * * @v iscsi iSCSI session * @v command SCSI command - * @ret aop Asynchronous operation for this SCSI command + * @v parent Parent asynchronous operation + * @ret rc Return status code */ -struct async_operation * iscsi_issue ( struct iscsi_session *iscsi, - struct scsi_command *command ) { +int iscsi_issue ( struct iscsi_session *iscsi, struct scsi_command *command, + struct async *parent ) { int rc; assert ( iscsi->command == NULL ); @@ -1219,7 +1220,7 @@ struct async_operation * iscsi_issue ( struct iscsi_session *iscsi, if ( iscsi->instant_rc ) { /* Abort immediately rather than retrying */ - iscsi_done ( iscsi, iscsi->instant_rc ); + return iscsi->instant_rc; } else if ( iscsi->status ) { /* Session already open: issue command */ iscsi_start_command ( iscsi ); @@ -1231,11 +1232,12 @@ struct async_operation * iscsi_issue ( struct iscsi_session *iscsi, 0 ) ) != 0 ) { DBGC ( iscsi, "iSCSI %p could not open TCP " "connection: %s\n", iscsi, strerror ( rc ) ); - iscsi_done ( iscsi, rc ); + return rc; } } - return &iscsi->aop; + async_init ( &iscsi->async, &default_async_operations, parent ); + return 0; } /** diff --git a/src/net/udp/dhcp.c b/src/net/udp/dhcp.c index 8f829701..a2d3db6b 100644 --- a/src/net/udp/dhcp.c +++ b/src/net/udp/dhcp.c @@ -517,7 +517,7 @@ static void dhcp_done ( struct dhcp_session *dhcp, int rc ) { ref_del ( &dhcp->netdev_ref ); /* Mark async operation as complete */ - async_done ( &dhcp->aop, rc ); + async_done ( &dhcp->async, rc ); } /** Address for transmitting DHCP requests */ @@ -713,14 +713,15 @@ static void dhcp_forget_netdev ( struct reference *ref ) { * Initiate DHCP on a network interface * * @v dhcp DHCP session - * @ret aop Asynchronous operation + * @v parent Parent asynchronous operation + * @ret rc Return status code * * If the DHCP operation completes successfully, the * dhcp_session::options field will be filled in with the resulting * options block. The caller takes responsibility for eventually * calling free_dhcp_options(). */ -struct async_operation * start_dhcp ( struct dhcp_session *dhcp ) { +int start_dhcp ( struct dhcp_session *dhcp, struct async *parent ) { int rc; /* Initialise DHCP session */ @@ -729,10 +730,8 @@ struct async_operation * start_dhcp ( struct dhcp_session *dhcp ) { dhcp->state = DHCPDISCOVER; /* Bind to local port */ - if ( ( rc = udp_open ( &dhcp->udp, htons ( BOOTPC_PORT ) ) ) != 0 ) { - async_done ( &dhcp->aop, rc ); - goto out; - } + if ( ( rc = udp_open ( &dhcp->udp, htons ( BOOTPC_PORT ) ) ) != 0 ) + return rc; /* Add persistent reference to net device */ dhcp->netdev_ref.forget = dhcp_forget_netdev; @@ -741,6 +740,6 @@ struct async_operation * start_dhcp ( struct dhcp_session *dhcp ) { /* Proof of concept: just send a single DHCPDISCOVER */ dhcp_send_request ( dhcp ); - out: - return &dhcp->aop; + async_init ( &dhcp->async, &default_async_operations, parent ); + return 0; } diff --git a/src/net/udp/tftp.c b/src/net/udp/tftp.c index 6753e838..69650ab0 100644 --- a/src/net/udp/tftp.c +++ b/src/net/udp/tftp.c @@ -142,7 +142,7 @@ static void tftp_done ( struct tftp_session *tftp, int rc ) { udp_close ( &tftp->udp ); /* Mark async operation as complete */ - async_done ( &tftp->aop, rc ); + async_done ( &tftp->async, rc ); } /** @@ -477,7 +477,7 @@ struct async_operation * tftp_get ( struct tftp_session *tftp ) { /* Open UDP connection */ if ( ( rc = udp_open ( &tftp->udp, 0 ) ) != 0 ) { - async_done ( &tftp->aop, rc ); + async_done ( &tftp->async, rc ); goto out; } @@ -485,5 +485,5 @@ struct async_operation * tftp_get ( struct tftp_session *tftp ) { tftp_send_packet ( tftp ); out: - return &tftp->aop; + return &tftp->async; } diff --git a/src/tests/aoeboot.c b/src/tests/aoeboot.c index 9d3013b2..0fa9ba82 100644 --- a/src/tests/aoeboot.c +++ b/src/tests/aoeboot.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include diff --git a/src/tests/dhcptest.c b/src/tests/dhcptest.c index 5d6f52dc..f35a3478 100644 --- a/src/tests/dhcptest.c +++ b/src/tests/dhcptest.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -7,6 +8,8 @@ #include #include +#if 0 + static int test_dhcp_aoe_boot ( struct net_device *netdev, char *aoename ) { unsigned int drivenum; @@ -263,3 +266,5 @@ int test_dhcp ( struct net_device *netdev ) { out_no_del_ipv4: return rc; } + +#endif diff --git a/src/tests/ftptest.c b/src/tests/ftptest.c deleted file mode 100644 index fe08ec52..00000000 --- a/src/tests/ftptest.c +++ /dev/null @@ -1,52 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -static void print_ftp_response ( char *data, size_t len ) { - unsigned int i; - char c; - - for ( i = 0 ; i < len ; i++ ) { - c = data[i]; - if ( c == '\r' ) { - /* Print nothing */ - } else if ( ( c == '\n' ) || ( c >= 32 ) || ( c <= 126 ) ) { - putchar ( c ); - } else { - putchar ( '.' ); - } - } -} - -void test_ftp ( struct sockaddr_tcpip *server, const char *filename ) { - char data[256]; - struct buffer buffer; - struct ftp_request ftp; - int rc; - - printf ( "FTP fetching %s\n", filename ); - - memset ( &buffer, 0, sizeof ( buffer ) ); - buffer.addr = virt_to_user ( data ); - buffer.len = sizeof ( data ); - - memset ( &ftp, 0, sizeof ( ftp ) ); - memcpy ( &ftp.server, server, sizeof ( ftp.server ) ); - ftp.filename = filename; - ftp.buffer = &buffer; - - rc = async_wait ( ftp_get ( &ftp ) ); - if ( rc ) { - printf ( "FTP fetch failed\n" ); - return; - } - - printf ( "FTP received %d bytes\n", buffer.fill ); - - print_ftp_response ( data, buffer.fill ); -} diff --git a/src/tests/hellotest.c b/src/tests/hellotest.c deleted file mode 100644 index a873c03e..00000000 --- a/src/tests/hellotest.c +++ /dev/null @@ -1,43 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -static void test_hello_callback ( char *data, size_t len ) { - unsigned int i; - char c; - - for ( i = 0 ; i < len ; i++ ) { - c = data[i]; - if ( c == '\r' ) { - /* Print nothing */ - } else if ( ( c == '\n' ) || ( c >= 32 ) || ( c <= 126 ) ) { - putchar ( c ); - } else { - putchar ( '.' ); - } - } -} - -void test_hello ( struct sockaddr_tcpip *server, const char *message ) { - /* Quick and dirty hack */ - struct sockaddr_in *sin = ( struct sockaddr_in * ) server; - struct hello_request hello; - int rc; - - printf ( "Saying \"%s\" to %s:%d\n", message, - inet_ntoa ( sin->sin_addr ), ntohs ( sin->sin_port ) ); - - memset ( &hello, 0, sizeof ( hello ) ); - memcpy ( &hello.server, server, sizeof ( hello.server ) ); - hello.message = message; - hello.callback = test_hello_callback; - - rc = async_wait ( say_hello ( &hello ) ); - if ( rc ) { - printf ( "HELLO fetch failed\n" ); - } -} diff --git a/src/usr/dhcpmgmt.c b/src/usr/dhcpmgmt.c index 6042f8b2..0e36d555 100644 --- a/src/usr/dhcpmgmt.c +++ b/src/usr/dhcpmgmt.c @@ -23,6 +23,7 @@ #include #include #include +#include #include /** @file @@ -43,6 +44,7 @@ int dhcp ( struct net_device *netdev ) { struct in_addr address = { htonl ( 0 ) }; struct in_addr netmask = { htonl ( 0 ) }; struct in_addr gateway = { INADDR_NONE }; + struct async async; int rc; /* Check we can open the interface first */ @@ -60,8 +62,14 @@ int dhcp ( struct net_device *netdev ) { printf ( "DHCP (%s %s)...", netdev->name, netdev_hwaddr ( netdev ) ); memset ( &dhcp, 0, sizeof ( dhcp ) ); dhcp.netdev = netdev; - if ( ( rc = async_wait ( start_dhcp ( &dhcp ) ) ) != 0 ) { - printf ( "failed\n" ); + async_init_orphan ( &async ); + if ( ( rc = start_dhcp ( &dhcp, &async ) ) != 0 ) { + printf ( "could not start (%s)\n", strerror ( rc ) ); + return rc; + } + async_wait ( &async, &rc, 1 ); + if ( rc != 0 ) { + printf ( "failed (%s)\n", strerror ( rc ) ); return rc; } printf ( "done\n" ); diff --git a/src/usr/fetch.c b/src/usr/fetch.c index ce0fea95..9e3c2de6 100644 --- a/src/usr/fetch.c +++ b/src/usr/fetch.c @@ -23,10 +23,12 @@ * */ +#include #include #include #include #include +#include #include #include @@ -46,32 +48,30 @@ * caller is responsible for eventually freeing the buffer with * efree(). */ -int fetch ( const char *filename, userptr_t *data, size_t *len ) { +int fetch ( const char *uri_string, userptr_t *data, size_t *len ) { + struct uri *uri; struct buffer buffer; int rc; + /* Parse the URI */ + uri = parse_uri ( uri_string ); + if ( ! uri ) { + rc = -ENOMEM; + goto err_parse_uri; + } + /* Allocate an expandable buffer to hold the file */ - if ( ( rc = ebuffer_alloc ( &buffer, 0 ) ) != 0 ) - return rc; + if ( ( rc = ebuffer_alloc ( &buffer, 0 ) ) != 0 ) { + goto err_ebuffer_alloc; + } #warning "Temporary pseudo-URL parsing code" /* Retrieve the file */ - union { - struct sockaddr_tcpip st; - struct sockaddr_in sin; - } server; - struct tftp_session tftp; - struct http_request http; - struct async_operation *aop; - - memset ( &tftp, 0, sizeof ( tftp ) ); - memset ( &http, 0, sizeof ( http ) ); - memset ( &server, 0, sizeof ( server ) ); - server.sin.sin_family = AF_INET; - find_global_dhcp_ipv4_option ( DHCP_EB_SIADDR, - &server.sin.sin_addr ); + struct async async; + int ( * download ) ( struct uri *uri, struct buffer *buffer, + struct async *parent ); #if 0 server.sin.sin_port = htons ( TFTP_PORT ); @@ -80,22 +80,31 @@ int fetch ( const char *filename, userptr_t *data, size_t *len ) { tftp.buffer = &buffer; aop = tftp_get ( &tftp ); #else - server.sin.sin_port = htons ( HTTP_PORT ); - memcpy ( &http.server, &server, sizeof ( http.server ) ); - http.hostname = inet_ntoa ( server.sin.sin_addr ); - http.filename = filename; - http.buffer = &buffer; - aop = http_get ( &http ); + download = http_get; #endif - if ( ( rc = async_wait ( aop ) ) != 0 ) { - efree ( buffer.addr ); - return rc; - } + async_init_orphan ( &async ); + if ( ( rc = download ( uri, &buffer, &async ) ) != 0 ) + goto err; + uri = NULL; + async_wait ( &async, &rc, 1 ); + if ( rc != 0 ) + goto err; /* Fill in buffer address and length */ *data = buffer.addr; *len = buffer.fill; + /* Release temporary resources. The ebuffer storage is now + * owned by our caller, so we don't free it. + */ + return 0; + + err: + efree ( buffer.addr ); + err_ebuffer_alloc: + free_uri ( uri ); + err_parse_uri: + return rc; }