PNG  IHDRxsBIT|d pHYs+tEXtSoftwarewww.inkscape.org<,tEXtComment File Manager

File Manager

Path: /opt/passenger/src/ruby_native_extension/

Viewing File: passenger_native_support.c

/*
 *  Phusion Passenger - https://www.phusionpassenger.com/
 *  Copyright (c) 2010-2025 Asynchronous B.V.
 *
 *  "Passenger", "Phusion Passenger" and "Union Station" are registered
 *  trademarks of Asynchronous 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.
 */
#include "ruby.h"
#ifdef HAVE_RUBY_IO_H
	/* Ruby 1.9 */
	#include "ruby/intern.h"
	#include "ruby/io.h"
#else
	/* Ruby 1.8 */
	#include "rubysig.h"
	#include "rubyio.h"
	#include "version.h"
#endif
#ifdef HAVE_RUBY_VERSION_H
	#include "ruby/version.h"
#endif
#ifdef HAVE_RUBY_THREAD_H
	#include "ruby/thread.h"
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <limits.h>
#include <grp.h>
#include <signal.h>
#include <pthread.h>
#ifdef HAVE_ALLOCA_H
	#include <alloca.h>
#endif
#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__NetBSD__) || defined(__OpenBSD__)
	#define HAVE_KQUEUE
	#include <sys/event.h>
	#include <sys/time.h>
#endif

#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#ifndef RARRAY_LEN
	#define RARRAY_LEN(ary) RARRAY(ary)->len
#endif
#ifndef RSTRING_PTR
	#define RSTRING_PTR(str) RSTRING(str)->ptr
#endif
#ifndef RSTRING_LEN
	#define RSTRING_LEN(str) RSTRING(str)->len
#endif
#if !defined(RUBY_UBF_IO) && defined(RB_UBF_DFL)
	/* MacRuby compatibility */
	#define RUBY_UBF_IO RB_UBF_DFL
#endif
#ifndef IOV_MAX
	/* Linux doesn't define IOV_MAX in limits.h for some reason. */
	#define IOV_MAX sysconf(_SC_IOV_MAX)
#endif
#ifdef HAVE_RB_THREAD_IO_BLOCKING_REGION
	/* Ruby doesn't define this function in its headers */
	VALUE rb_thread_io_blocking_region(rb_blocking_function_t *func, void *data1, int fd);
#endif

static VALUE mPassenger;
static VALUE mNativeSupport;
static VALUE S_ProcessTimes;
#ifdef HAVE_KQUEUE
	static VALUE cFileSystemWatcher;
#endif

/*
 * call-seq: disable_stdio_buffering
 *
 * Disables any kind of buffering on the C +stdout+ and +stderr+ variables,
 * so that +fprintf()+ on +stdout+ and +stderr+ have immediate effect.
 */
static VALUE
disable_stdio_buffering(VALUE self) {
	setvbuf(stdout, NULL, _IONBF, 0);
	setvbuf(stderr, NULL, _IONBF, 0);
	return Qnil;
}

/**
 * Split the given string into an hash. Keys and values are obtained by splitting the
 * string using the null character as the delimitor.
 */
static VALUE
split_by_null_into_hash(VALUE self, VALUE data) {
	const char *cdata   = RSTRING_PTR(data);
	unsigned long len   = RSTRING_LEN(data);
	const char *begin   = cdata;
	const char *current = cdata;
	const char *end     = cdata + len;
	VALUE result, key, value;

	result = rb_hash_new();
	while (current < end) {
		if (*current == '\0') {
			key   = rb_str_substr(data, begin - cdata, current - begin);
			begin = current = current + 1;
			while (current < end) {
				if (*current == '\0') {
					value = rb_str_substr(data, begin - cdata, current - begin);;
					begin = current = current + 1;
					rb_hash_aset(result, key, value);
					break;
				} else {
					current++;
				}
			}
		} else {
			current++;
		}
	}
	return result;
}

typedef struct {
	/* The IO vectors in this group. */
	struct iovec *io_vectors;

	/* The number of IO vectors in io_vectors. */
	unsigned int count;

	/* The combined size of all IO vectors in this group. */
	ssize_t      total_size;
} IOVectorGroup;

/* Given that _bytes_written_ bytes in _group_ had been successfully written,
 * update the information in _group_ so that the next writev() call doesn't
 * write the already written bytes.
 */
static void
update_group_written_info(IOVectorGroup *group, ssize_t bytes_written) {
	unsigned int i;
	size_t counter;
	struct iovec *current_vec;

	/* Find the last vector that contains data that had already been written. */
	counter = 0;
	for (i = 0; i < group->count; i++) {
		counter += group->io_vectors[i].iov_len;
		if (counter == (size_t) bytes_written) {
			/* Found. In fact, all vectors up to this one contain exactly
			 * bytes_written bytes. So discard all these vectors.
			 */
			group->io_vectors += i + 1;
			group->count -= i + 1;
			group->total_size -= bytes_written;
			return;
		} else if (counter > (size_t) bytes_written) {
			/* Found. Discard all vectors before this one, and
			 * truncate this vector.
			 */
			group->io_vectors += i;
			group->count -= i;
			group->total_size -= bytes_written;
			current_vec = &group->io_vectors[0];
			current_vec->iov_base = ((char *) current_vec->iov_base) +
				current_vec->iov_len - (counter - bytes_written);
			current_vec->iov_len = counter - bytes_written;
			return;
		}
	}
	rb_raise(rb_eRuntimeError, "writev() returned an unexpected result");
}

