public
Description: Phusion Passenger (mod_rails)
Homepage: http://www.modrails.com/
Clone URL: git://github.com/FooBarWidget/passenger.git
Click here to lend your support to: passenger and make a donation at www.pledgie.com !
Hongli Lai (Phusion) (author)
Tue May 06 12:04:48 -0700 2008
commit  ca88f5c24a147a04e4f94390ccfced863dfed96c
tree    19891c97828daa937df16285911e913e95fc8353
parent  8ec6efa06d48810822b3dedf6a26cca3576f5363
passenger / ext / passenger / native_support.c
100644 268 lines (238 sloc) 7.594 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
/*
* Phusion Passenger - http://www.modrails.com/
* Copyright (C) 2008 Phusion
*
* 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;
  #else
    char control_data[CMSG_SPACE(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 != CMSG_LEN(sizeof(int))
   || 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)));
}