Skip to content

Commit

Permalink
Move parts of MessageChannel::readFileDescriptor() and MessageChannel…
Browse files Browse the repository at this point in the history
…::writeFileDescriptor() into free functions in IOUtils.
  • Loading branch information
FooBarWidget committed Aug 3, 2011
1 parent c885c39 commit e3885d5
Show file tree
Hide file tree
Showing 6 changed files with 251 additions and 111 deletions.
96 changes: 2 additions & 94 deletions ext/common/MessageChannel.h
Expand Up @@ -359,49 +359,7 @@ class MessageChannel {
}
}

struct msghdr msg;
struct iovec vec;
char dummy[1];
#if defined(__APPLE__) || defined(__SOLARIS__) || defined(__arm__)
struct {
struct cmsghdr header;
int fd;
} control_data;
#else
char control_data[CMSG_SPACE(sizeof(int))];
#endif
struct cmsghdr *control_header;
int ret;

msg.msg_name = NULL;
msg.msg_namelen = 0;

/* Linux and Solaris require msg_iov to be non-NULL. */
dummy[0] = '\0';
vec.iov_base = dummy;
vec.iov_len = sizeof(dummy);
msg.msg_iov = &vec;
msg.msg_iovlen = 1;

msg.msg_control = (caddr_t) &control_data;
msg.msg_controllen = sizeof(control_data);
msg.msg_flags = 0;

control_header = CMSG_FIRSTHDR(&msg);
control_header->cmsg_level = SOL_SOCKET;
control_header->cmsg_type = SCM_RIGHTS;
#if defined(__APPLE__) || defined(__SOLARIS__) || defined(__arm__)
control_header->cmsg_len = sizeof(control_data);
control_data.fd = fileDescriptor;
#else
control_header->cmsg_len = CMSG_LEN(sizeof(int));
memcpy(CMSG_DATA(control_header), &fileDescriptor, sizeof(int));
#endif

ret = syscalls::sendmsg(fd, &msg, 0);
if (ret == -1) {
throw SystemException("Cannot send file descriptor with sendmsg()", errno);
}
Passenger::writeFileDescriptor(fd, fileDescriptor);