#ifndef TRAP_BEG
	typedef struct {
		int filedes;
		const struct iovec *iov;
		int iovcnt;
	} WritevWrapperData;

	#if defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
		static void *
		writev_wrapper(void *ptr) {
			WritevWrapperData *data = (WritevWrapperData *) ptr;
			return (void *) writev(data->filedes, data->iov, data->iovcnt);
		}
	#else
		static VALUE
		writev_wrapper(void *ptr) {
			WritevWrapperData *data = (WritevWrapperData *) ptr;
			return (VALUE) writev(data->filedes, data->iov, data->iovcnt);
		}
	#endif
#endif

static VALUE
f_generic_writev(VALUE fd, VALUE *array_of_components, unsigned int count) {
	VALUE components, str;
	unsigned int total_size, total_components, ngroups;
	IOVectorGroup *groups;
	unsigned int i, j, group_offset, vector_offset;
	unsigned long long ssize_max;
	ssize_t ret;
	int done, fd_num, e;
	#ifndef TRAP_BEG
		WritevWrapperData writev_wrapper_data;
	#endif

	/* First determine the number of components that we have. */
	total_components   = 0;
	for (i = 0; i < count; i++) {
	        Check_Type(array_of_components[i], T_ARRAY);
		total_components += (unsigned int) RARRAY_LEN(array_of_components[i]);
	}
	if (total_components == 0) {
		return NUM2INT(0);
	}

	/* A single writev() call can only accept IOV_MAX vectors, so we
	 * may have to split the components into groups and perform
	 * multiple writev() calls, one per group. Determine the number
	 * of groups needed, how big each group should be and allocate
	 * memory for them.
	 */
	if (total_components % IOV_MAX == 0) {
		ngroups = total_components / IOV_MAX;
		groups  = alloca(ngroups * sizeof(IOVectorGroup));
		if (groups == NULL) {
			rb_raise(rb_eNoMemError, "Insufficient stack space.");
		}
		memset(groups, 0, ngroups * sizeof(IOVectorGroup));
		for (i = 0; i < ngroups; i++) {
			groups[i].io_vectors = alloca(IOV_MAX * sizeof(struct iovec));
			if (groups[i].io_vectors == NULL) {
				rb_raise(rb_eNoMemError, "Insufficient stack space.");
			}
			groups[i].count = IOV_MAX;
		}
	} else {
		ngroups = total_components / IOV_MAX + 1;
		groups  = alloca(ngroups * sizeof(IOVectorGroup));
		if (groups == NULL) {
			rb_raise(rb_eNoMemError, "Insufficient stack space.");
		}
		memset(groups, 0, ngroups * sizeof(IOVectorGroup));
		for (i = 0; i < ngroups - 1; i++) {
			groups[i].io_vectors = alloca(IOV_MAX * sizeof(struct iovec));
			if (groups[i].io_vectors == NULL) {
				rb_raise(rb_eNoMemError, "Insufficient stack space.");
			}
			groups[i].count = IOV_MAX;
		}
		groups[ngroups - 1].io_vectors = alloca((total_components % IOV_MAX) * sizeof(struct iovec));
		if (groups[ngroups - 1].io_vectors == NULL) {
			rb_raise(rb_eNoMemError, "Insufficient stack space.");
		}
		groups[ngroups - 1].count = total_components % IOV_MAX;
	}

	/* Now distribute the components among the groups, filling the iovec
	 * array in each group. Also calculate the total data size while we're
	 * at it.
	 */
	total_size    = 0;
	group_offset  = 0;
	vector_offset = 0;
	for (i = 0; i < count; i++) {
		components = array_of_components[i];
		for (j = 0; j < (unsigned int) RARRAY_LEN(components); j++) {
			str = rb_ary_entry(components, j);
			str = rb_obj_as_string(str);
			total_size += (unsigned int) RSTRING_LEN(str);
			/* I know writev() doesn't write to iov_base, but on some
			 * platforms it's still defined as non-const char *
			 * :-(
			 */
			groups[group_offset].io_vectors[vector_offset].iov_base = (char *) RSTRING_PTR(str);
			groups[group_offset].io_vectors[vector_offset].iov_len  = RSTRING_LEN(str);
			groups[group_offset].total_size += RSTRING_LEN(str);
			vector_offset++;
			if (vector_offset == groups[group_offset].count) {
				group_offset++;
				vector_offset = 0;
			}
		}
	}

	/* We don't compare to SSIZE_MAX directly in order to shut up a compiler warning on OS X Snow Leopard. */
	ssize_max = SSIZE_MAX;
	if (total_size > ssize_max) {
		rb_raise(rb_eArgError, "The total size of the components may not be larger than SSIZE_MAX.");
	}

	/* Write the data. */
	fd_num = NUM2INT(fd);
	for (i = 0; i < ngroups; i++) {
		/* Wait until the file descriptor becomes writable before writing things. */
		rb_thread_fd_writable(fd_num);

		done = 0;
		while (!done) {
			#ifdef TRAP_BEG
				TRAP_BEG;
				ret = writev(fd_num, groups[i].io_vectors, groups[i].count);
				TRAP_END;
			#else
				writev_wrapper_data.filedes = fd_num;
				writev_wrapper_data.iov     = groups[i].io_vectors;
				writev_wrapper_data.iovcnt  = groups[i].count;
				#if defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
					ret = (ssize_t) rb_thread_call_without_gvl(writev_wrapper,
						&writev_wrapper_data, RUBY_UBF_IO, NULL);
				#elif defined(HAVE_RB_THREAD_IO_BLOCKING_REGION)
					ret = (ssize_t) rb_thread_io_blocking_region(writev_wrapper,
						&writev_wrapper_data, fd_num);
				#else
					ret = (ssize_t) rb_thread_blocking_region(writev_wrapper,
						&writev_wrapper_data, RUBY_UBF_IO, 0);
				#endif
			#endif
			if (ret == -1) {
				/* If the error is something like EAGAIN, yield to another
				 * thread until the file descriptor becomes writable again.
				 * In case of other errors, raise an exception.
				 */
				if (!rb_io_wait_writable(fd_num)) {
					rb_sys_fail("writev()");
				}
			} else if (ret < groups[i].total_size) {
				/* Not everything in this group has been written. Retry without
				 * writing the bytes that been successfully written.
				 */
				e = errno;
				update_group_written_info(&groups[i], ret);
				errno = e;
				rb_io_wait_writable(fd_num);
			} else {
				done = 1;
			}
		}
	}
	return INT2NUM(total_size);
}

