About this list Date view Thread view Subject view Author view Attachment view

From: Sam Vilain (sam_at_vilain.net)
Date: Sun 17 Oct 2004 - 08:54:57 BST


Herbert,

Could you please add this (and the equivalent to ext3_fs.h,
 reiserfs_fs.h, etc) to the head branch? I'd like to keep using
my ioctl() based scripts, until vunify works as well as unify-dirs,
and there is a vserver-build.immucp

--- ext2_fs.h.orig 2004-10-17 20:29:10.000000000 +1300
+++ ext2_fs.h 2004-10-17 20:26:47.000000000 +1300
@@ -196,8 +196,13 @@
 #define EXT2_IUNLINK_FL 0x08000000 /* Immutable
unlink */
 #define EXT2_RESERVED_FL 0x80000000 /* reserved for ext2
lib */
 
+#ifdef CONFIG_VSERVER_LEGACY
+#define EXT2_FL_USER_VISIBLE 0x0C03DFFF /* User visible flags */
+#define EXT2_FL_USER_MODIFIABLE 0x0C0380FF /* User
modifiable flags */
+#else
 #define EXT2_FL_USER_VISIBLE 0x0003DFFF /* User visible flags */
 #define EXT2_FL_USER_MODIFIABLE 0x000380FF /* User
modifiable flags */
+#endif
 
 /*
  * ioctl commands

I've attached the immucp script that I find smashing
for building new vservers in seconds. Shortly (once I get
the vs1.9.3-rcX server I'm building up "the way I like it" ;-)),
 it will be online.

-- 
Sam Vilain, sam /\T vilain |><>T net, PGP key ID: 0x05B52F13
(include my PGP key ID in personal replies to avoid spam filtering)

#!/usr/bin/perl -w

use strict;
use Fcntl ':mode';
use Carp qw(verbose);

BEGIN {

    use vars qw(@POD_HOOKS $be_random $do_suid);
    no strict 'refs';

=head1 NAME

immucp - Duplicate structures, with immutability/immulink support

=cut

    push @POD_HOOKS, NAME => sub {
        my @m;
        ( @m = m/(\S+) - (.*)/ ) &&
            do { *{PROGNAME} = sub { $m[0] };
                 *{SHORT_DESC} = sub { $m[1] }; }
        };

=head1 SYNOPSIS

immucp [options] dir1 dir2

=cut

    push @POD_HOOKS, SYNOPSIS => sub {
        my $a = $_;
        *{SYNOPSIS} = sub { $a }
    };

=head1 DESCRIPTION

immucp will copy the first location to the second location, but will
only ever make links, rather than copy.

It is very similar to using `cp -al', but has support for setting
Linux inode attributes along the way.

=cut

    push @POD_HOOKS, DESCRIPTION => sub {
        my $a = $_;
        *{DESCRIPTION} = sub { $a }
    };

=head1 COMMAND LINE OPTIONS

The following command line options are available:

=cut

    # Extract the command line options for the "usage" screen from the
    # POD ;-)
    use vars qw(@options);

    push @POD_HOOKS, 'COMMAND LINE OPTIONS' => sub {
        # This hook is deleted below under RELEASE
        &Pod::Constants::add_hook
                (#-debug => 1,
                 '*item' => sub {
                     my ($switches, $description) =
                         m/^(.*?)\n\n(.*)/s;
                     my (@switches, $longest);
                     $longest = "";
                     for my $switch
                         ($switches =~ m/\G
                                         ((?:-\w|--\w+))
                                         (?:,\s*)?
                                         /gx) {
                             push @switches, $switch;
                             if ( length $switch > length $longest) {
                                 $longest = $switch;
                             }
                         }
                     $longest =~ s/^-*//;
                     push @options,
                         $longest, {
                                    options => \@switches,
                                    description => $description,
                                   };
                 }
                );
    };

=over 4

=item -h, --help

Display program usage

=item -v, --verbose

Verbose program execution

=item -d, --debug

Even more verbose program execution

=item -V, --version

Print the program version

=item -i, --immutable

Sets the "immutable" inode attribute.

=item -l, --linkage

