--- 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") ));
}