/**
 * Writes all of the strings in the +components+ array into the given file
 * descriptor using the +writev()+ system call. Unlike IO#write, this method
 * does not require one to concatenate all those strings into a single buffer
 * in order to send the data in a single system call. Thus, #writev is a great
 * way to perform zero-copy I/O.
 *
 * Unlike the raw writev() system call, this method ensures that all given
 * data is written before returning, by performing multiple writev() calls
 * and whatever else is necessary.
 *
 *   writev(@socket.fileno, ["hello ", "world", "\n"])
 */
static VALUE
f_writev(VALUE self, VALUE fd, VALUE components) {
	return f_generic_writev(fd, &components, 1);
}

/**
 * Like #writev, but accepts two arrays. The data is written in the given order.
 *
 *   writev2(@socket.fileno, ["hello ", "world", "\n"], ["another ", "message\n"])
 */
static VALUE
f_writev2(VALUE self, VALUE fd, VALUE components1, VALUE components2) {
	VALUE array_of_components[2] = { components1, components2 };
	return f_generic_writev(fd, array_of_components, 2);
}

/**
 * Like #writev, but accepts three arrays. The data is written in the given order.
 *
 *   writev3(@socket.fileno,
 *     ["hello ", "world", "\n"],
 *     ["another ", "message\n"],
 *     ["yet ", "another ", "one", "\n"])
 */
static VALUE
f_writev3(VALUE self, VALUE fd, VALUE components1, VALUE components2, VALUE components3) {
	VALUE array_of_components[3] = { components1, components2, components3 };
	return f_generic_writev(fd, array_of_components, 3);
}

static VALUE
process_times(VALUE self) {
	struct rusage usage;
	unsigned long long utime, stime;

	if (getrusage(RUSAGE_SELF, &usage) == -1) {
		rb_sys_fail("getrusage()");
	}

	utime = (unsigned long long) usage.ru_utime.tv_sec * 1000000 + usage.ru_utime.tv_usec;
	stime = (unsigned long long) usage.ru_stime.tv_sec * 1000000 + usage.ru_stime.tv_usec;
	return rb_struct_new(S_ProcessTimes, rb_ull2inum(utime), rb_ull2inum(stime));
}

static void *
detach_process_main(void *arg) {
	pid_t pid = (pid_t) (long) arg;
	int ret;
	do {
		ret = waitpid(pid, NULL, 0);
	} while (ret == -1 && errno == EINTR);
	return NULL;
}

static VALUE
detach_process(VALUE self, VALUE pid) {
	pthread_t thr;
	pthread_attr_t attr;
	size_t stack_size = 96 * 1024;

	unsigned long min_stack_size;
	int stack_min_size_defined;
	int round_stack_size;

	#ifdef PTHREAD_STACK_MIN
		// PTHREAD_STACK_MIN may not be a constant macro so we need
		// to evaluate it dynamically.
		min_stack_size = PTHREAD_STACK_MIN;
		stack_min_size_defined = 1;
	#else
		// Assume minimum stack size is 128 KB.
		min_stack_size = 128 * 1024;
		stack_min_size_defined = 0;
	#endif
	if (stack_size != 0 && stack_size < min_stack_size) {
		stack_size = min_stack_size;
		round_stack_size = !stack_min_size_defined;
	} else {
		round_stack_size = 1;
	}

	if (round_stack_size) {
		// Round stack size up to page boundary.
		long page_size;
		#if defined(_SC_PAGESIZE)
			page_size = sysconf(_SC_PAGESIZE);
		#elif defined(_SC_PAGE_SIZE)
			page_size = sysconf(_SC_PAGE_SIZE);
		#elif defined(PAGESIZE)
			page_size = sysconf(PAGESIZE);
		#elif defined(PAGE_SIZE)
			page_size = sysconf(PAGE_SIZE);
		#else
			page_size = getpagesize();
		#endif
		if (stack_size % page_size != 0) {
			stack_size = stack_size - (stack_size % page_size) + page_size;
		}
	}

	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr, 1);
	pthread_attr_setstacksize(&attr, stack_size);
	pthread_create(&thr, &attr, detach_process_main, (void *) NUM2LONG(pid));
	pthread_attr_destroy(&attr);
	return Qnil;
}

/**
 * Freeze the current process forever. On Ruby 1.9 this never unlocks the GIL.
 * Useful for testing purposes.
 */
static VALUE
freeze_process(VALUE self) {
	while (1) {
		usleep(60 * 1000000);
	}
	return Qnil;
}

#if defined(HAVE_KQUEUE) || defined(IN_DOXYGEN)
typedef struct {
	VALUE klass;
	VALUE filenames;
	VALUE termination_pipe;

	/* File descriptor of termination_pipe. */
	int termination_fd;

	/* Whether something went wrong during initialization. */
	int preparation_error;

	/* Information for kqueue. */
	unsigned int events_len;
	int *fds;
	unsigned int fds_len;
	int kq;

	/* When the watcher thread is done it'll write to this pipe
	 * to signal the main (Ruby) thread.
	 */
	int notification_fd[2];

	/* When the main (Ruby) thread is interrupted it'll write to
	 * this pipe to tell the watcher thread to exit.
	 */
	int interruption_fd[2];
} FSWatcher;