if (negotiate) {
vector<string> args;
Expand Down Expand Up @@ -615,57 +573,7 @@ class MessageChannel {
write("pass IO", NULL);
}

struct msghdr msg;
struct iovec vec;
char dummy[1];
#if defined(__APPLE__) || defined(__SOLARIS__) || defined(__arm__)
// File descriptor passing macros (CMSG_*) seem to be broken
// on 64-bit MacOS X. This structure works around the problem.
struct {
struct cmsghdr header;
int fd;
} control_data;
#define EXPECTED_CMSG_LEN sizeof(control_data)
#else
char control_data[CMSG_SPACE(sizeof(int))];
#define EXPECTED_CMSG_LEN CMSG_LEN(sizeof(int))
#endif
struct cmsghdr *control_header;
int ret;

msg.msg_name = NULL;
msg.msg_namelen = 0;

dummy[0] = '\0';
vec.iov_base = dummy;
vec.iov_len = sizeof(dummy);
msg.msg_iov = &vec;
msg.msg_iovlen = 1;

msg.msg_control = (caddr_t) &control_data;
msg.msg_controllen = sizeof(control_data);
msg.msg_flags = 0;

ret = syscalls::recvmsg(fd, &msg, 0);
if (ret == -1) {
throw SystemException("Cannot read file descriptor with recvmsg()", errno);
}

control_header = CMSG_FIRSTHDR(&msg);
if (control_header == NULL) {
throw IOException("No valid file descriptor received.");
}
if (control_header->cmsg_len != EXPECTED_CMSG_LEN
|| control_header->cmsg_level != SOL_SOCKET
|| control_header->cmsg_type != SCM_RIGHTS) {
throw IOException("No valid file descriptor received.");
}

#if defined(__APPLE__) || defined(__SOLARIS__) || defined(__arm__)
int fd = control_data.fd;
#else
int fd = *((int *) CMSG_DATA(control_header));
#endif
int fd = Passenger::readFileDescriptor(this->fd);

if (negotiate) {
try {
Expand Down
110 changes: 110 additions & 0 deletions ext/common/Utils/IOUtils.cpp
Expand Up @@ -895,6 +895,116 @@ setWritevFunction(WritevFunction func) {
}
}

int
readFileDescriptor(int fd, unsigned long long *timeout) {
if (timeout != NULL && !waitUntilReadable(fd, timeout)) {
throw TimeoutException("Cannot receive file descriptor within the specified timeout");
}

struct msghdr msg;
struct iovec vec;
char dummy[1];
#if defined(__APPLE__) || defined(__SOLARIS__) || defined(__arm__)
// File descriptor passing macros (CMSG_*) seem to be broken
// on 64-bit MacOS X. This structure works around the problem.
struct {
struct cmsghdr header;
int fd;
} control_data;
#define EXPECTED_CMSG_LEN sizeof(control_data)
#else
char control_data[CMSG_SPACE(sizeof(int))];
#define EXPECTED_CMSG_LEN CMSG_LEN(sizeof(int))
#endif
struct cmsghdr *control_header;
int ret;

msg.msg_name = NULL;
msg.msg_namelen = 0;

dummy[0] = '\0';
vec.iov_base = dummy;
vec.iov_len = sizeof(dummy);
msg.msg_iov = &vec;
msg.msg_iovlen = 1;

msg.msg_control = (caddr_t) &control_data;
msg.msg_controllen = sizeof(control_data);
msg.msg_flags = 0;

ret = syscalls::recvmsg(fd, &msg, 0);
if (ret == -1) {
throw SystemException("Cannot read file descriptor with recvmsg()", errno);
}

control_header = CMSG_FIRSTHDR(&msg);
if (control_header == NULL) {
throw IOException("No valid file descriptor received.");
}
if (control_header->cmsg_len != EXPECTED_CMSG_LEN
|| control_header->cmsg_level != SOL_SOCKET
|| control_header->cmsg_type != SCM_RIGHTS) {
throw IOException("No valid file descriptor received.");
}

#if defined(__APPLE__) || defined(__SOLARIS__) || defined(__arm__)
return control_data.fd;
#else
return *((int *) CMSG_DATA(control_header));
#endif
}

void
writeFileDescriptor(int fd, int fdToSend, unsigned long long *timeout) {
if (timeout != NULL && !waitUntilWritable(fd, timeout)) {
throw TimeoutException("Cannot send file descriptor within the specified timeout");
}

struct msghdr msg;
struct iovec vec;
char dummy[1];
#if defined(__APPLE__) || defined(__SOLARIS__) || defined(__arm__)
struct {
struct cmsghdr header;
int fd;
} control_data;
#else
char control_data[CMSG_SPACE(sizeof(int))];
#endif
struct cmsghdr *control_header;
int ret;

msg.msg_name = NULL;
msg.msg_namelen = 0;

/* Linux and Solaris require msg_iov to be non-NULL. */
dummy[0] = '\0';
vec.iov_base = dummy;
vec.iov_len = sizeof(dummy);
msg.msg_iov = &vec;
msg.msg_iovlen = 1;

msg.msg_control = (caddr_t) &control_data;
msg.msg_controllen = sizeof(control_data);
msg.msg_flags = 0;

control_header = CMSG_FIRSTHDR(&msg);
control_header->cmsg_level = SOL_SOCKET;
control_header->cmsg_type = SCM_RIGHTS;
#if defined(__APPLE__) || defined(__SOLARIS__) || defined(__arm__)
control_header->cmsg_len = sizeof(control_data);
control_data.fd = fdToSend;
#else
control_header->cmsg_len = CMSG_LEN(sizeof(int));
memcpy(CMSG_DATA(control_header), &fdToSend, sizeof(int));
#endif

ret = syscalls::sendmsg(fd, &msg, 0);
if (ret == -1) {
throw SystemException("Cannot send file descriptor with sendmsg()", errno);
}
}

void
safelyClose(int fd) {
if (syscalls::close(fd) == -1) {
Expand Down
37 changes: 37 additions & 0 deletions ext/common/Utils/IOUtils.h
Expand Up @@ -365,6 +365,43 @@ void gatheredWrite(int fd, const StaticString data[], unsigned int dataCount,
*/
void setWritevFunction(WritevFunction func);

/**
* Receive a file descriptor over the given Unix domain socket.
*
* @param timeout A pointer to an integer, which specifies the maximum number of
* microseconds that may be spent on receiving the file descriptor.
* If the timeout expired then TimeoutException will be thrown.
* If this function returns without throwing an exception, then the
* total number of microseconds spent on receiving will be deducted
* from <tt>timeout</tt>.
* Pass NULL if you do not want to enforce a timeout.
* @return The received file descriptor.
* @throws SystemException Something went wrong.
* @throws IOException Whatever was received doesn't seem to be a
* file descriptor.
* @throws TimeoutException Unable to receive a file descriptor within
* <tt>timeout</tt> microseconds.
* @throws boost::thread_interrupted
*/
int readFileDescriptor(int fd, unsigned long long *timeout = NULL);

/**
* Pass the file descriptor 'fdToSend' over the Unix socket 'fd'.
*
* @param timeout A pointer to an integer, which specifies the maximum number of
* microseconds that may be spent on trying to pass the file descriptor.
* If the timeout expired then TimeoutException will be thrown.
* If this function returns without throwing an exception, then the
* total number of microseconds spent on writing will be deducted
* from <tt>timeout</tt>.
* Pass NULL if you do not want to enforce a timeout.
* @throws SystemException Something went wrong.
* @throws TimeoutException Unable to pass the file descriptor within
* <tt>timeout</tt> microseconds.
* @throws boost::thread_interrupted
*/
void writeFileDescriptor(int fd, int fdToSend, unsigned long long *timeout = NULL);

/**
* Closes the given file descriptor and throws an exception if anything goes wrong.
* This function also works around certain close() bugs on certain operating systems.
Expand Down
64 changes: 47 additions & 17 deletions test/cxx/IOUtilsTest.cpp
Expand Up @@ -55,23 +55,6 @@ namespace tut {
return p;
}

void writeUntilFull(int fd) {
char buf[1024 * 4];
memset(buf, 0, sizeof(buf));
bool done = false;
while (!done) {
ssize_t ret = write(fd, buf, sizeof(buf));
if (ret == -1) {
if (errno == EAGAIN) {
done = true;
} else {
int e = errno;
throw SystemException("Cannot write to pipe", e);
}
}
}
}

static void writeDataAfterSomeTime(int fd, unsigned int sleepTimeInUsec) {
try {
syscalls::usleep(sleepTimeInUsec);
Expand Down Expand Up @@ -813,4 +796,51 @@ namespace tut {
// Pass.
}
}

/***** Test readFileDescriptor() and writeFileDescriptor() *****/

TEST_METHOD(80) {
// Test whether it works.
SocketPair sockets = createUnixSocketPair();
Pipe pipes = createPipe();
writeFileDescriptor(sockets[0], pipes[1]);
FileDescriptor fd = readFileDescriptor(sockets[1]);
writeExact(fd, "hello");
char buf[6];
ensure_equals(readExact(pipes[0], buf, 5), 5u);
buf[5] = '\0';
ensure_equals(StaticString(buf), "hello");
}

TEST_METHOD(81) {
// Test whether timeout works.
SocketPair sockets = createUnixSocketPair();
Pipe pipes = createPipe();

unsigned long long timeout = 30000;
unsigned long long startTime = SystemTime::getUsec();
try {
FileDescriptor fd = readFileDescriptor(sockets[0], &timeout);
fail("TimeoutException expected");
} catch (const TimeoutException &) {
unsigned long long elapsed = SystemTime::getUsec() - startTime;
ensure("readFileDescriptor() timed out after 30 msec",
elapsed >= 29000 && elapsed <= 45000);
ensure(timeout <= 2000);
}

writeUntilFull(sockets[0]);

startTime = SystemTime::getUsec();
timeout = 30000;
try {
writeFileDescriptor(sockets[0], pipes[0], &timeout);
fail("TimeoutException expected");
} catch (const TimeoutException &) {
unsigned long long elapsed = SystemTime::getUsec() - startTime;
ensure("writeFileDescriptor() timed out after 30 msec",
elapsed >= 29000 && elapsed <= 45000);
ensure(timeout <= 2000);
}
}
}

0 comments on commit e3885d5

Please sign in to comment.