Sets the "immutable linkage invert" inode attribute.

=item -S, --suid

Link files that have set user/group ID bits set

=item -r, --random

Turns on randomising of directory scanning and tree traversal. This
option tries to prevent against racing symlink attacks. A better
solution is planned.

=back

=head1 INODE ATTRIBUTES AND IMMUTABILITY

Hard linking identical files between directories has a drawback: if
one is changed, then the other one changes too.

To counter this, you can set the "immutable" inode attribute on
combined files (see L<chattr>).

Setting inode attribute requires root privileges, C<CAP_SYS_ATTR>, and
a filesystem that supports it. Currently this includes default ext2
and ext3 in any recent kernel, or reiserfs with the "inode attributes"
patch applied (available from
C<ftp://ftp.namesys.com/pub/reiserfs-for-2.4/2.4.18.pending/>).

The problem with setting "immutable" is that then the file can not be
unlinked or renamed. In the case where you have a user without
CAP_SYS_ATTR, but otherwise with write permission to a file, they
cannot then change it.

In comes the "immutable linkage invert" flag. This flag will toggle
immutability of the file E<name>, but leave the file E<contents>,
E<permissions>, etc protected. This means that you can unlink the
file, and hence replace it, edit it with most editors, etc.

This option requires a kernel patch - it is included in the s_context
patch for the vserver project, which is at
L<http://www.solucorp.qc.ca/miscprj/s_context.hc>). It is also
available on its own from L<http://sam.vilain.net/immutable/>.

This works well with ext2 and ext3, but is a little trickier to get
working with reiserfs, as inode attributes are not a standard reiserfs
feature. See the above link for more information.

=head1 RELEASE

This is immulink version 0.2.

=cut

    push @POD_HOOKS, RELEASE => sub {
        Pod::Constants::delete_hook("*item");
        my $v;
        (($v) = m/(\d+\.\d+)/) && (*{VERSION} = sub {$v});
    };

};

no strict 'subs';

sub abort {
    print STDERR &PROGNAME.": aborting: @_\n", short_usage();
    exit(1);
}

sub barf { print STDERR &PROGNAME.": @_\n"; exit(1) }
sub moan { print STDERR &PROGNAME.": WARNING: @_\n"; }
sub say { print &PROGNAME.": @_\n"; }
sub mutter { }
sub whisper { }

use strict 'subs';

#=====================================================================
# MAIN SECTION STARTS HERE
#=====================================================================
my ($action, @dirs, $immutable, $linkage, $mode);

{
    use Getopt::Long;

    no strict "refs", 'vars';
    local ($^W) = 0;

    $action = "unify";
    $mode = "";

    Getopt::Long::config("bundling");
    #Getopt::Long::config("pass_through");
    Getopt::Long::GetOptions
            (
             'help|h' => sub { $action = "show_help" },
             'version|V' => sub { $action = "show_version" },
             'verbose|v' => sub { *{"mutter"} = \&say },
             'debug|d' => sub { *{"whisper"} = *{"mutter"} = \&say },
             'immutable|i' => \$immutable,
             'linkage|l' => \$linkage,
             'random|r' => sub { $be_random = 1 },
             'suid|S' => sub { $do_suid = 1 },
            );

    $mode .= "i" if $immutable;
    $mode .= "I" if $linkage;

    if ( ! -t STDOUT ) {
        if ( -t STDERR ) {
            eval "sub say { print STDERR \"\@_\n\" }";
        } else {
            eval "sub say { }";
        }
    }

    if ($action eq "show_help") {
        print usage();
        exit(0);
    } elsif ($action eq "show_version") {
        print version(), "\n";
        exit(0);
    }

}

my $source = shift @ARGV
    or abort "no source directory given";
my $dest = shift @ARGV
    or abort "no destination directory given";

@ARGV && abort "extra unparsed arguments: @ARGV";

copy_tree($source, $dest);

say "done";
exit(0);

=head1 INTERNAL FUNCTIONS

Documented for prosperity

=head2 chattr ($filename, $attr_string)

Calls the C<EXT2_IOC_SETFLAGS> IOCTL on $filename, setting flags as
per $attr_string.

