* Now with 87% more hot jail action! default
authorMahlon E. Smith <mahlon@martini.nu>
Tue, 03 Mar 2009 22:23:45 +0000
changeset 7 4460fc10c6a3
parent 6 2d52adc4adcc
* Now with 87% more hot jail action! * Predeclared all C methods in jail.h, so they could be arranged in logical order in jail.c * Fixed the extconf namespace. * Added rdoc. * Added usage examples, demonstrating jls, jexec, and jail ruby equivalents. * Re-added the "attach and execute within a block" code. * Added Enumerable and Comparable support. * Return 'path' as a Pathname object. TODO: * Create the actual 'jParallel' shell binary, now that we have a good backend framework. * Tests? How? * Add support for recently committed (will be part of 7.2-RELEASE) multiple IPs per jail, and jail labels.
examples/jail.rb
examples/jexec.rb
examples/jexec2.rb
examples/jls.rb
examples/startjail.rb
ext/bsdjail.c
ext/bsdjail.h
ext/extconf.rb
ext/jail.c
ext/jail.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/jail.rb	Tue Mar 03 22:23:45 2009 +0000
@@ -0,0 +1,21 @@
+#!/usr/bin/env ruby
+#
+# An example 'jail' utility in ruby.
+#
+
+BEGIN {
+        require 'pathname'
+        basedir = Pathname.new( __FILE__ ).dirname.parent
+
+        $LOAD_PATH.unshift basedir + "ext" unless 
+                $LOAD_PATH.include? basedir + "ext"
+}
+
+require 'jail'
+
+jid = BSD::Jail.create( '127.0.0.1', '/tmp', 'testjail' )
+puts "New jail created with id: %d" % [ jid ]
+
+# We're in the jail, now.
+Dir.foreach('.') { |i| puts i }
+
--- a/examples/jexec.rb	Sat Feb 28 06:52:48 2009 +0000
+++ b/examples/jexec.rb	Tue Mar 03 22:23:45 2009 +0000
@@ -1,29 +1,18 @@
-#!/usr/bin/ruby
+#!/usr/bin/env ruby
+#
+# An example 'jexec' in ruby.
+#
 
 BEGIN {
 	require 'pathname'
 	basedir = Pathname.new( __FILE__ ).dirname.parent
-	
+
 	$LOAD_PATH.unshift basedir + "ext" unless 
 		$LOAD_PATH.include? basedir + "ext"
 }
 
-
-require 'bsdjail'
-
-jid = Integer( ARGV.shift )
-args = ARGV
-
-$deferr.puts "In process #{Process.pid}, about to jail_attach() with a block"
+require 'jail'
 
-childpid = BSD::Jail.attach( jid ) do
-	Dir.chdir( "/" )
-	$deferr.puts "Child #{Process.pid} exec()ing:", "  " + args.join(" ")
-	exec( *args )
-end
+BSD::Jail.attach( ARGV.shift.to_i )
+exec( *ARGV )
 
