diff options
author | David 'Digit' Turner <digit@android.com> | 2010-11-18 16:14:03 +0100 |
---|---|---|
committer | David 'Digit' Turner <digit@android.com> | 2010-11-19 15:01:21 +0100 |
commit | 6d448806a80bcc2557ae0a38e7fd206967cf844e (patch) | |
tree | 08c567d36949bbc4263eb7a84b67936ce7789f6d /android | |
parent | 1bb627cd086588d3f9650fac04f4034961caf9f1 (diff) | |
download | external_qemu-6d448806a80bcc2557ae0a38e7fd206967cf844e.zip external_qemu-6d448806a80bcc2557ae0a38e7fd206967cf844e.tar.gz external_qemu-6d448806a80bcc2557ae0a38e7fd206967cf844e.tar.bz2 |
Introduce asynchronous operation helpers.
<android/async-utils.h> contains generic helpers to read, write
and connect to sockets.
<android/async-console.h> contains a helper class to connect
to Android console port asynchronously.
Change-Id: I5d0a49a770ad974c5d4382438d75e9eb624368d1
Diffstat (limited to 'android')
-rw-r--r-- | android/async-console.c | 147 | ||||
-rw-r--r-- | android/async-console.h | 63 | ||||
-rw-r--r-- | android/async-utils.c | 266 | ||||
-rw-r--r-- | android/async-utils.h | 187 |
4 files changed, 663 insertions, 0 deletions
diff --git a/android/async-console.c b/android/async-console.c new file mode 100644 index 0000000..f486df0 --- /dev/null +++ b/android/async-console.c @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "android/async-console.h" +#include <string.h> + +/* + * State diagram, ommitting the ERROR state + * + * INITIAL -->--+ + * | | + * | CONNECTING + * | | + * |<-----+ + * v + * READ_BANNER_1 + * | + * v + * READ_BANNER_2 + * | + * v + * COMPLETE + */ + +enum { + STATE_INITIAL, + STATE_CONNECTING, + STATE_ERROR, + STATE_READ_BANNER_1, + STATE_READ_BANNER_2, + STATE_COMPLETE +}; + +/* A helper function to prepare the line reader and switch to a new state */ +static AsyncStatus +_acc_prepareLineReader(AsyncConsoleConnector* acc, LoopIo* io, int newState) +{ + acc->state = newState; + asyncLineReader_init(acc->lreader, acc->lbuff, sizeof(acc->lbuff), io); + return ASYNC_NEED_MORE; +} + +AsyncStatus +asyncConsoleConnector_connect(AsyncConsoleConnector* acc, + const SockAddress* address, + LoopIo* io) +{ + acc->state = STATE_INITIAL; + acc->address = address[0]; + return asyncConsoleConnector_run(acc, io); +} + + +AsyncStatus +asyncConsoleConnector_run(AsyncConsoleConnector* acc, + LoopIo* io) +{ + AsyncStatus status = ASYNC_NEED_MORE; + + for (;;) { + switch (acc->state) + { + case STATE_ERROR: /* reporting previous error */ + errno = acc->error; + return ASYNC_ERROR; + + case STATE_INITIAL: /* initial connection attempt */ + acc->state = STATE_CONNECTING; + status = asyncConnector_init(acc->connector, &acc->address, io); + if (status == ASYNC_ERROR) + goto SET_ERROR; + + if (status == ASYNC_COMPLETE) { /* immediate connection */ + _acc_prepareLineReader(acc, io, STATE_READ_BANNER_1); + continue; + } + break; + + case STATE_CONNECTING: /* still trying to connect */ + status = asyncConnector_run(acc->connector, io); + if (status == ASYNC_ERROR) + goto SET_ERROR; + + if (status == ASYNC_COMPLETE) { + _acc_prepareLineReader(acc, io, STATE_READ_BANNER_1); + continue; + } + break; + + case STATE_READ_BANNER_1: /* reading the first banner line */ + status = asyncLineReader_read(acc->lreader, io); + if (status == ASYNC_ERROR) + goto SET_ERROR; + + if (status == ASYNC_COMPLETE) { + /* Check that first line starts with "Android Console:", + * otherwise we're not talking to the right program. */ + const char* line = asyncLineReader_getLine(acc->lreader); + if (line == NULL || memcmp(line, "Android Console:", 16)) { + goto BAD_BANNER; + } + /* ok, fine, prepare for the next banner line then */ + _acc_prepareLineReader(acc, io, STATE_READ_BANNER_2); + continue; + } + break; + + case STATE_READ_BANNER_2: /* reading the second banner line */ + status = asyncLineReader_read(acc->lreader, io); + if (status == ASYNC_ERROR) + goto SET_ERROR; + + if (status == ASYNC_COMPLETE) { + const char* line = asyncLineReader_getLine(acc->lreader); + if (line == NULL) { + goto BAD_BANNER; + } + /* ok, we're done !*/ + acc->state = STATE_COMPLETE; + return ASYNC_COMPLETE; + } + break; + + case STATE_COMPLETE: + status = ASYNC_COMPLETE; + } + return status; + } +BAD_BANNER: + errno = ENOPROTOOPT; +SET_ERROR: + acc->state = STATE_ERROR; + acc->error = errno; + return ASYNC_ERROR; +} diff --git a/android/async-console.h b/android/async-console.h new file mode 100644 index 0000000..6779ae2 --- /dev/null +++ b/android/async-console.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef ANDROID_ASYNC_CONSOLE_H +#define ANDROID_ASYNC_CONSOLE_H + +#include "android/async-utils.h" + +/* An AsyncConsoleConnector allows you to asynchronously connect to an + * Android console port. + */ +typedef struct { + int state; + int error; + LoopIo* io; + SockAddress address; + AsyncConnector connector[1]; + AsyncLineReader lreader[1]; + uint8_t lbuff[128]; +} AsyncConsoleConnector; + +/* Initialize the console connector. This attempts to connect to the address + * provided through 'io'. Use asyncConsoleConnect_run() after that. + */ +AsyncStatus +asyncConsoleConnector_connect(AsyncConsoleConnector* acc, + const SockAddress* address, + LoopIo* io); + +/* Asynchronous console connection management. Returns: + * + * ASYNC_COMPLETE: + * Connection was complete, and the console banner was properly read/eaten. + * you can now send/write commands through the console with 'io'. + * + * ASYNC_ERROR: + * An error occured, either during the connection itself, or when + * reading the content. This sets errno to ENOPROTOOPT if the connector + * detects that you're not connected to a proper Android emulator console + * port (i.e. if the banner was incorrect). Other errors are possible + * (e.g. in case of early connection termination). + * + * ASYNC_NEED_MORE: + * Not enough data was exchanged, call this function later. + */ +AsyncStatus +asyncConsoleConnector_run(AsyncConsoleConnector* acc, + LoopIo* io); + + +#endif /* ANDROID_ASYNC_CONSOLE_H */ diff --git a/android/async-utils.c b/android/async-utils.c new file mode 100644 index 0000000..678b0b4 --- /dev/null +++ b/android/async-utils.c @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "android/async-utils.h" +#include "unistd.h" + +void +asyncReader_init(AsyncReader* ar, + void* buffer, + size_t buffsize, + LoopIo* io) +{ + ar->buffer = buffer; + ar->buffsize = buffsize; + ar->pos = 0; + if (buffsize > 0) + loopIo_wantRead(io); +} + +AsyncStatus +asyncReader_read(AsyncReader* ar, + LoopIo* io) +{ + int ret; + + if (ar->pos >= ar->buffsize) { + return ASYNC_COMPLETE; + } + + do { + ret = read(io->fd, ar->buffer + ar->pos, ar->buffsize - ar->pos); + if (ret == 0) { + /* disconnection ! */ + errno = ECONNRESET; + return ASYNC_ERROR; + } + if (ret < 0) { + if (errno == EINTR) /* loop on EINTR */ + continue; + if (errno == EWOULDBLOCK || errno == EAGAIN) { + loopIo_wantRead(io); + return ASYNC_NEED_MORE; + } + return ASYNC_ERROR; + } + ar->pos += ret; + + } while (ar->pos < ar->buffsize); + + loopIo_dontWantRead(io); + return ASYNC_COMPLETE; +} + +void +asyncWriter_init(AsyncWriter* aw, + const void* buffer, + size_t buffsize, + LoopIo* io) +{ + aw->buffer = buffer; + aw->buffsize = buffsize; + aw->pos = 0; + if (buffsize > 0) + loopIo_wantWrite(io); +} + +AsyncStatus +asyncWriter_write(AsyncWriter* aw, + LoopIo* io) +{ + int ret; + + if (aw->pos >= aw->buffsize) { + return ASYNC_COMPLETE; + } + + do { + ret = write(io->fd, aw->buffer + aw->pos, aw->buffsize - aw->pos); + if (ret == 0) { + /* disconnection ! */ + errno = ECONNRESET; + return ASYNC_ERROR; + } + if (ret < 0) { + if (errno == EINTR) /* loop on EINTR */ + continue; + if (errno == EWOULDBLOCK || errno == EAGAIN) { + return ASYNC_NEED_MORE; + } + return ASYNC_ERROR; + } + aw->pos += ret; + + } while (aw->pos < aw->buffsize); + + loopIo_dontWantWrite(io); + return ASYNC_COMPLETE; +} + + +void +asyncLineReader_init(AsyncLineReader* alr, + void* buffer, + size_t buffsize, + LoopIo* io) +{ + alr->buffer = buffer; + alr->buffsize = buffsize; + alr->pos = 0; + if (buffsize > 0) + loopIo_wantRead(io); +} + +AsyncStatus +asyncLineReader_read(AsyncLineReader* alr, + LoopIo* io) +{ + int ret; + + if (alr->pos >= alr->buffsize) { + errno = ENOMEM; + return ASYNC_ERROR; + } + + do { + char ch; + ret = read(io->fd, &ch, 1); + if (ret == 0) { + /* disconnection ! */ + errno = ECONNRESET; + return ASYNC_ERROR; + } + if (ret < 0) { + if (errno == EINTR) /* loop on EINTR */ + continue; + if (errno == EWOULDBLOCK || errno == EAGAIN) { + loopIo_wantRead(io); + return ASYNC_NEED_MORE; + } + return ASYNC_ERROR; + } + alr->buffer[alr->pos++] = (uint8_t)ch; + if (ch == '\n') { + loopIo_dontWantRead(io); + return ASYNC_COMPLETE; + } + } while (alr->pos < alr->buffsize); + + /* Not enough room in the input buffer!*/ + loopIo_dontWantRead(io); + errno = ENOMEM; + return ASYNC_ERROR; +} + +const char* +asyncLineReader_getLineRaw(AsyncLineReader* alr, int *pLength) +{ + if (alr->pos == 0 || alr->pos > alr->buffsize) + return NULL; + + if (pLength != 0) + *pLength = alr->pos; + + return (const char*) alr->buffer; +} + +const char* +asyncLineReader_getLine(AsyncLineReader* alr) +{ + /* Strip trailing \n if any */ + size_t pos = alr->pos; + char* buffer = (char*) alr->buffer; + + if (pos == 0 || pos > alr->buffsize) + return NULL; + + pos--; + + /* Check that we have a proper terminator, and replace it with 0 */ + if (buffer[pos] != '\n') + return NULL; + + buffer[pos] = '\0'; + + /* Also strip \r\n */ + if (pos > 0 && buffer[--pos] == '\r') { + buffer[pos] = '\0'; + } + + return (const char*) buffer; +} + + +enum { + CONNECT_ERROR = 0, + CONNECT_CONNECTING, + CONNECT_COMPLETED +}; + +AsyncStatus +asyncConnector_init(AsyncConnector* ac, + const SockAddress* address, + LoopIo* io) +{ + int ret; + ac->error = 0; + ret = socket_connect(io->fd, address); + if (ret == 0) { + ac->state = CONNECT_COMPLETED; + return ASYNC_COMPLETE; + } + if (errno == EINPROGRESS || errno == EWOULDBLOCK || errno == EAGAIN) { + ac->state = CONNECT_CONNECTING; + /* The socket will be marked writable for select() when the + * connection is established, or when it is definitely + * refused / timed-out, for any reason. */ + loopIo_wantWrite(io); + return ASYNC_NEED_MORE; + } + ac->error = errno; + ac->state = CONNECT_ERROR; + return ASYNC_ERROR; +} + +AsyncStatus +asyncConnector_run(AsyncConnector* ac, LoopIo* io) +{ + switch (ac->state) { + case CONNECT_ERROR: + errno = ac->error; + return ASYNC_ERROR; + + case CONNECT_CONNECTING: + loopIo_dontWantWrite(io); + /* We need to read the socket error to determine if + * the connection was really succesful or not. This + * is optional, because in case of error a future + * read() or write() will fail anyway, but this + * allows us to get a better error value as soon as + * possible. + */ + ac->error = socket_get_error(io->fd); + if (ac->error == 0) { + ac->state = CONNECT_COMPLETED; + return ASYNC_COMPLETE; + } + ac->state = CONNECT_ERROR; + errno = ac->error; + return ASYNC_ERROR; + + default: + return ASYNC_COMPLETE; + } +} diff --git a/android/async-utils.h b/android/async-utils.h new file mode 100644 index 0000000..e34e1bb --- /dev/null +++ b/android/async-utils.h @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef ANDROID_ASYNC_UTILS_H +#define ANDROID_ASYNC_UTILS_H + +#include "android/looper.h" +#include "sockets.h" + +/* A set of useful data types to perform asynchronous operations. + * + * IMPORTANT NOTE: + * In case of network disconnection, read() and write() just return 0 + * the first time they are called. As a convenience, these functions + * will return ASYNC_ERROR and set 'errno' to ECONNRESET instead. + */ +typedef enum { + ASYNC_COMPLETE = 0, /* asynchronous operation completed */ + ASYNC_ERROR, /* an error occurred, look at errno */ + ASYNC_NEED_MORE /* more data is needed, try again later */ +} AsyncStatus; + +/* An AsyncReader makes it easier to read a given number of bytes into + * a target buffer asynchronously. Usage is the following: + * + * 1/ setup the reader with asyncReader_init(ar, buffer, buffsize,io); + * 2/ call asyncReader_read(ar, io), where 'io' is a LoopIo whenever + * you can receive data, i.e. just after the init() or in your + * own callback. + */ +typedef struct { + uint8_t* buffer; + size_t buffsize; + size_t pos; +} AsyncReader; + +/* Setup an ASyncReader, by giving the address of the read buffer, + * and the number of bytes we want to read. + * + * This also calls loopIo_wantRead(io) for you. + */ +void asyncReader_init(AsyncReader* ar, + void* buffer, + size_t buffsize, + LoopIo* io); + +/* Try to read data from 'io' and return the state of the read operation. + * + * Returns: + * ASYNC_COMPLETE: If the read operation was complete. This will also + * call loopIo_dontWantRead(io) for you. + * + * ASYNC_ERROR: If an error occured (see errno). The error will be + * ECONNRESET in case of disconnection. + * + * ASYNC_NEED_MORE: If there was not enough incoming data to complete + * the read (or if 'events' doesn't contain LOOP_IO_READ). + */ +AsyncStatus asyncReader_read(AsyncReader* ar, + LoopIo* io); + +/* An AsyncWriter is the counterpart of an AsyncReader, but for writing + * data to a file descriptor asynchronously. + */ +typedef struct { + const uint8_t* buffer; + size_t buffsize; + size_t pos; +} AsyncWriter; + +/* Setup an ASyncReader, by giving the address of the read buffer, + * and the number of bytes we want to read. + * + * This also calls loopIo_wantWrite(io) for you. + */ +void asyncWriter_init(AsyncWriter* aw, + const void* buffer, + size_t buffsize, + LoopIo* io); + +/* Try to write data to 'io' and return the state of the write operation. + * + * Returns: + * ASYNC_COMPLETE: If the write operation was complete. This will also + * call loopIo_dontWantWrite(io) for you. + * + * ASYNC_ERROR: If an error occured (see errno). The error will be + * ECONNRESET in case of disconnection. + * + * ASYNC_NEED_MORE: If not all bytes could be sent yet (or if 'events' + * doesn't contain LOOP_IO_READ). + */ +AsyncStatus asyncWriter_write(AsyncWriter* aw, + LoopIo* io); + + +/* An AsyncLineReader allows you to read one line of text asynchronously. + * The biggest difference with AsyncReader is that you don't know the line + * size in advance, so the object will read data byte-by-byte until it + * encounters a '\n'. + */ +typedef struct { + uint8_t* buffer; + size_t buffsize; + size_t pos; +} AsyncLineReader; + +/* Setup an AsyncLineReader to read at most 'buffsize' characters (bytes) + * into 'buffer'. The reader will stop when it finds a '\n' which will be + * part of the buffer by default. + * + * NOTE: buffsize must be > 0. If not, asyncLineReader_getLine will return + * ASYNC_ERROR with errno == ENOMEM. + * + * buffsize must also sufficiently big to hold the final '\n'. + * + * Also calls loopIo_wantRead(io) for you. + */ +void asyncLineReader_init(AsyncLineReader* alr, + void* buffer, + size_t buffsize, + LoopIo* io); + +/* Try to read line characters from 'io'. + * Returns: + * ASYNC_COMPLETE: An end-of-line was detected, call asyncLineReader_getLine + * to extract the line content. + * + * ASYNC_ERROR: An error occured. Note that in case of disconnection, + * errno will be set to ECONNRESET, but you should be able + * to call asyncLineReader_getLine to read the partial line + * that was read. + * + * In case of overflow, errno will be set to ENOMEM. + * + * ASYNC_NEED_MORE: If there was not enough incoming data (or events + * does not contain LOOP_IO_READ). + */ +AsyncStatus asyncLineReader_read(AsyncLineReader* alr, + LoopIo* io); + +/* Return a pointer to the NON-ZERO-TERMINATED line characters, if any. + * If 'pLength" is not NULL, the function sets '*pLength' to the length + * in bytes of the line. + * + * Returns: + * NULL if 'buffsize' was initially 0, otherwise, a pointer to 'buffer' + * as passed in asyncLineReader_setup(). + * + * NOTE: The data is *not* zero terminated, but its last character + * should be '\n' unless an error occured. + */ +const char* asyncLineReader_getLineRaw(AsyncLineReader* alr, int *pLength); + +/* Return a pointer to the ZERO-TERMINATED line, with final '\n' or '\r\n' + * stripped. This will be NULL in case of error though. + */ +const char* asyncLineReader_getLine(AsyncLineReader* alr); + +/* Asynchronous connection to a socket + */ +typedef struct { + int error; + int state; +} AsyncConnector; + +AsyncStatus +asyncConnector_init(AsyncConnector* ac, + const SockAddress* address, + LoopIo* io); + +AsyncStatus +asyncConnector_run(AsyncConnector* ac, LoopIo* io); + +#endif /* ANDROID_ASYNC_UTILS_H */ |