관리-도구
편집 파일: IOUtils.h
/* * Phusion Passenger - https://www.phusionpassenger.com/ * Copyright (c) 2010-2018 Phusion Holding B.V. * * "Passenger", "Phusion Passenger" and "Union Station" are registered * trademarks of Phusion Holding B.V. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #ifndef _PASSENGER_IOTOOLS_IO_UTILS_H_ #define _PASSENGER_IOTOOLS_IO_UTILS_H_ #include <sys/types.h> #include <sys/socket.h> #include <sys/uio.h> #include <cstdio> #include <cstddef> #include <unistd.h> #include <netdb.h> #include <string> #include <utility> #include <vector> #include <oxt/macros.hpp> #include <StaticString.h> #include <FileDescriptor.h> namespace Passenger { using namespace std; enum ServerAddressType { SAT_UNIX, SAT_TCP, SAT_UNKNOWN }; typedef ssize_t (*WritevFunction)(int fildes, const struct iovec *iov, int iovcnt); bool purgeStdio(FILE *f); /** * Accepts a server address in one of the following formats, and returns which one it is: * * Unix domain sockets * Format: "unix:/path/to/a/socket" * Returns: SAT_UNIX * * TCP sockets * Format: "tcp://host:port" * Returns: SAT_TCP * * Other * Returns: SAT_UNKNOWN */ ServerAddressType getSocketAddressType(const StaticString &address); /** * Parses a Unix domain socket address, as accepted by getSocketAddressType(), * and returns the socket filename. * * @throw ArgumentException <tt>address</tt> is not a valid Unix domain socket address. */ string parseUnixSocketAddress(const StaticString &address); /** * Parses a TCP socket address, as accepted by getSocketAddressType(), * and returns the host and port. * * @throw ArgumentException <tt>address</tt> is not a valid TCP socket address. */ void parseTcpSocketAddress(const StaticString & restrict_ref address, string & restrict_ref host, unsigned short & restrict_ref port); /** * Returns whether the given socket address (as accepted by getSocketAddressType()) * is an address that can only refer to a server on the local system. * * @throw ArgumentException <tt>address</tt> is not a valid TCP socket address. */ bool isLocalSocketAddress(const StaticString &address); /** * Sets a socket in blocking mode. * * @throws SystemException Something went wrong. * @ingroup Support */ void setBlocking(int fd); /** * Sets a socket in non-blocking mode. * * @throws SystemException Something went wrong. * @ingroup Support */ void setNonBlocking(int fd); /** * Try to call the Linux accept4() system call. If the system call is * not available, then -1 is returned and errno is set to ENOSYS. */ int callAccept4(int sock, struct sockaddr * restrict addr, socklen_t * restrict addr_len, int options); /** * Resolves the given host name and returns a list of IP addresses. * `hostname` may also be an IP address, in which case it is * returned. You may explicitly specify a `port` as a hint to * the DNS resolver; set to 0 if you don't care or can't provide a * port number. * * If `shuffle` is set, and the host name resolves to multiple * IP addresses, then these addresses will be shuffled before they are * returned in order to improve load balancing. * * @throws IOException DNS resolution failure. */ vector<string> resolveHostname(const string &hostname, unsigned int port = 0, bool shuffle = true); /** * Create a new Unix or TCP server socket, depending on the address type. * * @param address An address as defined by getSocketAddressType(). * @param backlogSize The size of the socket's backlog. Specify 0 to use * the paltform's maximum allowed backlog size. * @param autoDelete If <tt>address</tt> is a Unix socket that already exists, * whether that should be deleted. Otherwise this argument * is ignored. * @param file The name of the source file that called this function, * for file descriptor logging purposes. * @param line The line in the source file that called this function. * @return The file descriptor of the newly created server socket. * @throws ArgumentException The given address cannot be parsed. * @throws RuntimeException Something went wrong. * @throws SystemException Something went wrong while creating the Unix server socket. * @throws boost::thread_interrupted A system call has been interrupted. * @ingroup Support */ int createServer(const StaticString &address, unsigned int backlogSize = 0, bool autoDelete = true, const char *file = __FILE__, unsigned int line = __LINE__); /** * Create a new Unix server socket which is bounded to <tt>filename</tt>. * * @param filename The filename to bind the socket to. * @param backlogSize The size of the socket's backlog. Specify 0 to use the * platform's maximum allowed backlog size. * @param autoDelete Whether <tt>filename</tt> should be deleted, if it already exists. * @param file The name of the source file that called this function, * for file descriptor logging purposes. * @param line The line in the source file that called this function. * @return The file descriptor of the newly created Unix server socket. * @throws RuntimeException Something went wrong. * @throws SystemException Something went wrong while creating the Unix server socket. * @throws boost::thread_interrupted A system call has been interrupted. * @ingroup Support */ int createUnixServer(const StaticString &filename, unsigned int backlogSize = 0, bool autoDelete = true, const char *file = __FILE__, unsigned int line = __LINE__); /** * Create a new TCP server socket which is bounded to the given address and port. * SO_REUSEADDR will be set on the socket. * * @param address The IP address to bind the socket to. * @param port The port to bind the socket to, or 0 to have the OS automatically * select a free port. * @param backlogSize The size of the socket's backlog. Specify 0 to use the * platform's maximum allowed backlog size. * @param file The name of the source file that called this function, * for file descriptor logging purposes. * @param line The line in the source file that called this function. * @return The file descriptor of the newly created server socket. * @throws SystemException Something went wrong while creating the server socket. * @throws ArgumentException The given address cannot be parsed. * @throws boost::thread_interrupted A system call has been interrupted. * @ingroup Support */ int createTcpServer(const char *address = "0.0.0.0", unsigned short port = 0, unsigned int backlogSize = 0, const char *file = __FILE__, unsigned int line = __LINE__); /** * Connect to a server at the given address in a blocking manner. * * @param address An address as accepted by getSocketAddressType(). * @param file The name of the source file that called this function, * for file descriptor logging purposes. * @param line The line in the source file that called this function. * @return The file descriptor of the connected client socket. * @throws ArgumentException Unknown address type. * @throws RuntimeException Something went wrong. * @throws SystemException Something went wrong while connecting to the server. * @throws IOException Something went wrong while connecting to the server. * @throws boost::thread_interrupted A system call has been interrupted. * @ingroup Support */ int connectToServer(const StaticString &address, const char *file, unsigned int line); /** * Connect to a Unix server socket at <tt>filename</tt> in a blocking manner. * * @param filename The filename of the socket to connect to. * @param file The name of the source file that called this function, * for file descriptor logging purposes. * @param line The line in the source file that called this function. * @return The file descriptor of the connected client socket. * @throws RuntimeException Something went wrong. * @throws SystemException Something went wrong while connecting to the Unix server. * @throws boost::thread_interrupted A system call has been interrupted. * @ingroup Support */ int connectToUnixServer(const StaticString &filename, const char *file, unsigned int line); /** * Connect to a TCP server socket at the given host name and port in a blocking manner. * * @param hostname The host name of the TCP server. * @param port The port number of the TCP server. * @param file The name of the source file that called this function, * for file descriptor logging purposes. * @param line The line in the source file that called this function. * @return The file descriptor of the connected client socket. * @throws IOException Something went wrong while connecting to the Unix server. * @throws SystemException Something went wrong while connecting to the server. * @throws boost::thread_interrupted A system call has been interrupted. * @ingroup Support */ int connectToTcpServer(const StaticString &hostname, unsigned int port, const char *file, unsigned int line); /** State structure for non-blocking connectToUnixServer(). */ struct NUnix_State { FileDescriptor fd; string filename; }; /** * Setup a Unix domain socket for non-blocking connecting. When done, * the file descriptor can be accessed through <tt>state.fd</tt>. * * @param state A state structure. * @param filename The filename of the socket to connect to. * @param file The name of the source file that called this function, * for file descriptor logging purposes. * @param line The line in the source file that called this function. * @throws SystemException Something went wrong. * @throws boost::thread_interrupted A system call has been interrupted. * @ingroup Support */ void setupNonBlockingUnixSocket(NUnix_State & restrict_ref state, const StaticString & restrict_ref filename, const char *file, unsigned int line); /** * Connect a Unix domain socket in non-blocking mode. * * @param state A state structure. * @return True if the socket was successfully connected, false if the socket isn't * ready yet, in which case the caller should select() on the socket until it's writable. * @throws RuntimeException Something went wrong. * @throws SystemException Something went wrong while connecting to the Unix server. * @throws boost::thread_interrupted A system call has been interrupted. * @ingroup Support */ bool connectToUnixServer(NUnix_State &state); /** State structure for non-blocking connectToTcpServer(). */ struct NTCP_State { FileDescriptor fd; struct addrinfo hints, *res; string hostname; int port; NTCP_State() { memset(&hints, 0, sizeof(hints)); res = NULL; port = 0; } ~NTCP_State() { if (res != NULL) { freeaddrinfo(res); } } }; /** * Setup a TCP socket for non-blocking connecting. When done, * the file descriptor can be accessed through <tt>state.fd</tt>. * * @param state A state structure. * @param hostname The host name of the TCP server. * @param port The port number of the TCP server. * @param file The name of the source file that called this function, * for file descriptor logging purposes. * @param line The line in the source file that called this function. * @throws IOException Something went wrong. * @throws SystemException Something went wrong. * @throws boost::thread_interrupted A system call has been interrupted. * @ingroup Support */ void setupNonBlockingTcpSocket(NTCP_State & restrict_ref state, const StaticString & restrict_ref hostname, int port, const char *file, unsigned int line); /** * Connect a TCP socket in non-blocking mode. * * @param state A state structure. * @return True if the socket was successfully connected, false if the socket isn't * ready yet, in which case the caller should select() on the socket until it's writable. * @throws SystemException Something went wrong while connecting to the server. * @throws boost::thread_interrupted A system call has been interrupted. * @ingroup Support */ bool connectToTcpServer(NTCP_State &state); struct NConnect_State { ServerAddressType type; NUnix_State s_unix; NTCP_State s_tcp; }; /** * Setup a socket for non-blocking connecting to the given address. * * @param A state structure. * @param address An address as accepted by getSocketAddressType(). * @param file The name of the source file that called this function, * for file descriptor logging purposes. * @param line The line in the source file that called this function. * @throws ArgumentException Unknown address type. * @throws RuntimeException Something went wrong. * @throws SystemException Something went wrong. * @throws IOException Something went wrong. * @throws boost::thread_interrupted A system call has been interrupted. * @ingroup Support */ void setupNonBlockingSocket(NConnect_State & restrict_ref state, const StaticString & restrict_ref address, const char *file, unsigned int line); /** * Connect a socket in non-blocking mode. * * @param state A state structure. * @return True if the socket was successfully connected, false if the socket isn't * ready yet, in which case the caller should select() on the socket until it's writable. * @throws RuntimeException Something went wrong. * @throws SystemException Something went wrong. * @throws boost::thread_interrupted A system call has been interrupted. * @ingroup Support */ bool connectToServer(NConnect_State &state); /** * Checks whether the given TCP server is connectable. Because this check * can take (in theory) an arbitrary amount of time, you must also supply * a timeout. When the operation is done, the amount of time taken will be * deducted from the `*timeout` value. A timeout of 100000 microseconds is * recommended for most use cases. * * @throws IOException Something went wrong. * @throws SystemException Something went wrong. * @throws boost::thread_interrupted A system call has been interrupted. */ bool pingTcpServer(const StaticString &host, unsigned int port, unsigned long long *timeout); /** * Creates a Unix domain socket pair. * * @param file The name of the source file that called this function, * for file descriptor logging purposes. * @param line The line in the source file that called this function. * @throws SystemException * @throws boost::thread_interrupted */ SocketPair createUnixSocketPair(const char *file, unsigned int line); /** * Creates a pipe. * * @param file The name of the source file that called this function, * for file descriptor logging purposes. * @param line The line in the source file that called this function. * @throws SystemException * @throws boost::thread_interrupted */ Pipe createPipe(const char *file, unsigned int line); /** * Waits at most <tt>*timeout</tt> microseconds for the file descriptor to become readable. * Returns true if it become readable within the timeout, false if the timeout expired. * * <tt>*timeout</tt> may be 0, in which case this method will check whether the file * descriptor is readable, and immediately returns without waiting. * * If no exception is thrown, this method deducts the number of microseconds that has * passed from <tt>*timeout</tt>. * * @throws SystemException * @throws boost::thread_interrupted */ bool waitUntilReadable(int fd, unsigned long long *timeout); /** * Waits at most <tt>*timeout</tt> microseconds for the file descriptor to become writable. * Returns true if it become writable within the timeout, false if the timeout expired. * * <tt>*timeout</tt> may be 0, in which case this method will check whether the file * descriptor is writable, and immediately returns without waiting. * * If no exception is thrown, this method deducts the number of microseconds that has * passed from <tt>*timeout</tt>. * * @throws SystemException * @throws boost::thread_interrupted */ bool waitUntilWritable(int fd, unsigned long long *timeout); /** * Attempts to read exactly <tt>size</tt> bytes of data from the given file * descriptor, and put the result in <tt>buf</tt>. On non-blocking sockets * this function will block by poll()ing the socket. * * @param buf The buffer to place the read data in. This buffer must be at least * <tt>size</tt> bytes long. * @param size The number of bytes to read. * @param timeout A pointer to an integer, which specifies the maximum number of * microseconds that may be spent on reading the <tt>size</tt> bytes * of data. 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 reading will be deducted * from <tt>timeout</tt>. * Pass NULL if you do not want to enforce a timeout. * @return The number of bytes read. This is exactly equal to <em>size</em>, * except when EOF is encountered prematurely. * @pre buf != NULL * @throws SystemException * @throws TimeoutException Unable to read <tt>size</tt> bytes of data within * <tt>timeout</tt> microseconds. * @throws boost::thread_interrupted */ unsigned int readExact(int fd, void * restrict buf, unsigned int size, unsigned long long * restrict timeout = NULL); /** * Writes a block of data to the given file descriptor and blocks until everything * is written, even for non-blocking sockets. If not everything can be written (e.g. * because the peer closed the connection before accepting everything) then an * exception will be thrown. * * @note Security guarantee: this method will not copy the data in memory, * so it's safe to use this method to write passwords to the underlying * file descriptor. * * @param data The data to send. * @param size The number of bytes in <tt>data</tt>. * @param timeout A pointer to an integer, which specifies the maximum number of * microseconds that may be spent on writing the <tt>size</tt> bytes * of data. 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. * @pre data != NULL * @throws SystemException * @throws TimeoutException Unable to write <tt>size</tt> bytes of data within * <tt>timeout</tt> microseconds. * @throws boost::thread_interrupted */ void writeExact(int fd, const void * restrict data, unsigned int size, unsigned long long * restrict timeout = NULL); void writeExact(int fd, const StaticString & restrict_ref data, unsigned long long * restrict timeout = NULL); /** * Writes a bunch of data to the given file descriptor using a gathering I/O interface. * Instead of accepting a single buffer, this function accepts multiple buffers plus * a special 'rest' buffer. The rest buffer is written out first, and the data buffers * are then written out in the order as they appear. This all is done with a single * writev() system call without concatenating all data into a single buffer. * * This function is designed for use with non-blocking sockets. It returns the number * of bytes that have been written, and ensures that restBuffer will contain all data * that has not been written, i.e. should be written out as soon as the file descriptor * is writeable again. If everything has been successfully written out then restBuffer * will be empty. * A return value of 0 indicates that nothing could be written without blocking. * * Returns -1 if an error occurred other than one which indicates blocking. In this * case, <tt>errno</tt> is set appropriately. * * This function also takes care of all the stupid writev() limitations such as * IOV_MAX. It ensures that no more than IOV_MAX items will be passed to writev(). * * @param fd The file descriptor to write to. * @param data The data to write. * @param dataCount Number of elements in <tt>data</tt>. * @param restBuffer The rest buffer, as documented above. * @return The number of bytes that have been written out, or -1 on any error that * isn't related to non-blocking writes. * @throws boost::thread_interrupted */ ssize_t gatheredWrite(int fd, const StaticString * restrict data, unsigned int dataCount, string & restrict_ref restBuffer); /** * Writes a bunch of data to the given file descriptor using a gathering I/O interface. * Instead of accepting a single buffer, this function accepts multiple buffers * which are all written out in the order as they appear. This is done with a single * system call without concatenating all data into a single buffer. * * This method is a convenience wrapper around writev() but it blocks until all data * has been written and takes care of handling system limits (IOV_MAX) for you. * * This version is designed for blocking sockets so do not use it on non-blocking ones. * * @param fd The file descriptor to write to. * @param data An array of buffers to be written. * @param count Number of items in <em>data</em>. * @param timeout A pointer to an integer, which specifies the maximum number of * microseconds that may be spent on writing all the data. * 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 write all given data within * <tt>timeout</tt> microseconds. * @throws boost::thread_interrupted */ void gatheredWrite(int fd, const StaticString * restrict data, unsigned int dataCount, unsigned long long * restrict timeout = NULL); /** * Sets a writev-emulating function that gatheredWrite() should call instead of the real writev(). * Useful for unit tests. Pass NULL to restore back to the real writev(). */ void setWritevFunction(WritevFunction func); /** * Receive a file descriptor over the given Unix domain socket. * This is a low-level function that directly wraps the Unix file * descriptor passing system calls. You should not use this directly; * instead you should use readFileDescriptorWithNegotiation() from MessageIO.h * which is safer. See MessageIO.h for more information about the * negotiation protocol for file descriptor passing. * * @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'. * This is a low-level function that directly wraps the Unix file * descriptor passing system calls. You should not use this directly; * instead you should use writeFileDescriptorWithNegotiation() from MessageIO.h * which is safer. See MessageIO.h for more information about the * negotiation protocol for file descriptor passing. * * @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); /** * Return the effective UID and GID of the peer connected to a Unix domain socket. * * @throws SystemException Something went wrong. If reading credentials over a Unix * domain socket is not supported for the current platform, * then the error code is ENOSYS. If the socket does not * support credentials passing, then the error code is * EPROTONOSUPPORT. */ void readPeerCredentials(int sock, uid_t *uid, gid_t *gid); /** * Closes the given file descriptor and throws an exception if anything goes wrong. * This function also works around certain close() bugs and quirks on certain * operating systems, such as the FreeBSD ENOTCONN-on-close bug and the fact that * when close() returns EINTR the state of the file descriptor is unspecified. * See IOUtils.cpp and ext/oxt/system_calls.cpp for details. * * @throws SystemException * @throws boost::thread_interrupted */ #ifndef _PASSENGER_SAFELY_CLOSE_DEFINED_ #define _PASSENGER_SAFELY_CLOSE_DEFINED_ void safelyClose(int fd, bool ignoreErrors = false); #endif /** * Read all data from the given file descriptor until EOF, or until `maxSize` * is reached. * * Returns a pair `(contents, eof)`. * * - `contents` is the read file contents, which is at most `maxSize` bytes. * - `eof` indicates whether the entire file has been read. If false, then it * means the amount of data is larger than `maxSize`. * * @throws SystemException */ pair<string, bool> readAll(int fd, size_t maxSize); } // namespace Passenger #endif /* _PASSENGER_IOTOOLS_IO_UTILS_H_ */