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[1];
00261                 char buf[1];
00262                 struct {
00263                         struct cmsghdr hdr;
00264                         int fd;
00265                 } cmsg;
00266         
00267                 msg.msg_name = NULL;
00268                 msg.msg_namelen = 0;
00269         
00270                 /* Linux and Solaris doesn't work if msg_iov is NULL. */
00271                 buf[0] = '\0';
00272                 vec[0].iov_base = buf;
00273                 vec[0].iov_len = 1;
00274                 msg.msg_iov = vec;
00275                 msg.msg_iovlen = 1;
00276         
00277                 msg.msg_control = (caddr_t)&cmsg;
00278                 msg.msg_controllen = CMSG_SPACE(sizeof(int));
00279                 msg.msg_flags = 0;
00280                 cmsg.hdr.cmsg_len = CMSG_LEN(sizeof(int));
00281                 cmsg.hdr.cmsg_level = SOL_SOCKET;
00282                 cmsg.hdr.cmsg_type = SCM_RIGHTS;
00283                 cmsg.fd = fileDescriptor;
00284                 
00285                 if (sendmsg(fd, &msg, 0) == -1) {
00286                         throw SystemException("Cannot send file descriptor with sendmsg()", errno);
00287                 }
00288         }
00289         
00290         /**
00291          * Read an array message from the underlying file descriptor.
00292          *
00293          * @param args The message will be put in this variable.
00294          * @return Whether end-of-file has been reached. If so, then the contents
00295          *         of <tt>args</tt> will be undefined.
00296          * @throws SystemException If an error occured while receiving the message.
00297          * @see write()
00298          */
00299         bool read(vector<string> &args) {
00300                 uint16_t size;
00301                 int ret;
00302                 unsigned int alreadyRead = 0;
00303                 
00304                 do {
00305                         do {
00306                                 ret = ::read(fd, (char *) &size + alreadyRead, sizeof(size) - alreadyRead);
00307                         } while (ret == -1 && errno == EINTR);
00308                         if (ret == -1) {
00309                                 throw SystemException("read() failed", errno);
00310                         } else if (ret == 0) {
00311                                 return false;
00312                         }
00313                         alreadyRead += ret;
00314                 } while (alreadyRead < sizeof(size));
00315                 size = ntohs(size);
00316                 
00317                 string buffer;
00318                 args.clear();
00319                 buffer.reserve(size);
00320                 while (buffer.size() < size) {
00321                         char tmp[1024 * 8];
00322                         do {
00323                                 ret = ::read(fd, tmp, min(size - buffer.size(), sizeof(tmp)));
00324                         } while (ret == -1 && errno == EINTR);
00325                         if (ret == -1) {
00326                                 throw SystemException("read() failed", errno);
00327                         } else if (ret == 0) {
00328                                 return false;
00329                         }
00330                         buffer.append(tmp, ret);
00331                 }
00332                 
00333                 if (!buffer.empty()) {
00334                         string::size_type start = 0, pos;
00335                         const string &const_buffer(buffer);
00336                         while ((pos = const_buffer.find('\0', start)) != string::npos) {
00337                                 args.push_back(const_buffer.substr(start, pos - start));
00338                                 start = pos + 1;
00339                         }
00340                 }
00341                 return true;
00342         }
00343         
00344         /**
00345          * Read a scalar message from the underlying file descriptor.
00346          *
00347          * @param output The message will be put in here.
00348          * @returns Whether end-of-file was reached during reading.
00349          * @throws SystemException An error occured while writing the data to the file descriptor.
00350          * @see writeScalar()
00351          */
00352         bool readScalar(string &output) {
00353                 uint32_t size;
00354                 unsigned int remaining;
00355                 
00356                 if (!readRaw(&size, sizeof(uint32_t))) {
00357                         return false;
00358                 }
00359                 size = ntohl(size);
00360                 
00361                 output.clear();
00362                 output.reserve(size);
00363                 remaining = size;
00364                 while (remaining > 0) {
00365                         char buf[1024 * 32];
00366                         unsigned int blockSize = min((unsigned int) sizeof(buf), remaining);
00367                         
00368                         if (!readRaw(buf, blockSize)) {
00369                                 return false;
00370                         }
00371                         output.append(buf, blockSize);
00372                         remaining -= blockSize;
00373                 }
00374                 return true;
00375         }
00376         
00377         /**
00378          * Read exactly <tt>size</tt> bytes of data from the underlying file descriptor,
00379          * and put the result in <tt>buf</tt>. If end-of-file has been reached, or if
00380          * end-of-file was encountered before <tt>size</tt> bytes have been read, then
00381          * <tt>false</tt> will be returned. Otherwise (i.e. if the read was successful),
00382          * <tt>true</tt> will be returned.
00383          *
00384          * @param buf The buffer to place the read data in. This buffer must be at least
00385          *            <tt>size</tt> bytes long.
00386          * @param size The number of bytes to read.
00387          * @return Whether reading was successful or whether EOF was reached.
00388          * @pre buf != NULL
00389          * @throws SystemException Something went wrong during reading.
00390          * @see writeRaw()
00391          */
00392         bool readRaw(void *buf, unsigned int size) {
00393                 ssize_t ret;
00394                 unsigned int alreadyRead = 0;
00395                 
00396                 while (alreadyRead < size) {
00397                         do {
00398                                 ret = ::read(fd, (char *) buf + alreadyRead, size - alreadyRead);
00399                         } while (ret == -1 && errno == EINTR);
00400                         if (ret == -1) {
00401                                 throw SystemException("read() failed", errno);
00402                         } else if (ret == 0) {
00403                                 return false;
00404                         } else {
00405                                 alreadyRead += ret;
00406                         }
00407                 }
00408                 return true;
00409         }
00410         
00411         /**
00412          * Receive a file descriptor, which had been passed over the underlying
00413          * file descriptor.
00414          *
00415          * @return The passed file descriptor.
00416          * @throws SystemException If something went wrong during the
00417          *            receiving of a file descriptor. Perhaps the underlying
00418          *            file descriptor isn't a Unix socket.
00419          * @throws IOException Whatever was received doesn't seem to be a
00420          *            file descriptor.
00421          */
00422         int readFileDescriptor() {
00423                 struct msghdr msg;
00424                 struct iovec vec[2];
00425                 char buf[1];
00426                 struct {
00427                         struct cmsghdr hdr;
00428                         int fd;
00429                 } cmsg;
00430 
00431                 msg.msg_name = NULL;
00432                 msg.msg_namelen = 0;
00433         
00434                 vec[0].iov_base = buf;
00435                 vec[0].iov_len = sizeof(buf);
00436                 msg.msg_iov = vec;
00437                 msg.msg_iovlen = 1;
00438 
00439                 msg.msg_control = (caddr_t)&cmsg;
00440                 msg.msg_controllen = CMSG_SPACE(sizeof(int));
00441                 msg.msg_flags = 0;
00442                 cmsg.hdr.cmsg_len = CMSG_LEN(sizeof(int));
00443                 cmsg.hdr.cmsg_level = SOL_SOCKET;
00444                 cmsg.hdr.cmsg_type = SCM_RIGHTS;
00445                 cmsg.fd = -1;
00446 
00447                 if (recvmsg(fd, &msg, 0) == -1) {
00448                         throw SystemException("Cannot read file descriptor with recvmsg()", errno);
00449                 }
00450 
00451                 if (msg.msg_controllen != CMSG_SPACE(sizeof(int))
00452                  || cmsg.hdr.cmsg_len != CMSG_SPACE(0) + sizeof(int)
00453                  || cmsg.hdr.cmsg_level != SOL_SOCKET
00454                  || cmsg.hdr.cmsg_type != SCM_RIGHTS) {
00455                         throw IOException("No valid file descriptor received.");
00456                 }
00457                 return cmsg.fd;
00458         }
00459 };
00460 
00461 } // namespace Passenger
00462 
00463 #endif /* _PASSENGER_MESSAGE_CHANNEL_H_ */

Generated on Fri Apr 11 18:16:04 2008 for Passenger by  doxygen 1.5.3