MessageChannel.h

00001 /*
00002  *  Phusion Passenger - http://www.modrails.com/
00003  *  Copyright (C) 2008  Phusion
00004  *
00005  *  This program is free software; you can redistribute it and/or modify
00006  *  it under the terms of the GNU General Public License as published by
00007  *  the Free Software Foundation; version 2 of the License.
00008  *
00009  *  This program is distributed in the hope that it will be useful,
00010  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  *  GNU General Public License for more details.
00013  *
00014  *  You should have received a copy of the GNU General Public License along
00015  *  with this program; if not, write to the Free Software Foundation, Inc.,
00016  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
00017  */
00018 #ifndef _PASSENGER_MESSAGE_CHANNEL_H_
00019 #define _PASSENGER_MESSAGE_CHANNEL_H_
00020 
00021 #include <algorithm>
00022 #include <string>
00023 #include <list>
00024 #include <vector>
00025 
00026 #include <sys/types.h>
00027 #include <sys/socket.h>
00028 #include <arpa/inet.h>
00029 #include <errno.h>
00030 #include <unistd.h>
00031 #include <cstdarg>
00032 
00033 #include "Exceptions.h"
00034 #include "Utils.h"
00035 
00036 namespace Passenger {
00037 
00038 using namespace std;
00039 
00040 /**
00041  * Convenience class for I/O operations on file descriptors.
00042  *
00043  * This class provides convenience methods for:
00044  *  - sending and receiving raw data over a file descriptor.
00045  *  - sending and receiving messages over a file descriptor.
00046  *  - file descriptor passing over a Unix socket.
00047  * All of these methods use exceptions for error reporting.
00048  *
00049  * There are two kinds of messages:
00050  *  - Array messages. These are just a list of strings, and the message
00051  *    itself has a specific length. The contained strings may not
00052  *    contain NUL characters (<tt>'\\0'</tt>). Note that an array message
00053  *    must have at least one element.
00054  *  - Scalar messages. These are byte strings which may contain arbitrary
00055  *    binary data. Scalar messages also have a specific length.
00056  * The protocol is designed to be low overhead, easy to implement and
00057  * easy to parse.
00058  *
00059  * MessageChannel is to be wrapped around a file descriptor. For example:
00060  * @code
00061  *    int p[2];
00062  *    pipe(p);
00063  *    MessageChannel channel1(p[0]);
00064  *    MessageChannel channel2(p[1]);
00065  *    
00066  *    // Send an array message.
00067  *    channel2.write("hello", "world !!", NULL);
00068  *    list<string> args;
00069  *    channel1.read(args);    // args now contains { "hello", "world !!" }
00070  *
00071  *    // Send a scalar message.
00072  *    channel2.writeScalar("some long string which can contain arbitrary binary data");
00073  *    string str;
00074  *    channel1.readScalar(str);
00075  * @endcode
00076  *
00077  * The life time of a MessageChannel is independent from that of the
00078  * wrapped file descriptor. If a MessageChannel object is destroyed,
00079  * the file descriptor is not automatically closed. Call close()
00080  * if you want to close the file descriptor.
00081  *
00082  * @note I/O operations are not buffered.
00083  * @note Be careful with mixing the sending/receiving of array messages,
00084  *    scalar messages and file descriptors. If you send a collection of any
00085  *    of these in a specific order, then the receiving side must receive them
00086  *    in the exact some order. So suppose you first send a message, then a
00087  *    file descriptor, then a scalar, then the receiving side must first
00088  *    receive a message, then a file descriptor, then a scalar. If the
00089  *    receiving side does things in the wrong order then bad things will
00090  *    happen.
00091  * @note MessageChannel is thread-safe.
00092  *
00093  * @ingroup Support
00094  */
00095 class MessageChannel {
00096 private:
00097         const static char DELIMITER = '\0';
00098         int fd;
00099 
00100 public:
00101         /**
00102          * Construct a new MessageChannel with no underlying file descriptor.
00103          * Thus the resulting MessageChannel object will not be usable.
00104          * This constructor exists to allow one to declare an "empty"
00105          * MessageChannel variable which is to be initialized later.
00106          */
00107         MessageChannel() {
00108                 this->fd = -1;
00109         }
00110 
00111         /**
00112          * Construct a new MessageChannel with the given file descriptor.
00113          */
00114         MessageChannel(int fd) {
00115                 this->fd = fd;
00116         }
00117         
00118         /**
00119          * Close the underlying file descriptor. If this method is called multiple
00120          * times, the file descriptor will only be closed the first time.
00121          */
00122         void close() {
00123                 if (fd != -1) {
00124 			::close(fd);
00125                         fd = -1;
00126                 }
00127         }
00128 
00129         /**
00130          * Send an array message, which consists of the given elements, over the underlying
00131          * file descriptor.
00132          *
00133          * @param args The message elements.
00134          * @throws SystemException An error occured while writing the data to the file descriptor.
00135          * @pre None of the message elements may contain a NUL character (<tt>'\\0'</tt>).
00136          * @see read(), write(const char *, ...)
00137          */
00138         void write(const list<string> &args) {
00139                 list<string>::const_iterator it;
00140                 string data;
00141                 uint16_t dataSize = 0;
00142 
00143                 for (it = args.begin(); it != args.end(); it++) {
00144                         dataSize += it->size() + 1;
00145                 }
00146                 data.reserve(dataSize + sizeof(dataSize));
00147                 dataSize = htons(dataSize);
00148                 data.append((const char *) &dataSize, sizeof(dataSize));
00149                 for (it = args.begin(); it != args.end(); it++) {
00150                         data.append(*it);
00151                         data.append(1, DELIMITER);
00152                 }
00153                 
00154                 writeRaw(data);
00155         }
00156         
00157         /**
00158          * Send an array message, which consists of the given strings, over the underlying
00159          * file descriptor.
00160          *
00161          * @param name The first element of the message to send.
00162          * @param ... Other elements of the message. These *must* be strings, i.e. of type char*.
00163          *            It is also required to terminate this list with a NULL.
00164          * @throws SystemException An error occured while writing the data to the file descriptor.
00165          * @pre None of the message elements may contain a NUL character (<tt>'\\0'</tt>).
00166          * @see read(), write(const list<string> &)
00167          */
00168         void write(const char *name, ...) {
00169                 list<string> args;
00170                 args.push_back(name);
00171                 
00172                 va_list ap;
00173                 va_start(ap, name);
00174                 while (true) {
00175                         const char *arg = va_arg(ap, const char *);
00176                         if (arg == NULL) {
00177                                 break;
00178                         } else {
00179                                 args.push_back(arg);
00180                         }
00181                 }
00182                 va_end(ap);
00183                 write(args);
00184         }
00185         
00186         /**
00187          * Send a scalar message over the underlying file descriptor.
00188          *
00189          * @param str The scalar message's content.
00190          * @throws SystemException An error occured while writing the data to the file descriptor.
00191          * @see readScalar(), writeScalar(const char *, unsigned int)
00192          */
00193         void writeScalar(const string &str) {
00194                 writeScalar(str.c_str(), str.size());
00195         }
00196         
00197         /**
00198          * Send a scalar message over the underlying file descriptor.
00199          *
00200          * @param data The scalar message's content.
00201          * @param size The number of bytes in <tt>data</tt>.
00202          * @pre <tt>data != NULL</tt>
00203          * @throws SystemException An error occured while writing the data to the file descriptor.
00204          * @see readScalar(), writeScalar(const string &)
00205          */
00206         void writeScalar(const char *data, unsigned int size) {
00207                 uint32_t l = htonl(size);
00208                 writeRaw((const char *) &l, sizeof(uint32_t));
00209                 writeRaw(data, size);
00210         }
00211         
00212         /**
00213          * Send a block of data over the underlying file descriptor.
00214          * This method blocks until everything is sent.
00215          *
00216          * @param data The data to send.
00217          * @param size The number of bytes in <tt>data</tt>.
00218          * @pre <tt>data != NULL</tt>
00219          * @throws SystemException An error occured while writing the data to the file descriptor.
00220          * @see readRaw()
00221          */
00222         void writeRaw(const char *data, unsigned int size) {
00223                 ssize_t ret;
00224                 unsigned int written = 0;
00225                 do {
00226                         do {
00227                                 ret = ::write(fd, data + written, size - written);
00228                         } while (ret == -1 && errno == EINTR);
00229                         if (ret == -1) {
00230                                 throw SystemException("write() failed", errno);
00231                         } else {
00232                                 written += ret;
00233                         }
00234                 } while (written < size);
00235         }
00236         
00237         /**
00238          * Send a block of data over the underlying file descriptor.
00239          * This method blocks until everything is sent.
00240          *
00241          * @param data The data to send.
00242          * @pre <tt>data != NULL</tt>
00243          * @throws SystemException An error occured while writing the data to the file descriptor.
00244          */
00245         void writeRaw(const string &data) {
00246                 writeRaw(data.c_str(), data.size());
00247         }
00248         
00249         /**
00250          * Pass a file descriptor. This only works if the underlying file
00251          * descriptor is a Unix socket.
00252          *
00253          * @param fileDescriptor The file descriptor to pass.
00254          * @throws SystemException Something went wrong during file descriptor passing.
00255          * @pre <tt>fileDescriptor >= 0</tt>
00256          * @see readFileDescriptor()
00257          */
00258         void writeFileDescriptor(int fileDescriptor) {
00259                 struct msghdr msg;
00260                 struct iovec vec;
00261                 char dummy[1];
00262                 #ifdef __APPLE__
00263                         struct {
00264                                 struct cmsghdr header;
00265                                 int fd;
00266                         } control_data;
00267                 #else
00268                         char control_data[CMSG_SPACE(sizeof(int))];
00269                 #endif
00270                 struct cmsghdr *control_header;
00271         
00272                 msg.msg_name = NULL;
00273                 msg.msg_namelen = 0;
00274         
00275                 /* Linux and Solaris require msg_iov to be non-NULL. */
00276                 dummy[0]       = '\0';
00277                 vec.iov_base   = dummy;
00278                 vec.iov_len    = sizeof(dummy);
00279                 msg.msg_iov    = &vec;
00280                 msg.msg_iovlen = 1;
00281         
00282                 msg.msg_control    = (caddr_t) &control_data;
00283                 msg.msg_controllen = sizeof(control_data);
00284                 msg.msg_flags      = 0;
00285                 
00286                 control_header = CMSG_FIRSTHDR(&msg);
00287                 control_header->cmsg_level = SOL_SOCKET;
00288                 control_header->cmsg_type  = SCM_RIGHTS;
00289                 #ifdef __APPLE__
00290                         control_header->cmsg_len = sizeof(control_data);
00291                         control_data.fd = fileDescriptor;
00292                 #else
00293                         control_header->cmsg_len = CMSG_LEN(sizeof(int));
00294                         memcpy(CMSG_DATA(control_header), &fileDescriptor, sizeof(int));
00295                 #endif
00296                 
00297                 if (sendmsg(fd, &msg, 0) == -1) {
00298                         throw SystemException("Cannot send file descriptor with sendmsg()", errno);
00299                 }
00300         }
00301         
00302         /**
00303          * Read an array message from the underlying file descriptor.
00304          *
00305          * @param args The message will be put in this variable.
00306          * @return Whether end-of-file has been reached. If so, then the contents
00307          *         of <tt>args</tt> will be undefined.
00308          * @throws SystemException If an error occured while receiving the message.
00309          * @see write()
00310          */
00311         bool read(vector<string> &args) {
00312                 uint16_t size;
00313                 int ret;
00314                 unsigned int alreadyRead = 0;
00315                 
00316                 do {
00317                         do {
00318                                 ret = ::read(fd, (char *) &size + alreadyRead, sizeof(size) - alreadyRead);
00319                         } while (ret == -1 && errno == EINTR);
00320                         if (ret == -1) {
00321                                 throw SystemException("read() failed", errno);
00322                         } else if (ret == 0) {
00323                                 return false;
00324                         }
00325                         alreadyRead += ret;
00326                 } while (alreadyRead < sizeof(size));
00327                 size = ntohs(size);
00328                 
00329                 string buffer;
00330                 args.clear();
00331                 buffer.reserve(size);
00332                 while (buffer.size() < size) {
00333                         char tmp[1024 * 8];
00334                         do {
00335                                 ret = ::read(fd, tmp, min(size - buffer.size(), sizeof(tmp)));
00336                         } while (ret == -1 && errno == EINTR);
00337                         if (ret == -1) {
00338                                 throw SystemException("read() failed", errno);
00339                         } else if (ret == 0) {
00340                                 return false;
00341                         }
00342                         buffer.append(tmp, ret);
00343                 }
00344                 
00345                 if (!buffer.empty()) {
00346                         string::size_type start = 0, pos;
00347                         const string &const_buffer(buffer);
00348                         while ((pos = const_buffer.find('\0', start)) != string::npos) {
00349                                 args.push_back(const_buffer.substr(start, pos - start));
00350                                 start = pos + 1;
00351                         }
00352                 }
00353                 return true;
00354         }
00355         
00356         /**
00357          * Read a scalar message from the underlying file descriptor.
00358          *
00359          * @param output The message will be put in here.
00360          * @returns Whether end-of-file was reached during reading.
00361          * @throws SystemException An error occured while writing the data to the file descriptor.
00362          * @see writeScalar()
00363          */
00364         bool readScalar(string &output) {
00365                 uint32_t size;
00366                 unsigned int remaining;
00367                 
00368                 if (!readRaw(&size, sizeof(uint32_t))) {
00369                         return false;
00370                 }
00371                 size = ntohl(size);
00372                 
00373                 output.clear();
00374                 output.reserve(size);
00375                 remaining = size;
00376                 while (remaining > 0) {
00377                         char buf[1024 * 32];
00378                         unsigned int blockSize = min((unsigned int) sizeof(buf), remaining);
00379                         
00380                         if (!readRaw(buf, blockSize)) {
00381                                 return false;
00382                         }
00383                         output.append(buf, blockSize);
00384                         remaining -= blockSize;
00385                 }
00386                 return true;
00387         }
00388         
00389         /**
00390          * Read exactly <tt>size</tt> bytes of data from the underlying file descriptor,
00391          * and put the result in <tt>buf</tt>. If end-of-file has been reached, or if
00392          * end-of-file was encountered before <tt>size</tt> bytes have been read, then
00393          * <tt>false</tt> will be returned. Otherwise (i.e. if the read was successful),
00394          * <tt>true</tt> will be returned.
00395          *
00396          * @param buf The buffer to place the read data in. This buffer must be at least
00397          *            <tt>size</tt> bytes long.
00398          * @param size The number of bytes to read.
00399          * @return Whether reading was successful or whether EOF was reached.
00400          * @pre buf != NULL
00401          * @throws SystemException Something went wrong during reading.
00402          * @see writeRaw()
00403          */
00404         bool readRaw(void *buf, unsigned int size) {
00405                 ssize_t ret;
00406                 unsigned int alreadyRead = 0;
00407                 
00408                 while (alreadyRead < size) {
00409                         do {
00410                                 ret = ::read(fd, (char *) buf + alreadyRead, size - alreadyRead);
00411                         } while (ret == -1 && errno == EINTR);
00412                         if (ret == -1) {
00413                                 throw SystemException("read() failed", errno);
00414                         } else if (ret == 0) {
00415                                 return false;
00416                         } else {
00417                                 alreadyRead += ret;
00418                         }
00419                 }
00420                 return true;
00421         }
00422         
00423         /**
00424          * Receive a file descriptor, which had been passed over the underlying
00425          * file descriptor.
00426          *
00427          * @return The passed file descriptor.
00428          * @throws SystemException If something went wrong during the
00429          *            receiving of a file descriptor. Perhaps the underlying
00430          *            file descriptor isn't a Unix socket.
00431          * @throws IOException Whatever was received doesn't seem to be a
00432          *            file descriptor.
00433          */
00434         int readFileDescriptor() {
00435                 struct msghdr msg;
00436                 struct iovec vec;
00437                 char dummy[1];
00438                 #ifdef __APPLE__
00439                         // File descriptor passing macros (CMSG_*) seem to be broken
00440                         // on 64-bit MacOS X. This structure works around the problem.
00441                         struct {
00442                                 struct cmsghdr header;
00443                                 int fd;
00444                         } control_data;
00445                         #define EXPECTED_CMSG_LEN sizeof(control_data)
00446                 #else
00447                         char control_data[CMSG_SPACE(sizeof(int))];
00448                         #define EXPECTED_CMSG_LEN CMSG_LEN(sizeof(int))
00449                 #endif
00450                 struct cmsghdr *control_header;
00451 
00452                 msg.msg_name    = NULL;
00453                 msg.msg_namelen = 0;
00454                 
00455                 dummy[0]       = '\0';
00456                 vec.iov_base   = dummy;
00457                 vec.iov_len    = sizeof(dummy);
00458                 msg.msg_iov    = &vec;
00459                 msg.msg_iovlen = 1;
00460 
00461                 msg.msg_control    = (caddr_t) &control_data;
00462                 msg.msg_controllen = sizeof(control_data);
00463                 msg.msg_flags      = 0;
00464                 
00465                 if (recvmsg(fd, &msg, 0) == -1) {
00466                         throw SystemException("Cannot read file descriptor with recvmsg()", errno);
00467                 }
00468                 
00469                 control_header = CMSG_FIRSTHDR(&msg);
00470                 if (control_header->cmsg_len   != EXPECTED_CMSG_LEN
00471                  || control_header->cmsg_level != SOL_SOCKET
00472                  || control_header->cmsg_type  != SCM_RIGHTS) {
00473                         throw IOException("No valid file descriptor received.");
00474                 }
00475                 #ifdef __APPLE__
00476                         return control_data.fd;
00477                 #else
00478                         return *((int *) CMSG_DATA(control_header));
00479                 #endif
00480         }
00481 };
00482 
00483 } // namespace Passenger
00484 
00485 #endif /* _PASSENGER_MESSAGE_CHANNEL_H_ */

Generated on Wed May 7 20:28:18 2008 for Passenger by  doxygen 1.5.3