-$deferr.puts "Parent: waiting on imprisoned child #{childpid}"
-Process.waitpid( childpid )
-
-$deferr.puts "Child exited with exit code: %d" % [ $?.exitstatus ]
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/jexec2.rb	Tue Mar 03 22:23:45 2009 +0000
@@ -0,0 +1,43 @@
+#!/usr/bin/env ruby
+#
+# A 'smarter' jexec in ruby.
+#
+# Run a command in multiple jails in parallel.
+# Jails can be selected via host, ip, or jid.
+#
+#   "Run 'ls' in all jails that match the hostname 'trac':
+#       ./jexec2.rb trac ls
+#
+
+BEGIN {
+        require 'pathname'
+        basedir = Pathname.new( __FILE__ ).dirname.parent
+
+        $LOAD_PATH.unshift basedir + "ext" unless 
+                $LOAD_PATH.include? basedir + "ext"
+}
+
+require 'jail'
+
+jarg = ARGV.shift
+args = ARGV
+
+jails = BSD::Jail.find_all do |j|
+    j.jid     == jarg.to_i ||
+    j.ip.to_s == jarg      ||
+    j.host    =~ /#{jarg}/
+end or raise "Unable to find jails that match '#{jarg}'."
+
+
+jails.each do |j|
+    $deferr.puts "Parent #{Process.pid} about to attach to #{j.host} in a block!"
+    childpid = j.attach do
+        $deferr.puts "Child #{Process.pid} exec()ing:", "  " + args.join(" ")
+        exec( *args )
+    end
+
+    $deferr.puts "Parent: waiting on imprisoned child #{childpid}"
+    Process.waitpid( childpid )
+    $deferr.puts "Child exited with exit code: %d" % [ $?.exitstatus ]
+end
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/examples/jls.rb	Tue Mar 03 22:23:45 2009 +0000
@@ -0,0 +1,21 @@
+#!/usr/bin/env ruby
+#
+# An example of the 'jls' utility in ruby.
+# Output is sorted by hostname.
+#
+
+BEGIN {
+        require 'pathname'
+        basedir = Pathname.new( __FILE__ ).dirname.parent
+
+        $LOAD_PATH.unshift basedir + "ext" unless 
+                $LOAD_PATH.include? basedir + "ext"
+}
+
+require 'jail'
+
+puts "   JID  IP Address      Hostname                      Path"
+BSD::Jail.sort_by {|j| j.host }.each do |j|
+	puts "%6d  %-15.15s %-29.29s %.74s" % [ j.jid, j.ip, j.host, j.path ]
+end
+
--- a/ext/bsdjail.c	Sat Feb 28 06:52:48 2009 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,366 +0,0 @@
-/*
- *  bsdjail.c - JParallel extension for FreeBSD jail(2) functions
- *  $Id$
- *  
- *  Authors:
- *    * Michael Granger <ged@FaerieMUD.org>
- *    * Mahlon E. Smith <mahlon@martini.nu>
- *  
- *  Copyright (c) 2008, Michael Granger and Mahlon Smith
- *  All rights reserved.
- *  
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are met:
- *  
- *    * Redistributions of source code must retain the above copyright notice,
- *      this list of conditions and the following disclaimer.
- *  
- *    * Redistributions in binary form must reproduce the above copyright notice,
- *      this list of conditions and the following disclaimer in the documentation
- *      and/or other materials provided with the distribution.
- *  
- *    * Neither the name of the author/s, nor the names of the project's
- *      contributors may be used to endorse or promote products derived from this
- *      software without specific prior written permission.
- *  
- *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- *  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
- *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- *  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- *  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- *  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *  
- */
-
-#include "bsdjail.h"
-
-VALUE rbjail_mBSD;
-VALUE rbjail_cBSDJail;
-
-
-/*
- *  Debug logging function
- */
-void
-#ifdef HAVE_STDARG_PROTOTYPES
-rbjail_debug(const char *fmt, ...)
-#else
-rbjail_debug( const char *fmt, va_dcl )
-#endif
-{
-	char buf[BUFSIZ], buf2[BUFSIZ];
-	va_list	args;
-
-	if ( !RTEST(ruby_debug) ) return;
-
-	snprintf( buf, BUFSIZ, "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;
-	char            *hostname;
-	u_int32_t       ip_number;
-};
-*/
-
-/*
- * Allocation function
- */
-static jail *
-rbjail_jail_alloc() {
-	jail *ptr = ALLOC( jail );
-	
-	ptr->version	= 0;
-	ptr->path		= NULL;
-	ptr->hostname	= NULL;
-	ptr->ip_number	= 0;
-	
-	debugMsg(( "Initialized a jail pointer <%p>", ptr ));
-	return ptr;
-}
-
-
-/*
- * GC Free function
- */
-static void
-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 jail" ));
-	}
-}
-
-
-/*
- * Object validity checker. Returns the data pointer.
- */
-static jail *
-rbjail_check_jail( VALUE self ) {
-	debugMsg(( "Checking a BSD::Jail object (%d).", self ));
-	Check_Type( self, T_DATA );
-
-    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 )) );
-    }
-	
-	return DATA_PTR( self );
-}
-
-
-/*
- * Fetch the data pointer and check it for sanity.
- */
-static jail *
-rbjail_get_jail( VALUE self ) {
-	jail *ptr = check_sentence( self );
-
-	debugMsg(( "Fetching a Jail (%p).", ptr ));
-	if ( !ptr )
-		rb_raise( rb_eRuntimeError, "uninitialized Sentence" );
-
-	return ptr;
-}
-
-
-/* --------------------------------------------------------------
- * Jail utility functions
- * -------------------------------------------------------------- */
-
-/*
- * Try to jail_attach() to the specified +jid+, raising an exception if it fails.
- */
-static void
-rbjail_do_jail_attach( int jid )
-{
-	if ( jail_attach(jid) == -1 )
-		rb_sys_fail( "jail_attach" );
-}
-
-
-/* 
- * Fork + Block function for rbjail_attach(). Mostly stolen from Ruby's process.c.
- */
-static VALUE
-rbjail_attach_block( int jid )
-{
-    int pid;
-
-    rb_secure(2);
-
-    fflush( stdout );
-    fflush( stderr );
-
-	switch ( pid = fork() ) {
-		case 0:
-			rb_thread_atfork();
-			if ( rb_block_given_p() ) {
-				int status;
-
-				rbjail_do_jail_attach( jid );
-				rb_protect( rb_yield, Qundef, &status );
-				ruby_stop( status );
-			}
-			return Qnil;
-
-		case -1:
-			rb_sys_fail( "fork(2)" );
-			return Qnil;
-
-		default:
-			return INT2FIX( pid );
-	}
-}
-
-
-/* --------------------------------------------------------------
- * Class methods
- * -------------------------------------------------------------- */
-
-/*
- *  call-seq:
- *     BSD::Jail.allocate   -> bsdjail
- *
- *  Allocate a new BSD::Jail object.
- *
- */
-static VALUE
-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;
-	
-	rb_scan_args( argc, argv, "1", &jidnum );
-	jid = NUM2INT( jidnum );
-
-	if ( rb_block_given_p() ) {
-		rval = rbjail_attach_block( jid );
-	}
-	
-	else {
-		rbjail_do_jail_attach( jid );
-		rval = Qtrue;
-	}
-	
-	return rval;
-}
-
-
-/*
- * I can't remember how the hell you document a class, but that will go here.
- */
-void
-Init_bsdjail( void ) {
-	rbjail_mBSD = rb_define_module( "BSD" );
-	rbjail_cBSDJail = rb_define_class_under( rbjail_mBSD, "Jail" );
-
-	/* 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 );
-	
-	/* 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 );
-	
-}
-
--- a/ext/bsdjail.h	Sat Feb 28 06:52:48 2009 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,74 +0,0 @@
-/*
- *  bsdjail.h - Header for the bsdjail extension
- *  $Id$
- *  
- *  Authors:
- *    * Michael Granger <ged@FaerieMUD.org>
- *    * Mahlon E. Smith <mahlon@martini.nu>
- *  
- *  Copyright (c) 2008, Michael Granger and Mahlon Smith
- *  All rights reserved.
- *  
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are met:
- *  
- *    * Redistributions of source code must retain the above copyright notice,
- *      this list of conditions and the following disclaimer.
- *  
- *    * Redistributions in binary form must reproduce the above copyright notice,
- *      this list of conditions and the following disclaimer in the documentation
- *      and/or other materials provided with the distribution.
- *  
- *    * Neither the name of the author/s, nor the names of the project's
- *      contributors may be used to endorse or promote products derived from this
- *      software without specific prior written permission.
- *  
- *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- *  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
- *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- *  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- *  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- *  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *  
- */
-
-#ifndef _BSDJAIL_H_
-#define _BSDJAIL_H_
-
-
-#include <stdio.h>
-#include <sys/param.h>
-#include <sys/jail.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <ruby.h>
-#include <intern.h>				/* For rb_dbl2big() */
-
-
-/* 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 */
-
-
-#endif /* _BSDJAIL_H_ */
-
--- a/ext/extconf.rb	Sat Feb 28 06:52:48 2009 +0000
+++ b/ext/extconf.rb	Tue Mar 03 22:23:45 2009 +0000
@@ -22,15 +22,18 @@
 end
 
 
-dir_config( 'bsdjail' )
+dir_config( 'bsd/jail' )
 
-have_header( 'stdio.h' ) 		or fail "Can't find the stdio.h header."
-have_header( 'sys/param.h' ) 	or fail "Can't find the sys/param.h header."
-have_header( 'sys/jail.h' ) 	or fail "Can't find the sys/jail.h header."
-have_header( 'sys/types.h' ) 	or fail "Can't find the sys/types.h header."
-have_header( 'unistd.h' ) 		or fail "Can't find the unistd.h header."
+have_header( 'stdio.h' )      or fail "Can't find the stdio.h header."
+have_header( 'sys/param.h' )  or fail "Can't find the sys/param.h header."
+have_header( 'sys/jail.h' )   or fail "Can't find the sys/jail.h header."
+have_header( 'sys/types.h' )  or fail "Can't find the sys/types.h header."
+have_header( 'sys/sysctl.h' ) or fail "Can't find the sys/sysctl.h header."
+have_header( 'unistd.h' )     or fail "Can't find the unistd.h header."
+have_header( 'arpa/inet.h' )  or fail "Can't find the arpa/inet.h header."
+have_header( 'errno.h' )      or fail "Can't find the errno.h header."
 
-have_func( "jail_attach" )		or fail "Can't find jail_attach in the stdlib."
+have_func( "jail_attach" ) or fail "Can't find jail_attach in the stdlib."
 
-create_makefile( 'bsdjail' )
+create_makefile( 'bsd/jail' )
 
--- a/ext/jail.c	Sat Feb 28 06:52:48 2009 +0000
+++ b/ext/jail.c	Tue Mar 03 22:23:45 2009 +0000
@@ -1,5 +1,5 @@
 /*
- *  jail.c - Ruby jparallel
+ *  jail.c - Ruby jParallel
  *
  *  vim: set nosta noet ts=4 sw=4:
  *
@@ -39,13 +39,12 @@
  *  
  */
 
-
 #include "jail.h"
 
-VALUE rbjail_mBSD;
-VALUE rbjail_cBSDJail;
-VALUE rbjail_cIPAddr;
 
+/* --------------------------------------------------------------
+ * Utility functions
+ * -------------------------------------------------------------- */
 
 /*
  *  Debug logging function
@@ -83,7 +82,7 @@
 
 	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 )) );
+			rb_obj_classname( self ));
 	}
 
 	return DATA_PTR( self );
@@ -106,6 +105,62 @@
 
 
 /*
+ * Attach to a running jail and chdir to the root.
+ */
+static int
+rbjail_do_attach( int jid )
+{
+	int attach_status = jail_attach(jid);
+
+	if ( attach_status == -1 )
+		rb_sys_fail( "jail_attach" );
+	if ( chdir("/") == -1 ) rb_sys_fail( "chdir" );
+
+	return attach_status;
+}
+
+
+/*
+ * Attach to a running jail from within a block, forking first
+ * and returning the child pid.
+ */
+static VALUE
+rbjail_attach_block( int jid )
+{
+	int pid;
+	int status;
+
+	if ( ! rb_block_given_p() ) return Qnil;
+
+	rb_secure(2);
+
+	fflush(stdout);
+	fflush(stderr);
+
+	switch ( pid = fork() ) {
+		case 0:
+			rb_thread_atfork();
+			rbjail_do_attach( jid );
+			rb_protect( rb_yield, Qundef, &status );
+			rb_exit( status );
+			return Qnil;
+
+		case -1:
+			rb_sys_fail( "fork(2)" );
+			return Qnil;
+
+		default:
+			return INT2FIX( pid );
+	}
+}
+
+
+
+/* --------------------------------------------------------------
+ * Memory-management functions
+ * -------------------------------------------------------------- */
+
+/*
  * Copy memory from the given 'xp' to a ruby managed object.
  */
 static VALUE
@@ -124,56 +179,6 @@
 
 
 /*
- *
- */
-static VALUE
-rbjail_get_ip( VALUE self )
-{
-	struct xprison *xp = rbjail_get_jailptr( self );
-	struct in_addr in;
-	char *ip;
-
-	in.s_addr = ntohl( xp->pr_ip );
-	ip = inet_ntoa(in);
-
-	return rb_funcall( rbjail_cIPAddr, rb_intern("new"), 1, rb_str_new2(ip) );
-}
-
-
-/*
- *
- */
-static VALUE
-rbjail_get_jid( VALUE self )
-{
-	struct xprison *xp = rbjail_get_jailptr( self );
-	return INT2FIX( xp->pr_id );
-}
-
-
-/*
- *
- */
-static VALUE
-rbjail_get_host( VALUE self )
-{
-	struct xprison *xp = rbjail_get_jailptr( self );
-	return rb_str_new2( xp->pr_host );
-}
-
-
-/*
- *
- */
-static VALUE
-rbjail_get_path( VALUE self )
-{
-	struct xprison *xp = rbjail_get_jailptr( self );
-	return rb_str_new2( xp->pr_path );
-}
-
-
-/*
  * GC Free function
  */
 static void
@@ -198,18 +203,30 @@
 }
 
 
