Checkpoint commit.
authorMichael Granger <ged@FaerieMUD.org>
Thu, 16 Oct 2008 02:43:08 +0000
changeset 2 0c24586f579a
parent 1 09d0d209d06d
child 3 7f6da371e2ce
Checkpoint commit.
LICENSE
README
Rakefile
examples/startjail.rb
ext/bsdjail.c
ext/bsdjail.h
misc/monkeypatches.rb
project.yml
--- a/LICENSE	Fri Aug 15 16:23:03 2008 +0000
+++ b/LICENSE	Thu Oct 16 02:43:08 2008 +0000
@@ -1,4 +1,4 @@
-Copyright (c) 2008, Michae Granger and Mahlon Smith
+Copyright (c) 2008, Michael Granger and Mahlon Smith
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README	Thu Oct 16 02:43:08 2008 +0000
@@ -0,0 +1,19 @@
+= jparallel
+
+jparallel is a "parallel jail shell" written in Ruby. 
+
+This is shell that can be used to interact with multiple FreeBSD jail instances
+simultaneously. It includes a Ruby binding to the FreeBSD jail(2) functions. 
+
+In the meantime, you can check out the current development source with Subversion from the 
+following URL:
+
+    svn://deveiate.org/jparallel/trunk
+
+The project page is also likely to have more details:
+
+    http://deveiate.org/projects/Jparallel/
+
+== License
+
+See the LICENSE file in the same directory for licensing details.
--- a/Rakefile	Fri Aug 15 16:23:03 2008 +0000
+++ b/Rakefile	Thu Oct 16 02:43:08 2008 +0000
@@ -7,7 +7,7 @@
 # Copyright (c) 2008 The FaerieMUD Consortium
 #
 # Authors:
-#  * Michae Granger and Mahlon Smith <ged@FaerieMUD.org, mahlon@martini.nu>
+#  * Michael Granger and Mahlon Smith <ged@FaerieMUD.org, mahlon@martini.nu>
 #
 
 BEGIN {
@@ -21,9 +21,10 @@
 	$LOAD_PATH.unshift( extdir.to_s ) unless $LOAD_PATH.include?( extdir.to_s )
 }
 
+require 'rubygems'
+gem 'rake', '>= 0.8.3'
 
 require 'rbconfig'
-require 'rubygems'
 require 'rake'
 require 'rake/rdoctask'
 require 'rake/testtask'
@@ -34,26 +35,33 @@
 
 ### Config constants
 BASEDIR       = Pathname.new( __FILE__ ).dirname.relative_path_from( Pathname.getwd )
+BINDIR        = BASEDIR + 'bin'
 LIBDIR        = BASEDIR + 'lib'
 EXTDIR        = BASEDIR + 'ext'
 DOCSDIR       = BASEDIR + 'docs'
 PKGDIR        = BASEDIR + 'pkg'
+DATADIR       = BASEDIR + 'data'
 
-PKG_NAME      = 'jparallel'
+PROJECT_NAME  = 'jparallel'
+PKG_NAME      = PROJECT_NAME.downcase
 PKG_SUMMARY   = 'A "parallel jail shell" written in Ruby'
 VERSION_FILE  = LIBDIR + 'jparallel.rb'
-PKG_VERSION   = VERSION_FILE.read[ /VERSION = '(\d+\.\d+\.\d+)'/, 1 ]
+PKG_VERSION   = VERSION_FILE.read[ /VERSION\s*=\s*'(\d+\.\d+\.\d+)'/, 1 ]
 PKG_FILE_NAME = "#{PKG_NAME.downcase}-#{PKG_VERSION}"
 GEM_FILE_NAME = "#{PKG_FILE_NAME}.gem"
 
 ARTIFACTS_DIR = Pathname.new( ENV['CC_BUILD_ARTIFACTS'] || 'artifacts' )
 
 TEXT_FILES    = %w( Rakefile ChangeLog README LICENSE ).collect {|filename| BASEDIR + filename }
+BIN_FILES     = Pathname.glob( BINDIR + '*' ).delete_if {|item| item =~ /\.svn/ }
 LIB_FILES     = Pathname.glob( LIBDIR + '**/*.rb' ).delete_if {|item| item =~ /\.svn/ }
 EXT_FILES     = Pathname.glob( EXTDIR + '**/*.{c,h,rb}' ).delete_if {|item| item =~ /\.svn/ }
