* Merge of the mahlon experimental branch.
/*
* 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 );
}