+/* --------------------------------------------------------------
+ * Class methods
+ * -------------------------------------------------------------- */
+
 /*
- * Create a new jail object.
- * Returns the +id+ of the newly created jail.
+ *  call-seq:
+ *     BSD::Jail.new( ip, path, host )   => jail_id
+ *
+ *  Create a new BSD::Jail object from required +ip+, +path+, and +host+
+ *  arguments.  You can optionally pass a +securelevel+ fourth argument.
+ *
+ *  Returns the +id+ of the newly created jail.
  */
 static VALUE
-rbjail_jail( int argc, VALUE *argv, VALUE self ) {
+rbjail_jail( int argc, VALUE *argv, VALUE self )
+{
 	struct jail j;
 	struct in_addr in;
-	VALUE ip, path, host, securelevel;
+	VALUE ip, path, host, sec_level;
 	int id;
+	int securelevel = -1;
 
-	rb_scan_args( argc, argv, "31", &ip, &path, &host, &securelevel );
+	rb_scan_args( argc, argv, "31", &ip, &path, &host, &sec_level );
+	if ( sec_level != Qnil ) securelevel = FIX2INT( sec_level );
 
 	if ( inet_aton( RSTRING_PTR( rb_obj_as_string(ip) ), &in ) == 0 )
 		rb_raise( rb_eArgError, "Could not make sense of ip number: %s", ip );
@@ -226,16 +243,62 @@
 	if ( id == -1 ) rb_sys_fail( "jail" );
 	if ( chdir("/") == -1 ) rb_sys_fail( "chdir" );
 
+	if ( securelevel > -1 && securelevel < 4 ) {
+		debugMsg(( "Setting securelevel to: %d", securelevel ));
+		if ( sysctlbyname("kern.securelevel", NULL, 0, &securelevel, sizeof(securelevel)) )
+			rb_sys_fail( "securelevel" );
+	}
+
 	debugMsg(( "New jail created with id: %d\n", id ));
 	return INT2FIX( id );
 }
 
 
 /*
- * Iterate over the currently instantiated jails, returning a jail
- * object that matches the given string/ip/JID -- or nil if none do. 
- * Without an argument, return an array of all JIDs. 
- */ 
+ *  call-seq:
+ *     BSD::Jail.find_by_jid( jid ) => BSD::Jail
+ *
+ *  Iterate over the currently instantiated jails, returning a jail
+ *  object that matches the given +jid+, or nil if no jail is found.
+ *
+ */
+static VALUE
+rbjail_find_by_jid( VALUE self, VALUE jid )
+{
+	VALUE args[0];
+
+	if ( TYPE(jid) != T_FIXNUM )
+		rb_raise( rb_eTypeError, "invalid argument to find_by_jid(): %s",
+			RSTRING_PTR( rb_inspect(jid)) );
+
+	args[0] = jid;
+	return rbjail_find( 1, args, self );
+}
+
+
+/*
+ *  call-seq:
+ *     BSD::Jail.search( id )                      => BSD::Jail
+ *     BSD::Jail.search( hostname )                => [ BSD::Jail, ... ]
+ *     BSD::Jail.search( IPAddr.new('127.0.0.1') ) => [ BSD::Jail, ... ]
+ *     BSD::Jail.search( Pathname.new('/tmp') )    => [ BSD::Jail, ... ]
+ *     BSD::Jail.search                            => [ BSD::Jail, BSD::Jail, ... ]
+ *     BSD::Jail.search { |obj| block }            => obj
+ *
+ *  Iterate over the currently instantiated jails, returning a jail
+ *  object that matches the given argument. 
+ *
+ *  If the argument is an integer, it is assumed to be a JID.
+ *  Otherwise, it is converted to a string, and compared against
+ *  IP addresses, hostnames, and paths.  JIDs are unique, so there is
+ *  only one object that can match.  Only the matching object (or nil)
+ *  is returned in that event.  IPs, hostnames, and paths can be
+ *  shared between jails, so searching on those return an array populated
+ *  with matched jail objects -- or if there are no valid matches, an empty array.
+ *
+ *  Without an argument, return an array of all jails as objects.
+ *
+ */
 static VALUE
 rbjail_find( int argc, VALUE *argv, VALUE self )
 { 
@@ -262,8 +325,8 @@
 				jid = FIX2INT( arg );
 				break;
 
-				// find by IP/hostname
-				//
+			// find by IP/hostname/path
+			//
 			case T_OBJECT:
 			case T_DATA:
 			case T_STRING:
@@ -272,8 +335,8 @@
 				break;
 
 			default:
-				rb_raise( rb_eTypeError, "invalid argument to find(): %s",
-						RSTRING_PTR(rb_inspect(arg)) );
+				rb_raise( rb_eTypeError, "invalid argument: %s",
+						RSTRING_PTR( rb_inspect(arg)) );
 		}
 	}
 