typedef struct {
	int fd;
	ssize_t ret;
	char byte;
	int error;
} FSWatcherReadByteData;

static void
fs_watcher_real_close(FSWatcher *watcher) {
	unsigned int i;

	if (watcher->kq != -1) {
		close(watcher->kq);
		watcher->kq = -1;
	}
	if (watcher->notification_fd[0] != -1) {
		close(watcher->notification_fd[0]);
		watcher->notification_fd[0] = -1;
	}
	if (watcher->notification_fd[1] != -1) {
		close(watcher->notification_fd[1]);
		watcher->notification_fd[1] = -1;
	}
	if (watcher->interruption_fd[0] != -1) {
		close(watcher->interruption_fd[0]);
		watcher->interruption_fd[0] = -1;
	}
	if (watcher->interruption_fd[1] != -1) {
		close(watcher->interruption_fd[1]);
		watcher->interruption_fd[1] = -1;
	}
	if (watcher->fds != NULL) {
		for (i = 0; i < watcher->fds_len; i++) {
			close(watcher->fds[i]);
		}
		free(watcher->fds);
		watcher->fds = NULL;
		watcher->fds_len = 0;
	}
}

static void
fs_watcher_free(void *obj) {
	FSWatcher *watcher = (FSWatcher *) obj;
	fs_watcher_real_close(watcher);
	free(watcher);
}

static VALUE
fs_watcher_init(VALUE arg) {
	FSWatcher *watcher = (FSWatcher *) arg;
	struct kevent *events;
	VALUE filename;
	unsigned int i;
	uint32_t fflags;
	VALUE filenum;
	struct stat buf;
	int fd;

	/* Open each file in the filenames list and add each one to the events array. */

	/* +2 for the termination pipe and the interruption pipe. */
	events = alloca((RARRAY_LEN(watcher->filenames) + 2) * sizeof(struct kevent));
	watcher->fds = malloc(RARRAY_LEN(watcher->filenames) * sizeof(int));
	if (watcher->fds == NULL) {
		rb_raise(rb_eNoMemError, "Cannot allocate memory.");
		return Qnil;
	}
	for (i = 0; i < RARRAY_LEN(watcher->filenames); i++) {
		filename = rb_ary_entry(watcher->filenames, i);
		if (TYPE(filename) != T_STRING) {
			filename = rb_obj_as_string(filename);
		}

		if (stat(RSTRING_PTR(filename), &buf) == -1) {
			watcher->preparation_error = 1;
			goto end;
		}

		#ifdef O_EVTONLY
			fd = open(RSTRING_PTR(filename), O_EVTONLY);
		#else
			fd = open(RSTRING_PTR(filename), O_RDONLY);
		#endif
		if (fd == -1) {
			watcher->preparation_error = 1;
			goto end;
		}

		watcher->fds[i] = fd;
		watcher->fds_len++;
		fflags = NOTE_WRITE | NOTE_EXTEND | NOTE_RENAME | NOTE_DELETE | NOTE_REVOKE;
		EV_SET(&events[i], fd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR,
			fflags, 0, 0);
	}

	watcher->events_len = watcher->fds_len;

	/* Create pipes for inter-thread communication. */

	if (pipe(watcher->notification_fd) == -1) {
		rb_sys_fail("pipe()");
		return Qnil;
	}
	if (pipe(watcher->interruption_fd) == -1) {
		rb_sys_fail("pipe()");
		return Qnil;
	}

	/* Create a kqueue and register all events. */

	watcher->kq = kqueue();
	if (watcher->kq == -1) {
		rb_sys_fail("kqueue()");
		return Qnil;
	}

	if (watcher->termination_pipe != Qnil) {
		filenum = rb_funcall(watcher->termination_pipe,
			rb_intern("fileno"), 0);
		EV_SET(&events[watcher->events_len], NUM2INT(filenum),
			EVFILT_READ, EV_ADD | EV_ENABLE | EV_CLEAR, 0, 0, 0);
		watcher->termination_fd = NUM2INT(filenum);
		watcher->events_len++;
	}
	EV_SET(&events[watcher->events_len], watcher->interruption_fd[0],
		EVFILT_READ, EV_ADD | EV_ENABLE | EV_CLEAR, 0, 0, 0);
	watcher->events_len++;

	if (kevent(watcher->kq, events, watcher->events_len, NULL, 0, NULL) == -1) {
		rb_sys_fail("kevent()");
		return Qnil;
	}

end:
	if (watcher->preparation_error) {
		for (i = 0; i < watcher->fds_len; i++) {
			close(watcher->fds[i]);
		}
		free(watcher->fds);
		watcher->fds = NULL;
		watcher->fds_len = 0;
	}
	return Data_Wrap_Struct(watcher->klass, NULL, fs_watcher_free, watcher);
}

static VALUE
fs_watcher_new(VALUE klass, VALUE filenames, VALUE termination_pipe) {
	FSWatcher *watcher;
	VALUE result;
	int status;

	Check_Type(filenames, T_ARRAY);
	watcher = (FSWatcher *) calloc(1, sizeof(FSWatcher));
	if (watcher == NULL) {
		rb_raise(rb_eNoMemError, "Cannot allocate memory.");
		return Qnil;
	}
	watcher->klass = klass;
	watcher->filenames = filenames;
	watcher->termination_pipe = termination_pipe;
	watcher->termination_fd = -1;
	watcher->kq = -1;
	watcher->notification_fd[0] = -1;
	watcher->notification_fd[1] = -1;
	watcher->interruption_fd[0] = -1;
	watcher->interruption_fd[1] = -1;

	result = rb_protect(fs_watcher_init, (VALUE) watcher, &status);
	if (status) {
		fs_watcher_free(watcher);
		rb_jump_tag(status);
		return Qnil;
	} else {
		return result;
	}
}

