/* * Phusion Passenger - http://www.modrails.com/ * Copyright (c) 2008, 2009 Phusion * * "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui. * * 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_APPLICATION_POOL_STATUS_REPORTER_H_ #define _PASSENGER_APPLICATION_POOL_STATUS_REPORTER_H_ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "StandardApplicationPool.h" #include "MessageChannel.h" #include "Exceptions.h" #include "Logging.h" #include "Utils.h" namespace Passenger { using namespace boost; using namespace oxt; using namespace std; /** * An ApplicationPoolStatusReporter allows commandline admin tools to inspect * the status of a StandardApplicationPool. It does so by creating a Unix socket * in the Passenger temp folder, which tools can connect to to query for * information. * * An ApplicationPoolStatusReporter creates a background thread for handling * connections on the socket. This thread will be automatically cleaned up upon * destroying the ApplicationPoolStatusReporter object. */ class ApplicationPoolStatusReporter { private: /** * Wrapper class around a file descriptor integer, for RAII behavior. * * A FileDescriptor object behaves just like an int, so that you can pass it to * system calls such as read(). It performs reference counting. When the last * copy of a FileDescriptor has been destroyed, the underlying file descriptor * will be automatically closed. */ class FileDescriptor { private: struct SharedData { int fd; /** * Constructor to assign this file descriptor's handle. */ SharedData(int fd) { this->fd = fd; } /** * Attempts to close this file descriptor. When created on the stack, * this destructor will automatically be invoked as a result of C++ * semantics when exiting the scope this object was created in. This * ensures that stack created objects with destructors like these will * de-allocate their resources upon leaving their corresponding scope. * This pattern is also known Resource Acquisition Is Initialization (RAII). * * @throws SystemException File descriptor could not be closed. */ ~SharedData() { this_thread::disable_syscall_interruption dsi; if (syscalls::close(fd) == -1) { throw SystemException("Cannot close file descriptor", errno); } } }; /* Shared pointer for reference counting on this file descriptor */ shared_ptr data; public: FileDescriptor() { // Do nothing. } /** * Creates a new FileDescriptor instance with the given fd as a handle. */ FileDescriptor(int fd) { data = ptr(new SharedData(fd)); } /** * Overloads the integer cast operator so that it will return the file * descriptor handle as an integer. * * @return This file descriptor's handle as an integer. */ operator int () const { return data->fd; } }; /** The application pool to monitor. */ StandardApplicationPoolPtr pool; /** The socket's filename. */ char filename[PATH_MAX]; /** The socket's file descriptor. */ int serverFd; /** The main thread. */ oxt::thread *mainThread; /** The mutex which protects the 'threads' member. */ boost::mutex threadsLock; /** A map which maps a client file descriptor to its handling thread. */ map< int, shared_ptr > threads; void writeScalarAndIgnoreErrors(MessageChannel &channel, const string &data) { try { channel.writeScalar(data); } catch (const SystemException &e) { // Don't care about write errors. } } void mainThreadFunction() { TRACE_POINT(); try { while (!this_thread::interruption_requested()) { UPDATE_TRACE_POINT(); sockaddr_un addr; socklen_t addr_len = sizeof(addr); FileDescriptor fd(syscalls::accept(serverFd, (struct sockaddr *) &addr, &addr_len)); if (fd == -1) { int e = errno; P_ERROR("Cannot accept new client on status reporter socket: " << strerror(e) << " (" << e << ")"); break; } boost::lock_guard l(threadsLock); this_thread::disable_syscall_interruption dsi; this_thread::disable_interruption di; shared_ptr thread(new oxt::thread( bind(&ApplicationPoolStatusReporter::clientThreadFunction, this, fd), "Status reporter client thread " + toString(fd), 1024 * 128 )); threads[fd] = thread; } } catch (const boost::thread_interrupted &) { P_TRACE(2, "Status reporter main thread interrupted."); } catch (const exception &e) { P_ERROR("Error in status reporter main thread: " << e.what()); } } void clientThreadFunction(FileDescriptor fd) { TRACE_POINT(); MessageChannel channel(fd); try { while (!this_thread::interruption_requested()) { vector args; UPDATE_TRACE_POINT(); if (!channel.read(args) || args.size() < 1) { break; } if (args[0] == "backtraces") { UPDATE_TRACE_POINT(); writeScalarAndIgnoreErrors(channel, oxt::thread::all_backtraces()); } else if (args[0] == "status") { UPDATE_TRACE_POINT(); writeScalarAndIgnoreErrors(channel, pool->toString()); } else if (args[0] == "status_xml") { UPDATE_TRACE_POINT(); writeScalarAndIgnoreErrors(channel, pool->toXml()); } else { P_ERROR("Error in status reporter client thread: unknown query '" << args[0] << "'."); } } } catch (const boost::thread_interrupted &) { P_TRACE(2, "Status reporter client thread " << fd << " interrupted."); } catch (const exception &e) { P_ERROR("Error in status reporter client thread: " << e.what()); } boost::lock_guard l(threadsLock); this_thread::disable_syscall_interruption dsi; this_thread::disable_interruption di; threads.erase(fd); } public: /** * Creates a new ApplicationPoolStatusReporter. * * @param pool The application pool to monitor. * @param userSwitching Whether user switching is enabled. This is used * for determining the optimal permissions for the * FIFO file and the temp directory that might get * created. * @param permissions The permissions with which the FIFO should * be created. * @param uid The UID of the user who should own the FIFO file, or * -1 if the current user should be set as owner. * @param gid The GID of the user who should own the FIFO file, or * -1 if the current group should be set as group. * @throws RuntimeException An error occurred. * @throws SystemException An error occurred while creating the server socket. * @throws boost::thread_resource_error Something went wrong during * creation of the thread. * @throws boost::thread_interrupted A system call has been interrupted. */ ApplicationPoolStatusReporter(StandardApplicationPoolPtr &pool, bool userSwitching, mode_t permissions = S_IRUSR | S_IWUSR, uid_t uid = -1, gid_t gid = -1) { int ret; this->pool = pool; createPassengerTempDir(getSystemTempDir(), userSwitching, "nobody", geteuid(), getegid()); snprintf(filename, sizeof(filename) - 1, "%s/info/status.socket", getPassengerTempDir().c_str()); filename[PATH_MAX - 1] = '\0'; serverFd = createUnixServer(filename, 10); /* Set the socket file's permissions... */ do { ret = chmod(filename, permissions); } while (ret == -1 && errno == EINTR); /* ...and ownership. */ if (uid != (uid_t) -1 && gid != (gid_t) -1) { do { ret = chown(filename, uid, gid); } while (ret == -1 && errno == EINTR); if (errno == -1) { int e = errno; char message[1024]; snprintf(message, sizeof(message) - 1, "Cannot set the owner for socket file '%s' to %lld and its group to %lld", filename, (long long) uid, (long long) gid); message[sizeof(message) - 1] = '\0'; do { ret = close(serverFd); } while (ret == -1 && errno == EINTR); throw SystemException(message, e); } } try { mainThread = new oxt::thread( bind(&ApplicationPoolStatusReporter::mainThreadFunction, this), "Status reporter main thread", 1024 * 128 ); } catch (...) { do { ret = close(serverFd); } while (ret == -1 && errno == EINTR); throw; } } ~ApplicationPoolStatusReporter() { this_thread::disable_syscall_interruption dsi; this_thread::disable_interruption di; int ret; do { ret = unlink(filename); } while (ret == -1 && errno == EINTR); mainThread->interrupt_and_join(); delete mainThread; do { ret = close(serverFd); } while (ret == -1 && errno == EINTR); /* We make a copy of the data structure here to avoid deadlocks. */ map< int, shared_ptr > threadsCopy; { boost::lock_guard l(threadsLock); threadsCopy = threads; } map< int, shared_ptr >::iterator it; for (it = threadsCopy.begin(); it != threadsCopy.end(); it++) { it->second->interrupt_and_join(); } } }; } // namespace Passenger #endif /* _PASSENGER_APPLICATION_POOL_STATUS_REPORTER_H_ */