@@ -302,10 +365,13 @@
 	if ( len < sizeof(*xp) || len % sizeof(*xp) || xp->pr_version != XPRISON_VERSION )
 		rb_fatal("Kernel and userland out of sync");
 
-	// No arguments to find() -- return an array of all JIDs
+	// No arguments to find() -- yield each successive jail,
+	// and return an array of all jail objects.
 	//
 	if ( argc == 0 ) {
 		for ( i = 0; i < len / sizeof(*xp); i++ ) {
+			rbjail = rbjail_alloc( self, xp );
+			if ( rb_block_given_p() ) rb_yield( rbjail );
 			rb_ary_push( jails, rbjail_alloc( self, xp ) );
 			xp++;
 		}
@@ -315,21 +381,32 @@
 	}
 
 	// Argument passed to find(): walk the jail list, comparing the arg
-	// with each current jail.  Return first match.
+	// with each current jail.  Return all matches.
 	//
 	for ( i = 0; i < len / sizeof(*xp); i++ ) {
 		in.s_addr = ntohl(xp->pr_ip);
 		if (( compare == 0 && xp->pr_id == jid ) ||
 				( compare == 1 &&
 				  (( strcmp( str, inet_ntoa(in) ) == 0 ) ||
-				   (  strcmp( str, xp->pr_host )  == 0 ))
+				   ( strcmp( str, xp->pr_host )   == 0 ) ||
+				   ( strcmp( str, xp->pr_path )   == 0 ))
 				)) {
 
 			debugMsg(( "Located jail: %d", xp->pr_id ));
-			rbjail = rbjail_alloc( self, xp );
 
-			free( sxp );
-			return rbjail;
+			// If the user already knows the JID, there can only be
+			// one match.  Return it immediately.
+			//
+			if ( compare == 0 ) {
+				free( sxp );
+				return rbjail_alloc( self, xp );
+			}
+
+			// If searching for anything other than JID, the argument
+			// isn't unique.  Put matching jails into an array.
+			//
+			rb_ary_push( jails, rbjail_alloc( self, xp ) );
+			xp++;
 		}
 		else {
 
@@ -338,102 +415,295 @@
 	}
 
 	free( sxp );