static void *
fs_watcher_wait_on_kqueue(void *arg) {
	FSWatcher *watcher = (FSWatcher *) arg;
	struct kevent *events;
	int nevents;
	ssize_t ret;

	events = alloca(sizeof(struct kevent) * watcher->events_len);
	nevents = kevent(watcher->kq, NULL, 0, events, watcher->events_len, NULL);
	if (nevents == -1) {
		ret = write(watcher->notification_fd[1], "e", 1);
	} else if (nevents >= 1 && (
		   events[0].ident == (uintptr_t) watcher->termination_fd
		|| events[0].ident == (uintptr_t) watcher->interruption_fd[0]
	)) {
		ret = write(watcher->notification_fd[1], "t", 1);
	} else {
		ret = write(watcher->notification_fd[1], "f", 1);
	}
	if (ret == -1) {
		close(watcher->notification_fd[1]);
		watcher->notification_fd[1] = -1;
	}
	return NULL;
}

static VALUE
fs_watcher_wait_fd(VALUE _fd) {
	int fd = (int) _fd;
	rb_thread_wait_fd(fd);
	return Qnil;
}

#ifndef TRAP_BEG
	#if defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
		static void *
		fs_watcher_read_byte_from_fd_wrapper(void *_arg) {
			FSWatcherReadByteData *data = (FSWatcherReadByteData *) _arg;
			data->ret = read(data->fd, &data->byte, 1);
			data->error = errno;
			return NULL;
		}
	#else
		static VALUE
		fs_watcher_read_byte_from_fd_wrapper(void *_arg) {
			FSWatcherReadByteData *data = (FSWatcherReadByteData *) _arg;
			data->ret = read(data->fd, &data->byte, 1);
			data->error = errno;
			return Qnil;
		}
	#endif
#endif

static VALUE
fs_watcher_read_byte_from_fd(VALUE _arg) {
	FSWatcherReadByteData *data = (FSWatcherReadByteData *) _arg;
	#if defined(TRAP_BEG)
		TRAP_BEG;
		data->ret = read(data->fd, &data->byte, 1);
		TRAP_END;
		data->error = errno;
	#elif defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL)
		rb_thread_call_without_gvl2(fs_watcher_read_byte_from_fd_wrapper,
			data, RUBY_UBF_IO, NULL);
	#elif defined(HAVE_RB_THREAD_IO_BLOCKING_REGION)
		rb_thread_io_blocking_region(fs_watcher_read_byte_from_fd_wrapper,
			data, data->fd);
	#else
		rb_thread_blocking_region(fs_watcher_read_byte_from_fd_wrapper,
			data, RUBY_UBF_IO, 0);
	#endif
	return Qnil;
}

static VALUE
fs_watcher_wait_for_change(VALUE self) {
	FSWatcher *watcher;
	pthread_t thr;
	ssize_t ret;
	int e, interrupted = 0;
	FSWatcherReadByteData read_data;

	Data_Get_Struct(self, FSWatcher, watcher);

	if (watcher->preparation_error) {
		return Qfalse;
	}

	/* Spawn a thread, and let the thread perform the blocking kqueue
	 * wait. When kevent() returns the thread will write its status to the
	 * notification pipe. In the mean time we let the Ruby interpreter wait
	 * on the other side of the pipe for us so that we don't block Ruby
	 * threads.
	 */

	e = pthread_create(&thr, NULL, fs_watcher_wait_on_kqueue, watcher);
	if (e != 0) {
		errno = e;
		rb_sys_fail("pthread_create()");
		return Qnil;
	}

	/* Note that rb_thread_wait() does not wait for the fd when the app
	 * is single threaded, so we must join the thread after we've read
	 * from the notification fd.
	 */
	rb_protect(fs_watcher_wait_fd, (VALUE) watcher->notification_fd[0], &interrupted);
	if (interrupted) {
		/* We got interrupted so tell the watcher thread to exit. */
		ret = write(watcher->interruption_fd[1], "x", 1);
		if (ret == -1) {
			e = errno;
			fs_watcher_real_close(watcher);
			errno = e;
			rb_sys_fail("write() to interruption pipe");
			return Qnil;
		}
		pthread_join(thr, NULL);

		/* Now clean up stuff. */
		fs_watcher_real_close(watcher);
		rb_jump_tag(interrupted);
		return Qnil;
	}

	read_data.fd = watcher->notification_fd[0];
	rb_protect(fs_watcher_read_byte_from_fd, (VALUE) &read_data, &interrupted);
	if (interrupted) {
		/* We got interrupted so tell the watcher thread to exit. */
		ret = write(watcher->interruption_fd[1], "x", 1);
		if (ret == -1) {
			e = errno;
			fs_watcher_real_close(watcher);
			errno = e;
			rb_sys_fail("write() to interruption pipe");
			return Qnil;
		}
		pthread_join(thr, NULL);

		/* Now clean up stuff. */
		fs_watcher_real_close(watcher);
		rb_jump_tag(interrupted);
		return Qnil;
	}

	pthread_join(thr, NULL);

	if (read_data.ret == -1) {
		fs_watcher_real_close(watcher);
		errno = read_data.error;
		rb_sys_fail("read()");
		return Qnil;
	} else if (read_data.ret == 0) {
		fs_watcher_real_close(watcher);
		errno = read_data.error;
		rb_raise(rb_eRuntimeError, "Unknown error: unexpected EOF");
		return Qnil;
	} else if (read_data.byte == 't') {
		/* termination_fd or interruption_fd became readable */
		return Qnil;
	} else if (read_data.byte == 'f') {
		/* a file or directory changed */
		return Qtrue;
	} else {
		fs_watcher_real_close(watcher);
		errno = read_data.error;
		rb_raise(rb_eRuntimeError, "Unknown error: unexpected notification data");
		return Qnil;
	}
}

