Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
SocketCancel.c
/* |
File: SocketCancel.c |
Contains: A program to demonstrate how to write BSD sockets code that can be cancelled. |
Written by: DTS |
Copyright: Copyright (c) 2005 by Apple Computer, Inc., All Rights Reserved. |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. |
("Apple") in consideration of your agreement to the following terms, and your |
use, installation, modification or redistribution of this Apple software |
constitutes acceptance of these terms. If you do not agree with these terms, |
please do not use, install, modify or redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and subject |
to these terms, Apple grants you a personal, non-exclusive license, under Apple's |
copyrights in this original Apple software (the "Apple Software"), to use, |
reproduce, modify and redistribute the Apple Software, with or without |
modifications, in source and/or binary forms; provided that if you redistribute |
the Apple Software in its entirety and without modifications, you must retain |
this notice and the following text and disclaimers in all such redistributions of |
the Apple Software. Neither the name, trademarks, service marks or logos of |
Apple Computer, Inc. may be used to endorse or promote products derived from the |
Apple Software without specific prior written permission from Apple. Except as |
expressly stated in this notice, no other rights or licenses, express or implied, |
are granted by Apple herein, including but not limited to any patent rights that |
may be infringed by your derivative works or by other works in which the Apple |
Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO |
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED |
WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN |
COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR |
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE |
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION |
OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT |
(INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN |
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
Change History (most recent first): |
$Log: SocketCancel.c,v $ |
Revision 1.1 2005/08/02 10:31:35 eskimo1 |
First checked in. The only difference from version 1.0a1 is that I've changed the definition of some local variables from int to socklen_t to quieten GCC 4.0's warnings. |
*/ |
///////////////////////////////////////////////////////////////// |
#include <stdio.h> |
#include <assert.h> |
#include <pthread.h> |
#include <pthread.h> |
#include <sys/types.h> |
#include <sys/socket.h> |
#include <netinet/in.h> |
#include <string.h> |
#include <fcntl.h> |
#include <sys/time.h> |
#include <unistd.h> |
#include <stdbool.h> |
#include <stdlib.h> |
#include <netdb.h> |
#include <errno.h> |
///////////////////////////////////////////////////////////////// |
#pragma mark **** Cancellable Socket Abstraction Layer |
struct CSocket { |
int sock; |
int sndCancel; |
int rcvCancel; |
int maxFD; |
bool didCancel; |
}; |
typedef struct CSocket CSocket; |
static void CSocketClose(CSocket *cSock); // forward declaration |
static int CSocketOpen(int domain, int type, int protocol, CSocket **cSockPtr) |
// Our equivalent to "socket". Creates an CSocket object for the specified |
// domain, type and protocol. The result is an errno-style error. |
{ |
int err; |
CSocket * cSock; |
assert( cSockPtr != NULL); |
assert(*cSockPtr == NULL); |
// Allocate the CSocket. |
err = 0; |
cSock = (CSocket *) calloc(1, sizeof(*cSock)); |
if (cSock == NULL) { |
err = ENOMEM; |
} |
// Initialise it, create the data socket, set it non-blocking, and |
// then create the socket pair we use for cancellation. That socket |
// pair remains blocking, because we only ever send a single byte on it. |
if (err == 0) { |
cSock->sock = -1; |
cSock->sndCancel = -1; |
cSock->rcvCancel = -1; |
assert( cSock->didCancel == false ); // cleared by calloc, above |
cSock->sock = socket(domain, type, protocol); |
if (cSock->sock < 0) { |
err = errno; |
} |
} |
if (err == 0) { |
err = fcntl(cSock->sock, F_SETFL, O_NONBLOCK); |
if (err < 0) { |
err = errno; |
} |
} |
if (err == 0) { |
int tmpSock[2]; |
err = socketpair(AF_UNIX, SOCK_STREAM, 0, tmpSock); |
if (err < 0) { |
err = errno; |
} else { |
cSock->sndCancel = tmpSock[0]; |
cSock->rcvCancel = tmpSock[1]; |
} |
} |
if (err == 0) { |
cSock->maxFD = cSock->sock; |
if (cSock->sndCancel > cSock->maxFD) { |
cSock->maxFD = cSock->sndCancel; |
} |
if (cSock->rcvCancel > cSock->maxFD) { |
cSock->maxFD = cSock->rcvCancel; |
} |
} |
// Clean up. |
if (err != 0) { |
CSocketClose(cSock); |
cSock = NULL; |
} |
*cSockPtr = cSock; |
assert( (err == 0) && (cSockPtr != NULL) ); |
return err; |
} |
static void CSocketClose(CSocket *cSock) |
// Our equivalent to "close". Closes an CSocket object. |
{ |
int junk; |
if (cSock != NULL) { |
if (cSock->sock != -1) { |
junk = close(cSock->sock); |
assert(junk == 0); |
} |
if (cSock->sndCancel != -1) { |
junk = close(cSock->sndCancel); |
assert(junk == 0); |
} |
if (cSock->rcvCancel != -1) { |
junk = close(cSock->rcvCancel); |
assert(junk == 0); |
} |
free(cSock); |
} |
} |
static bool CSocketValid(const CSocket *cSock) |
// Returns true if the CSocket object is valid. |
{ |
return (cSock != NULL) |
&& (cSock->sock != -1) |
&& (cSock->sndCancel != -1) |
&& (cSock->rcvCancel != -1) |
&& (cSock->maxFD >= cSock->sock) |
&& (cSock->maxFD >= cSock->sndCancel) |
&& (cSock->maxFD >= cSock->rcvCancel); |
} |
typedef enum { |
kCSocketSelectConnect, |
kCSocketSelectRead, |
kCSocketSelectWrite |
} CSocketSelectMode; |
static int CSocketSelect(CSocket *cSock, CSocketSelectMode mode, bool *dataPresent) |
// Waits for activity on the data socket associated with cSock |
// and the socket used to signal cancellating, and sets |
// *dataPresent if there was activity on the data socket. Result |
// is an errno-style error. A value of 0 indicates that the select |
// was successful and some data is present. A value of ECANCELED |
// indicates that the select unblocked with data on the cancellation |
// socket. Loops on EINTR, so EINTR is never returned. |
// |
// The exact sockets are controlled by mode. |
// |
// Mode read FD set write FD set dataPresents tests |
// ---- ----------- ------------ ------------------ |
// kCSocketSelectConnect rcvCancel, sock sock - |
// kCSocketSelectRead rcvCancel, sock - sock in read FD set |
// kCSocketSelectWrite rcvCancel sock sock in write FD set |
{ |
int err; |
fd_set readFDs; |
fd_set writeFDs; |
fd_set * dataPresentFDs; |
int selectResult; |
FD_ZERO(&readFDs); |
FD_ZERO(&writeFDs); |
// We always want the cancel socket in the read FD set. |
FD_SET(cSock->rcvCancel, &readFDs); |
// Add other sockets to the FD sets. Also remember which |
// FD set we're supposed to return whether data is present in. |
switch (mode) { |
case kCSocketSelectConnect: |
FD_SET(cSock->sock, &readFDs); |
FD_SET(cSock->sock, &writeFDs); |
dataPresentFDs = NULL; |
break; |
case kCSocketSelectRead: |
FD_SET(cSock->sock, &readFDs); |
dataPresentFDs = &readFDs; |
break; |
case kCSocketSelectWrite: |
FD_SET(cSock->sock, &writeFDs); |
dataPresentFDs = &writeFDs; |
break; |
default: |
assert(false); |
break; |
} |
// Do the select, looping while we get EINTR errors. |
err = 0; |
do { |
selectResult = select(cSock->maxFD + 1, &readFDs, &writeFDs, NULL, NULL); |
if (selectResult < 0) { |
err = errno; |
} |
} while (err == EINTR); |
if (err == 0) { |
// We have an infinite timeout, so a result of 0 should be impossible, |
// so assert that selectResult is positive. |
assert(selectResult > 0); |
// Check for cancellation first. |
if ( FD_ISSET(cSock->rcvCancel, &readFDs) ) { |
err = ECANCELED; |
} else { |
if ( (dataPresent != NULL) && (dataPresentFDs != NULL) ) { |
*dataPresent = ( FD_ISSET(cSock->sock, dataPresentFDs) != 0 ); |
} |
} |
} |
return err; |
} |
static int CSocketConnect(CSocket *cSock, const struct sockaddr *name, int namelen) |
// Our equivalent to "connect". Connects the CSocket object to the host |
// specified by name and namelen. The result is an errno-style error. |
{ |
int err; |
assert(CSocketValid(cSock)); |
assert(name != NULL); |
// Start the connect. |
err = connect(cSock->sock, name, namelen); |
// Handle any error conditions. |
if (err < 0) { |
err = errno; |
// EINPROGRESS means that the connect started, and we have to wait |
// for it to complete. Let's do that. Any other error is passed to |
// the caller. |
if (err == EINPROGRESS) { |
bool connected; |
socklen_t len; |
connected = false; |
err = CSocketSelect(cSock, kCSocketSelectConnect, NULL); |
if (err == 0) { |
// Not cancelled, so must have either connected or failed. |
// Check to see if we're connected by calling getpeername. |
len = 0; |
err = getpeername(cSock->sock, NULL, &len); |
if (err < 0) { |
err = errno; |
} |
if (err == 0) { |
// The connection attempt worked. |
connected = true; |
} else if (err == ENOTCONN) { |
int tmpErr; |
// The connection failed. Get the error associated with |
// the connection attempt. |
len = sizeof(tmpErr); |
err = getsockopt(cSock->sock, SOL_SOCKET, SO_ERROR, &tmpErr, &len); |
if (err < 0) { |
err = errno; |
} else { |
assert(tmpErr != 0); |
err = tmpErr; |
} |
} |
} |
} |
} |
return err; |
} |
static int CSocketRead(CSocket *cSock, void *buf, size_t nbytes, size_t *bytesReadPtr) |
// Our equivalent to "read". Reads nbytes of data from the CSocket object |
// to the buffer specified by buf and nbytes. The result is an errno-style |
// error. Returns EPIPE if end of file is encountered. |
// |
// IMPORTANT: Unlike standard UNIX read, this routine will block until |
// either the entire buffer is full or an end of file is reached. |
// If you get an EPIPE error, *bytesReadPtr indicates how much data |
// is valid in the buffer. |
{ |
int err; |
size_t bytesLeft; |
char * cursor; |
ssize_t bytesThisTime; |
bool dataPresent; |
assert(CSocketValid(cSock)); |
assert(buf != NULL); |
err = 0; |
bytesLeft = nbytes; |
cursor = (char *) buf; |
while ( (err == 0) && (bytesLeft != 0) ) { |
bytesThisTime = read(cSock->sock, buf, nbytes); |
if (bytesThisTime > 0) { |
cursor += bytesThisTime; |
bytesLeft -= bytesThisTime; |
} else if (bytesThisTime == 0) { |
err = EPIPE; |
} else { |
assert(bytesThisTime == -1); |
err = errno; |
assert(err != 0); |
// We don't need to handle EINTR because read never blocks, |
// and thus can never be interrupted. |
if (err == EAGAIN) { |
err = CSocketSelect(cSock, kCSocketSelectRead, &dataPresent); |
assert( (err != 0) || dataPresent ); |
} |
} |
} |
// Clean up. |
if (bytesReadPtr != NULL) { |
*bytesReadPtr = nbytes - bytesLeft; |
} |
return err; |
} |
static int CSocketWrite(CSocket *cSock, const void *buf, size_t nbytes, size_t *bytesWrittenPtr) |
// Our equivalent to "write". Writes data from the buffer specified by buf |
// and nbytes to the CSocket object. The result is an errno-style error. |
// All data is written, unless the operation fails with an error. |
// On failure, only some of the bytes may have been written. If you |
// want to find out how many, pass non-NULL to bytesWrittenPtr. If not, |
// pass NULL to bytesWrittenPtr. |
{ |
int err; |
size_t bytesLeft; |
const char * cursor; |
ssize_t bytesThisTime; |
bool spaceAvailable; |
assert(CSocketValid(cSock)); |
assert(buf != NULL); |
err = 0; |
bytesLeft = nbytes; |
cursor = (const char *) buf; |
while ( (err == 0) && (bytesLeft != 0) ) { |
bytesThisTime = write(cSock->sock, cursor, bytesLeft); |
if (bytesThisTime > 0) { |
cursor += bytesThisTime; |
bytesLeft -= bytesThisTime; |
} else if (bytesThisTime == 0) { |
assert(false); |
err = EPIPE; |
} else { |
assert(bytesThisTime == -1); |
err = errno; |
assert(err != 0); |
// We don't need to handle EINTR because write never blocks, |
// and thus can never be interrupted. |
if (err == EAGAIN) { |
err = CSocketSelect(cSock, kCSocketSelectWrite, &spaceAvailable); |
assert( (err != 0) || spaceAvailable ); |
} |
} |
} |
// Clean up. |
if (bytesWrittenPtr != NULL) { |
*bytesWrittenPtr = nbytes - bytesLeft; |
} |
return err; |
} |
static int CSocketCancel(CSocket *cSock) |
// Cancels a blocked CSocket operation. Once you call this, |
// all subsequent CSocketConnect, CSocketRead, and CSocketWrite |
// operations that try to block will return ECANCELED. |
{ |
int err; |
ssize_t bytesWritten; |
static const char kCancelMessage = 0; |
assert(CSocketValid(cSock)); |
err = 0; |
if ( ! cSock->didCancel ) { |
bytesWritten = write(cSock->sndCancel, &kCancelMessage, sizeof(kCancelMessage)); |
if (bytesWritten < 0) { |
err = errno; |
// I don't bother handling EINTR here because writing a single |
// byte will never block, and thus can't be interrupted. |
assert( err != EINTR); |
} else { |
cSock->didCancel = true; |
} |
} |
return err; |
} |
///////////////////////////////////////////////////////////////// |
#pragma mark **** Driver Program |
static void SIGINTHandler(int sigNum) |
{ |
char nl; |
assert(sigNum == SIGINT); |
// Do nothing. The signal simply breaks us out of the "pause" call. |
// Print a newline character to prevent subsequent messages showing |
// up on the same line as the ^C. |
nl = '\n'; |
(void) write(STDIN_FILENO, &nl, sizeof(nl)); |
} |
static int InstallSIGINTHandler(void) |
// Installs a dummy SIGINT handler which prevents us |
// from quitting when we get a SIGINT. |
{ |
int err; |
struct sigaction act; |
act.sa_handler = SIGINTHandler; |
(void) sigemptyset(&act.sa_mask); |
act.sa_flags = 0; |
err = sigaction(SIGINT, &act, NULL); |
if (err < 0) { |
err = errno; |
} |
return err; |
} |
// ConnectionRecord is the parameter we pass to ConnectThreadEntry. |
// It tells ConnectThreadEntry where to connect to, and what to do |
// once it connects. |
struct ConnectionRecord { |
pthread_t thread; |
CSocket * sock; |
struct sockaddr * connectAddr; |
bool readData; |
}; |
typedef struct ConnectionRecord ConnectionRecord; |
// We need a mighty big buffer in order for the write test to fill |
// up the kernel socket buffer and start to block. |
static char gJunkBuf[256 * 1024]; |
static void *ConnectThreadEntry(void *param) |
// A separate thread is used for the connect and I/O operations, |
// so we can test cancellation. This is its entry point. |
{ |
int err; |
ConnectionRecord * cr; |
cr = (ConnectionRecord *) param; |
// First connect to the host specified by the connection record. |
fprintf(stderr, "Connect thread is connecting.\n"); |
err = CSocketConnect(cr->sock, cr->connectAddr, cr->connectAddr->sa_len); |
// After the connect, do a read or write. |
if (err == 0) { |
if (cr->readData) { |
size_t bytesRead; |
fprintf(stderr, "Connect thread is reading.\n"); |
err = CSocketRead(cr->sock, gJunkBuf, sizeof(gJunkBuf), &bytesRead); |
if (err == EPIPE) { |
fprintf(stderr, "Looks like the server threw us off.\n"); |
} |
} else { |
fprintf(stderr, "Connect thread is writing.\n"); |
err = CSocketWrite(cr->sock, gJunkBuf, sizeof(gJunkBuf), NULL); |
} |
} |
fprintf(stderr, "Connect thread done, err = %d\n", err); |
return (void *) err; |
} |
static int DoConnectReadTest(const char *hostName) |
// Do a test that involves connecting to the HTTP service |
// on hostName and trying to read data from it. HTTP servers |
// don't spontaneously generate data (you have to send them a |
// request first), so this will always block indefinitely |
// (or until the server thinks we're trying to mount a denial |
// of service attack, and closes the connection). |
{ |
int err; |
int junk; |
struct addrinfo * allAddrs; |
struct addrinfo * thisAddr; |
struct addrinfo hints; |
ConnectionRecord crStore; |
ConnectionRecord * cr; |
fprintf(stderr, "Test connect and read to %s\n", hostName); |
allAddrs = NULL; |
memset(&crStore, 0, sizeof(crStore)); |
cr = &crStore; |
cr->readData = true; |
// Resolve hostName and put a copy into the connection record. |
memset(&hints, 0, sizeof(hints)); |
hints.ai_family = AF_INET; // Should be PF_UNSPEC, but this code can't handle connecting to multiple addresses yet. |
hints.ai_socktype = SOCK_STREAM; |
fprintf(stderr, "Main thread is looking up name.\n"); |
err = getaddrinfo(hostName, "http", &hints, &allAddrs); |
if (err == 0) { |
thisAddr = allAddrs; // right now we only use the first IPv4 address |
cr->connectAddr = malloc(thisAddr->ai_addrlen); |
if (cr->connectAddr == NULL) { |
err = ENOMEM; |
} else { |
(void *) memcpy(cr->connectAddr, thisAddr->ai_addr, thisAddr->ai_addrlen); |
} |
} |
// Open the socket and create the connection thread. |
if (err == 0) { |
err = CSocketOpen(thisAddr->ai_family, thisAddr->ai_socktype, thisAddr->ai_protocol, &cr->sock); |
} |
if (err == 0) { |
err = pthread_create(&cr->thread, NULL, ConnectThreadEntry, cr); |
} |
// Install a NOP SIGINT handler and then wait for a signal. |
// After getting the signal, cancel the thread and wait |
// for it to complete. |
if (err == 0) { |
err = InstallSIGINTHandler(); |
} |
if (err == 0) { |
void *threadResult; |
fprintf(stderr, "SIGINT (typically ^C) to cancel\n"); |
(void) pause(); |
junk = CSocketCancel(cr->sock); |
assert(junk == 0); |
err = pthread_join(cr->thread, &threadResult); |
if (err == 0) { |
fprintf(stderr, "threadResult = %d\n", (int) threadResult); |
} |
} |
// Clean up. |
if (cr->sock != NULL) { |
CSocketClose(cr->sock); |
} |
free(cr->connectAddr); |
if (allAddrs != NULL) { |
freeaddrinfo(allAddrs); |
} |
return err; |
} |
static int DoWriteTest(void) |
// Do a test which involve opening a listening socket on the local |
// machine and then starting a thread that connects to the socket |
// and writes a whole bunch of data to it, eventually blocking. |
// Then we let the user cancel the blocked write. This code |
// doesn't even bother trying to be protocol independent. |
{ |
int err; |
int junk; |
int listener; |
int accepted; |
ConnectionRecord crStore; |
ConnectionRecord * cr; |
struct sockaddr_in bindAddr; |
fprintf(stderr, "Test write to localhost\n"); |
listener = -1; |
accepted = -1; |
memset(&crStore, 0, sizeof(crStore)); |
cr = &crStore; |
cr->readData = false; |
// Open a listening socket on localhost on port 10666. |
err = 0; |
listener = socket(AF_INET, SOCK_STREAM, 0); |
if (listener < 0) { |
err = errno; |
} |
if (err == 0) { |
bindAddr.sin_len = sizeof(bindAddr); |
bindAddr.sin_family = AF_INET; |
bindAddr.sin_port = htons(10666); |
bindAddr.sin_addr.s_addr = INADDR_ANY; |
err = bind(listener, (struct sockaddr *) &bindAddr, sizeof(bindAddr)); |
if (err < 0) { |
err = errno; |
} |
} |
if (err == 0) { |
err = listen(listener, 10); |
if (err < 0) { |
err = errno; |
} |
} |
// Start a thread to connect to that socket and write a lot of data. |
if (err == 0) { |
cr->connectAddr = malloc(sizeof(bindAddr)); |
if (cr->connectAddr == NULL) { |
err = ENOMEM; |
} else { |
(void *) memcpy(cr->connectAddr, &bindAddr, sizeof(bindAddr)); |
} |
} |
if (err == 0) { |
err = CSocketOpen(AF_INET, SOCK_STREAM, 0, &cr->sock); |
} |
if (err == 0) { |
err = pthread_create(&cr->thread, NULL, ConnectThreadEntry, cr); |
} |
// Accept the incoming connection from the pthread and then specifically |
// don't read the data that's coming from the thread. The thread will |
// eventually block when the socket buffer fills up. |
if (err == 0) { |
socklen_t addrlen; |
addrlen = 0; |
accepted = accept(listener, NULL, &addrlen); |
if (accepted < 0) { |
err = errno; |
} |
} |
// Install a NOP SIGINT handler and then wait for a signal. |
// After getting the signal, cancel the thread and wait |
// for it to complete. |
if (err == 0) { |
err = InstallSIGINTHandler(); |
} |
if (err == 0) { |
void *threadResult; |
fprintf(stderr, "SIGINT (typically ^C) to cancel\n"); |
(void) pause(); |
junk = CSocketCancel(cr->sock); |
assert(junk == 0); |
err = pthread_join(cr->thread, &threadResult); |
if (err == 0) { |
fprintf(stderr, "threadResult = %d\n", (int) threadResult); |
} |
} |
// Clean up. |
if (cr->sock != NULL) { |
CSocketClose(cr->sock); |
} |
free(cr->connectAddr); |
if (listener != -1) { |
junk = close(listener); |
assert(junk == 0); |
} |
if (accepted != -1) { |
junk = close(accepted); |
assert(junk == 0); |
} |
return err; |
} |
static const char *gProgramName; |
static void PrintUsage(const char *command) |
{ |
fprintf(stderr, "%s: Usage: BootstrapPortDump [ host ]\n", gProgramName); |
} |
int main (int argc, const char * argv[]) |
{ |
int err; |
const char * hostName; |
// Set gProgramName to the last path component of argv[0] |
gProgramName = strrchr(argv[0], '/'); |
if (gProgramName == NULL) { |
gProgramName = argv[0]; |
} else { |
gProgramName += 1; |
} |
// Parse our arguments. |
err = 0; |
switch (argc) { |
case 1: |
hostName = NULL; |
break; |
case 2: |
hostName = argv[1]; |
break; |
default: |
PrintUsage(argv[0]); |
err = EINVAL; |
break; |
} |
// Do one of two different tests. |
if (err == 0) { |
if (hostName == NULL) { |
err = DoWriteTest(); |
} else { |
err = DoConnectReadTest(hostName); |
} |
} |
if (err == 0) { |
fprintf(stderr, "Success!\n"); |
return EXIT_SUCCESS; |
} else { |
fprintf(stderr, "Failed with error %d.\n", err); |
return EXIT_FAILURE; |
} |
} |
Copyright © 2005 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2005-08-10