+DATA_FILES    = Pathname.glob( DATADIR + '**/*' ).delete_if {|item| item =~ /\.svn/ }
 
 SPECDIR       = BASEDIR + 'spec'
-SPEC_FILES    = Pathname.glob( SPECDIR + '**/*_spec.rb' ).delete_if {|item| item =~ /\.svn/ }
+SPECLIBDIR    = SPECDIR + 'lib'
+SPEC_FILES    = Pathname.glob( SPECDIR + '**/*_spec.rb' ).delete_if {|item| item =~ /\.svn/ } +
+                Pathname.glob( SPECLIBDIR + '**/*.rb' ).delete_if {|item| item =~ /\.svn/ }
 
 TESTDIR       = BASEDIR + 'tests'
 TEST_FILES    = Pathname.glob( TESTDIR + '**/*.tests.rb' ).delete_if {|item| item =~ /\.svn/ }
@@ -64,13 +72,15 @@
 LOCAL_RAKEFILE = BASEDIR + 'Rakefile.local'
 
 EXTRA_PKGFILES = []
-EXTRA_PKGFILES += Pathname.glob( BASEDIR + 'examples/*.{c,rb}' ).delete_if {|item| item =~ /\.svn/ } 
+EXTRA_PKGFILES.concat Pathname.glob( BASEDIR + 'examples/*.{c,rb}' ).delete_if {|item| item =~ /\.svn/ } 
 
 RELEASE_FILES = TEXT_FILES + 
 	SPEC_FILES + 
 	TEST_FILES + 
+	BIN_FILES +
 	LIB_FILES + 
 	EXT_FILES + 
+	DATA_FILES + 
 	RAKE_TASKLIBS +
 	EXTRA_PKGFILES
 
@@ -108,6 +118,7 @@
 SNAPSHOT_GEM_NAME = "#{SNAPSHOT_PKG_NAME}.gem"
 
 # Documentation constants