static VALUE
fs_watcher_close(VALUE self) {
	FSWatcher *watcher;
	Data_Get_Struct(self, FSWatcher, watcher);
	fs_watcher_real_close(watcher);
	return Qnil;
}
#endif


/***************************/

void
Init_passenger_native_support() {
	struct sockaddr_un addr;

	/* Only defined on Ruby >= 1.9.3 */
	#ifdef RUBY_API_VERSION_CODE
		if (ruby_api_version[0] != RUBY_API_VERSION_MAJOR
		 || ruby_api_version[1] != RUBY_API_VERSION_MINOR
		 || ruby_api_version[2] != RUBY_API_VERSION_TEENY)
		{
			fprintf(stderr, " --> passenger_native_support was compiled for Ruby API version %d.%d.%d, "
				"but you're currently running a Ruby interpreter with API version %d.%d.%d.\n",
				RUBY_API_VERSION_MAJOR,
				RUBY_API_VERSION_MINOR,
				RUBY_API_VERSION_TEENY,
				ruby_api_version[0],
				ruby_api_version[1],
				ruby_api_version[2]);
			fprintf(stderr, "     Refusing to load existing passenger_native_support.\n");
			return;
		}
		/* Because native extensions may be linked to libruby, loading
		 * a Ruby 1.9 native extension may not fail on Ruby 1.8 (even though
		 * the extension will crash later on). We detect such a case here and
		 * abort early.
		 */
		if (strlen(ruby_version) >= sizeof("1.8.7") - 1
		 && ruby_version[0] == '1'
		 && ruby_version[1] == '.'
		 && ruby_version[2] == '8')
		{
			fprintf(stderr, " --> passenger_native_support was compiled for Ruby %d.%d, "
				"but you're currently running Ruby %s\n",
				RUBY_API_VERSION_MAJOR,
				RUBY_API_VERSION_MINOR,
				ruby_version);
			fprintf(stderr, "     Refusing to load existing passenger_native_support.\n");
			return;
		}
	#else
		/* Ruby 1.8 - 1.9.2 */

		/* We may not have included Ruby 1.8's version.h because of compiler
		 * header file search paths, so we can't rely on RUBY_VERSION being
		 * defined.
		 */
		#ifdef RUBY_VERSION
			#define ESTIMATED_RUBY_VERSION RUBY_VERSION
		#else
			#ifdef HAVE_RUBY_IO_H
				#define ESTIMATED_RUBY_VERSION "1.9.1 or 1.9.2"
			#else
				#define ESTIMATED_RUBY_VERSION "1.8"
			#endif
		#endif
		#ifdef HAVE_RUBY_IO_H
			#define ESTIMATED_RUBY_MINOR_VERSION '9'
		#else
			#define ESTIMATED_RUBY_MINOR_VERSION '8'
		#endif

		#ifdef HAVE_RUBY_VERSION
			if (strlen(ruby_version) < sizeof("1.8.7") - 1
			 || ruby_version[0] != '1'
			 || ruby_version[1] != '.'
			 || ruby_version[2] != ESTIMATED_RUBY_MINOR_VERSION)
			{
				fprintf(stderr, " --> passenger_native_support was compiled for Ruby %s, "
					"but you're currently running Ruby %s\n",
					ESTIMATED_RUBY_VERSION, ruby_version);
				fprintf(stderr, "     Refusing to load existing passenger_native_support.\n");
				return;
			}
		#endif
	#endif

	mPassenger = rb_define_module("PhusionPassenger");

	/*
	 * Utility functions for accessing system functionality.
	 */
	mNativeSupport = rb_define_module_under(mPassenger, "NativeSupport");

	S_ProcessTimes = rb_struct_define("ProcessTimes", "utime", "stime", NULL);

	rb_define_singleton_method(mNativeSupport, "disable_stdio_buffering", disable_stdio_buffering, 0);
	rb_define_singleton_method(mNativeSupport, "split_by_null_into_hash", split_by_null_into_hash, 1);
	rb_define_singleton_method(mNativeSupport, "writev", f_writev, 2);
	rb_define_singleton_method(mNativeSupport, "writev2", f_writev2, 3);
	rb_define_singleton_method(mNativeSupport, "writev3", f_writev3, 4);
	rb_define_singleton_method(mNativeSupport, "process_times", process_times, 0);
	rb_define_singleton_method(mNativeSupport, "detach_process", detach_process, 1);
	rb_define_singleton_method(mNativeSupport, "freeze_process", freeze_process, 0);

	#ifdef HAVE_KQUEUE
		cFileSystemWatcher = rb_define_class_under(mNativeSupport,
			"FileSystemWatcher", rb_cObject);
		rb_define_singleton_method(cFileSystemWatcher, "_new",
			fs_watcher_new, 2);
		rb_define_method(cFileSystemWatcher, "wait_for_change",
			fs_watcher_wait_for_change, 0);
		rb_define_method(cFileSystemWatcher, "close",
			fs_watcher_close, 0);
		rb_undef_alloc_func(cFileSystemWatcher);
	#endif

	/* The maximum length of a Unix socket path, including terminating null. */
	rb_define_const(mNativeSupport, "UNIX_PATH_MAX", INT2NUM(sizeof(addr.sun_path)));
	/* The maximum size of the data that may be passed to #writev. */
	rb_define_const(mNativeSupport, "SSIZE_MAX", LL2NUM(SSIZE_MAX));
}
b IDATxytVսϓ22 A@IR :hCiZ[v*E:WũZA ^dQeQ @ !jZ'>gsV仿$|?g)&x-EIENT ;@xT.i%-X}SvS5.r/UHz^_$-W"w)Ɗ/@Z &IoX P$K}JzX:;` &, ŋui,e6mX ԵrKb1ԗ)DADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADA݀!I*]R;I2$eZ#ORZSrr6mteffu*((Pu'v{DIߔ4^pIm'77WEEE;vƎ4-$]'RI{\I&G :IHJ DWBB=\WR޽m o$K(V9ABB.}jѢv`^?IOȅ} ڶmG}T#FJ`56$-ھ}FI&v;0(h;Б38CӧOWf!;A i:F_m9s&|q%=#wZprrrla A &P\\СC[A#! {olF} `E2}MK/vV)i{4BffV\|ۭX`b@kɶ@%i$K z5zhmX[IXZ` 'b%$r5M4º/l ԃߖxhʔ)[@=} K6IM}^5k㏷݆z ΗÿO:gdGBmyT/@+Vɶ纽z񕏵l.y޴it뭷zV0[Y^>Wsqs}\/@$(T7f.InݺiR$푔n.~?H))\ZRW'Mo~v Ov6oԃxz! S,&xm/yɞԟ?'uaSѽb,8GלKboi&3t7Y,)JJ c[nzӳdE&KsZLӄ I?@&%ӟ۶mSMMњ0iؐSZ,|J+N ~,0A0!5%Q-YQQa3}$_vVrf9f?S8`zDADADADADADADADADAdqP,تmMmg1V?rSI꒟]u|l RCyEf٢9 jURbztѰ!m5~tGj2DhG*{H9)꒟ר3:(+3\?/;TUݭʴ~S6lڧUJ*i$d(#=Yݺd{,p|3B))q:vN0Y.jkק6;SɶVzHJJЀ-utѹսk>QUU\޲~]fFnK?&ߡ5b=z9)^|u_k-[y%ZNU6 7Mi:]ۦtk[n X(e6Bb."8cۭ|~teuuw|ήI-5"~Uk;ZicEmN/:]M> cQ^uiƞ??Ңpc#TUU3UakNwA`:Y_V-8.KKfRitv޲* 9S6ֿj,ՃNOMߤ]z^fOh|<>@Å5 _/Iu?{SY4hK/2]4%it5q]GGe2%iR| W&f*^]??vq[LgE_3f}Fxu~}qd-ږFxu~I N>\;͗O֊:̗WJ@BhW=y|GgwܷH_NY?)Tdi'?խwhlmQi !SUUsw4kӺe4rfxu-[nHtMFj}H_u~w>)oV}(T'ebʒv3_[+vn@Ȭ\S}ot}w=kHFnxg S 0eޢm~l}uqZfFoZuuEg `zt~? b;t%>WTkķh[2eG8LIWx,^\thrl^Ϊ{=dž<}qV@ ⠨Wy^LF_>0UkDuʫuCs$)Iv:IK;6ֲ4{^6եm+l3>݆uM 9u?>Zc }g~qhKwڭeFMM~pМuqǿz6Tb@8@Y|jx](^]gf}M"tG -w.@vOqh~/HII`S[l.6nØXL9vUcOoB\xoǤ'T&IǍQw_wpv[kmO{w~>#=P1Pɞa-we:iǏlHo׈꒟f9SzH?+shk%Fs:qVhqY`jvO'ρ?PyX3lх]˾uV{ݞ]1,MzYNW~̈́ joYn}ȚF߾׮mS]F z+EDxm/d{F{-W-4wY듏:??_gPf ^3ecg ҵs8R2מz@TANGj)}CNi/R~}c:5{!ZHӋӾ6}T]G]7W6^n 9*,YqOZj:P?Q DFL|?-^.Ɵ7}fFh׶xe2Pscz1&5\cn[=Vn[ĶE鎀uˌd3GII k;lNmشOuuRVfBE]ۣeӶu :X-[(er4~LHi6:Ѻ@ԅrST0trk%$Č0ez" *z"T/X9|8.C5Feg}CQ%͞ˣJvL/?j^h&9xF`њZ(&yF&Iݻfg#W;3^{Wo^4'vV[[K';+mӍִ]AC@W?1^{එyh +^]fm~iԵ]AB@WTk̏t uR?l.OIHiYyԶ]Aˀ7c:q}ힽaf6Z~қm(+sK4{^6}T*UUu]n.:kx{:2 _m=sAߤU@?Z-Vކеz왍Nэ{|5 pڶn b p-@sPg]0G7fy-M{GCF'%{4`=$-Ge\ eU:m+Zt'WjO!OAF@ik&t݆ϥ_ e}=]"Wz_.͜E3leWFih|t-wZۍ-uw=6YN{6|} |*={Ѽn.S.z1zjۻTH]흾 DuDvmvK.`V]yY~sI@t?/ϓ. m&["+P?MzovVЫG3-GRR[(!!\_,^%?v@ҵő m`Y)tem8GMx.))A]Y i`ViW`?^~!S#^+ѽGZj?Vģ0.))A꨷lzL*]OXrY`DBBLOj{-MH'ii-ϰ ok7^ )쭡b]UXSְmռY|5*cֽk0B7镹%ڽP#8nȎq}mJr23_>lE5$iwui+ H~F`IjƵ@q \ @#qG0".0" l`„.0! ,AQHN6qzkKJ#o;`Xv2>,tێJJ7Z/*A .@fفjMzkg @TvZH3Zxu6Ra'%O?/dQ5xYkU]Rֽkق@DaS^RSּ5|BeHNN͘p HvcYcC5:y #`οb;z2.!kr}gUWkyZn=f Pvsn3p~;4p˚=ē~NmI] ¾ 0lH[_L hsh_ғߤc_њec)g7VIZ5yrgk̞W#IjӪv>՞y睝M8[|]\շ8M6%|@PZڨI-m>=k='aiRo-x?>Q.}`Ȏ:Wsmu u > .@,&;+!!˱tﭧDQwRW\vF\~Q7>spYw$%A~;~}6¾ g&if_=j,v+UL1(tWake:@Ș>j$Gq2t7S?vL|]u/ .(0E6Mk6hiۺzښOrifޱxm/Gx> Lal%%~{lBsR4*}{0Z/tNIɚpV^#Lf:u@k#RSu =S^ZyuR/.@n&΃z~B=0eg뺆#,Þ[B/?H uUf7y Wy}Bwegל`Wh(||`l`.;Ws?V@"c:iɍL֯PGv6zctM̠':wuW;d=;EveD}9J@B(0iհ bvP1{\P&G7D޴Iy_$-Qjm~Yrr&]CDv%bh|Yzni_ˆR;kg}nJOIIwyuL}{ЌNj}:+3Y?:WJ/N+Rzd=hb;dj͒suݔ@NKMԄ jqzC5@y°hL m;*5ezᕏ=ep XL n?מ:r`۵tŤZ|1v`V뽧_csج'ߤ%oTuumk%%%h)uy]Nk[n 'b2 l.=͜E%gf$[c;s:V-͞WߤWh-j7]4=F-X]>ZLSi[Y*We;Zan(ӇW|e(HNNP5[= r4tP &0<pc#`vTNV GFqvTi*Tyam$ߏWyE*VJKMTfFw>'$-ؽ.Ho.8c"@DADADADADADADADADA~j*֘,N;Pi3599h=goضLgiJ5փy~}&Zd9p֚ e:|hL``b/d9p? fgg+%%hMgXosج, ΩOl0Zh=xdjLmhݻoO[g_l,8a]٭+ӧ0$I]c]:粹:Teꢢ"5a^Kgh,&= =՟^߶“ߢE ܹS J}I%:8 IDAT~,9/ʃPW'Mo}zNƍ쨓zPbNZ~^z=4mswg;5 Y~SVMRXUյڱRf?s:w ;6H:ºi5-maM&O3;1IKeamZh͛7+##v+c ~u~ca]GnF'ټL~PPPbn voC4R,ӟgg %hq}@#M4IÇ Oy^xMZx ) yOw@HkN˖-Sǎmb]X@n+i͖!++K3gd\$mt$^YfJ\8PRF)77Wא!Cl$i:@@_oG I{$# 8磌ŋ91A (Im7֭>}ߴJq7ޗt^ -[ԩSj*}%]&' -ɓ'ꫯVzzvB#;a 7@GxI{j޼ƌ.LÇWBB7`O"I$/@R @eee@۷>}0,ɒ2$53Xs|cS~rpTYYY} kHc %&k.], @ADADADADADADADADA@lT<%''*Lo^={رc5h %$+CnܸQ3fҥK}vUVVs9G R,_{xˇ3o߾;TTTd}馛]uuuG~iԩ@4bnvmvfϞ /Peeeq}}za I~,誫{UWW뮻}_~YƍSMMMYχ֝waw\ďcxꩧtEƍկ_?۷5@u?1kNׯWzz/wy>}zj3 k(ٺuq_Zvf̘:~ ABQ&r|!%KҥKgԞ={<_X-z !CyFUUz~ ABQIIIjݺW$UXXDٳZ~ ABQƍecW$<(~<RSSvZujjjԧOZQu@4 8m&&&jԩg$ď1h ͟?_{768@g =@`)))5o6m3)ѣƌJ;wҿUTT /KZR{~a=@0o<*狔iFɶ[ˎ;T]]OX@?K.ۈxN pppppppppppppppppPfl߾] ,{ァk۶mڿo5BTӦMӴiӴ|r DB2e|An!Dy'tkΝ[A $***t5' "!駟oaDnΝ:t֭[gDШQ06qD;@ x M6v(PiizmZ4ew"@̴ixf [~-Fٱc&IZ2|n!?$@{[HTɏ#@hȎI# _m(F /6Z3z'\r,r!;w2Z3j=~GY7"I$iI.p_"?pN`y DD?: _  Gÿab7J !Bx@0 Bo cG@`1C[@0G @`0C_u V1 aCX>W ` | `!<S `"<. `#c`?cAC4 ?c p#~@0?:08&_MQ1J h#?/`7;I  q 7a wQ A 1 Hp !#<8/#@1Ul7=S=K.4Z?E_$i@!1!E4?`P_  @Bă10#: "aU,xbFY1 [n|n #'vEH:`xb #vD4Y hi.i&EΖv#O H4IŶ}:Ikh @tZRF#(tXҙzZ ?I3l7q@õ|ۍ1,GpuY Ꮿ@hJv#xxk$ v#9 5 }_$c S#=+"K{F*m7`#%H:NRSp6I?sIՖ{Ap$I$I:QRv2$Z @UJ*$]<FO4IENDB`