1 package CloudStore::API;
7 use base qw(CloudStore::Gearman CloudStore::MD5sum);
9 use File::Path qw(make_path remove_tree);
11 use Data::Dump qw(dump);
12 use Carp qw(confess cluck);
15 my ($class,$slice) = @_;
17 warn "## DEPRICIATED $slice specified" if $slice;
20 passwd => '/var/lib/extrausers/passwd',
24 $self->{md5} = $self->user_info("md5") || die "can't find user md5";
26 $self->{md5}->{dir} = "$dir/md5";
27 if ( ! -e $self->{md5}->{dir} ) {
28 make_path $self->{md5}->{dir}, { uid => $self->{md5}->{uid}, gid => $self->{md5}->{gid} };
29 warn "## CREATED md5pool $self->{md5}->{dir}\n";
37 my ($self,$slice) = @_;
38 my ( undef, $dir, $port, undef ) = getgrnam($slice);
39 die "getgrnam $slice: $!" if $!;
40 warn "# slice_dir_port $slice = $dir $port\n";
41 return ( $dir, $port );
50 return join('_', $dir, @_);
54 my ($self,$login) = @_;
56 confess "need login" unless $login;
58 my @n = qw/ login passwd uid gid quota comment gecos dir shell expire /;
59 my @p = $login =~ m/^\d+$/ ? getpwuid $login : getpwnam $login;
60 die "user_info $login: $@" if $@;
62 $user->{$_} = shift @p foreach @n;
67 my ( $self, $new_email, $new_passwd, $new_quota ) = @_;
72 open(my $fh, '<', $self->{passwd});
74 my ( $login, $passwd, $uid, $gid, $email, $dir, $shell ) = split(/:/,$_);
75 $max_uid = $uid if $uid > $max_uid;
76 $found = $login if $email eq $new_email;
80 my $slice = $ENV{SLICE} || 's1';
81 $slice =~ s{/.+/(\w+)$}{$1};
82 my ( $dir, $port ) = $self->slice_dir_port( $slice );
90 warn "# create_user $slice $new_email $new_quota = $max_uid $dir";
91 open(my $fh, '>>', $self->{passwd});
92 print $fh "u$max_uid:$new_passwd:$max_uid:$port:$new_email:$dir:/bin/true\n";
97 chown $max_uid, $port, $dir;
99 my $path = "$dir/.meta/secrets";
100 $self->mkbasepath($path);
101 open($fh, '>', $path);
102 print $fh "u$max_uid:$new_passwd\n";
106 # FIXME update quota only on create?
107 $self->gearman_do( $self->dir2gearman( $dir, 'quota', 'set' ) => "$found $new_quota" );
113 my ($self,$path,$opts) = @_;
114 cluck "ERROR: mkbasepath called without opts, so user is root!" unless $opts;
116 warn "# mkbasepath $path ",dump($opts);
117 $opts->{verbose} ||= 1;
119 $path =~ s{/[^/]+$}{};
121 make_path $path, $opts;
126 my ( $self, $user, $dir ) = @_;
127 $user = $self->user_info($user) unless ref $user eq 'HASH';
129 if ( exists $user->{dir} ) {
130 $path = $user->{dir} . '/.meta/' . $dir;
132 die "no dir in ", dump $user;
137 $self->mkbasepath( $path, { uid => $user->{uid} } );
138 open(my $fh, '>', $path);
140 chown $user->{uid}, $user->{gid}, $path;
141 warn "# user_dir created $path\n";
144 #warn "### user_dir $path";
150 $self->append_meta( 'usage', @_ );
157 my $path = $self->user_dir( $user => $log );
159 $delimiter = ' ' if $log =~ m/md5sum$/;
160 my $line = join($delimiter,@_);
161 open(my $fh, '>>', $path);
164 warn "## $path $line\n";
168 my ( $self, $user ) = @_;
169 $user = $self->user_info($user) unless ref $user eq 'HASH';
171 my $usage_path = $user->{dir} . '/.meta/files.usage';
172 $self->mkbasepath( $usage_path, { uid => $user->{uid} } );
173 if ( ! -e $usage_path ) {
174 warn "# usage $usage_path missing";
175 $self->list_files($user);
178 open(my $fh, '<', $usage_path);
182 warn "# usage $user->{login} $size bytes\n";
187 my $path = $self->user_dir( $user => 'usage');
189 open(my $fh, '<', $path);
192 my @v = split(/#/,$_);
193 $sum->{ $v[0] } += $v[1];
194 $sum->{_usage} += $v[1];
196 my ( $usage, $quota ) = split(/ /,
197 $self->gearman_do( $self->dir2gearman( $user->{dir}, 'quota', 'get' ) => $user->{uid} )
199 $sum->{_usage} += $usage;
200 $sum->{_quota} = $quota;
201 warn "## usage ",dump($user, $sum), $/;
209 my ( $self, $f_uid,$f_path, $t_uid,$t_path ) = @_;
211 my $f = $self->user_info($f_uid);
212 die "FIXME not on current slice" if $f->{dir} !~ m/^$ENV{SLICE}/;
213 my $t = $self->user_info($t_uid);
215 my $f_full = "$f->{dir}/$f_path";
216 my $t_full = "$t->{dir}/$t_path";
218 $self->mkbasepath( $t_full, { uid => $t->{uid} } );
220 my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks) = stat $f_full;
221 if ( $uid == $f->{uid} ) {
222 warn "# send_file - move $f_uid $f_path to pool\n";
223 chown $self->{md5}->{uid}, $self->{md5}->{gid}, $f_full;
224 chmod oct("0444"), $f_full;
225 $self->append( $f, 'sent', -s $f_full, $t->{uid}, $f_path );
226 } elsif ( $uid == $self->{md5}->{uid} ) {
227 warn "# send_file - shared $f_full\n";
230 $self->delete( $t, $t_path ) if -e $t_full;
232 my $size = -s $f_full;
238 $ok = link $f_full, $t_full
240 if ( ! $ok || $! =~ m/cross-link/ ) {
241 $ok = symlink $f_full, $t_full;
245 if ( $f->{uid} == $self->{md5}->{uid} ) {
246 $md5 = $f_path; # we don't have local md5sum db for md5 user!
248 $md5 = $self->md5_get($f_full);
254 $self->append( $t, 'recv', $size, $f->{uid}, $t_path );
255 $self->append_meta('md5sum', $t, $md5 => $t_path ) if $md5; # md5sum for received files! FIXME -- cross-slice md5
256 $self->refresh_file_list( $t );
258 warn "ERROR: send_file $f_full -> $t_full: $!";
265 my ( $self, $user, $from, $to ) = @_;
266 $user = $self->user_info($user) unless ref $user eq 'HASH';
268 my $f_full = "$user->{dir}/$from";
269 my $t_full = "$user->{dir}/$to";
271 $self->mkbasepath( $t_full, { uid => $user->{uid}, gid => $user->{gid} } );
272 my $ok = rename $f_full, $t_full;
274 $self->refresh_file_list( $user );
276 my $md5 = $self->md5_get($t_full);
278 warn "ERROR: no md5sum for $from";
279 return $ok; # XXX our internal error
282 $self->append_meta('md5sum', $user, 'rename' => $from );
283 $self->append_meta('md5sum', $user, $md5 => $from );
290 my ( $self, $user, $path ) = @_;
291 $user = $self->user_info($user) unless ref $user eq 'HASH';
293 my $deleted_size = 0;
294 my $full_path = "$user->{dir}/$path";
296 if ( -d $full_path ) {
302 my ($uid,$size) = (stat($_))[4,7];
303 warn "## find $uid $size $_\n";
304 if ( $uid == $self->{md5}->{uid} ) {
305 $deleted_size += $size;
309 remove_tree $full_path;
311 $deleted_size += -s $full_path;
315 warn "delete $deleted_size bytes shared\n";
317 $self->append( $user, 'delete', -$deleted_size, $user->{uid}, $path );
318 $self->append_meta('md5sum', $user, 'delete', $path );
320 $self->refresh_file_list( $user );
326 my ( $self, $user, $path ) = @_;
327 $user = $self->user_info($user) unless ref $user eq 'HASH';
329 my $full_path = "$user->{dir}/$path";
330 my $size = -s $full_path;
331 warn "# file_size $full_path = $size bytes\n";
336 my ( $self, $user, $path ) = @_;
338 $user =~ s{/+$}{} && warn "cleanup list_files arg [$user]";
340 $user = $self->user_info($user) unless ref $user eq 'HASH';
342 die "no dir for ",dump($user) unless exists $user->{dir};
344 my $files = $user->{dir} . '/.meta/files';
345 $self->mkbasepath( $files, { uid => $user->{uid} } );
346 if ( -e $files && -s $files > 0 && -e "$files.usage") {
348 open(my $fh, '<', $files);
351 warn "# list_files $user->{login} from cache ", length($list), " bytes\n";
355 my $dir = $user->{dir};
356 open(my $pipe, '-|', qq|find -L $dir -printf "%y %s %p\n"|);
357 open(my $fh, '>', "$files.new");
362 my ( $type, $size, $name ) = split(/\s/, $_, 3);
363 $name =~ s{$dir}{./};
365 my $line = "$type $size $name\n";
368 $total_usage += $size;
372 rename "$files.new", $files;
374 open(my $usage, '>', "$files.usage.new");
375 print $usage $total_usage;
377 rename "$files.usage.new", "$files.usage";
379 warn "# list_files $dir usage: $total_usage\n";
384 sub refresh_file_list {
385 my ( $self, $user ) = @_;
386 $user = $self->user_info($user) unless ref $user eq 'HASH';
387 my $full_path = "$user->{dir}/.meta/files";
388 if ( -e $full_path ) {
389 warn "## refresh_file_list $full_path";
390 unlink $full_path || warn "unlink $full_path: $!";
392 warn "## refresh_file_list $full_path missing";
395 unlink "$full_path.usage" if -e "$full_path.usage";