+RDOCDIR = DOCSDIR + 'api'
 RDOC_OPTIONS = [
 	'-w', '4',
 	'-SHN',
@@ -124,7 +135,8 @@
 PROJECT_HOST = 'deveiate.org'
 PROJECT_PUBDIR = "/usr/local/www/public/code"
 PROJECT_DOCDIR = "#{PROJECT_PUBDIR}/#{PKG_NAME}"
-PROJECT_SCPURL = "#{PROJECT_HOST}:#{PROJECT_DOCDIR}"
+PROJECT_SCPPUBURL = "#{PROJECT_HOST}:#{PROJECT_PUBDIR}"
+PROJECT_SCPDOCURL = "#{PROJECT_HOST}:#{PROJECT_DOCDIR}"
 
 # Rubyforge stuff
 RUBYFORGE_GROUP = 'deveiate'
@@ -134,9 +146,25 @@
 DEPENDENCIES = {
 }
 
+# Developer Gem dependencies: gemname => version
+DEVELOPMENT_DEPENDENCIES = {
+	'amatch'      => '>= 0.2.3',
+	'rake'        => '>= 0.8.1',
+	'rcodetools'  => '>= 0.7.0.0',
+	'rcov'        => '>= 0',
+	'RedCloth'    => '>= 4.0.3',
+	'rspec'       => '>= 0',
+	'rubyforge'   => '>= 0',
+	'termios'     => '>= 0',
+	'text-format' => '>= 1.0.0',
+	'tmail'       => '>= 1.2.3.1',
+	'ultraviolet' => '>= 0.10.2',
+	'libxml-ruby' => '>= 0.8.3',
+}
+
 # Non-gem requirements: packagename => version
 REQUIREMENTS = {
-	'FreeBSD' => '>= 7.0',
+	'FreeBSD' => '>= 5.0',
 }
 
 # RubyGem specification
@@ -147,11 +175,10 @@
 	gem.summary           = PKG_SUMMARY
 	gem.description       = <<-EOD
 	This is shell that can be used to interact with multiple FreeBSD jail instances
-
 	simultaneously. It includes a Ruby binding to the FreeBSD jail(2) functions.
 	EOD
 
-	gem.authors           = 'Michae Granger and Mahlon Smith'
+	gem.authors           = 'Michael Granger and Mahlon Smith'
 	gem.email             = 'ged@FaerieMUD.org, mahlon@martini.nu'
 	gem.homepage          = 'http://deveiate.org/projects/Jparallel/'
 	gem.rubyforge_project = RUBYFORGE_PROJECT
@@ -159,6 +186,9 @@
 	gem.has_rdoc          = true
 	gem.rdoc_options      = RDOC_OPTIONS
 
+	gem.bindir            = BINDIR.relative_path_from(BASEDIR).to_s
+	
+
 	gem.files             = RELEASE_FILES.
 		collect {|f| f.relative_path_from(BASEDIR).to_s }
 	gem.test_files        = SPEC_FILES.
@@ -166,7 +196,15 @@
 		
 	DEPENDENCIES.each do |name, version|
 		version = '>= 0' if version.length.zero?
-		gem.add_dependency( name, version )
+		gem.add_runtime_dependency( name, version )
+	end
+	
+	# Developmental dependencies don't work as of RubyGems 1.2.0
+	unless Gem::Version.new( Gem::RubyGemsVersion ) <= Gem::Version.new( "1.2.0" )
+		DEVELOPMENT_DEPENDENCIES.each do |name, version|
+			version = '>= 0' if version.length.zero?
+			gem.add_development_dependency( name, version )
+		end
 	end
 	
 	REQUIREMENTS.each do |name, version|
@@ -174,6 +212,9 @@
 	end
 end
 
+# Manual-generation config
+MANUALDIR = DOCSDIR + 'manual'
+
 $trace = Rake.application.options.trace ? true : false
 $dryrun = Rake.application.options.dryrun ? true : false
 
@@ -203,7 +244,10 @@
 #####################################################################
 
 ### Default task
-task :default  => [:clean, :spec, :rdoc, :package]
+task :default  => [:clean, :local, :spec, :rdoc, :package]
+
+### Task the local Rakefile can append to -- no-op by default
+task :local
 
 
 ### Task: clean
@@ -226,13 +270,16 @@
 
 ### Task: cruise (Cruisecontrol task)
 desc "Cruisecontrol build"
-task :cruise => [:clean, :spec, :package] do |task|
+task :cruise => [:clean, 'spec:quiet', :package] do |task|
 	raise "Artifacts dir not set." if ARTIFACTS_DIR.to_s.empty?
 	artifact_dir = ARTIFACTS_DIR.cleanpath
 	artifact_dir.mkpath
 	
-	$stderr.puts "Copying coverage stats..."
-	FileUtils.cp_r( 'coverage', artifact_dir )
+	coverage = BASEDIR + 'coverage'
+	if coverage.exist? && coverage.directory?
+		$stderr.puts "Copying coverage stats..."
+		FileUtils.cp_r( 'coverage', artifact_dir )
+	end
 	
 	$stderr.puts "Copying packages..."
 	FileUtils.cp_r( FileList['pkg/*'].to_a, artifact_dir )
--- a/ext/bsdjail.c	Fri Aug 15 16:23:03 2008 +0000
+++ b/ext/bsdjail.c	Thu Oct 16 02:43:08 2008 +0000
@@ -29,6 +29,32 @@
 
 
 /*
+ *  Debug logging function
+ */
+void
+#ifdef HAVE_STDARG_PROTOTYPES
+rlink_debug(const char *fmt, ...)
+#else
+rlink_debug( const char *fmt, va_dcl )
+#endif
+{
+	char buf[BUFSIZ], buf2[BUFSIZ];
+	va_list	args;
+
+	if ( !RTEST(ruby_debug) ) return;
+
+	snprintf( buf, BUFSIZ, "Jail Debug>>> %s", fmt );
+
+	va_init_list( args, fmt );
+	vsnprintf( buf2, BUFSIZ, buf, args );
+	fputs( buf2, stderr );
+	fputs( "\n", stderr );
+	fflush( stderr );
+	va_end( args );
+}
+
+
+/*
 struct jail {
 	u_int32_t       version;
 	char            *path;
@@ -41,8 +67,7 @@
  * Allocation function
  */
 static jail *
-rbjail_jail_alloc()
-{
+rbjail_jail_alloc() {
 	jail *ptr = ALLOC( jail );
 	
 	ptr->version	= 0;
@@ -59,17 +84,19 @@
  * GC Free function
  */
 static void
-rbjail_jail_gc_free( ptr )
-	jail *ptr;
-{
+rbjail_gc_free( jail *ptr ) {
 	if ( ptr ) {
+		if ( ptr->path ) xfree( ptr->path );
+		if ( ptr->hostname ) xfree( ptr->hostname );
+		
 		ptr->path		= NULL;
 		ptr->hostname	= NULL;
+
 		xfree( ptr );
 	}
 	
 	else {
-		debugMsg(( "Not freeing an uninitialized rlink_SENTENCE" ));
+		debugMsg(( "Not freeing an uninitialized jail" ));
 	}
 }
 
@@ -77,15 +104,13 @@
 /*
  * Object validity checker. Returns the data pointer.
  */
-static rlink_SENTENCE *
-check_sentence( self )
-	 VALUE	self;
-{
-	debugMsg(( "Checking a LinkParser::Sentence object (%d).", self ));
+static jail *
+rbjail_check_jail( VALUE self ) {
+	debugMsg(( "Checking a BSD::Jail object (%d).", self ));
 	Check_Type( self, T_DATA );
 
-    if ( !IsSentence(self) ) {
-		rb_raise( rb_eTypeError, "wrong argument type %s (expected LinkParser::Sentence)",
+    if ( !rb_obj_is_kind_of(self, rbjail_cBSDJail) ) {
+		rb_raise( rb_eTypeError, "wrong argument type %s (expected BSD::Jail)",
 				  rb_class2name(CLASS_OF( self )) );
     }
 	
@@ -96,13 +121,11 @@
 /*
  * Fetch the data pointer and check it for sanity.
  */
-static rlink_SENTENCE *
-get_sentence( self )
-	 VALUE self;
-{
-	rlink_SENTENCE *ptr = check_sentence( self );
+static jail *
+rbjail_get_jail( VALUE self ) {
+	jail *ptr = check_sentence( self );
 
-	debugMsg(( "Fetching a Sentence (%p).", ptr ));
+	debugMsg(( "Fetching a Jail (%p).", ptr ));
 	if ( !ptr )
 		rb_raise( rb_eRuntimeError, "uninitialized Sentence" );
 
@@ -110,21 +133,13 @@
 }
 
 
+/* --------------------------------------------------------------
+ * Jail utility functions
+ * -------------------------------------------------------------- */
+
 /*
- * Publicly-usable sentence-fetcher
+ * Try to jail_attach() to the specified +jid+, raising an exception if it fails.
  */
-rlink_SENTENCE *
-rlink_get_sentence( self )
-	VALUE self;
-{
-	return get_sentence( self );
-}
-
-
-
-
-
-
 static void
 rbjail_do_jail_attach( int jid )
 {
@@ -132,7 +147,10 @@
 		rb_sys_fail( "jail_attach" );
 }
 
-/* Mostly ripped off from Ruby's process.c */
+
+/* 
+ * Fork + Block function for rbjail_attach(). Mostly stolen from Ruby's process.c.
+ */
 static VALUE
 rbjail_attach_block( int jid )
 {
@@ -140,8 +158,8 @@
 
     rb_secure(2);
 
-    fflush(stdout);
-    fflush(stderr);
+    fflush( stdout );
+    fflush( stderr );
 
 	switch ( pid = fork() ) {
 		case 0:
@@ -164,9 +182,132 @@
 	}
 }
 
+
+/* --------------------------------------------------------------
+ * Class methods
+ * -------------------------------------------------------------- */
+
+/*
+ *  call-seq:
+ *     BSD::Jail.allocate   -> bsdjail
+ *
+ *  Allocate a new BSD::Jail object.
+ *
+ */
 static VALUE
-rbjail_attach( int argc, VALUE *argv, VALUE self )
-{
+rbjail_jail_s_allocate( VALUE klass ) {
+	return Data_Wrap_Struct( klass, 0, rbjail_gc_free, 0 );
+}
+
+
+/*
+ *  call-seq:
+ *     BSD::Jail.list   -> array
+ *
+ *  Return an Array of all the running jails on the host.
+ *
+ */
+static VALUE
+rbjail_jail_s_list( VALUE klass ) {
+	VALUE rval = rb_ary_new();
+	struct xprison *xp;
+	struct in_addr in;
+	size_t i, len;
+
+	if ( sysctlbyname("jail.list", NULL, &len, NULL, 0) == -1 )
+		rb_sys_fail( "sysctlbyname(): jail.list" );
+
+	xp = ALLOCA_N( xprison, 1 );
+
+	if ( sysctlbyname("jail.list", xp, &len, NULL, 0) == -1 ) {
+		rb_sys_fail( "sysctlbyname(): jail.list" );
+	}
+
+	if ( len < sizeof(*xp) || len % sizeof(*xp) ||
+		xp->pr_version != KINFO_PRISON_VERSION )
+		rb_fatal( "Kernel and userland out of sync" );
+
+	len /= sizeof( *xp );
+	for ( i = 0; i < len; i++ ) {
+		VALUE jail, args[3];
+		
+		/* Hostname */
+		args[0] = rb_str_new2( xp->pr_host );
+
+		/* IP */
+		in.s_addr = ntohl( xp->pr_ip );
+		args[1] = rb_str_new2( inet_ntoa(in) );
+		
+		/* Path */
+		args[2] = rb_str_new2( xp->pr_path );
+		
+		jail = rb_class_new_instance( 3, args, klass );
+		rb_ary_push( rval, jail );
+
+		xp++;
+	}
+
+	return rval;
+}
+
+
+
+
+/* --------------------------------------------------------------
+ * Instance methods
+ * -------------------------------------------------------------- */
+
+/*
+ *  call-seq:
+ *     BSD::Jail.new( hostname, ip, path )           -> new_jail
+ *
+ *  Create a new jail for the given +hostname+, +ip+, and +path+.
+ */
+static VALUE
+rbjail_jail_initialize( VALUE self, VALUE hostname, VALUE ip, VALUE path ) {
+	if ( !rbjail_check_jail(self) ) {
+		struct jail *ptr = rbjail_jail_alloc();
+		char *pathstr = NULL, *hostnamestr = NULL;
+
+		Check_Type( hostname, T_STRING );
+		Check_Type( ip, T_STRING );
+
+		pathstr = ALLOC_N( char, RSTRING(path)->len );
+		strncpy( pathstr, RSTRING(path)->ptr, RSTRING(path)->len );
+
+		hostnamestr = ALLOC_N( char, RSTRING(hostname)->len );
+		strncpy( hostnamestr, RSTRING(hostname)->ptr, RSTRING(hostname)->len );
+		
+		/*
+		struct jail {
+			u_int32_t       version;
+			char            *path;
+			char            *hostname;
+			u_int32_t       ip_number;
+		};
+		*/
+		ptr->version = 0;   /* Per the manpage's recommendation */
+		ptr->ip_number = inet_addr( StringValuePtr(ip) );
+		ptr->path = path;
+		ptr->hostname = hostname;
+	}
+}
+
+
+/*
+ *  call-seq:
+ *     jail.attach             -> true or false
+ *     jail.attach { block }   -> pid
+ *
+ *  Attach to the given jail. In the non-block form, attach the current process to the
+ *  jail and return +true+ if it succeeds. This is a one-way operation, and requires root
+ *  privileges.
+ *  
+ *  In the block form, the process will be forked, and the block will be attached to the jail and 
+ *  run by the child. The parent process will receive the process ID of the child.
+ */
+static VALUE
+rbjail_attach( int argc, VALUE *argv, VALUE self ) {
 	VALUE jidnum, rval;
 	int jid;
 	
@@ -185,49 +326,27 @@
 	return rval;
 }
 
-static VALUE
-	rbjail_list( VALUE self )
-{
-	struct kinfo_prison *sxp, *xp;
-	struct in_addr in;
-	size_t i, len;
 
-	if (sysctlbyname("jail.list", NULL, &len, NULL, 0) == -1)
-		rb_sys_fail("sysctlbyname(): jail.list");
-
-	xp = ALLOCA_N( kinfo_prison, 1 );
-
-	if (sysctlbyname("jail.list", xp, &len, NULL, 0) == -1) {
-		rb_sys_fail("sysctlbyname(): jail.list");
-	}
-
-	if (len < sizeof(*xp) || len % sizeof(*xp) ||
-		xp->pr_version != KINFO_PRISON_VERSION)
-		rb_fatal("Kernel and userland out of sync");
-
-	len /= sizeof(*xp);
-	printf("   JID  IP Address      Hostname                      Path\n");
-	for (i = 0; i < len; i++) {
-		in.s_addr = ntohl(xp->pr_ip);
-		printf("%6d  %-15.15s %-29.29s %.74s\n",
-			xp->pr_id, inet_ntoa(in), xp->pr_host, xp->pr_path);
-		xp++;
-	}
-	free(sxp);
-	exit(0);
-
-}
-
+/*
+ * I can't remember how the hell you document a class, but that will go here.
+ */
 void
-Init_bsdjail( void )
-{
+Init_bsdjail( void ) {
 	rbjail_mBSD = rb_define_module( "BSD" );
 	rbjail_cBSDJail = rb_define_class_under( rbjail_mBSD, "Jail" );
 
-	rb_define_singleton_method( rbjail_cBSDJail, "list", rbjail_list, 0 );
-	rb_define_alloc_function( rbjail_cBSDJail, )
+	/* Class methods */
+	rb_define_alloc_function( rbjail_cBSDJail, rbjail_jail_s_allocate );
+	rb_define_singleton_method( rbjail_cBSDJail, "jail", rbjail_jail_s_jail, 0 );
+	rb_define_singleton_method( rbjail_cBSDJail, "list", rbjail_jail_s_list, 0 );
 	
-	rb_define_method( rbjail_cBSDJail, "attach", rbjail_attach, -1 );
+	/* Instance methods */
+	rb_define_method( rbjail_cBSDJail, "initialize", rbjail_jail_initialize, 3 );
+	rb_define_method( rbjail_cBSDJail, "attach", rbjail_jail_attach, -1 );
+	rb_define_method( rbjail_cBSDJail, "hostname", rbjail_jail_hostname, 0 );
+	rb_define_method( rbjail_cBSDJail, "path", rbjail_jail_path, 0 );
+	rb_define_method( rbjail_cBSDJail, "ip", rbjail_jail_ip, 0 );
+	rb_define_method( rbjail_cBSDJail, "jid", rbjail_jail_jid, 0 );
 	
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ext/bsdjail.h	Thu Oct 16 02:43:08 2008 +0000
@@ -0,0 +1,31 @@
+
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <ruby.h>
+#include <intern.h>				/* For rb_dbl2big() */
+
+#include <link-grammar/link-includes.h>
+
+
+
+/* Debugging functions/macros */
+#ifdef HAVE_STDARG_PROTOTYPES
+#include <stdarg.h>
+#define va_init_list(a,b) va_start(a,b)
+extern void rbjail_debug(const char *fmt, ...);
+#else
+#include <varargs.h>
+#define va_init_list(a,b) va_start(a)
+extern void rbjail_debug(fmt, va_alist);
+#endif
+
+
+/* Debugging macro */
+#if DEBUG
+#  define debugMsg(f)	rbjail_debug f
+#else /* ! DEBUG */
+#  define debugMsg(f) 
+#endif /* DEBUG */
+
--- a/misc/monkeypatches.rb	Fri Aug 15 16:23:03 2008 +0000
+++ b/misc/monkeypatches.rb	Thu Oct 16 02:43:08 2008 +0000
@@ -42,12 +42,14 @@
 	task :default => EXT
 
 	rule '.#{objext}' => '.#{@source_extension}' do |t|
-	  sh "\#{CC} \#{CFLAGS} \#{INCLUDES} -c \#{t.source}"
+		$stderr.puts "  building \#{t.name} from \#{t.source}"
+		sh "\#{CC} \#{CFLAGS} \#{INCLUDES} -c \#{t.source}"
 	end
 
 	desc "Build this extension"
 	file EXT => OBJ do
-	  sh "\#{LDSHARED} \#{LIBPATH} #{@available.ld_outfile(@extension_name)} \#{OBJ} \#{ADDITIONAL_OBJECTS} \#{LIBS} \#{LIBRUBYARG_SHARED}"
+		$stderr.puts "  linking \#{OBJ.join(', ')} into \#{EXT}"
+		sh "\#{LDSHARED} \#{LIBPATH} #{@available.ld_outfile(@extension_name)} \#{OBJ} \#{ADDITIONAL_OBJECTS} \#{LIBS} \#{LIBRUBYARG_SHARED}"
 	end
 
 
@@ -55,7 +57,7 @@
 
 	desc "Install this extension"
 	task :install => [EXT, RUBYARCHDIR] do
-	  install EXT, RUBYARCHDIR, :verbose => true
+		install EXT, RUBYARCHDIR, :verbose => true
 	end
 
 	#{additional_code}
@@ -66,7 +68,83 @@
 module Mkrf
 
 	class Availability
-		# No-op the stupid squashing of output
+
+		# Create a new Availability instance.
+		#
+		# Valid keys for the options hash include:
+		# * <tt>:loaded_libs</tt> -- libraries to load by default
+		# * <tt>:library_paths</tt> -- libraries paths to include by default
+		# * <tt>:headers</tt> -- headers to load by default
+		# * <tt>:compiler</tt> -- which compiler to use when determining availability
+		# * <tt>:includes</tt> -- directories that should be searched for include files
+		def initialize(options = {})      
+			@loaded_libs = options[:loaded_libs] || []
+			@loaded_libs.flatten!
+
+			@library_paths = [(options[:library_paths] || [])].flatten
+			# Not sure what COMMON_HEADERS looks like when populated
+			@headers = options[:headers] || [] # Config::CONFIG["COMMON_HEADERS"]
+			@compiler = options[:compiler] || Config::CONFIG["CC"]
+			@includes = [(options[:includes] || DEFAULT_INCLUDES)].flatten
+			@logger = Logger.new('mkrf.log')
+			@defines = []
+		end
+
+
+		def can_link?( function_body )
+			silence_command_line do
+				create_source(function_body)
+				cmd = link_command()
+				@logger.debug "Running link command: #{cmd}"
+				system( cmd )
+			end
+		ensure
+			FileUtils.rm_f TEMP_SOURCE_FILE
+			FileUtils.rm_f TEMP_EXECUTABLE
+		end
+
+
+		# Add the LIBRUBYARG_SHARED setting to the library paths for non-windows boxen. This is
+		# necessary if Ruby is installed in a directory that isn't in the default library
+		# search path (e.g., on FreeBSD where Ruby is /usr/local/bin/ruby).
+		def library_paths_compile_string
+			if RUBY_PLATFORM =~ /mswin/
+				@library_paths.collect {|l| "/libpath:#{l}"}.join(' ')
+			else
+				Config::CONFIG['LIBRUBYARG_SHARED'] + @library_paths.collect {|l| "-L#{l}"}.join(' ')
+			end
+		end
+
+
+		# Separate includes with a newline instead of a literal '\n'
+		def header_include_string
+			@headers.collect {|header| "#include <#{header}>"}.join( "\n" )
+		end
+
+
+		# Log the created source to the logfile
+		def create_source( src )
+			@logger.debug "Creating source file:\n#{src}"
+			File.open( TEMP_SOURCE_FILE, "w+" ) do |f|
+				f.write( src )
+			end
+		end
+
+		# Prepend the LIBS string directly to the @loaded_libs, as not all arguments in
+		# it 
+		def library_compile_string
+			added_libs = nil
+			if RUBY_PLATFORM =~ /mswin/
+				added_libs = @loaded_libs.join(' ')
+			else
+				added_libs = @loaded_libs.collect {|l| "-l#{l}"}.join(' ')
+			end
+			
+			return Config::CONFIG["LIBS"] + ' ' + added_libs
+		end
+
+
+		# Redirect to the mkrf log instead of /dev/null
 		def silence_stream( stream )
 			old_stream = stream.dup
 			stream.reopen( @logger.instance_variable_get(:@logdev).dev )
--- a/project.yml	Fri Aug 15 16:23:03 2008 +0000
+++ b/project.yml	Thu Oct 16 02:43:08 2008 +0000
@@ -12,6 +12,9 @@
 
 project_summary: A "parallel jail shell" written in Ruby
 project_name: jparallel
+version_file: "jparallel.rb"
 additional_pkgfiles: 
 - examples/*.{c,rb}
+dev_dependencies: {}
+
 author_email: ged@FaerieMUD.org, mahlon@martini.nu