=over

=item Permitted chattr attributes

The following table lists the allowed contents of $attr_string, and
the corresponding bitmask to the IOCTL.

  i => 0x00000010 ("immutable")
  I => 0x00008000 ("immutable linkage invert")

=back

=cut

BEGIN { use vars qw(%attr);
        push @POD_HOOKS,
            "Permitted chattr attributes" => sub {
                %attr = map {
                    if ( m/(\w) => (0x\w+)/) {
                        $1 => pack("L", hex($2));
                    } else { () }
                } split /\r?\n/, $_;
            }; }

use vars qw($using_system);

sub chattr {
    my( $file, $attr_str ) = @_;
    whisper ("chattr($file, $attr_str)");
    my $EXT2_IOC_SETFLAGS = 0x40046602;
    my $flags = pack("L", 0);
    {
my $x = $attr_str;
    while( my $flag = chop($attr_str) ){
        $flags |= $attr{$flag};
    }
    $attr_str=$x;
    }
    open( F, $file ) or die "Can't open $file : $!";
    ioctl( F, $EXT2_IOC_SETFLAGS, $flags ) or do {
            moan "can't set attr(".sprintf("%x",unpack("I",$flags)).") on $file: $!; using system('setattr')" unless $using_system++;

        system("/usr/lib/util-vserver/setattr", ($attr_str =~ /i/ ? "--immutable" : ()), ($attr_str =~ /I/ ? "--immulink" : () ), $file) == 0
                or die "setattr failed (rc=$?)";
    };
    close F;
}

=head2 copy_tree($source, $dest)

=cut

use File::Find;
use Cwd qw(getcwd);

sub copy_tree {

    my $source = shift;
    my $dest = shift;

    barf "$source is not readable"
        unless ( -d $source and -r _ );

    ( -d $dest ) or do {
            mkdir $dest or barf "failed to make $dest; $!";
    };

    if (my $pid = open IPC, "-|") {
        
        local($/)="\0";
        chdir($dest);

        $dest = getcwd;
        chop($source = <IPC>);
        defined($source) or barf "child process failed";

        my @utimes;

        # parent, this one does the linking
        while (<IPC>) {
            chop;
            my (@stat) = split ":", $_;
            my $fn;
            chop($fn = <IPC>);
            if (!defined($stat[2])) {
                    moan "file `$fn' in source failed stat? (@stat)";
                next;
            }

            if (S_ISDIR($stat[2])) {
                    if ( ! -d "$dest/$fn" ) {
                    mutter "making dir $dest/$fn";
                    mkdir("$dest/$fn" )
                            or barf "failed to make $dest/$fn; $!";
                    chown(@stat[4,5], "$dest/$fn")
                            or barf "failed to change ownership on $dest/$fn; $!";
                    chmod(S_IMODE($stat[2]), "$dest/$fn")
                            or barf "failed to change mode on $dest/$fn; $!";

                    # directory times must be set later
                    push @utimes, @stat[8,9], $fn;
                }
                chdir("$dest/$fn") or barf "failed to chdir '$dest/$fn'; $!";
            } elsif (S_ISREG($stat[2])) {
                my $src = "$source/$fn";
                    $fn =~ s{.*/}{};
                    if ( ! -e "$fn" ) {
                    mutter "making $fn";
                    chattr($src, $mode) if ( $mode and $mode eq "iI" );
                    chattr($src, "") if ( $mode and $mode =~ /^[iI]$/ );

                    link $src, "$fn" or do {
                        die "link ($src,${dest}unify$$) failed; $!";
                        next;
                    };

                    # don't change times unnecessarily
                    utime(@stat[8,9], $fn);

                    chattr($src, $mode) if ( $mode and $mode =~ /^[iI]$/ );
                }
            } elsif (S_ISLNK($stat[2])) {
                my $target = pop @stat;
                    $fn =~ s{.*/}{};
                ( -l $fn ) && next;
                mutter "$fn -> $target";
                symlink($target, $fn)
                    or barf "symlink($target, $fn) failed; $!";
            } else {
                    mutter "ignoring non-regular file $fn";
            }
        }

    } elsif (!defined $pid) {
        barf "fork() failed; $!";
    } else {

        chdir($source)
            or barf "failed to chdir to $source; $!";

        $\ = "\0";
        $, = ":";
        print getcwd;

        # child, this one does the finding
        my $name;
        find(sub {
                if ( -l ) {
                    print lstat _, readlink;
                } else {
                    print stat _;
                }
                ($name = $File::Find::name) =~ s{^\.}{};
                print $name;
             },
             ".");

        exit(0);
    }

}

