new implementation of md5 using attr
[cloudstore.git] / lib / CloudStore / API.pm
1 package CloudStore::API;
2 use warnings;
3 use strict;
4 use autodie;
5
6 use lib 'lib';
7 use base qw(CloudStore::Gearman CloudStore::MD5sum);
8
9 use File::Path qw(make_path remove_tree);
10 use File::Find;
11 use Data::Dump qw(dump);
12 use Carp qw(confess);
13
14 sub new {
15         my ($class,$slice) = @_;
16
17         my ( undef, $dir, $port, undef ) = getgrnam($slice);
18         die "can't find group $slice: $!" unless $dir && $port;
19         my $self = {
20                 passwd => '/var/lib/extrausers/passwd',
21                 PORT => $port,
22                 SLICE => $dir,
23         };
24         bless $self, $class;
25
26         $self->{md5} = $self->user_info("md5") || die "can't find user md5";
27         $self->{md5}->{dir} = "$dir/md5";
28         if ( ! -e $self->{md5}->{dir} ) {
29                 make_path $self->{md5}->{dir}, { uid => $self->{md5}->{uid}, gid => $self->{md5}->{gid} };
30                 warn "## CREATED md5pool $self->{md5}->{dir}\n";
31         }
32
33         my $name = $self->{SLICE};
34         $name =~ s/\W+/_/g;
35         $name =~ s/^_+//;
36         $self->{quota} = $name . '_quota';
37
38         return $self;
39 }
40
41 sub user_info {
42         my ($self,$login) = @_;
43
44         confess "need login" unless $login;
45
46         my @n = qw/ login passwd uid gid quota comment gecos dir shell expire /;
47         my @p = $login =~ m/^\d+$/ ? getpwuid $login : getpwnam $login;
48         die "user_info $login: $@" if $@;
49         my $user;
50         $user->{$_} = shift @p foreach @n;
51         return $user;
52 }
53
54 sub create_user {
55         my ( $self, $new_email, $new_passwd, $new_quota ) = @_;
56
57         my $max_uid = 0;
58         my $found = 0;
59
60         open(my $fh, '<', $self->{passwd});
61         while(<$fh>) {
62                 my ( $login, $passwd, $uid, $gid, $email, $dir, $shell ) = split(/:/,$_);
63                 $max_uid = $uid if $uid > $max_uid;
64                 $found = $uid if $email eq $new_email;
65         }
66         close($fh);
67
68         if ( ! $found ) {
69                 $max_uid++;
70                 my $dir = "$self->{SLICE}/$max_uid";
71                 warn "# create_user $new_email $new_quota = $max_uid $dir";
72                 open(my $fh, '>>', $self->{passwd});
73                 print $fh "u$max_uid:$new_passwd:$max_uid:$self->{PORT}:$new_email:$dir:/bin/true\n";
74                 close($fh);
75                 $found = $max_uid;
76
77                 mkdir $dir;
78                 chown $max_uid, $self->{PORT}, $dir;
79
80                 my $path = "$dir/.meta/secrets";
81                 $self->mkbasepath($path);
82                 open($fh, '>', $path);
83                 print $fh "u$max_uid:$new_passwd\n";
84                 close $fh;
85         }
86
87         # FIXME update quota only on create?
88         $self->gearman_do( "$self->{quota}_set" => "$found $new_quota" );
89
90         return $found;
91 }
92
93 sub mkbasepath {
94         my ($self,$path,$opts) = @_;
95         $path =~ s{/[^/]+$}{};
96         make_path $path unless -d $path;
97 }
98
99 sub user_dir {
100         my ( $self,$user, $dir ) = @_;
101         $user = $self->user_info($user) unless ref $user eq 'HASH';
102         my $path;
103         if ( exists $user->{dir} ) {
104                 $path = $user->{dir} . '/.meta/' . $dir;
105         } else {
106                 die "no dir in ", dump $user;
107         }
108         $path =~ s{//+}{/}g;
109
110         if ( ! -e $path ) {
111                 $self->mkbasepath( $path, { uid => $user->{uid} } );
112                 open(my $fh, '>', $path);
113                 close $fh;
114                 chown $user->{uid}, $user->{gid}, $path;
115                 warn "# user_dir created $path\n";
116         }
117
118         #warn "### user_dir $path";
119         return $path;
120 }
121
122 sub append {
123         my $self = shift @_;
124         $self->append_meta( 'usage', @_ );
125 }
126
127 sub append_meta {
128         my $self = shift @_;
129         my $log  = shift @_;
130         my $user = shift @_;
131         my $path = $self->user_dir( $user => $log );
132         my $delimiter = '#';
133            $delimiter = '  ' if $log =~ m/md5sum$/;
134         my $line = join($delimiter,@_);
135         open(my $fh, '>>', $path);
136         print $fh "$line\n";
137         close $fh;
138         warn "## $path $line\n";
139 }
140
141 sub usage {
142         my ( $self, $user ) = @_;
143         $user = $self->user_info($user) unless ref $user eq 'HASH';
144         my $path = $self->user_dir( $user => 'usage');
145         my $sum;
146         open(my $fh, '<', $path);
147         while(<$fh>) {
148                 chomp;
149                 my @v = split(/#/,$_);
150                 $sum->{ $v[0] } += $v[1];
151                 $sum->{_usage}  += $v[1];
152         }
153         my ( $usage, $quota ) = split(/ /,
154                 $self->gearman_do( "$self->{quota}_get" => $user->{uid} )
155         );
156         $sum->{_usage} += $usage;
157         $sum->{_quota} = $quota;
158         warn "## usage ",dump($user, $sum), $/;
159         return $sum;
160 }
161
162 sub send_file {
163         my ( $self, $f_uid,$f_path, $t_uid,$t_path ) = @_;
164
165         my $f = $self->user_info($f_uid);
166         die "FIXME not on current slice" if $f->{dir} !~ m/^$self->{SLICE}/;
167         my $t = $self->user_info($t_uid);
168
169         my $f_full = "$f->{dir}/$f_path";
170         my $t_full = "$t->{dir}/$t_path";
171
172         $self->mkbasepath( $t_full, { uid => $t->{uid} } );
173
174         my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks) = stat $f_full;
175         if ( $uid == $f->{uid} ) {
176                 warn "# send_file - move $f_uid $f_path to pool\n";
177                 chown $self->{md5}->{uid}, $self->{md5}->{gid}, $f_full;
178                 chmod oct("0444"), $f_full;
179                 $self->append( $f, 'sent', -s $f_full, $t->{uid}, $f_path );
180         } elsif ( $uid == $self->{md5}->{uid} ) {
181                 warn "# send_file - shared $f_full\n";
182         }
183
184         $self->delete( $t, $t_path ) if -e $t_full;
185
186         my $ok = link $f_full, $t_full; 
187         $self->append( $t, 'recv', -s $t_full, $f->{uid}, $t_path );
188
189         $ok = -s $t_full if $ok; # replace true with file size
190
191         my $md5;
192         if ( $f->{uid} == $self->{md5}->{uid} ) {
193                 $md5 = $f_path; # we don't have local md5sum db for md5 user!
194         } else {
195                 $md5 = $self->md5_get($f_full);
196         }
197         if ( ! $md5 ) {
198                 warn "ERROR: no md5 for $f_path";
199                 return $ok;
200         }
201
202         $self->append_meta('md5sum', $t, $md5 => $t_path ); # md5sum for received files!
203
204         return $ok;
205 }
206
207 sub rename_file {
208         my ( $self, $user, $from, $to ) = @_;
209         $user = $self->user_info($user) unless ref $user eq 'HASH';
210
211         my $f_full = "$user->{dir}/$from";
212         my $t_full = "$user->{dir}/$to";
213
214         $self->mkbasepath( $t_full, { uid => $user->{uid}, gid => $user->{gid} } );
215         my $ok = rename $f_full, $t_full;
216
217         my $md5 = $self->md5_get($t_full);
218         if ( ! $md5 ) {
219                 warn "ERROR: no md5sum for $from";
220                 return $ok; # XXX our internal error
221         }
222
223         $self->append_meta('md5sum', $user, 'rename' => $from );
224         $self->append_meta('md5sum', $user, $md5 => $from );
225
226         return $ok;
227 }
228
229
230 sub delete {
231         my ( $self, $user, $path ) = @_;
232         $user = $self->user_info($user) unless ref $user eq 'HASH';
233
234         my $deleted_size = 0;
235         my $full_path = "$user->{dir}/$path";
236
237         if ( -d $full_path ) {
238
239                 find({ 
240                 no_chdir => 1,
241                 wanted => sub {
242                         return if -d $_;
243                         my ($uid,$size) = (stat($_))[4,7];
244                         warn "## find $uid $size $_\n";
245                         if ( $uid == $self->{md5}->{uid} ) {
246                                 $deleted_size += $size;
247                         }
248                 }}, $full_path);
249
250                 remove_tree $full_path;
251         } else {
252                 $deleted_size += -s $full_path;
253                 unlink $full_path;
254         }
255
256         warn "delete $deleted_size bytes shared\n";
257
258         $self->append( $user, 'delete', -$deleted_size, $user->{uid}, $path );
259         $self->append_meta('md5sum', $user, 'delete', $path );
260
261         return $full_path;
262 }
263
264 sub file_size {
265         my ( $self, $user, $path ) = @_;
266         $user = $self->user_info($user) unless ref $user eq 'HASH';
267
268         my $full_path = "$user->{dir}/$path";
269         return -s $full_path;
270 }
271
272 1;