use strict;
use autodie;
+use lib 'lib';
+use base qw(CloudStore::Gearman CloudStore::MD5sum);
+
use File::Path qw(make_path remove_tree);
use File::Find;
use Data::Dump qw(dump);
+use Carp qw(confess cluck);
sub new {
- my $class = shift;
- my $self = {@_};
- bless $self, $class;
+ my ($class,$slice) = @_;
+
+ cluck "DEPRICIATED $slice specified" if $slice;
- $self->{passwd} ||= '/var/lib/extrausers/passwd';
- $self->{PORT} ||= $ENV{PORT} || die "no PORT in env";
- $self->{SLICE} ||= $ENV{SLICE} || die "no SLICE in env";
+ my $self = {
+ passwd => '/var/lib/extrausers/passwd',
+ };
+ bless $self, $class;
- $self->{md5} = $self->user_info('md5');
+ $self->{md5} = $self->user_info("md5") || die "can't find user md5";
+=for removed
+ $self->{md5}->{dir} = "$dir/md5";
+ if ( ! -e $self->{md5}->{dir} ) {
+ make_path $self->{md5}->{dir}, { uid => $self->{md5}->{uid}, gid => $self->{md5}->{gid} };
+ warn "## CREATED md5pool $self->{md5}->{dir}\n";
+ }
+=cut
return $self;
}
+sub slice_dir_port {
+ my ($self,$slice) = @_;
+ my ( undef, $dir, $port, undef ) = getgrnam($slice);
+ die "getgrnam $slice: $!" if $!;
+ warn "# slice_dir_port $slice = $dir $port\n";
+ return ( $dir, $port );
+}
+
+sub dir2gearman {
+ my $self = shift;
+ my $dir = shift;
+ $dir =~ s/\W+/_/g;
+ $dir =~ s/^_+//;
+ $dir =~ s{_\d+$}{};
+ return join('_', $dir, @_);
+}
+
sub user_info {
my ($self,$login) = @_;
+ confess "need login" unless $login;
+
my @n = qw/ login passwd uid gid quota comment gecos dir shell expire /;
my @p = $login =~ m/^\d+$/ ? getpwuid $login : getpwnam $login;
+ die "user_info $login: $@" if $@;
my $user;
$user->{$_} = shift @p foreach @n;
return $user;
while(<$fh>) {
my ( $login, $passwd, $uid, $gid, $email, $dir, $shell ) = split(/:/,$_);
$max_uid = $uid if $uid > $max_uid;
- $found = $uid if $email eq $new_email;
+ $found = $login if $email eq $new_email;
}
close($fh);
+ my $slice = $ENV{SLICE} || 's1';
+ $slice =~ s{/.+/(\w+)$}{$1};
+ my ( $dir, $port ) = $self->slice_dir_port( $slice );
+
+ $dir ||= $ENV{SLICE};
+ $port ||= 6501;
+
if ( ! $found ) {
$max_uid++;
- my $dir = "$self->{SLICE}/$max_uid";
- warn "# create_user $new_email $new_quota = $max_uid $dir";
+ $dir .= "/$max_uid";
+ warn "# create_user $slice $new_email $new_quota = $max_uid $dir";
open(my $fh, '>>', $self->{passwd});
- print $fh "u$max_uid:$new_passwd:$max_uid:$self->{PORT}:$new_email:$dir:/bin/true\n";
+ print $fh "u$max_uid:$new_passwd:$max_uid:$port:$new_email:$dir:/bin/true\n";
close($fh);
- $found = $max_uid;
+ $found = "u$max_uid";
mkdir $dir;
- chown $max_uid, $self->{PORT}, $dir;
+ chown $max_uid, $port, $dir;
+
+ my $path = "$dir/.meta/secrets";
+ $self->mkbasepath($path);
+ open($fh, '>', $path);
+ print $fh "u$max_uid:$new_passwd\n";
+ close $fh;
}
+ # FIXME update quota only on create?
+ $self->gearman_do( $self->dir2gearman( $dir, 'quota', 'set' ) => "$found $new_quota" );
+
return $found;
}
sub mkbasepath {
- my ($path,$opts) = @_;
+ my ($self,$path,$opts) = @_;
+ cluck "ERROR: mkbasepath called without opts, so user is root!" unless $opts;
+ if ( $ENV{DEBUG} ) {
+ warn "# mkbasepath $path ",dump($opts);
+ $opts->{verbose} ||= 1;
+ }
$path =~ s{/[^/]+$}{};
- make_path $path unless -d $path;
+ if ( ! -d $path ) {
+ make_path $path, $opts;
+ }
}
-sub append {
- my $self = shift @_;
- my $user = shift @_;
- my $path;
+sub user_dir {
+ my ( $self, $user, $dir ) = @_;
$user = $self->user_info($user) unless ref $user eq 'HASH';
+ my $path;
if ( exists $user->{dir} ) {
- $path = $user->{dir} . '/.log';
+ $path = $user->{dir} . '/.meta/' . $dir;
} else {
die "no dir in ", dump $user;
}
- my $line = join('#',@_);
+ $path =~ s{//+}{/}g;
+
+ if ( ! -e $path ) {
+ $self->mkbasepath( $path, { uid => $user->{uid} } );
+ open(my $fh, '>', $path);
+ close $fh;
+ chown $user->{uid}, $user->{gid}, $path;
+ warn "# user_dir created $path\n";
+ }
+
+ #warn "### user_dir $path";
+ return $path;
+}
+
+sub append {
+ my $self = shift @_;
+ $self->append_meta( 'usage', @_ );
+}
+
+sub append_meta {
+ my $self = shift @_;
+ my $log = shift @_;
+ my $user = shift @_;
+ my $path = $self->user_dir( $user => $log );
+ my $delimiter = '#';
+ $delimiter = ' ' if $log =~ m/md5sum$/;
+ my $line = join($delimiter,@_);
open(my $fh, '>>', $path);
print $fh "$line\n";
close $fh;
warn "## $path $line\n";
}
+sub usage {
+ my ( $self, $user ) = @_;
+ $user = $self->user_info($user) unless ref $user eq 'HASH';
+
+ my $usage_path = $user->{dir} . '/.meta/files.usage';
+ $self->mkbasepath( $usage_path, { uid => $user->{uid} } );
+ if ( ! -e $usage_path ) {
+ warn "# usage $usage_path missing";
+ $self->list_files($user);
+ }
+
+ open(my $fh, '<', $usage_path);
+ my $size = <$fh>;
+ chomp $size;
+
+ warn "# usage $user->{login} $size bytes\n";
+ return $size;
+
+=for slow and broken
+
+ my $path = $self->user_dir( $user => 'usage');
+ my $sum;
+ open(my $fh, '<', $path);
+ while(<$fh>) {
+ chomp;
+ my @v = split(/#/,$_);
+ $sum->{ $v[0] } += $v[1];
+ $sum->{_usage} += $v[1];
+ }
+ my ( $usage, $quota ) = split(/ /,
+ $self->gearman_do( $self->dir2gearman( $user->{dir}, 'quota', 'get' ) => $user->{uid} )
+ );
+ $sum->{_usage} += $usage;
+ $sum->{_quota} = $quota;
+ warn "## usage ",dump($user, $sum), $/;
+ return $sum;
+
+=cut
+
+}
+
sub send_file {
my ( $self, $f_uid,$f_path, $t_uid,$t_path ) = @_;
my $f = $self->user_info($f_uid);
+ die "FIXME not on current slice" if $f->{dir} !~ m/^$ENV{SLICE}/;
my $t = $self->user_info($t_uid);
my $f_full = "$f->{dir}/$f_path";
my $t_full = "$t->{dir}/$t_path";
- mkbasepath $t_full, { uid => $t->{uid} };
+ $self->mkbasepath( $t_full, { uid => $t->{uid} } );
my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks) = stat $f_full;
if ( $uid == $f->{uid} ) {
warn "# send_file - move $f_uid $f_path to pool\n";
chown $self->{md5}->{uid}, $self->{md5}->{gid}, $f_full;
chmod oct("0444"), $f_full;
+ $self->append( $f, 'sent', -s $f_full, $t->{uid}, $f_path );
} elsif ( $uid == $self->{md5}->{uid} ) {
warn "# send_file - shared $f_full\n";
}
$self->delete( $t, $t_path ) if -e $t_full;
- $self->append( $f, 'sent', -s $f_full, $t->{uid}, $f_path );
- link $f_full, $t_full;
- $self->append( $t, 'recv', -s $t_full, $f->{uid}, $t_path );
+ my $size = -s $f_full;
+ my $md5;
+
+ my $ok;
+ {
+ no autodie qw(link);
+ $ok = link $f_full, $t_full
+ };
+ if ( ! $ok || $! =~ m/cross-link/ ) {
+ $ok = symlink $f_full, $t_full;
+ } else {
+ $size = -s $t_full;
+
+ if ( $f->{uid} == $self->{md5}->{uid} ) {
+ $md5 = $f_path; # we don't have local md5sum db for md5 user!
+ } else {
+ $md5 = $self->md5_get($f_full);
+ }
+
+ }
+
+ if ( $ok ) {
+ $self->append( $t, 'recv', $size, $f->{uid}, $t_path );
+ $self->append_meta('md5sum', $t, $md5 => $t_path ) if $md5; # md5sum for received files! FIXME -- cross-slice md5
+ $self->refresh_file_list( $t );
+ } else {
+ warn "ERROR: send_file $f_full -> $t_full: $!";
+ }
+
+ return $size;
}
sub rename_file {
my ( $self, $user, $from, $to ) = @_;
$user = $self->user_info($user) unless ref $user eq 'HASH';
- $self->append( $user, 'rename', $from, $to );
+ my $f_full = "$user->{dir}/$from";
+ my $t_full = "$user->{dir}/$to";
+
+ $self->mkbasepath( $t_full, { uid => $user->{uid}, gid => $user->{gid} } );
+ my $ok = rename $f_full, $t_full;
+
+ $self->refresh_file_list( $user );
+
+ my $md5 = $self->md5_get($t_full);
+ if ( ! $md5 ) {
+ warn "ERROR: no md5sum for $from";
+ return $ok; # XXX our internal error
+ }
+
+ $self->append_meta('md5sum', $user, 'rename' => $from );
+ $self->append_meta('md5sum', $user, $md5 => $from );
+
+ return $ok;
}
warn "delete $deleted_size bytes shared\n";
- $self->append( $user, 'delete', -$deleted_size, $path );
+ $self->append( $user, 'delete', -$deleted_size, $user->{uid}, $path );
+ $self->append_meta('md5sum', $user, 'delete', $path );
+
+ $self->refresh_file_list( $user );
+
+ return $full_path;
}
-sub usage {
- my ( $uid ) = @_;
+sub file_size {
+ my ( $self, $user, $path ) = @_;
+ $user = $self->user_info($user) unless ref $user eq 'HASH';
+
+ my $full_path = "$user->{dir}/$path";
+ my $size = -s $full_path;
+ warn "# file_size $full_path = $size bytes\n";
+ return $size;
+}
+
+sub list_files {
+ my ( $self, $user, $path ) = @_;
+
+ $user =~ s{/+$}{} && warn "cleanup list_files arg [$user]";
+
+ $user = $self->user_info($user) unless ref $user eq 'HASH';
+
+ die "no dir for ",dump($user) unless exists $user->{dir};
+
+ my $files = $user->{dir} . '/.meta/files';
+ $self->mkbasepath( $files, { uid => $user->{uid} } );
+ if ( -e $files && -s $files > 0 && -e "$files.usage") {
+ local $/ = undef;
+ open(my $fh, '<', $files);
+ my $list = <$fh>;
+ close($fh);
+ warn "# list_files $user->{login} from cache ", length($list), " bytes\n";
+ return $list;
+ }
+
+ my $dir = $user->{dir};
+ open(my $pipe, '-|', qq|find -L $dir -printf "%y %s %p\n"|);
+ open(my $fh, '>', "$files.new");
+ my $total_usage = 0;
+ my $list_txt;
+ while(<$pipe>) {
+ chomp;
+ my ( $type, $size, $name ) = split(/\s/, $_, 3);
+ $name =~ s{$dir}{./};
+ $name =~ s{//+}{/}g;
+ my $line = "$type $size $name\n";
+ print $fh $line;
+ $list_txt .= $line;
+ $total_usage += $size;
+ }
+ close($pipe);
+ close($fh);
+ rename "$files.new", $files;
+
+ open(my $usage, '>', "$files.usage.new");
+ print $usage $total_usage;
+ close($usage);
+ rename "$files.usage.new", "$files.usage";
+
+ warn "# list_files $dir usage: $total_usage\n";
+
+ return $list_txt;
+}
+
+sub refresh_file_list {
+ my ( $self, $user ) = @_;
+ $user = $self->user_info($user) unless ref $user eq 'HASH';
+ my $full_path = "$user->{dir}/.meta/files";
+ if ( -e $full_path ) {
+ warn "## refresh_file_list $full_path";
+ unlink $full_path || warn "unlink $full_path: $!";
+ } else {
+ warn "## refresh_file_list $full_path missing";
+ }
+ unlink "$full_path.usage" if -e "$full_path.usage";
}
1;