/*
* Phusion Passenger - http://www.modrails.com/
* Copyright (C) 2008 Phusion
*
* Phusion Passenger is a trademark of Hongli Lai & Ninh Bui.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "ruby.h"
#include <sys/types.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
static VALUE mPassenger;
static VALUE mNativeSupport;
/*
* call-seq: send_fd(socket_fd, fd_to_send)
*
* Send a file descriptor over the given Unix socket. You do not have to call
* this function directly. A convenience wrapper is provided by IO#send_io.
*
* - +socket_fd+ (integer): The file descriptor of the socket.
* - +fd_to_send+ (integer): The file descriptor to send.
* - Raises +SystemCallError+ if something went wrong.
*/
static VALUE
send_fd(VALUE self, VALUE socket_fd, VALUE fd_to_send) {
struct msghdr msg;
struct iovec vec;
char dummy[1];
#ifdef __APPLE__
struct {
struct cmsghdr header;
int fd;
} control_data;
#else
char control_data[CMSG_SPACE(sizeof(int))];
#endif
struct cmsghdr *control_header;
int control_payload;
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;
control_payload = NUM2INT(fd_to_send);
#ifdef __APPLE__
control_header->cmsg_len = sizeof(control_data);
control_data.fd = control_payload;
#else
control_header->cmsg_len = CMSG_LEN(sizeof(int));
memcpy(CMSG_DATA(control_header), &control_payload, sizeof(int));
#endif
if (sendmsg(NUM2INT(socket_fd), &msg, 0) == -1) {
rb_sys_fail("sendmsg(2)");
return Qnil;
}
return Qnil;
}
/*
* call-seq: recv_fd(socket_fd)
*
* Receive a file descriptor from the given Unix socket. Returns the received
* file descriptor as an integer. Raises +SystemCallError+ if something went
* wrong.
*
* You do not have call this method directly. A convenience wrapper is
* provided by IO#recv_io.
*/
static VALUE
recv_fd(VALUE self, VALUE socket_fd) {
struct msghdr msg;
struct iovec vec;
char dummy[1];
#ifdef __APPLE__
// 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;
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;
if (recvmsg(NUM2INT(socket_fd), &msg, 0) == -1) {
rb_sys_fail("Cannot read file descriptor with recvmsg()");
return Qnil;
}
control_header = CMSG_FIRSTHDR(&msg);
if (control_header->cmsg_len != EXPECTED_CMSG_LEN
|| control_header->cmsg_level != SOL_SOCKET
|| control_header->cmsg_type != SCM_RIGHTS) {
rb_sys_fail("No valid file descriptor received.");
return Qnil;
}
#ifdef __APPLE__
return INT2NUM(control_data.fd);
#else
return INT2NUM(*((int *) CMSG_DATA(control_header)));
#endif
}
/*
* call-seq: create_unix_socket(filename, backlog)
*
* Create a SOCK_STREAM server Unix socket. Unlike Ruby's UNIXServer class,
* this function is also able to create Unix sockets on the abstract namespace
* by prepending the filename with a null byte.
*
* - +filename+ (string): The filename of the Unix socket to create.
* - +backlog+ (integer): The backlog to use for listening on the socket.
* - Returns: The file descriptor of the created Unix socket, as an integer.
* - Raises +SystemCallError+ if something went wrong.
*/
static VALUE
create_unix_socket(VALUE self, VALUE filename, VALUE backlog) {
int fd, ret;
struct sockaddr_un addr;
char *filename_str;
long filename_length;
filename_str = rb_str2cstr(filename, &filename_length);
fd = socket(PF_UNIX, SOCK_STREAM, 0);
if (fd == -1) {
rb_sys_fail("Cannot create a Unix socket");
return Qnil;
}
addr.sun_family = AF_UNIX;
memcpy(addr.sun_path, filename_str, MIN(filename_length, sizeof(addr.sun_path)));
addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
ret = bind(fd, (const struct sockaddr *) &addr, sizeof(addr));
if (ret == -1) {
int e = errno;
close(fd);
errno = e;
rb_sys_fail("Cannot bind Unix socket");
return Qnil;
}
ret = listen(fd, NUM2INT(backlog));
if (ret == -1) {
int e = errno;
close(fd);
errno = e;
rb_sys_fail("Cannot listen on Unix socket");
return Qnil;
}
return INT2NUM(fd);
}
/*
* call-seq: accept(fileno)
*
* Accept a new client from the given socket.
*
* - +fileno+ (integer): The file descriptor of the server socket.
* - Returns: The accepted client's file descriptor.
* - Raises +SystemCallError+ if something went wrong.
*/
static VALUE
f_accept(VALUE self, VALUE fileno) {
int fd = accept(NUM2INT(fileno), NULL, NULL);
if (fd == -1) {
rb_sys_fail("accept() failed");
return Qnil;
} else {
return INT2NUM(fd);
}
}
/*
* call-seq: close_all_file_descriptors(exceptions)
*
* Close all file descriptors, except those given in the +exceptions+ array.
* For example, the following would close all file descriptors except standard
* input (0) and standard output (1).
*
* close_all_file_descriptors([0, 1])
*/
static VALUE
close_all_file_descriptors(VALUE self, VALUE exceptions) {
long i, j;
for (i = sysconf(_SC_OPEN_MAX) - 1; i >= 0; i--) {
int is_exception = 0;
for (j = 0; j < RARRAY(exceptions)->len && !is_exception; j++) {
long fd = NUM2INT(rb_ary_entry(exceptions, j));
is_exception = i == fd;
}
if (!is_exception) {
close(i);
}
}
return Qnil;
}
void
Init_native_support() {
struct sockaddr_un addr;
/* */
mPassenger = rb_define_module("Passenger"); // Do not remove the above comment. We want the Passenger module's rdoc to be empty.
/*
* Utility functions for accessing system functionality.
*/
mNativeSupport = rb_define_module_under(mPassenger, "NativeSupport");
rb_define_singleton_method(mNativeSupport, "send_fd", send_fd, 2);
rb_define_singleton_method(mNativeSupport, "recv_fd", recv_fd, 1);
rb_define_singleton_method(mNativeSupport, "create_unix_socket", create_unix_socket, 2);
rb_define_singleton_method(mNativeSupport, "accept", f_accept, 1);
rb_define_singleton_method(mNativeSupport, "close_all_file_descriptors", close_all_file_descriptors, 1);
/* The maximum length of a Unix socket path, including terminating null. */
rb_define_const(mNativeSupport, "UNIX_PATH_MAX", INT2NUM(sizeof(addr.sun_path)));
}