-	return Qnil;
-}
-
 
-static void
-rbjail_do_jail_attach( int jid )
-{
-	if ( jail_attach(jid) == -1 )
-		rb_sys_fail( "jail_attach" );
+	if ( compare == 0 && rb_ary_shift( jails ) == Qnil )
+		return Qnil;
+
+	return jails;
 }
 
 
-/* Mostly ripped off from Ruby's process.c */
+/*
+ *  call-seq:
+ *     BSD::Jail.attach( id )       => true
+ *     BSD::Jail.attach( hostname ) => true
+ *
+ *  Attach to a currently running jail instance directly.
+ *  Operates under the same rules as BSD::Jail#search -- you may
+ *  specify a jail by IP Address, hostname, or jid.
+ *
+ *  Please note that attaching your process into a jail is a one way
+ *  operation that requires root privileges.  You must fork() if
+ *  your process needs to continue in the host environment.
+ *
+ */
 static VALUE
-rbjail_attach_block( int jid )
+rbjail_class_attach( VALUE self, VALUE arg )
 {
-	int pid;
-
-	rb_secure(2);
-
-	fflush(stdout);
-	fflush(stderr);
+	int jid = 0;
+	VALUE jails;
+	VALUE find_args [0];
 
-	switch ( pid = fork() ) {
-		case 0:
-			rb_thread_atfork();
-			if ( rb_block_given_p() ) {
-				int status;
+	switch ( TYPE(arg) ) {
+
+		// The user knows the JID already, attach directly.
+		//
+		case T_FIXNUM:
+
+			jid = FIX2INT( arg );
+			rbjail_do_attach( jid );
+			break;
 
-				rbjail_do_jail_attach( jid );
-				rb_protect( rb_yield, Qundef, &status );
-				ruby_stop( status );
-			}
-			return Qnil;
+		// Find the JID to attach to.  First match wins.
+		//
+		case T_OBJECT:
+		case T_DATA:
+		case T_STRING:
 
-		case -1:
-			rb_sys_fail( "fork(2)" );
-			return Qnil;
+			find_args[0] = arg;
+			jails = rbjail_find( 1, find_args, self );
+			jid = FIX2INT( rbjail_get_jid( rb_ary_shift(jails) ));
+			rbjail_do_attach( jid );
+			break;
 
 		default:
-			return INT2FIX( pid );
+			rb_raise( rb_eTypeError, "invalid argument to attach(): %s",
+						RSTRING_PTR( rb_inspect(arg)) );
 	}
+
+	return Qtrue;
 }
 
+
+/* --------------------------------------------------------------
+ * Instance methods
+ * -------------------------------------------------------------- */
+
+/*
+ *  call-seq:
+ *     BSD::Jail <=> BSD::Jail
+ *
+ *  Interface for Comparable.
+ *
+ */
 static VALUE
-rbjail_attach( int argc, VALUE *argv, VALUE self )
+rbjail_compare( VALUE self, VALUE other )
 {
-	VALUE jidnum, rval;
-	int jid;
+	debugMsg(("self id: %s, other id: %s",
+				RSTRING_PTR(rb_inspect( self )),
+				RSTRING_PTR(rb_inspect( other ))));
 
-	rb_scan_args( argc, argv, "1", &jidnum );
-	jid = NUM2INT( jidnum );
+	return rb_funcall( 
+		rbjail_get_jid( self ),
+		rb_intern("<=>"),
+		1,
+		rbjail_get_jid( other )
+	);
+}
+
 
-	if ( rb_block_given_p() ) {
-		rval = rbjail_attach_block( jid );
-	}
+/*
+ * Fetch the configured IP address for the jail.
+ * Returns an IPAddr object.
+ *
+ */
+static VALUE
+rbjail_get_ip( VALUE self )
+{
+	struct xprison *xp = rbjail_get_jailptr( self );
+	struct in_addr in;
+	VALUE args [0];
 
-	else {
-		rbjail_do_jail_attach( jid );
-		rval = Qtrue;
-	}
+	in.s_addr = ntohl( xp->pr_ip );
+	args[0] = rb_str_new2( inet_ntoa(in) );
+
+	return rb_class_new_instance( 1, args, rbjail_cIPAddr );
+}
+
 
-	return rval;
+/*
+ * Fetch the assigned JID for the jail.
+ *
+ */
+static VALUE
+rbjail_get_jid( VALUE self )
+{
+	struct xprison *xp = rbjail_get_jailptr( self );
+	return INT2FIX( xp->pr_id );
 }
 
 
 /*
- * Ruby initializer.
+ * Fetch the configured hostname for the jail as a string.
+ *
+ */
+static VALUE
+rbjail_get_host( VALUE self )
+{
+	struct xprison *xp = rbjail_get_jailptr( self );
+	return rb_str_new2( xp->pr_host );
+}
+
+
+/*
+ * Fetch the configured path for the jail.
+ * Returns a Pathname object.
+ *
+ */
+static VALUE
+rbjail_get_path( VALUE self )
+{
+	struct xprison *xp = rbjail_get_jailptr( self );
+	VALUE args[0];
+
+	args[0] = rb_str_new2( xp->pr_path );
+
+	return rb_class_new_instance( 1, args, rbjail_cPathname );
+}
+
+
+/*
+ * Return a human readable version of the object.
+ */
+static VALUE
+rbjail_inspect( VALUE self )
+{
+	char inspect_str[BUFSIZ];
+
+	sprintf( inspect_str, "#<%s:0x%07x jid:%d (%s)>",
+		rb_obj_classname( self ),
+		NUM2UINT(rb_obj_id( self )) * 2,
+		FIX2INT(rbjail_get_jid( self )),
+		RSTRING_PTR(rbjail_get_host( self ))
+	);
+
+	return rb_str_new2( inspect_str );
+}
+
+
+/*
+ *  call-seq:
+ *     jail.attach
+ *     jail.attach do ... end  => child pid
+ *
+ *  Attach to the jail.
+ *
+ *  Please note that attaching your process into a jail is a one way
+ *  operation that requires root privileges.  You must fork() if
+ *  your process needs to continue in the host environment.
+ *
+ * 	In the block form, a fork() is performed automatically.
+ *
+ */
+static VALUE
+rbjail_instance_attach( VALUE self )
+{
+	int jid = FIX2INT( rbjail_get_jid( self ) );
+
+	if ( rb_block_given_p() ) {
+		return rbjail_attach_block( jid );
+	}
+
+	else {
+		rbjail_do_attach( jid );
+	}
+
+	return Qtrue;
+}
+
+
+/* --------------------------------------------------------------
+ * Initalizer
+ * -------------------------------------------------------------- */
+
+/*
+ *  BSD::Jail
+ *
+ *  Ruby bindings for the FreeBSD jail(2) subsystem.
+ *
+ *  Example usage:
+ *
+ *  	require 'bsd/jail'
+ *
+ *      # create a new jail
+ *      jid = BSD::Jail.create( '127.0.0.1', '/tmp', 'testjail' )
+ *
+ *      # find existing jail(s)
+ *      jail  = BSD::Jail.find_by_jid( jid )
+ *      jails = BSD::Jail.search( hostname )
+ *      jails = BSD::Jail.search( IPAddr.new('127.0.0.1') )
+ *      jails = BSD::Jail.search( Pathname.new('/tmp') )
+ *
+ *      # attach to jails
+ *      BSD::Jail.attach( jid )
+ *      jail.attach do
+ *     	    ... do something fancy!
+ *      end
+ *
+ *  BSD::Jail includes behaviors from Enumerable and Comparable,
+ *  so you can do things such as the following:
+ *
+ *      # Are these two instantiated jails the same jid?
+ *      BSD::Jail.search( '/tmp' ) == BSD::Jail.search( 'testjail' )
+ *      
+ *      # Alternative interface for finding a jail by jid
+ *      BSD::Jail.find { |j| j.jid == 3 }
+ *      
+ *      # Return all jails as an array
+ *      jails = BSD::Jail.find_all
+ *      
+ *      # What jails have IPs within the 192.168.16.0/24 class C netblock?
+ *      nb = IPAddr.new( '192.168.16.0/24' );
+ *      jails = BSD::Jail.find? { |j| nb.include?(j.ip) }
+ *
  */
 void
 Init_jail( void )
 {
+	// namespacing
+	//
 	rbjail_mBSD	= rb_define_module( "BSD" );
 	rbjail_cBSDJail = rb_define_class_under( rbjail_mBSD, "Jail", rb_cObject );
 
-	rb_require("ipaddr");
-	rbjail_cIPAddr = rb_const_get( rb_cObject, rb_intern("IPAddr") );
-
+	// struct wrapping function
 	rb_define_alloc_func( rbjail_cBSDJail, rbjail_s_alloc );
 
 	// Make the 'new' method private.
 	rb_funcall( rbjail_cBSDJail, rb_intern("private_class_method"), 1, ID2SYM(rb_intern("new")) );
 
-	// main utility functions
+	// class methods
+	//
+	rb_define_singleton_method( rbjail_cBSDJail, "attach", rbjail_class_attach, 1 );
+	rb_define_singleton_method( rbjail_cBSDJail, "create", rbjail_jail, -1 );
+	rb_define_singleton_method( rbjail_cBSDJail, "search", rbjail_find, -1 );
+	rb_define_singleton_method( rbjail_cBSDJail, "find_by_jid", rbjail_find_by_jid, 1 );
+
+	// instance methods
 	//
-	rb_define_singleton_method( rbjail_cBSDJail, "jail", rbjail_jail, -1 );
-	rb_define_singleton_method( rbjail_cBSDJail, "find", rbjail_find, -1 );
-	rb_define_method( rbjail_cBSDJail, "attach", rbjail_attach, -1 );
-	//	rb_define_alias( rbjail_cBSDJail, "list", "find" );
-	
-	// accessor functions
+	rb_define_method( rbjail_cBSDJail, "attach", rbjail_instance_attach, 0 );
+	rb_define_method( rbjail_cBSDJail, "host", rbjail_get_host, 0 );
+	rb_define_alias(  rbjail_cBSDJail, "hostname", "host" );
+	rb_define_method( rbjail_cBSDJail, "inspect", rbjail_inspect, 0 );
+	rb_define_method( rbjail_cBSDJail, "ip",   rbjail_get_ip,   0 );
+	rb_define_method( rbjail_cBSDJail, "jid",  rbjail_get_jid,  0 );
+	rb_define_method( rbjail_cBSDJail, "path", rbjail_get_path, 0 );
+
+	// Additional (base) modules for accessor wrapping
 	//
-	rb_define_method( rbjail_cBSDJail, "jid",  rbjail_get_jid,  0 );
-	rb_define_method( rbjail_cBSDJail, "ip",   rbjail_get_ip,   0 );
-	rb_define_method( rbjail_cBSDJail, "host", rbjail_get_host, 0 );
-	rb_define_method( rbjail_cBSDJail, "path", rbjail_get_path, 0 );
+	rb_require( "ipaddr" );
+	rb_require( "pathname" );
+	rbjail_cIPAddr   = rb_const_get( rb_cObject, rb_intern("IPAddr") );
+	rbjail_cPathname = rb_const_get( rb_cObject, rb_intern("Pathname") );
+
+	// Enumerable!
+	//
+	rb_define_singleton_method( rbjail_cBSDJail, "each", rbjail_find, -1 );
+	rb_extend_object( rbjail_cBSDJail, rb_const_get( rb_cObject, rb_intern("Enumerable") ));
+
+	// Comparable!
+	//
+	rb_define_method( rbjail_cBSDJail, "<=>", rbjail_compare, 1 );
+	rb_include_module( rbjail_cBSDJail, rb_const_get( rb_cObject, rb_intern("Comparable") ));
 }
 
 
--- a/ext/jail.h	Sat Feb 28 06:52:48 2009 +0000
+++ b/ext/jail.h	Tue Mar 03 22:23:45 2009 +0000
@@ -76,3 +76,47 @@
 
 #endif /* _BSDJAIL_H_ */
 
+
+/* --------------------------------------------------------------
+ * Predeclarations
+ * -------------------------------------------------------------- */
+
+// globals
+//
+VALUE rbjail_mBSD;
+VALUE rbjail_cBSDJail;
+VALUE rbjail_cIPAddr;
+VALUE rbjail_cPathname;
+
+// utility
+//
+static struct xprison * rbjail_check_jail( VALUE self );
+static struct xprison * rbjail_get_jailptr( VALUE self );
+static int rbjail_do_attach( int jid );
+
+// memory management
+//
+static VALUE rbjail_alloc( VALUE class, struct xprison *xp );
+static void rbjail_gc_free( struct xprison *ptr );
+static VALUE rbjail_s_alloc( VALUE class );
+
+// class
+//
+static VALUE rbjail_jail( int argc, VALUE *argv, VALUE self );
+static VALUE rbjail_find( int argc, VALUE *argv, VALUE self );
+static VALUE rbjail_find_by_jid( VALUE self, VALUE jid );
+static VALUE rbjail_class_attach( VALUE self, VALUE arg );
+static VALUE rbjail_compare( VALUE self, VALUE other );
+
+// instance
+//
+static VALUE rbjail_inspect( VALUE self );
+static VALUE rbjail_get_ip( VALUE self );
+static VALUE rbjail_get_jid( VALUE self );
+static VALUE rbjail_get_host( VALUE self );
+static VALUE rbjail_get_path( VALUE self );
+static VALUE rbjail_instance_attach( VALUE self );
+static VALUE rbjail_attach_block( int jid );
+
+void Init_jail( void );
+