sub no_dups {
    return keys %{{ map { $_ => 1 } @_ }}
}

BEGIN {

    eval "use Pod::Constants -trim => 1, \@POD_HOOKS";
    die $@ if $@;

}

#---------------------------------------------------------------------
# Usage functions
#---------------------------------------------------------------------

sub short_usage {
    return ("Usage: ${\(SYNOPSIS)}\n"
            ."Try `${\(PROGNAME)} --help' for a summary of options."
            ."\n");
}

use Text::Wrap qw(wrap fill);
use Term::ReadKey;

=head2 usage

Prints the program usage (extracted from the POD).

=cut

sub usage {

    # alright, I'm admit this function is silly.

    my $options_string;
    my $OPTIONS_INDENT = 2;
    my $OPTIONS_WIDTH = 20;
    my $OPTIONS_GAP = 2;
    my $TOTAL_WIDTH = (GetTerminalSize())[0] - 10 || 70;

    my $DESCRIPTION_WIDTH = ($TOTAL_WIDTH - $OPTIONS_GAP -
                             $OPTIONS_INDENT - $OPTIONS_WIDTH);

    # go through each option, and format it for the screen

    for ( my $i = 0; $i < (@options>>1); $i ++ ) {
        my $option = $options[$i*2 + 1];

        $Text::Wrap::huge = "overflow";
        $Text::Wrap::columns = $OPTIONS_WIDTH;
        my @lhs = map { split /\n/ }
            wrap("","",join ", ",
                 sort { length $a <=> length $b }
                 @{$option->{options}});

        $Text::Wrap::huge = "wrap";
        $Text::Wrap::columns = $DESCRIPTION_WIDTH;
        my @rhs = map { split /\n/ }
            fill("","",$option->{description});

        while ( @lhs or @rhs ) {
            my $left = shift @lhs;
            my $right = shift @rhs;
            chomp($left);
            $options_string .= join
                ("",
                 " " x $OPTIONS_INDENT,
                 $left . (" " x ($OPTIONS_WIDTH - length $left)),
                 " " x $OPTIONS_GAP,
                 $right,
                 "\n");
        }
    }

    $Text::Wrap::huge = "overflow";
    $Text::Wrap::columns = $TOTAL_WIDTH;

    return (fill("","",PROGNAME . " - " . SHORT_DESC)
            ."\n\n"
            ."Usage: ".SYNOPSIS."\n\n"
            .fill(" ","",DESCRIPTION)."\n\n"
            .fill(" ","","Command line options:")
            ."\n\n"
            .$options_string."\n"
            ."See `perldoc $0' for more information.\n");
}

__END__

=head1 BUGS/TODO

Some suggestions for use as a security tool:

 I came up with these extra options for unify-dirs:
 -s --save <filename>
 save list of files in /etc/vserver/unified.vservername if no <filename>
 is given.
 -c --compare compare <filename>
 compare current link status with saved list (see -s :-)
 -t --test
 don't actually do any linking. just print out list of files that could
 be unified. together with -s option one could merely create an initial
 list, without doing anything else..

=head1 AUTHOR

Sam Vilain, <sam_at_vilain.net>

=cut

_______________________________________________
Vserver mailing list
Vserver_at_list.linux-vserver.org
http://list.linux-vserver.org/mailman/listinfo/vserver


About this list Date view Thread view Subject view Author view Attachment view
[Next/Previous Months] [Main vserver Project Homepage] [Howto Subscribe/Unsubscribe] [Paul Sladen's vserver stuff]
Generated on Sun 17 Oct 2004 - 08:55:28 BST by hypermail 2.1.3