4 # Copyright (c) 2005 Michael Schroeder (mls@suse.de)
6 # This program is licensed under the BSD license, read LICENSE.BSD
7 # for further information
11 use Fcntl qw(:DEFAULT :flock);
19 require Compress::Zlib;
24 $have_time_hires = 1 if defined &Time::HiRes::gettimeofday;
28 $SIG{'PIPE'} = 'IGNORE';
30 #######################################################################
31 # Common code user for Client and Server
32 #######################################################################
34 my $makedeltarpm = 'makedeltarpm';
35 my $combinedeltarpm = 'combinedeltarpm';
36 my $applydeltarpm = 'applydeltarpm';
37 my $fragiso = 'fragiso';
46 return if $! != POSIX::EAGAIN;
51 open(STDIN, "<&I") || die("dup stdin: $!\n");
63 for ($tries = 0; $tries < 100; $tries++) {
64 if (sysopen(F, "$tmpdir/drpmsync.$$.$tries", POSIX::O_RDWR|POSIX::O_CREAT|POSIX::O_EXCL, 0600)) {
65 unlink("$tmpdir/drpmsync.$$.$tries");
72 # cannot use IPC::Open3, sigh...
74 return runprg_job(undef, @_);
78 my ($job, $if, $of, @prg) = @_;
79 local (*O, *OW, *E, *EW);
81 pipe(O, OW) || die("pipe: $!\n");
83 pipe(E, EW) || die("pipe: $!\n");
88 return ('', "runprg: fork: $!") if $! != POSIX::EAGAIN;
98 if (fileno(OW) != 1) {
99 open(STDOUT, ">&OW") || die("dup stdout: $!\n");
102 if (fileno(EW) != 2) {
103 open(STDERR, ">&EW") || die("dup stderr: $!\n");
108 if (fileno(I) != 0) {
109 open(STDIN, "<&I") || die("dup stdin: $!\n");
113 open(STDIN, "</dev/null");
116 die("$prg[0]: $!\n");
118 close(OW) unless $of;
122 $job->{'PID'} = $pid;
125 $job->{'O'} = *O unless $of;
129 $job->{'PID'} = $pid;
131 $job->{'O'} = *O unless $of;
132 return runprg_finish($job);
138 die("runprg_finish: no job running\n") unless $job && $job->{'PID'};
139 my ($out, $err) = ('', '');
140 my $pid = $job->{'PID'};
141 local *E = $job->{'E'};
144 if (exists $job->{'O'}) {
148 delete $job->{'PID'};
156 vec($rin, $ofd, 1) = 1;
158 vec($rin, $efd, 1) = 1;
161 my $openfds = $of ? 2 : 3;
163 $nfound = select($rout = $rin, undef, undef, undef);
164 if (!defined($nfound)) {
165 $err .= "select: $!";
166 close(O) if $openfds & 1;
167 close(E) if $openfds & 2;
170 if (!$of && vec($rout, $ofd, 1)) {
171 if (!sysread(O, $out, 4096, length($out))) {
172 vec($rin, $ofd, 1) = 0;
177 if (vec($rout, $efd, 1)) {
178 if (!sysread(E, $err, 4096, length($err))) {
179 vec($rin, $efd, 1) = 0;
186 if (waitpid($pid, 0) == $pid) {
187 $err = "Error $?" if $? && $err eq '';
190 if ($! != POSIX::EINTR) {
191 $err = "waitpid: $!";
200 my ($wri, $verify, $ml) = @_;
206 $ctx = Digest::MD5->new if $verify;
210 while (length($buf) < 96 + 16) {
211 $l = sysread(F, $buf, defined($ml) && $ml < 4096 ? $ml : 4096, length($buf));
212 return "read error" unless $l;
213 $ml -= $l if defined $ml;
215 my ($magic, $sigtype) = unpack('N@78n', $buf);
216 return "not a rpm (bad magic of header type" unless $magic == 0xedabeedb && $sigtype == 5;
217 my ($headmagic, $cnt, $cntdata) = unpack('@96N@104NN', $buf);
218 return "not a rpm (bad sig header magic)" unless $headmagic == 0x8eade801;
219 my $hlen = 96 + 16 + $cnt * 16 + $cntdata;
220 $hlen = ($hlen + 7) & ~7;
221 while (length($buf) < $hlen) {
222 $l = sysread(F, $buf, defined($ml) && $ml < 4096 ? $ml : 4096, length($buf));
223 return "read error" unless $l;
224 $ml -= $l if defined $ml;
226 my $lmd5 = Digest::MD5::md5_hex(substr($buf, 0, $hlen));
227 my $idxarea = substr($buf, 96 + 16, $cnt * 16);
228 if (!($idxarea =~ /\A(?:.{16})*\000\000\003\354\000\000\000\007(....)\000\000\000\020/s)) {
229 return "no md5 signature header";
231 my $md5off = unpack('N', $1);
232 return "bad md5 offset" if $md5off >= $cntdata;
233 $md5off += 96 + 16 + $cnt * 16;
234 my $hmd5 = unpack("\@${md5off}H32", $buf);
235 return "write error" if $wri && (syswrite(WF, substr($buf, 0, $hlen)) || 0) != $hlen;
236 $buf = substr($buf, $hlen);
237 while (length($buf) < 16) {
238 $l = sysread(F, $buf, defined($ml) && $ml < 4096 ? $ml : 4096, length($buf));
239 return "read error" unless $l;
240 $ml -= $l if defined $ml;
242 ($headmagic, $cnt, $cntdata) = unpack('N@8NN', $buf);
243 return "not a rpm (bad header magic)" unless $headmagic == 0x8eade801;
244 $hlen = 16 + $cnt * 16;
245 while (length($buf) < $hlen) {
246 $l = sysread(F, $buf, defined($ml) && $ml < 4096 ? $ml : 4096, length($buf));
247 return "read error" unless $l;
248 $ml -= $l if defined $ml;
250 my ($nameoff, $archoff, $btoff);
251 $idxarea = substr($buf, 0, $hlen);
253 if (!($idxarea =~ /\A(?:.{16})*\000\000\004\024/s)) {
254 if (($idxarea =~ /\A(?:.{16})*\000\000\004[\033\034]/s)) {
260 if (($idxarea =~ /\A(?:.{16})*\000\000\003\350\000\000\000\006(....)\000\000\000\001/s)) {
261 $nameoff = unpack('N', $1);
263 if (($idxarea =~ /\A(?:.{16})*\000\000\003\376\000\000\000\006(....)\000\000\000\001/s)) {
264 $archoff = unpack('N', $1);
266 if (($idxarea =~ /\A(?:.{16})*\000\000\003\356\000\000\000\004(....)\000\000\000\001/s)) {
267 $btoff = unpack('N', $1);
269 return "rpm contains no name tag" unless defined $nameoff;
270 return "rpm contains no arch tag" unless defined $archoff;
271 return "rpm contains no build time" unless defined $btoff;
272 return "bad name/arch offset" if $nameoff >= $cntdata || $archoff >= $cntdata || $btoff + 3 >= $cntdata;
273 $ctx->add(substr($buf, 0, $hlen)) if $verify;
274 return "write error" if $wri && (syswrite(WF, substr($buf, 0, $hlen)) || 0) != $hlen;
275 $buf = substr($buf, $hlen);
276 my $maxoff = $nameoff > $archoff ? $nameoff : $archoff;
277 $maxoff += 1024; # should be enough
278 $maxoff = $btoff + 4 if $btoff + 4 > $maxoff;
279 $maxoff = $cntdata if $maxoff > $cntdata;
280 while (length($buf) < $maxoff) {
281 $l = sysread(F, $buf, defined($ml) && $ml < 4096 ? $ml : 4096, length($buf));
282 return "read error" unless $l;
283 $ml -= $l if defined $ml;
285 my $name = unpack("\@${nameoff}Z*", $buf);
286 my $arch = unpack("\@${archoff}Z*", $buf);
287 my $bt = unpack("\@${btoff}H8", $buf);
288 if ($verify || $wri) {
289 $ctx->add($buf) if $verify;
290 return "write error" if $wri && (syswrite(WF, $buf) || 0) != length($buf);
292 last if defined($ml) && $ml == 0;
293 $l = sysread(F, $buf, defined($ml) && $ml < 8192 ? $ml : 8192);
294 last if !$l && !defined($ml);
295 return "read error" unless $l;
296 $ml -= $l if defined $ml;
297 $ctx->add($buf) if $verify;
298 return "write error" if $wri && (syswrite(WF, $buf) || 0) != $l;
301 my $rmd5 = $ctx->hexdigest;
302 return "rpm checksum error ($rmd5 != $hmd5)" if $rmd5 ne $hmd5;
305 $name = "unknown" if $name =~ /[\000-\040\/]/;
306 $arch = "unknown" if $arch =~ /[\000-\040\/]/;
307 $arch = $srctype if $srctype;
308 return ("$lmd5$hmd5", $bt, "$name.$arch");
318 $ctx = Digest::MD5->new;
321 $l = sysread(F, $buf, 8192);
323 die("cpfile read error\n") unless $l;
325 die("cpfile write error\n") if $wri && (syswrite(WF, $buf) || 0) != $l;
327 return ($ctx->hexdigest);
332 my @info = cprpm($fd);
334 warn("$rpm: $info[0]\n");
343 if (!open(RPM, '<', $rpm)) {
347 my @ret = rpminfo_f(*RPM, $rpm);
355 my $ctx = Digest::MD5->new;
357 return $ctx->hexdigest;
363 if (!open(FN, '<', $fn)) {
367 my @ret = fileinfo_f(*FN, $fn);
374 my $fnc = readlink($fn);
375 if (!defined($fnc)) {
379 return Digest::MD5::md5_hex($fnc);
383 my @filter_arch_comp;
388 my @f = @filter_comp;
391 my ($ft, $fre) = splice(@f, 0, 3);
392 my @xx = grep {/$fre/} @x;
393 my %xx = map {$_ => 1} @xx;
395 @x = grep {!$xx{$_}} @x;
402 my @f = @filter_comp;
404 my ($ft, $fre) = splice(@f, 0, 3);
406 return 1 if $n =~ /$fre/;
408 return if $n =~ /$fre/;
418 for my $rule (@rules) {
419 die("bad filter type, must be '+' or '-'\n") unless $rule =~ /^([+-])(.*)$/;
420 my $type = $1 eq '+' ? 1 : 0;
422 my $anchored = $match =~ s/^\///;
423 my @match = split(/\[(\^?.(?:\\.|[^]])*)\]/, $match, -1);
428 s/([^-\^a-zA-Z0-9])/\\$1/g;
429 s/\\\\(\\[]\\\]]|-)/"\\".substr($1, -1)/ge;
438 $match = join('', @match);
442 $match = "(?:^|\/)$match";
444 $match .= '\/?' if $match !~ /\/$/;
447 push @comp, $type, qr/$match/s, $rule;
449 die("bad filter rule: $rule\n") if $@;
454 sub filelist_apply_filter {
456 return unless @filter_comp;
461 next if substr($e->[0], 0, length($x)) eq $x;
465 if (!run_filter_one("$e->[0]/")) {
470 next if !run_filter_one("$e->[0]");
477 sub filelist_apply_filter_arch {
479 return unless @filter_arch_comp;
481 my @filter_comp_save = @filter_comp;
482 @filter_comp = @filter_arch_comp;
485 if (@$e > 5 && !run_filter_one((split('\.', $e->[5]))[-1])) {
486 if ($e->[0] =~ /(.*)\.rpm$/) {
487 $filtered{"$1.changes"} = 1;
488 $filtered{"$1-MD5SUMS.meta"} = 1;
489 $filtered{"$1-MD5SUMS.srcdir"} = 1;
495 @filter_comp = @filter_comp_save;
498 # second pass to remove meta files
501 next if @$e == 4 && $filtered{$e->[0]};
508 sub filelist_exclude_drpmsync {
510 @$flp = grep {$_->[0] =~ /(?:^|\/)drpmsync\//s || (@$_ == 3 && $_->[0] =~ /(?:^|\/)drpmsync$/s)} @$flp;
519 my ($bdir, $dir, $keepdrpmdir, $norecurse) = @_;
522 if (!opendir(DH, "$bdir$dir")) {
526 my @ents = sort readdir(DH);
528 $bdir .= '/' if $dir eq '';
529 $dir .= '/' if $dir ne '';
530 if ($dir ne '' && grep {$_ eq 'drpmsync'} @ents) {
531 readcache("$bdir${dir}drpmsync/cache") if -f "$bdir${dir}drpmsync/cache";
535 @ents = grep {$_ ne '.' && $_ ne '..'} @ents;
536 my @fents = run_filter(map {"$dir$_"} @ents);
537 if (@fents != @ents) {
538 %fents = map {("$dir$_" => 1)} @ents;
539 delete $fents{$_} for @fents;
542 for my $ent (@ents) {
543 next if $ent eq '.' || $ent eq '..';
544 next if $ent =~ /\.new\d*$/;
545 my @s = lstat "$bdir$dir$ent";
547 warn("$bdir$dir$ent: $!\n");
550 next unless -l _ || -d _ || -f _;
551 my $id = "$s[9]/$s[7]/$s[1]";
552 my $mode = -l _ ? 0x2000 : -f _ ? 0x1000 : 0x0000;
553 $mode |= $s[2] & 07777;
554 my @data = ($id, sprintf("%04x%08x", $mode, $s[9]));
556 next if $ent eq 'drpmsync' && ($dir eq '' || !$keepdrpmdir);
557 next if @filter_comp && !run_filter_one("$dir$ent/");
558 push @files, [ "$dir$ent", @data ];
560 findfiles($bdir, "$dir$ent", $keepdrpmdir);
562 next if @filter_comp && $fents{"$dir$ent"};
565 @xdata = @{$cache{$id}};
566 if (@xdata == ($ent =~ /\.[sr]pm$/) ? 3 : 1) {
568 push @files, [ "$dir$ent", @data, @xdata ];
572 # print "miss $id ($ent)\n";
575 @xdata = linkinfo("$bdir$dir$ent");
577 $cache{$id} = \@xdata;
578 push @files, [ "$dir$ent", @data, @xdata ];
582 if (!open(F, '<', "$bdir$dir$ent")) {
583 warn("$bdir$dir$ent: $!\n");
588 warn("$bdir$dir$ent: $!\n");
591 $id = "$s[9]/$s[7]/$s[1]";
592 @data = ($id, sprintf("1%03x%08x", ($s[2] & 07777), $s[9]));
593 if ($ent =~ /\.[sr]pm$/) {
594 @xdata = rpminfo_f(*F, "$bdir$dir$ent");
596 @xdata = fileinfo_f(*F, "$bdir$dir$ent");
600 $cache{$id} = \@xdata;
601 push @files, [ "$dir$ent", @data, @xdata ];
610 open(CF, '<', $cf) || return;
614 next unless @s == 4 || @s == 2;
625 open(CF, '>', "$cf.new") || die("$cf.new: $!\n");
627 next if @$_ < 4; # no need to cache dirs
629 print CF "$_->[1] $_->[3] $_->[4] $_->[5]\n";
631 print CF "$_->[1] $_->[3]\n";
635 rename("$cf.new", $cf) || die("rename $cf.new $cf: $!\n");
638 #######################################################################
640 #######################################################################
653 $x =~ s/([\000-\040<>\"#&\+=%[\177-\377])/sprintf("%%%02X",ord($1))/ge;
660 open(FN, '<', $fn) || return ('', "$fn: $!");
662 while ((sysread(FN, $out, 8192, length($out)) || 0) == 8192) {}
678 my $servertmp = '/var/tmp';
681 sub readconfig_server {
694 die("config not set\n") unless $cf;
695 open(CF, '<', $cf) || die("$cf: $!\n");
700 next if $_ eq '' || /^#/;
701 my @s = split(' ', $_);
704 my $s1 = @s > 1 ? $s[1] : undef;
706 if ($s0 eq 'allow' || $s0 eq 'deny') {
710 eval { local $::SIG{'__DIE__'}; "" =~ /^$_$/; };
711 die("$s0: bad regexp: $_\n") if $@;
713 s/([^a-zA-Z0-9*])/\\$1/g;
717 if ($s0 eq 'allow') {
722 } elsif ($s0 eq 'denymsg') {
727 if ($s1 =~ /^\/(.*)\/$/) {
729 eval { local $::SIG{'__DIE__'}; "" =~ /^$s1$/; };
730 die("$s0: bad regexp: $s1\n") if $@;
732 $s1 =~ s/([^a-zA-Z0-9*])/\\$1/g;
736 push @denymsg, [ $s1, join(' ', @s) ];
737 } elsif ($s0 eq 'no_combine') {
738 $no_combine = ($s1 && $s1 =~ /true/i);
739 } elsif ($s0 eq 'log') {
741 } elsif ($s0 eq 'serverlog') {
743 } elsif ($s0 eq 'deltadirs') {
745 } elsif ($s0 eq 'deltarpmpath') {
746 my $p = defined($s1) ? "$s1/" : '';
747 $makedeltarpm = "${p}makedeltarpm";
748 $combinedeltarpm = "${p}combinedeltarpm";
749 $fragiso = "${p}fragiso";
750 } elsif ($s0 eq 'maxclients') {
751 $maxclients = $s1 || 1;
752 } elsif ($s0 eq 'servername') {
754 } elsif ($s0 eq 'serveraddr') {
756 } elsif ($s0 eq 'serveruser') {
758 } elsif ($s0 eq 'servergroup') {
760 } elsif ($s0 eq 'pidfile') {
761 $serverpidfile = $s1;
762 } elsif ($s0 eq 'maxdeltasize') {
764 } elsif ($s0 eq 'maxdeltasizeabs') {
765 $maxdeltasizeabs = $s1;
766 } elsif ($s0 eq 'tree') {
767 die("tree: two arguments required\n") if @s != 2;
768 $trees{$s[0]} = { 'allow' => [ @allow ],
770 'denymsg' => [ @denymsg ],
771 'no_combine' => $no_combine,
772 'maxdeltasize' => $maxdeltasize,
773 'maxdeltasizeabs' => $maxdeltasizeabs,
774 'deltadirs' => $deltadirs,
780 die("$cf: unknown configuration parameter: $s0\n");
793 for (split(/[\r\n]+/, $t)) {
796 next unless defined $field;
800 ($field, $data) = split(/\s*:\s*/, $_, 2);
801 $field =~ tr/A-Z/a-z/;
802 if ($h->{$field} && $h->{$field} ne '') {
803 $h->{$field} = $h->{$field}.','.$data;
805 $h->{$field} = $data;
814 return unless $serverlog;
816 my @lt = localtime(time());
819 $id = defined($id) ? " [$id]" : '';
820 printf SERVERLOG "%04d-%02d-%02d %02d:%02d:%02d%s: %s\n", @lt[5,4,3,2,1,0], $id, $str;
826 pipe(SR, SW) || die("setsid pipe: $!\n");
829 last if defined $pid;
830 die("fork: $!") if $! != POSIX::EAGAIN;
836 sysread(SR, $dummy, 1);
842 open(STDIN, "</dev/null");
843 open(STDOUT, ">/dev/null");
844 open(STDERR, ">/dev/null");
851 # not called from web server, go for standalone
853 readconfig_server($config);
854 unlink($serverpidfile) if $serverpidfile;
855 if ($serverlog && !open(SERVERLOG, '>>', $serverlog)) {
856 my $err = "$serverlog: $!\n";
857 undef $serverlog; # do not log in die() hook
860 serverlog(undef, "server start");
861 $servername = '' unless defined $servername;
862 $servername = Net::Domain::hostfqdn().$servername if $servername eq '' || $servername =~ /^:\d+$/;
863 die("need servername for standalone mode\n") unless $servername;
864 if (defined($serveruser) && $serveruser =~ /[^\d]/) {
865 my $uid = getpwnam($serveruser);
866 die("$serveruser: unknown user\n") unless defined $uid;
869 if (defined($servergroup) && $servergroup =~ /[^\d]/) {
870 my $gid = getgrnam($servergroup);
871 die("$servergroup: unknown group\n") unless defined $gid;
874 my ($servern, $servera, $serverp);
875 ($servern, $serverp) = $servername =~ /^([^\/]+?)(?::(\d+))?$/;
876 die("bad servername: $servername\n") unless $servern;
878 $servera = INADDR_ANY;
880 $servera = inet_aton($serveraddr) || die("could not resolv $serveraddr\n");
882 my $tcpproto = getprotobyname('tcp');
883 socket(MS , PF_INET, SOCK_STREAM, $tcpproto) || die("socket: $!\n");
884 setsockopt(MS, SOL_SOCKET, SO_REUSEADDR, pack("l",1));
885 bind(MS, sockaddr_in($serverp, $servera)) || die "bind: $!\n";
886 listen(MS , 512) || die "listen: $!\n";
889 if ($serverpidfile) {
890 open(SERVERPID, '>', $serverpidfile) || die("$serverpidfile: $!\n");
893 if (defined($servergroup)) {
894 ($(, $)) = ($servergroup, $servergroup);
895 die "setgid: $!\n" if $) != $servergroup;
897 if (defined($serveruser)) {
898 ($<, $>) = ($serveruser, $serveruser);
899 die "setuid: $!\n" if $> != $serveruser;
901 serverdetach() unless $nobg;
903 if ($serverpidfile) {
904 syswrite(SERVERPID, "$$\n");
905 close(SERVERPID) || die("$serverpidfile: $!\n");
908 fcntl(MS, F_SETFL, 0);
911 $remote_addr = accept(S, MS) || die "accept: $!\n";
915 last if defined($pid);
921 $remote_addr = inet_ntoa((sockaddr_in($remote_addr))[1]);
923 $pid = waitpid(-1, keys %chld < $maxclients ? WNOHANG : 0);
924 delete $chld{$pid} if $pid && $pid > 0;
925 last if !($pid && $pid > 0) && keys %chld < $maxclients;
930 setsockopt(S, SOL_SOCKET, SO_KEEPALIVE, pack("l",1));
931 $remote_addr = inet_ntoa((sockaddr_in($remote_addr))[1]);
936 my ($cgip, $query_string) = @_;
939 my @query_string = split('&', $query_string);
940 while (@query_string) {
941 my ($name, $value) = split('=', shift(@query_string), 2);
942 next unless defined $name && $name ne '';
944 $name =~ s/%([a-fA-F0-9]{2})/chr(hex($1))/ge;
945 if (defined($value)) {
947 $value =~ s/%([a-fA-F0-9]{2})/chr(hex($1))/ge;
949 if ($name eq 'filter' || $name eq 'filter_arch') {
950 push @{$cgip->{$name}}, $value;
952 $cgip->{$name} = $value;
960 die($qu eq '' ? "empty query\n" : "received truncated query\n") if !sysread(S, $qu, 1024, length($qu));
961 } while ($qu !~ /^(.*?)\r?\n/s);
963 my ($act, $path, $vers, undef) = split(' ', $req, 4);
965 die("400 No method name\n") if !$act;
967 die("501 Bad method: $act\n") if $act ne 'GET' && $act ne 'HEAD' && $act ne 'POST';
968 while ($qu !~ /^(.*?)\r?\n\r?\n(.*)$/s) {
969 die("received truncated query\n") if !sysread(S, $qu, 1024, length($qu));
971 $qu =~ /^(.*?)\r?\n\r?\n(.*)$/s;
973 gethead(\%headers, "Request: $1");
974 } elsif ($act ne 'GET') {
975 die("501 Bad method, must be GET\n");
978 my $query_string = '';
979 if ($path =~ /^(.*?)\?(.*)$/) {
983 if ($act eq 'POST') {
985 my $cl = $headers{'content-length'};
986 while (length($qu) < $cl) {
987 sysread(S, $qu, $cl - length($qu), length($qu)) || die("400 Truncated body\n");
989 $query_string = substr($qu, 0, $cl);
990 $qu = substr($qu, $cl);
992 $path =~ s/%([a-fA-F0-9]{2})/chr(hex($1))/ge;
993 return ($path, $query_string, $headers{'via'} ? 1 : 0);
998 my ($flen, $str, $ctx, @hi) = @_;
999 die("replystream: bad param\n") unless $flen;
1000 unshift @hi, "HTTP/1.1 200 OK";
1001 push @hi, "Server: drpmsync";
1002 push @hi, "Cache-Control: no-cache";
1003 push @hi, "Content-length: ".(length($str) + $flen + 32);
1004 $str = join("\r\n", @hi)."\r\n\r\n".$str;
1006 fcntl(S, F_SETFL,O_NONBLOCK);
1008 1 while sysread(S, $dummy, 1024, 0);
1009 fcntl(S, F_SETFL,0);
1012 while (length($str) || $flen) {
1013 if ($flen && length($str) < 16384) {
1015 my $r = sysread(FF, $d, $flen > 8192 ? 8192 : $flen);
1017 die("replystream: read error: $!\n") unless defined $r;
1018 die("replystream: unexpected EOF\n");
1020 die("replystream: too much data\n") if $r > $flen;
1024 $str .= $ctx->hexdigest if !$flen;
1026 $r = syswrite(S, $str, length($str));
1027 die("replystream: write error: $!\n") unless $r;
1028 $str = substr($str, $r);
1033 my ($str, @hi) = @_;
1036 if (@hi && $hi[0] =~ /^status: (\d+.*)/i) {
1037 $hi[0] = "HTTP/1.1 $1";
1039 unshift @hi, "HTTP/1.1 200 OK";
1042 push @hi, "Server: drpmsync";
1043 push @hi, "Cache-Control: no-cache";
1044 push @hi, "Content-length: ".length($str);
1045 $str = join("\r\n", @hi)."\r\n\r\n$str";
1050 fcntl(S, F_SETFL,O_NONBLOCK);
1052 1 while sysread(S, $dummy, 1024, 0);
1053 fcntl(S, F_SETFL,0);
1055 while (length($str)) {
1056 $l = syswrite(S, $str, length($str));
1057 die("write error: $!\n") unless $l;
1058 $str = substr($str, $l);
1063 my ($err, $cgi, $remote_addr) = @_;
1064 serverlog($remote_addr, $err) if $serverlog && !$sendlogid;
1065 sendlog($err) if $sendlogid;
1066 die($err) if $standalone == 1;
1068 if (exists($cgi->{'drpmsync'})) {
1069 my $data = 'DRPMSYNC0001ERR 00000000'.sprintf("%08x", length($err)).$err;
1070 reply($data, "Content-type: application/octet-stream");
1071 } elsif ($err =~ /^(\d+[^\r\n]*)/) {
1072 reply("<pre>$err</pre>\n", "Status: $1", "Content-type: text/html");
1074 reply("<pre>$err</pre>\n", "Status: 404 Error", "Content-type: text/html");
1079 my $check_access_cache_addr;
1080 my $check_access_cache_name;
1083 my ($tree, $remote_addr) = @_;
1084 my ($remote_name, $access_ok);
1086 $remote_name = $check_access_cache_name if $check_access_cache_addr && $check_access_cache_addr eq $remote_addr;
1088 if (@{$tree->{'deny'}}) {
1089 if (!$remote_name) {
1090 $remote_name = gethostbyaddr(inet_aton($remote_addr), AF_INET);
1091 die("could not resolve $remote_addr\n") unless $remote_name;
1092 $check_access_cache_addr = $remote_addr;
1093 $check_access_cache_name = $remote_name;
1095 for my $deny (@{$tree->{'deny'}}) {
1096 if ($deny =~ /^!/) {
1097 my $d1 = substr($deny, 1);
1098 last if $remote_name =~ /^$d1$/i;
1099 last if $remote_addr =~ /^$d1$/i;
1101 goto denied if $remote_name =~ /^$deny$/i;
1102 goto denied if $remote_addr =~ /^$deny$/i;
1105 for my $allow (@{$tree->{'allow'}}) {
1106 last if $allow =~ /^!/;
1107 return if $remote_addr =~ /^$allow$/i;
1109 if (!$remote_name) {
1110 $remote_name = gethostbyaddr(inet_aton($remote_addr), AF_INET);
1111 die("could not resolve $remote_addr\n") unless $remote_name;
1112 $check_access_cache_addr = $remote_addr;
1113 $check_access_cache_name = $remote_name;
1115 for my $allow (@{$tree->{'allow'}}) {
1116 if ($allow =~ /^!/) {
1117 my $a1 = substr($allow, 1);
1118 last if $remote_name =~ /^$a1$/i;
1119 last if $remote_addr =~ /^$a1$/i;
1121 return if $remote_addr =~ /^$allow$/i;
1122 return if $remote_name =~ /^$allow$/i;
1125 my $denymsg = "access denied [%h]";
1126 for my $dmsg (@{$tree->{'denymsg'}}) {
1127 if ($remote_name =~ /^$dmsg->[0]$/i || $remote_addr =~ /^$dmsg->[0]$/i) {
1128 $denymsg = $dmsg->[1];
1132 $denymsg =~ s/%h/$remote_addr/g;
1133 $denymsg =~ s/%n/$remote_name/g;
1139 return unless $sendlogid;
1141 my @lt = localtime(time());
1144 printf SENDLOG "%05d %04d-%02d-%02d %02d:%02d:%02d %s: %s\n", $$, @lt[5,4,3,2,1,0], $sendlogid, $str;
1148 my ($have2, $info2, @dirs) = @_;
1151 for my $dir (@dirs) {
1152 if (opendir(D, $dir)) {
1153 push @avail, map {"$dir/$_"} grep {/^[0-9a-f]{96}$/} readdir(D);
1157 return () unless @avail;
1160 if ($have2->{substr($_, -96, 32)}) {
1165 return () unless $gotone;
1166 my @chains = ([$info2]);
1168 push @{$avail{substr($_, -32, 32)}}, $_ for @avail;
1169 while (@chains && @{$chains[0]} <= @avail) {
1170 for my $pos (splice @chains) {
1171 for my $a (@{$avail{$pos->[0]}}) {
1172 my @n = (@$pos, $a);
1173 $n[0] = substr($a, -96, 32);
1174 if ($have2->{$n[0]}) {
1189 tmpopen(*F2, $servertmp);
1190 defined(sysseek(F, $o, 0)) || die("extractrpm: sysseek: $!\n");
1193 my $r = sysread(F, $buf, $l > 8192 ? 8192 : $l);
1195 die("extractrpm: read error: $!\n") unless defined $r;
1196 die("extractrpm: unexpected EOF\n");
1198 die("extractrpm: read too much data\n") if $r > $l;
1199 die("extractrpm: write error: $!\n") if (syswrite(F2, $buf) || 0) != $r;
1205 open(F, "<&F2") || die("extractrpm: dup: $!\n");
1211 if ($v >= 4294967295) {
1212 my $v2 = int($v / 4294967296);
1213 return sprintf("FFFFFFFF%02x%08x", $v2, $v - 4294967296 * $v2);
1215 return sprintf("%08x", $v);
1220 my $deltadirscacheid;
1223 my ($ddconfig, $path) = @_;
1226 if ($deltadirscache) {
1227 my @ddstat = stat($ddconfig);
1228 undef $deltadirscache if !@ddstat || "$ddstat[9]/$ddstat[7]/$ddstat[1]" ne $deltadirscacheid;
1230 if (!$deltadirscache) {
1233 if (open(DD, '<', $ddconfig)) {
1237 if (@ddc && /^\s*\+\s*(.*)/) {
1238 push @{$ddc[-1]}, split(' ', $1);
1240 push @ddc, [ split(' ', $_) ];
1243 my @ddstat = stat(DD);
1245 $deltadirscache = \@ddc;
1246 $deltadirscacheid = "$ddstat[9]/$ddstat[7]/$ddstat[1]";
1249 if ($deltadirscache) {
1250 for my $dd (@$deltadirscache) {
1252 my $ddre = shift @dd;
1254 push @dirs, @dd if $path =~ /$ddre/;
1262 my ($cgi, $path_info, $script_name, $remote_addr, $keep_ok) = @_;
1265 $path_info = '' unless defined $path_info;
1266 die("invalid path\n") if $path_info =~ /\/(\.|\.\.)?\//;
1267 die("invalid path\n") if $path_info =~ /\/(\.|\.\.)$/;
1268 die("invalid path\n") if "$path_info/" =~ /(\.|\.\.)\//;
1269 die("invalid path\n") if $path_info ne '' && ($path_info !~ /^\//);
1270 die("$script_name not exported\n") unless $trees{$script_name};
1272 my $sendlog = $trees{$script_name}->{'log'};
1273 if ($tree && $tree->{'log'} && (!$sendlog || $tree->{'log'} ne $sendlog)) {
1277 if ($sendlog && (!$tree || !$tree->{'log'} || $tree->{'log'} ne $sendlog)) {
1278 open(SENDLOG, '>>', $sendlog) || die("$sendlog: $!\n");
1282 $sendlogid = "[$remote_addr] $trees{$script_name}->{'id'}";
1284 $tree = $trees{$script_name};
1285 check_access($tree, $remote_addr);
1287 my $spath_info = $path_info;
1288 $spath_info =~ s/^\///;
1290 my $root = $tree->{'root'};
1291 die("$root: $!\n") unless -d $root;
1293 my $replyid = $keep_ok ? 'DRPMSYNK' : 'DRPMSYNC';
1295 if ($path_info =~ /(.*)\/drpmsync\/closesock$/ && exists $cgi->{'drpmsync'}) {
1297 sendlog(". $croot bye");
1302 if ($path_info =~ /^(.*)\/drpmsync\/contents$/) {
1304 die("$croot: does not exist\n") unless -e "$root$croot";
1305 die("$croot: not a directory\n") unless -d "$root$croot";
1306 sendlog("# $croot contents request");
1308 readcache("$root$croot/drpmsync/cache");
1310 $cachehits = $cachemisses = 0;
1311 @filter_comp = compile_filter(@{$cgi->{'filter'} || []});
1312 @filter_arch_comp = compile_filter(@{$cgi->{'filter_arch'} || []});
1313 findfiles("$root$croot", '', 0, exists($cgi->{'norecurse'}) ? 1 : 0);
1314 filelist_apply_filter_arch(\@files) if @filter_arch_comp;
1317 my ($stamp1, $stamp2);
1318 $stamp1 = $stamp2 = sprintf("%08x", time());
1319 if (open(STAMP, '<', "$root$croot/drpmsync/timestamp")) {
1321 if ((sysread(STAMP, $s, 16) || 0) == 16 && $s !~ /[^0-9a-f]/) {
1322 $stamp1 = substr($s, 0, 8);
1323 $stamp2 = substr($s, 8, 8);
1328 if (!exists $cgi->{'drpmsync'}) {
1331 $l[0] = aescape($l[0]);
1332 $l[5] = aescape($l[5]) if @l > 5;
1334 $data .= join(' ', @l)."\n";
1336 sendlog("h $croot contents ($cachehits/$cachemisses/$ti)");
1337 reply($data, "Content-type: text/plain");
1340 $data = pack('H*', "$stamp1$stamp2");
1341 $data = pack("Nw/a*w/a*", scalar(@files), $tree->{'id'}, $data);
1346 $b = pack('H*', "$l[2]$l[3]$l[4]").$l[5];
1348 $b = pack('H*', "$l[2]$l[3]");
1350 $b = pack('H*', $l[2]);
1352 $data .= pack("w/a*w/a*", $l[0], $b);
1355 my $dataid = 'SYNC';
1356 if ($have_zlib && exists($cgi->{'zlib'})) {
1357 $data = Compress::Zlib::compress($data);
1359 sendlog("z $croot contents ($cachehits/$cachemisses/$ti)");
1361 sendlog("f $croot contents ($cachehits/$cachemisses/$ti)");
1363 $data = sprintf("1%03x%08x", 0644, time()).$data;
1364 $data = "${replyid}0001${dataid}00000000".sprintf("%08x", length($data)).$data.Digest::MD5::md5_hex($data);
1365 reply($data, "Content-type: application/octet-stream");
1369 my @s = lstat("$root$path_info");
1371 if (!exists($cgi->{'drpmsync'})) {
1372 die("$spath_info: $!\n") unless @s;
1374 die("$spath_info: bad file type\n") unless -f _;
1375 sendlog("h $path_info");
1376 open(F, '<', "$root$path_info") || die("$spath_info: $!\n");
1378 while ((sysread(F, $c, 4096, length($c)) || 0) == 4096) {}
1380 my $ct = 'text/plain';
1381 if ($spath_info =~ /\.(gz|rpm|spm|bz2|tar|tgz|jpg|jpeg|gif|png|pdf)$/) {
1382 $ct = 'application/octet-stream';
1384 reply($c, "Content-type: $ct");
1387 if (($path_info !~ s/\/$//)) {
1389 reply("The document has moved", "Status: 302 Found", "Content-type: text/html", "Location: http://$servername$tree->{'id'}$path_info/");
1391 reply("The document has moved", "Status: 302 Found", "Content-type: text/html", "Location: http://$ENV{'SERVER_NAME'}$tree->{'id'}$path_info/");
1395 sendlog("h $path_info");
1396 opendir(DIR, "$root$path_info") || die("$root$path_info: $!\n");
1397 my @ents = sort readdir(DIR);
1399 @ents = grep {$_ ne '.' && $_ ne '..'} @ents;
1400 unshift @ents, '.', '..';
1401 my $data = "<pre>\n";
1402 for my $ent (@ents) {
1403 @s = lstat("$root$path_info/$ent");
1405 $data .= escape("$ent: $!\n");
1410 $info = 'c' if -c _;
1411 $info = 'b' if -b _;
1412 $info = '-' if -f _;
1413 $info = 'd' if -d _;
1416 $ent2 = readlink("$root$path_info/$ent");
1417 die("$root$path_info/$ent: $!") unless defined $ent2;
1418 $ent2 = escape(" -> $ent2");
1420 my $mode = $s[2] & 0777;
1421 for (split('', 'rwxrwxrwx')) {
1422 $info .= $mode & 0400 ? $_ : '-';
1425 my @lt = localtime($s[9]);
1428 $info = sprintf("%s %4d root root %8d %04d-%02d-%02d %02d:%02d:%02d", $info, $s[3], $s[7], @lt[5, 4, 3, 2, 1, 0]);
1429 $info = escape($info);
1430 my $ne = "$path_info/$ent";
1431 $ne = $path_info if $ent eq '.';
1437 if ((-d _) && ! (-l _)) {
1438 $ent = "<a href=\"".aescape("$script_name$ne/")."\">".escape("$ent")."</a>$ent2";
1439 } elsif ((-f _) && ! (-l _)) {
1440 $ent = "<a href=\"".aescape("$script_name$ne")."\">".escape("$ent")."</a>$ent2";
1442 $ent = escape("$ent").$ent2;
1444 $data .= "$info $ent\n";
1446 $data .= "</pre>\n";
1447 reply($data, "Content-type: text/html");
1452 sendlog("- $path_info");
1453 my $data = "${replyid}0001GONE".sprintf("%08x", length($spath_info)).'00000000'.$spath_info;
1454 reply($data, "Content-type: application/octet-stream");
1459 # oops, this is bad, the file is now a directory
1460 # send GONE so it will get removed
1461 sendlog("X $path_info");
1462 my $data = "${replyid}0001GONE".sprintf("%08x", length($spath_info)).'00000000'.$spath_info;
1463 reply($data, "Content-type: application/octet-stream");
1468 sendlog("f $path_info");
1469 my $lc = readlink("$root$path_info");
1470 die("readlink: $!\n") unless defined($lc);
1471 $lc = sprintf("2%03x%08x", $s[2] & 07777, $s[9]).$lc;
1472 my $data = "${replyid}0001FILE".sprintf("%08x%08x", length($spath_info), length($lc)).$spath_info.$lc.Digest::MD5::md5_hex($lc);
1473 reply($data, "Content-type: application/octet-stream");
1477 die("$spath_info: bad file type\n") unless -f _;
1478 open(F, '<', "$root$path_info") || die("$spath_info: $!\n");
1483 if ((exists($cgi->{'fiso'}) || exists($cgi->{'extract'})) && ($spath_info =~ /(?<!\.delta)\.iso$/i)) {
1484 if (!$cgi->{'extract'}) {
1485 tmpopen(*F2, $servertmp);
1486 my (undef, $err) = runprg(*F, *F2, $fragiso, 'make', '-', '-');
1487 die("fragiso make failed: $err\n") if $err;
1489 sysseek(F2, 0, 0); # currently at EOF
1490 sendlog("i $path_info");
1492 my $ctx = Digest::MD5->new;
1493 my $data = sprintf("1%03x%08x", $s[2] & 07777, $s[9]);
1495 $data = "${replyid}0001FISO".sprintf("%08x", length($spath_info)).hexit(length($data) + $flen).$spath_info.$data;
1496 replystream(*F2, $flen, $data, $ctx, "Content-type: application/octet-stream");
1500 die("bad extract: $cgi->{'extract'}\n") unless $cgi->{'extract'} =~ /^([0-9a-fA-F]{2})([0-9a-fA-F]{8}):([0-9a-fA-F]{8})$/;
1501 # always fits in perl's floats
1502 $extracto = hex($1) * 4294967296 + hex($2);
1503 $extractl = hex($3);
1504 defined(sysseek(F, $extracto, 0)) || die("seek error: $!\n");
1505 $path_info .= "\@$cgi->{'extract'}";
1507 } elsif ($spath_info !~ /\.[sr]pm$/) {
1509 my $data = sprintf("1%03x%08x", $s[2] & 07777, $s[9]);
1510 if ($s[7] >= 67108864) {
1511 sendlog("f $path_info");
1512 my $ctx = Digest::MD5->new;
1514 $data = "${replyid}0001FILE".sprintf("%08x", length($spath_info)).hexit(length($data) + $flen).$spath_info.$data;
1515 replystream(*F, $flen, $data, $ctx, "Content-type: application/octet-stream");
1518 while ((sysread(F, $data, 4096, length($data)) || 0) == 4096) {}
1520 my $dataid = 'FILE';
1521 if (length($data) >= 12 + 64 && $have_zlib && exists($cgi->{'zlib'}) && substr($data, 12, 2) ne "\037\213" && substr($data, 12, 2) ne "BZ") {
1522 $data = substr($data, 0, 12).Compress::Zlib::compress(substr($data, 12));
1524 sendlog("z $path_info");
1526 sendlog("f $path_info");
1528 $data = "${replyid}0001$dataid".sprintf("%08x%08x", length($spath_info), length($data)).$spath_info.$data.Digest::MD5::md5_hex($data);
1529 reply($data, "Content-type: application/octet-stream");
1534 my $deltaintro = '';
1536 my $sendrpm = exists($cgi->{'withrpm'}) ? 1 : 0;
1538 if ($cgi->{'have'}) {
1540 for (split(',', $cgi->{'havealso'} ? "$cgi->{'have'},$cgi->{'havealso'}" : $cgi->{'have'})) {
1541 die("bad have parameter\n") if (length($_) != 32 && length($_) != 64) || /[^0-9a-f]/;
1542 $have2{substr($_, -32, 32)} = 1;
1544 my @info = rpminfo_f(*F, $spath_info);
1545 die("$spath_info: bad info\n") unless @info;
1546 # seek needed because of perl's autoflush when forking
1547 seek(F, $extracto, 0);
1548 # only sysread after this!
1549 defined(sysseek(F, $extracto, 0)) || die("sysseek: $!\n");
1550 $path_info .= " ($info[2])" if $extracto;
1551 my $info = $info[0];
1552 my $info1 = substr($info, 0, 32);
1553 my $info2 = substr($info, 32, 32);
1554 if ($have2{$info2}) {
1556 # switch to real rpm
1557 extractrpm(*F, $extracto, $extractl);
1561 # identical payload, create sign only delta
1562 # sendlog("$path_info: makedeltarpm sign only");
1563 my ($out, $err) = runprg(*F, undef, $makedeltarpm, '-u', '-r', '-', '-');
1564 die("makedeltarpm failed: $err\n") if $err;
1565 $deltaintro .= sprintf("1%03x%08x$info2$info1$info2%08x", $s[2] & 07777, $s[9], length($out));
1569 $sendrpm = 0; # no need to send full rpm in this case
1570 } elsif (!exists($cgi->{'nocomplexdelta'})) {
1571 # ok, lets see if we can build a chain from info2 back to have2
1579 my $deltadir = "$root$path_info";
1580 if ($path_info ne '') {
1581 $deltadir =~ s/[^\/]+$//;
1582 $deltadir =~ s/\/$//;
1583 while ($deltadir ne $root) {
1584 last if -d "$deltadir/drpmsync/deltas";
1585 $deltadir =~ s/[^\/]+$//;
1586 $deltadir =~ s/\/$//;
1589 $deltadir = "$deltadir/drpmsync/deltas/$dpn";
1591 if (length($cgi->{'have'}) == 64 && -f "$deltadir/$cgi->{'have'}$info2") {
1592 @solution = ("$deltadir/$cgi->{'have'}$info2");
1594 my @deltadirs = ( $deltadir );
1595 push @deltadirs, map {"$_/$dpn"} getdeltadirs($tree->{'deltadirs'}, $spath_info) if $tree->{'deltadirs'};
1596 @solution = solve(\%have2, $info2, @deltadirs);
1600 goto lost_delta if ! -e $_;
1601 die("bad deltarpm: $_\n") if ! -f _;
1602 if (!exists($cgi->{'uncombined'}) && !$tree->{'no_combine'}) {
1603 $dsize = -s _ if (-s _) > $dsize;
1608 my $maxdeltasize = $cgi->{'maxdeltasize'};
1609 $maxdeltasize = $tree->{'maxdeltasize'} if defined($tree->{'maxdeltasize'}) && (!defined($maxdeltasize) || $maxdeltasize > $tree->{'maxdeltasize'});
1610 if (defined($maxdeltasize)) {
1612 $flen = $extractl if defined $extractl;
1613 @solution = () if $dsize >= ($flen * $maxdeltasize) / 100;
1615 my $maxdeltasizeabs = $cgi->{'maxdeltasizeabs'};
1616 $maxdeltasizeabs = $tree->{'maxdeltasizeabs'} if defined($tree->{'maxdeltasizeabs'}) && (!defined($maxdeltasizeabs) || $maxdeltasizeabs > $tree->{'maxdeltasizeabs'});
1617 @solution = () if defined($maxdeltasizeabs) && $dsize >= $maxdeltasizeabs;
1619 # sendlog("$path_info: solution @solution");
1621 $key = scalar(@solution) if @solution > 1;
1623 for my $dn (@solution) {
1625 next if @combine < @solution && !exists($cgi->{'uncombined'}) && !$tree->{'no_combine'};
1626 my @ds = stat($combine[0]);
1627 goto lost_delta if !@ds || ! -f _;
1629 if ($dn eq $solution[-1] && substr($dn, -64, 32) ne $info1) {
1630 # sendlog("$path_info: combinedeltarpm -S @combine");
1632 # switch to real rpm
1633 extractrpm(*F, $extracto, $extractl);
1637 ($out, $err) = runprg(*F, undef, $combinedeltarpm, '-S', '-', @combine, '-');
1638 defined(sysseek(F, 0, 0)) || die("sysseek: $!\n");
1639 substr($combine[-1], -64, 32) = $info1 unless $err;
1641 } elsif (@combine > 1) {
1642 # sendlog("$path_info: combinedeltarpm @combine");
1643 ($out, $err) = runprg(undef, undef, $combinedeltarpm, @combine, '-');
1645 # sendlog("$path_info: readfile @combine");
1646 ($out, $err) = readfile($dn);
1649 goto lost_delta if grep {! -f $_} @combine;
1651 sendlog("! $path_info $err");
1652 %have2 = (); # try without deltas
1655 $deltaintro .= sprintf("1%03x%08x".substr($combine[0], -96, 32).substr($combine[-1], -64, 64)."%08x", $ds[2] & 07777, $ds[9], length($out));
1660 $key .= $deltanum if $deltanum != 1;
1664 if (exists($cgi->{'deltaonly'}) && !$deltanum) {
1665 sendlog("O $path_info");
1666 my $data = "${replyid}0001NODR".sprintf("%08x", length($spath_info)).'00000000'.$spath_info;
1667 reply($data, "Content-type: application/octet-stream");
1670 $sendrpm = 1 if !$deltanum;
1671 $key .= 'r' if $sendrpm;
1672 $key = '?' if $key eq '';
1673 sendlog("$key $path_info");
1676 $flen = $extractl if defined $extractl;
1677 if ($flen > 100000 || defined($extractl)) {
1678 my $data = sprintf("1%03x%08x", $s[2] & 07777, $s[9]);
1679 $data .= sprintf("%08x%08x", $deltanum, $sendrpm).$deltaintro.$deltadata;
1680 my $ctx = Digest::MD5->new;
1682 $data = "${replyid}0001RPM ".sprintf("%08x%08x", length($spath_info), length($data) + $flen).$spath_info.$data;
1683 replystream(*F, $flen, $data, $ctx, "Content-type: application/octet-stream");
1690 while ((sysread(F, $rdata, 4096, length($rdata)) || 0) == 4096) {}
1692 my $data = sprintf("1%03x%08x", $s[2] & 07777, $s[9]);
1693 $data .= sprintf("%08x%08x", $deltanum, $sendrpm).$deltaintro.$deltadata.$rdata;
1695 $data = "${replyid}0001RPM ".sprintf("%08x%08x", length($spath_info), length($data)).$spath_info.$data.Digest::MD5::md5_hex($data);
1696 reply($data, "Content-type: application/octet-stream");
1701 if ($::ENV{'REQUEST_METHOD'} || (@ARGV && ($ARGV[0] eq '-s' || $ARGV[0] eq '-S'))) {
1704 my $request_method = $::ENV{'REQUEST_METHOD'};
1705 if ($request_method) {
1706 my $query_string = $::ENV{'QUERY_STRING'};
1707 my $script_name = $::ENV{'SCRIPT_NAME'};
1708 my $path_info = $::ENV{'PATH_INFO'};
1709 my $remote_addr = $::ENV{'REMOTE_ADDR'};
1710 if ($request_method eq 'POST') {
1712 read(STDIN, $query_string, 0 + $::ENV{'CONTENT_LENGTH'});
1715 parse_cgi(\%cgi, $query_string);
1716 my $config = $::ENV{'DRPMSYNC_CONFIG'};
1717 readconfig_server($config);
1718 serve_request(\%cgi, $path_info, $script_name, $remote_addr, 0);
1721 reply_err($@, \%cgi, $remote_addr);
1724 my $remote_addr = startserver($ARGV[1], $ARGV[0] eq '-S' ? 1 : 0);
1728 my ($path, $query_string, $has_via) = getrequest(\%cgi);
1729 $request_method = 'GET';
1730 parse_cgi(\%cgi, $query_string);
1731 my $keep_ok = !$has_via && exists($cgi{'drpmsync'});
1732 my @mtrees = grep {$path eq $_->{'id'} || substr($path, 0, length($_->{'id'}) + 1) eq "$_->{'id'}/" } sort {length($b->{'id'}) <=> length($a->{'id'})} values %trees;
1733 die("not exported\n") unless @mtrees;
1734 my $script_name = $mtrees[0]->{'id'};
1735 my $path_info = substr($path, length($script_name));
1736 serve_request(\%cgi, $path_info, $script_name, $remote_addr, $keep_ok);
1737 exit(0) unless $keep_ok;
1740 reply_err($@, \%cgi, $remote_addr);
1745 #######################################################################
1747 #######################################################################
1750 my $config_generate_deltas;
1751 my $config_keep_deltas;
1752 my $config_keep_uncombined;
1753 my $config_always_get_rpm;
1754 my @config_generate_delta_compression;
1756 my $config_delta_max_age;
1760 my @config_filter_arch;
1771 my $synchost = Net::Domain::hostfqdn();
1778 sub readconfig_client {
1781 open(CF, '<', $cf) || die("$cf: $!\n");
1786 next if $_ eq '' || /^#/;
1787 my @s = split(' ', $_);
1789 if ($s[0] eq 'source:') {
1791 @config_source = @s;
1792 } elsif ($s[0] eq 'generate_deltas:') {
1793 $config_generate_deltas = ($s[1] && $s[1] =~ /true/i);
1794 } elsif ($s[0] eq 'generate_delta_compression:') {
1795 @config_generate_delta_compression = ();
1796 @config_generate_delta_compression = ('-z', $s[1]) if $s[1];
1797 } elsif ($s[0] eq 'keep_deltas:') {
1798 $config_keep_deltas = ($s[1] && $s[1] =~ /true/i);
1799 } elsif ($s[0] eq 'keep_uncombined:') {
1800 $config_keep_uncombined = ($s[1] && $s[1] =~ /true/i);
1801 } elsif ($s[0] eq 'always_get_rpm:') {
1802 $config_always_get_rpm = ($s[1] && $s[1] =~ /true/i);
1803 } elsif ($s[0] eq 'delta_max_age:') {
1804 $config_delta_max_age = @s > 1 ? $s[1] : undef;
1805 } elsif ($s[0] eq 'timeout:') {
1806 $config_timeout = @s > 1 ? $s[1] : undef;
1807 } elsif ($s[0] eq 'deltarpmpath:') {
1808 my $p = defined($s[1]) ? "$s[1]/" : '';
1809 $makedeltarpm = "${p}makedeltarpm";
1810 $combinedeltarpm = "${p}combinedeltarpm";
1811 $applydeltarpm = "${p}applydeltarpm";
1812 $fragiso = "${p}fragiso";
1813 } elsif ($s[0] eq 'log:') {
1814 $config_recvlog = @s > 1 ? $s[1] : undef;
1815 } elsif ($s[0] eq 'repo:') {
1816 $config_repo = @s > 1 ? $s[1] : undef;
1817 } elsif ($s[0] eq 'exclude:') {
1818 push @config_filter, map {"-$_"} @s;
1819 } elsif ($s[0] eq 'include:') {
1820 push @config_filter, map {"+$_"} @s;
1821 } elsif ($s[0] eq 'exclude_arch:') {
1822 push @config_filter_arch, map {"-$_"} @s;
1823 } elsif ($s[0] eq 'include_arch:') {
1824 push @config_filter_arch, map {"+$_"} @s;
1827 die("$cf: unknown configuration parameter: $s[0]\n");
1830 $config_keep_deltas ||= $config_generate_deltas;
1831 $config_keep_deltas ||= $config_keep_uncombined;
1835 #######################################################################
1840 mkdir_p($1) if $dir =~ /^(.*)\//;
1841 mkdir($dir, 0777) || die("mkdir: $dir: $!\n");
1844 #######################################################################
1847 my @lt = localtime($_[0]);
1850 return sprintf "%04d-%02d-%02d %02d:%02d:%02d", @lt[5,4,3,2,1,0];
1853 #######################################################################
1858 return unless $config_recvlog;
1859 my @lt = localtime(time());
1862 printf RECVLOG "%04d-%02d-%02d %02d:%02d:%02d %s\n", @lt[5,4,3,2,1,0], $str;
1871 #######################################################################
1874 my ($from, $to, $drpm) = @_;
1875 # print "makedeltarpm $from $to\n";
1876 if (substr($drpm, -96, 32) eq substr($drpm, -32, 32)) {
1877 system($makedeltarpm, @config_generate_delta_compression, '-u', '-r', $to, $drpm) && die("makedeltarpm failed\n");
1879 system($makedeltarpm, @config_generate_delta_compression, '-r', $from, $to, $drpm) && die("makedeltarpm failed\n");
1881 die("makedeltarpm did not create delta\n") unless -s $drpm;
1886 my ($job, $from, $to, $extractoff, @deltas) = @_;
1887 my $dn = $deltas[0];
1889 my $ddir = $deltas[0];
1890 $ddir =~ s/\/[^\/]+$//;
1891 my $d1 = $deltas[0];
1892 my $d2 = $deltas[-1];
1893 my @d1s = stat($d1);
1894 die("$d1: $!\n") if !@d1s;
1897 $dn = "$ddir/".substr($d1, 0, 32).substr($d2, 32, 64);
1898 die("combined delta already exists?\n") if -f $dn;
1899 # print "combinedeltarpm @deltas\n";
1900 if (system($combinedeltarpm, @deltas, $dn) || ! -s $dn) {
1901 recvlog_print("! combinedeltarpm @deltas $dn failed");
1905 utime($d1s[9], $d1s[9], $dn);
1907 # print "applydeltarpm $from $dn\n";
1911 if (!open(EXTR, '+<', $to)) {
1912 recvlog_print("! open $to failed: $!");
1916 if (!defined(sysseek(EXTR, $extractoff, 0))) {
1917 recvlog_print("! sysseek $to failed: $!");
1921 (undef, $err) = runprg_job($job, undef, *EXTR, $applydeltarpm, '-r', $from, $dn, '-');
1924 (undef, $err) = runprg_job($job, undef, undef, $applydeltarpm, '-r', $from, $dn, $to);
1927 recvlog_print("! applydeltarpm -r $from $dn $to failed: $err");
1932 $job->{'applydeltas'} = [$from, $dn, $to, @deltas];
1935 if ($config_keep_uncombined || @deltas <= 1) {
1937 unlink($dn) || die("unlink $dn: $!\n");
1941 for my $d (@deltas) {
1942 unlink($d) || die("unlink $d: $!\n");
1947 sub applydeltas_finish {
1949 die("job not running\n") unless $job && $job->{'applydeltas'};
1950 my ($from, $dn, $to, @deltas) = @{$job->{'applydeltas'}};
1951 delete $job->{'applydeltas'};
1953 (undef, $err) = runprg_finish($job);
1955 recvlog_print("! applydeltarpm -r $from $dn $to failed: $err");
1959 if ($config_keep_uncombined || @deltas <= 1) {
1961 unlink($dn) || die("unlink $dn: $!\n");
1965 for my $d (@deltas) {
1966 unlink($d) || die("unlink $d: $!\n");
1973 return unless $runningjob;
1974 my $job = $runningjob;
1976 return if $job->{'wip'} ne $pn;
1979 my @args = @{$job->{'finishargs'}};
1980 delete $job->{'finishargs'};
1981 $job->{'finish'}->(@args);
1985 #######################################################################
1987 #######################################################################
1992 open(F, '<', "$config_repo/$dpn") || return ();
1993 my $k2 = substr($k, 32, 32);
1996 while (defined($l = <F>)) {
1998 my @l = split(' ', $l, 3);
2001 } elsif (substr($l[0], 32, 32) eq $k2) {
2008 return (@r1, @r2, @r3);
2015 for my $r (splice(@r)) {
2016 if ($r->[2] =~ /^(.*)@([0-9a-f]{10}:[0-9a-f]{8}$)/) {
2021 push @r, $r if @s && $r->[1] eq "$s[9]/$s[7]";
2027 my ($r, $bdir, $to, $extractoff) = @_;
2029 my $d = "$bdir/$to";
2034 if ($r->[2] =~ /^(.*)@([0-9a-f]{2})([0-9a-f]{8}):([0-9a-f]{8}$)/) {
2036 open(F, '<', $iso) || return undef;
2038 if (!@s || $r->[1] ne "$s[9]/$s[7]") {
2043 if (!$len || !defined(sysseek(F, hex($2) * 4294967296 + hex($3), 0))) {
2048 open(F, '<', $r->[2]) || return undef;
2050 if (!@s || $r->[1] ne "$s[9]/$s[7]") {
2056 if (!open(OF, '+<', $d)) {
2060 if (!defined(sysseek(OF, $extractoff, 0))) {
2066 if (!open(OF, '>', $d)) {
2071 my @info = cprpm(*F, *OF, 1, $len);
2078 if (@info != 3 || $info[0] ne $r->[0]) {
2087 return [ $to, "$s[9]/$s[7]/$s[1]", sprintf("1%03x%08x", ($s[2] & 07777), $s[9]), @info ];
2093 return unless open(F, '-|', $fragiso, 'listiso', $fn);
2095 return unless close(F);
2097 for my $f (@frags) {
2098 my @f = split(' ', $f, 3);
2099 repo_add("$fn\@$f[0]", [ "$fn\@$f[0]", $d->[1], $d->[2], $f[1], undef, $f[2] ] );
2106 return if $fn =~ m!drpmsync/wip.*/!;
2108 repo_add_iso($fn, $d) if $fn =~ /(?<!\.delta)\.iso$/i;
2111 return if $fn =~ /[\000-\037]/;
2112 return if $d->[5] =~ /[\000-\037\/]/ || length($d->[5]) < 3;
2116 $nlid =~ s/\/[^\/]*$//;
2118 $nl = "$d->[3] $nlid $fn" if $nlid;
2120 $kill = $1 if $fn =~ /^(.*)@[0-9a-f]{2}[0-9a-f]{8}:[0-9a-f]{8}$/;
2121 $kill = $fn if !$nlid && $fn =~ /(?<!\.delta)\.iso$/i;
2123 if (!sysopen(OLD, "$config_repo/$d->[5]", POSIX::O_RDWR|POSIX::O_CREAT, 0666)) {
2124 if (!sysopen(OLD, "$config_repo/$d->[5]", POSIX::O_RDONLY)) {
2125 warn("$config_repo/$d->[5]: $!\n");
2129 if (!flock(OLD, LOCK_EX)) {
2130 warn("$config_repo/$d->[5]: flock: $!\n");
2133 if (!(stat(OLD))[3]) {
2139 while ((sysread(OLD, $old, 8192, length($old)) || 0) == 8192) {};
2140 for my $l (split("\n", $old)) {
2141 if ($nl && $l eq $nl) {
2145 my @lf = split(' ', $l);
2146 next if $lf[2] =~ /^(.*)@[0-9a-f]{2}[0-9a-f]{8}:[0-9a-f]{8}$/ && $kill eq $1 && $lf[1] ne $nlid;
2148 next if (split(' ', $l))[2] eq $fn;
2155 } elsif ($old eq $new) {
2159 if (!sysopen(NEW, "$config_repo/$d->[5].new", POSIX::O_WRONLY|POSIX::O_CREAT|POSIX::O_TRUNC, 0666)) {
2160 warn("$config_repo/$d->[5].new open: $!\n");
2164 if ((syswrite(NEW, $new) || 0) != length($new) || !close(NEW)) {
2165 warn("$config_repo/$d->[5].new write: $!\n");
2168 unlink("$config_repo/$d->[5].new");
2171 if (!rename("$config_repo/$d->[5].new", "$config_repo/$d->[5]")) {
2172 warn("$config_repo/$d->[5] rename: $!\n");
2174 unlink("$config_repo/$d->[5].new");
2186 return if $fn !~ /(?<!\.delta)\.iso$/i;
2190 opendir(DIR, $config_repo) || return;
2191 my @ds = grep {$_ ne '.' && $_ ne '..' && !/\..*\.new$/} readdir(DIR);
2194 repo_add($fn, [undef, '', undef, undef, undef, $ds]);
2197 repo_add($fn, [undef, '', undef, undef, undef, $dir]);
2205 opendir(DIR, $config_repo) || return;
2206 my @ds = grep {$_ ne '.' && $_ ne '..' && !/\..*\.new$/} readdir(DIR);
2216 if (!sysopen(OLD, "$config_repo/$d", POSIX::O_RDWR|POSIX::O_CREAT, 0666)) {
2217 if (!sysopen(OLD, "$config_repo/$d", POSIX::O_RDONLY)) {
2218 warn("$config_repo/$d: $!\n");
2222 if (!flock(OLD, LOCK_EX)) {
2223 warn("$config_repo/$d: flock: $!\n");
2226 if (!(stat(OLD))[3]) {
2232 while ((sysread(OLD, $old, 8192, length($old)) || 0) == 8192) {};
2233 for my $l (split("\n", $old)) {
2234 my @lf = split(' ', $l);
2236 if ($lf[2] =~ /^(.*)@[0-9a-f]{2}[0-9a-f]{8}:[0-9a-f]{8}$/) {
2241 next if !@s || "$s[9]/$s[7]" ne $lf[1];
2248 if (!sysopen(NEW, "$config_repo/$d.new", POSIX::O_WRONLY|POSIX::O_CREAT|POSIX::O_TRUNC, 0666)) {
2249 warn("$config_repo/$d.new open: $!\n");
2253 if ((syswrite(NEW, $new) || 0) != length($new) || !close(NEW)) {
2254 warn("$config_repo/$d.new write: $!\n");
2257 unlink("$config_repo/$d.new");
2260 if (!rename("$config_repo/$d.new", "$config_repo/$d")) {
2261 warn("$config_repo/$d rename: $!\n");
2263 unlink("$config_repo/$d.new");
2269 #######################################################################
2277 $dir =~ s/[^\/]+$//;
2279 return unless $dir ne '';
2280 my $d = $files{$dir};
2281 return unless $d && $d->[2] =~ /^0/;
2282 $d->[2] = substr($d->[2], 0, 4)."ffffffff";
2286 ##################################################################
2289 my $net_start_rvbytes;
2290 my $net_recv_bytes = 0;
2291 my $net_spent_time = 0;
2299 if ($proto eq 'file') {
2300 *get_syncfiles = \&file_get_syncfiles;
2301 *get_update = \&file_get_update;
2302 *send_fin = \&file_send_fin;
2303 } elsif ($proto eq 'drpmsync') {
2304 *get_syncfiles = \&drpmsync_get_syncfiles;
2305 *get_update = \&drpmsync_get_update;
2306 *send_fin = \&drpmsync_send_fin;
2307 } elsif ($proto eq 'rsync') {
2308 *get_syncfiles = \&rsync_get_syncfiles;
2309 *get_update = \&rsync_get_update;
2310 *send_fin = \&rsync_send_fin;
2311 } elsif ($proto eq 'null') {
2312 *get_syncfiles = sub {return ()};
2313 *get_update = sub {die;};
2316 die("unsupported protocol: $proto\n");
2320 #######################################################################
2322 #######################################################################
2324 sub file_get_syncfiles {
2325 my $norecurse = shift;
2327 my @oldfiles = @files;
2328 my @oldcache = %cache;
2329 my $oldcachehits = $cachehits;
2330 my $oldcachemisses = $cachemisses;
2332 $cachehits = $cachemisses = 0;
2333 readcache("$syncroot/drpmsync/cache");
2334 findfiles($syncroot, '', 0, $norecurse);
2335 my @syncfiles = @files;
2338 $cachehits = $oldcachehits;
2339 $cachemisses = $oldcachemisses;
2340 $newstamp1 = $newstamp2 = sprintf("%08x", time);
2344 sub file_get_update {
2345 my ($dto, $tmpnam, $reqext, $rextract) = @_;
2347 die("rextract in FILE transport\n") if $rextract;
2348 my @s = lstat("$syncroot/$dto->[0]");
2349 return 'GONE' unless @s;
2354 my $lc = readlink("$syncroot/$dto->[0]");
2355 return 'GONE' unless defined $lc;
2356 symlink($lc, $tmpnam) || die("symlink: $!\n");
2357 @info = linkinfo($tmpnam);
2364 open(F, '<', "$syncroot/$dto->[0]") || return 'GONE';
2366 die("stat: $!\n") unless @s;
2367 open(NF, '>', $tmpnam) || die("$tmpnam: $!\n");
2368 if ($dto->[0] !~ /\.[sr]pm$/) {
2369 @info = cpfile(*F, *NF);
2371 @info = cprpm(*F, *NF);
2373 defined(sysseek(F, 0, 0)) || die("sysseek: $!\n");
2375 open(NF, '>', $tmpnam) || die("$tmpnam: $!\n");
2376 @info = cpfile(*F, *NF);
2380 close(NF) || die("$tmpnam: $!\n");
2381 fixmodetime($tmpnam, sprintf("1%03x%08x", ($s[2] & 07777), $s[9]));
2383 @s = lstat($tmpnam);
2384 die("$tmpnam: $!\n") unless @s;
2386 return 'RPM ', [ $dto->[0], "$s[9]/$s[7]/$s[1]", sprintf("1%03x%08x", ($s[2] & 07777), $s[9]), @info ];
2388 return 'FILE', [ $dto->[0], "$s[9]/$s[7]/$s[1]", sprintf("$type%03x%08x", ($s[2] & 07777), $s[9]), @info ];
2396 #######################################################################
2398 #######################################################################
2406 my $r = sysread(SS, $ret, $len, length($ret));
2407 die("read error") unless $r;
2409 die("read too much") if $r < 0;
2416 my ($var, $len) = @_;
2417 $len = length($var) unless defined $len;
2419 (syswrite(SS, $var, $len) || 0) == $len || die("syswrite: $!\n");
2422 my $rsync_muxbuf = '';
2428 #print "muxread $len\n";
2429 while(length($rsync_muxbuf) < $len) {
2430 #print "muxbuf len now ".length($muxbuf)."\n";
2432 $tag = sread(*SS, 4);
2433 $tag = unpack('V', $tag);
2434 my $tlen = 0+$tag & 0xffffff;
2437 $rsync_muxbuf .= sread(*SS, $tlen);
2440 if ($tag == 8 || $tag == 9) {
2441 my $msg = sread(*SS, $tlen);
2442 die("$msg\n") if $tag == 8;
2443 print "info: $msg\n";
2446 die("unknown tag: $tag\n");
2448 my $ret = substr($rsync_muxbuf, 0, $len);
2449 $rsync_muxbuf = substr($rsync_muxbuf, $len);
2454 my $rsync_checksum_seed;
2457 sub rsync_get_syncfiles {
2458 my $norecurse = shift;
2460 my $user = $syncuser;
2461 my $password = $syncpassword;
2462 if (!defined($have_md4)) {
2465 require Digest::MD4;
2469 $syncroot =~ s/^\/+//;
2470 my $module = $syncroot;
2471 $module =~ s/\/.*//;
2472 my $tcpproto = getprotobyname('tcp');
2473 socket(S, PF_INET, SOCK_STREAM, $tcpproto) || die("socket: $!\n");
2474 connect(S, sockaddr_in($syncport, $syncaddr)) || die("connect: $!\n");
2475 my $hello = "\@RSYNCD: 28\n";
2478 sysread(S, $buf, 4096);
2479 die("protocol error [$buf]\n") if $buf !~ /^\@RSYNCD: (\d+)\n/s;
2480 $rsync_protocol = $1;
2481 $rsync_protocol = 28 if $rsync_protocol > 28;
2482 swrite(*S, "$module\n");
2484 sysread(S, $buf, 4096);
2485 die("protocol error [$buf]\n") if $buf !~ s/\n//s;
2486 last if $buf eq "\@RSYNCD: OK";
2487 die("$buf\n") if $buf =~ /^\@ERROR/s;
2488 if ($buf =~ /^\@RSYNCD: AUTHREQD /) {
2489 die("'$module' needs authentification, but Digest::MD4 is not installed\n") unless $have_md4;
2490 $user = "nobody" if !defined($user) || $user eq '';
2491 $password = '' unless defined $password;
2492 my $digest = "$user ".Digest::MD4::md4_base64("\0\0\0\0$password".substr($buf, 18))."\n";
2493 swrite(*S, $digest);
2497 my @args = ('--server', '--sender', '-rl');
2498 push @args, '--exclude=/*/*' if $norecurse;
2499 for my $arg (@args, '.', "$syncroot/.", '') {
2500 swrite(*S, "$arg\n");
2502 $rsync_checksum_seed = unpack('V', sread(*S, 4));
2503 swrite(*S, "\0\0\0\0");
2512 $flags = muxread(*S, 1);
2513 $flags = ord($flags);
2514 # printf "flags = %02x\n", $flags;
2515 last if $flags == 0;
2516 $flags |= ord(muxread(*S, 1)) << 8 if $rsync_protocol >= 28 && ($flags & 0x04) != 0;
2517 my $l1 = $flags & 0x20 ? ord(muxread(*S, 1)) : 0;
2518 my $l2 = $flags & 0x40 ? unpack('V', muxread(*S, 4)) : ord(muxread(*S, 1));
2519 $name = substr($name, 0, $l1).muxread(*S, $l2);
2520 my $len = unpack('V', muxread(*S, 4));
2521 if ($len == 0xffffffff) {
2522 $len = unpack('V', muxread(*S, 4));
2523 my $len2 = unpack('V', muxread(*S, 4));
2524 $len += $len2 * 4294967296;
2526 $mtime = unpack('V', muxread(*S, 4)) unless $flags & 0x80;
2527 $mode = unpack('V', muxread(*S, 4)) unless $flags & 0x02;
2528 my $id = "$mtime/$len/";
2530 my $mmode = $mode & 07777;
2531 if (($mode & 0170000) == 0100000) {
2534 } elsif (($mode & 0170000) == 0040000) {
2536 } elsif (($mode & 0170000) == 0120000) {
2538 my $ln = muxread(*S, unpack('V', muxread(*S, 4)));
2539 @info = (Digest::MD5::md5_hex($ln));
2542 print "$name: unknown mode: $mode\n";
2545 push @filelist, [$name, $id, sprintf("%04x%08x", $mmode, $mtime), @info];
2547 my $io_error = unpack('V', muxread(*S, 4));
2548 @filelist = sort {$a->[0] cmp $b->[0]} @filelist;
2550 $_->[1] .= $fidx++ for @filelist;
2551 $newstamp1 = $newstamp2 = sprintf("%08x", time);
2552 return grep {$_->[0] ne '.'} @filelist;
2555 sub rsync_adapt_filelist {
2564 next if @$_ == 3 || $_->[3] ne 'x';
2568 my @info = @{$c{$i}};
2569 splice(@info, 0, 3);
2570 splice(@$_, 3, 1, @info);
2574 sub rsync_get_update {
2575 my ($dto, $tmpnam, $reqext, $rextract) = @_;
2577 die("rextract in RSYNC transport\n") if $rextract;
2578 my $fidx = $dto->[1];
2579 if ($dto->[2] =~ /^2/) {
2580 $fidx =~ s/^[^\/]*\/[^\/]*\///s;
2581 $fidx =~ s/\/[^\/]*$//s;
2582 symlink($fidx, $tmpnam) || die("symlink: $!\n");
2583 my @s = lstat($tmpnam);
2584 die("$tmpnam: $!\n") unless @s;
2585 return 'FILE', [ $dto->[0], "$s[9]/$s[7]/$s[1]", sprintf("2%03x%08x", ($s[2] & 07777), $s[9]), linkinfo($tmpnam) ];
2588 swrite(*S, pack('V', $fidx));
2589 swrite(*S, ("\0\0\0\0" x ($rsync_protocol >= 27 ? 4 : 3)));
2590 my $rfidx = unpack('V', muxread(*S, 4));
2591 die("rsync file mismatch $rfidx - $fidx\n") if $rfidx != $fidx;
2592 my $sumhead = muxread(*S, 4 * ($rsync_protocol >= 27 ? 4 : 3));
2594 $md4ctx = Digest::MD4->new if $have_md4;
2595 $md4ctx->add(pack('V', $rsync_checksum_seed)) if $have_md4;
2597 open(OF, '>', $tmpnam) || die("$tmpnam: $!\n");
2599 my $l = unpack('V', muxread(*S, 4));
2601 die("received negative token\n") if $l < 0;
2602 my $chunk = muxread(*S, $l);
2603 $md4ctx->add($chunk) if $have_md4;
2604 syswrite(OF, $chunk) == $l || die("syswrite: $!\n");
2606 close(OF) || die("close: $!\n");
2607 my $md4sum = muxread(*S, 16);
2609 die("data corruption on net\n") if unpack("H32", $md4sum) ne $md4ctx->hexdigest();
2611 fixmodetime($tmpnam, $dto->[2]);
2612 my @s = lstat($tmpnam);
2613 die("$tmpnam: $!\n") unless @s;
2614 if ($dto->[0] =~ /\.[sr]pm$/) {
2615 return 'RPM ', [ $dto->[0], "$s[9]/$s[7]/$s[1]", sprintf("1%03x%08x", ($s[2] & 07777), $s[9]), rpminfo($tmpnam) ];
2617 return 'FILE', [ $dto->[0], "$s[9]/$s[7]/$s[1]", sprintf("1%03x%08x", ($s[2] & 07777), $s[9]), fileinfo($tmpnam) ];
2621 sub rsync_send_fin {
2622 swrite(*S, pack('V', -1)); # switch to phase 2
2623 swrite(*S, pack('V', -1)); # switch to phase 3
2624 if ($rsync_protocol >= 24) {
2625 swrite(*S, pack('V', -1)); # goodbye
2630 #######################################################################
2632 #######################################################################
2637 local (*SOCK) = shift;
2639 while (length($ans) < $l) {
2640 die("received truncated answer\n") if !sysread(SOCK, $ans, $l - length($ans), length($ans));
2646 return copytofile_seek($_[0], $_[1], 0, $_[2], $_[3], $_[4]);
2649 sub copytofile_seek {
2650 local (*SOCK) = shift;
2651 my ($fn, $extractoff, $ans, $l, $ctx) = @_;
2655 open(FD, '+<', $fn) || die("$fn: $!\n");
2656 defined(sysseek(FD, $extractoff, 0)) || die("sysseek: $!\n");
2658 open(FD, '>', $fn) || die("$fn: $!\n");
2660 my $al = length($ans);
2662 die("$fn: write error\n") if syswrite(FD, $ans, $l) != $l;
2663 die("$fn: write error\n") unless close(FD);
2664 $ctx->add(substr($ans, 0, $l));
2665 return substr($ans, $l);
2668 die("$fn: write error\n") if syswrite(FD, $ans, $al) != $al;
2674 die("received truncated answer\n") if !sysread(SOCK, $ans, $l > 8192 ? 8192 : $l, 0);
2676 die("$fn: write error\n") if syswrite(FD, $ans, $al) != $al;
2681 die("$fn: write error\n") unless close(FD);
2686 return if $sock_isopen;
2687 my $tcpproto = getprotobyname('tcp');
2688 socket(S, PF_INET, SOCK_STREAM, $tcpproto) || die("socket: $!\n");
2689 connect(S, sockaddr_in($syncport, $syncaddr)) || die("connect: $!\n");
2694 local (*SOCK) = shift;
2695 my ($ans, $ctx, $id) = @_;
2698 $ans = tolength(*SOCK, $ans, 32);
2699 my $netmd5 = substr($ans, 0, 32);
2700 die("network error: bad md5 digest\n") if $netmd5 =~ /[^a-f0-9]/;
2701 my $md5 = $ctx->hexdigest;
2702 die("network error: $md5 should be $netmd5\n") if $md5 ne $netmd5;
2703 $ans = substr($ans, 32);
2705 alarm(0) if $config_timeout;
2706 if ($have_time_hires && defined($net_start_tv)) {
2707 $net_spent_time += Time::HiRes::tv_interval($net_start_tv);
2708 $net_recv_bytes += $rvbytes - $net_start_rvbytes;
2709 $net_start_rvbytes = $rvbytes;
2710 undef $net_start_tv;
2712 if ($id && ($id ne 'DRPMSYNK' || length($ans))) {
2719 sub drpmsync_get_syncfiles {
2720 my ($norecurse, $filelist_data) = @_;
2723 if (defined($filelist_data)) {
2724 $data = $filelist_data;
2725 goto use_filelist_data;
2727 alarm($config_timeout) if $config_timeout;
2728 opensock() unless $sock_isopen;
2730 $opts .= '&zlib' if $have_zlib;
2731 $opts .= '&norecurse' if $norecurse;
2733 my @fc = @filter_comp;
2737 $r =~ s/([\000-\040<>\"#&\+=%[\177-\377])/sprintf("%%%02X",ord($1))/sge;
2738 $opts .= "&filter=$r";
2741 if (@filter_arch_comp) {
2742 my @fc = @filter_arch_comp;
2746 $r =~ s/([\000-\040<>\"#&\+=%[\177-\377])/sprintf("%%%02X",ord($1))/sge;
2747 $opts .= "&filter_arch=$r";
2750 my $query = "GET $esyncroot/drpmsync/contents?drpmsync$opts HTTP/1.0\r\nHost: $synchost\r\n\r\n";
2751 $txbytes += length($query);
2752 (syswrite(S, $query, length($query)) || 0) == length($query) || die("network write failed\n");
2755 die("received truncated answer\n") if !sysread(S, $ans, 1024, length($ans));
2756 } while ($ans !~ /\n\r?\n/s);
2757 $rvbytes += length($ans);
2758 $ans =~ /\n\r?\n(.*)$/s;
2759 $rvbytes -= length($1);
2760 $ans = tolength(*S, $1, 32);
2761 my $id = substr($ans, 0, 8);
2762 die("received bad answer\n") if $id ne 'DRPMSYNC' && $id ne 'DRPMSYNK';
2763 my $vers = hex(substr($ans, 8, 4));
2764 die("answer has bad version\n") if $vers != 1;
2765 my $type = substr($ans, 12, 4);
2766 if ($type eq 'ERR ') {
2767 my $anssize = hex(substr($ans, 24, 8));
2768 $ans = tolength(*S, $ans, 32 + $anssize);
2769 die("remote error: ".substr($ans, 32, $anssize)."\n");
2771 die("can only sync complete trees\n") if $type eq 'GONE';
2772 die("server send wrong answer\n") if $type ne 'SYNC' && $type ne 'SYNZ';
2773 die("server send bad answer\n") if hex(substr($ans, 16, 8));
2774 my $anssize = hex(substr($ans, 24, 8));
2775 die("answer is too short\n") if $anssize < 28;
2776 $rvbytes += 32 + $anssize + 32;
2777 $ans = substr($ans, 32);
2778 $ans = tolength(*S, $ans, $anssize);
2779 $data = substr($ans, 0, $anssize);
2780 $ans = substr($ans, $anssize);
2781 my $ctx = Digest::MD5->new;
2783 $ans = finishreq(*S, $ans, $ctx, $id);
2784 $data = substr($data, 12);
2785 if ($type eq 'SYNZ') {
2786 die("cannot uncompress\n") unless $have_zlib;
2787 $data = Compress::Zlib::uncompress($data);
2790 my $filesnum = unpack('N', $data);
2791 # work around perl 5.8.0 bug, where "(w/a*w/a*)*" does not work
2792 my @data = unpack("x[N]".("w/a*w/a*" x ($filesnum + 1)), $data);
2793 die("bad tree start\n") if @data < 2 || length($data[1]) != 8;
2794 die("bad number of file entries\n") if @data != 2 * $filesnum + 2;
2795 $synctree = shift @data;
2796 $synctree .= '/' if $synctree ne '/';
2797 ($newstamp1, $newstamp2) = unpack('H8H8', shift @data);
2800 my ($name, $hex) = splice @data, 0, 2;
2801 die("bad file name in list: $name\n") if "/$name/" =~ /\/(\.|\.\.|)\//;
2802 if (length($hex) == 6) {
2803 push @syncfiles, [ $name, undef, unpack('H12', $hex) ];
2804 } elsif (length($hex) == 6 + 16) {
2805 push @syncfiles, [ $name, undef, unpack('H12H32', $hex) ];
2806 } elsif (length($hex) >= 6 + 32 + 4) {
2807 my @l = ($name, undef, unpack('H12H64H8a*', $hex));
2808 die("bad name.arch in file list: $l[5]\n") if $l[5] eq '.' || $l[5] eq '..' || $l[5] =~ /\//;
2809 push @syncfiles, \@l;
2811 die("bad line for $name: $hex\n");
2814 # validate that no entry is listed twice
2818 die("entry $_->[0] is listed twice\n") if exists $ents{$_->[0]};
2820 if ($_->[2] =~ /^0/) {
2822 die("directory $_->[0] has bad data\n") unless @$_ == 3;
2824 die("entry $_->[0] has bad data\n") unless @$_ > 3;
2827 # validate that all files are connected to dirs
2829 next unless /^(.*)\//;
2830 die("entry $_->[0] is not connected\n") unless $dirs{$1};
2835 sub drpmsync_send_fin {
2836 return unless $sock_isopen;
2837 my $query = "GET $esyncroot/drpmsync/closesock?drpmsync HTTP/1.0\r\nHost: $synchost\r\n\r\n";
2838 $txbytes += length($query);
2839 syswrite(S, $query, length($query)) == length($query) || die("network write failed\n");
2844 sub drpmsync_get_update {
2845 my ($dto, $tmpnam, $reqext, $rextract) = @_;
2850 die("bad extract parameter\n") unless $rextract =~ /^([0-9a-fA-F]{2})([0-9a-fA-F]{8}):[0-9a-fA-F]{8}$/;
2851 $extractoff = hex($1) * 4294967296 + hex($2);
2854 my $req = aescape($dto->[0]);
2855 $req = "/$req?drpmsync";
2856 $req .= "&extract=$rextract" if $rextract;
2857 $req .= $reqext if $reqext;
2858 # XXX print "-> $req\n";
2859 alarm($config_timeout) if $config_timeout;
2860 opensock() unless $sock_isopen;
2861 my $query = "GET $esyncroot$req HTTP/1.0\r\nHost: $synchost\r\n\r\n";
2862 $txbytes += length($query);
2863 if (syswrite(S, $query, length($query)) != length($query)) {
2864 die("network write failed\n");
2866 $net_start_tv = [Time::HiRes::gettimeofday()] if $have_time_hires;
2867 $net_start_rvbytes = $rvbytes;
2870 die("received truncated answer\n") if !sysread(S, $ans, 1024, length($ans));
2871 } while ($ans !~ /\n\r?\n/s);
2872 $rvbytes += length($ans);
2873 $ans =~ /\n\r?\n(.*)$/s;
2874 $rvbytes -= length($1);
2875 $ans = tolength(*S, $1, 32);
2876 my $id = substr($ans, 0, 8);
2877 die("received bad answer: $ans\n") if $id ne 'DRPMSYNC' && $id ne 'DRPMSYNK';
2878 my $vers = hex(substr($ans, 8, 4));
2879 die("answer has bad version\n") if $vers != 1;
2880 my $type = substr($ans, 12, 4);
2881 my $namelen = hex(substr($ans, 16, 8));
2882 my $anssize = hex(substr($ans, 24, 8));
2883 if ($anssize == 4294967295) {
2884 $ans = tolength(*S, $ans, 32 + 10);
2885 $anssize = hex(substr($ans, 32, 2)) * 4294967296 + hex(substr($ans, 32 + 2, 8));
2886 $ans = substr($ans, 10);
2888 $rvbytes += 32 + $namelen + $anssize + 32;
2889 if ($type eq 'ERR ') {
2890 $ans = tolength(*S, $ans, 32 + $namelen + $anssize);
2891 return $type , substr($ans, 32 + $namelen, $anssize);
2893 $ans = tolength(*S, $ans, 32 + $namelen);
2894 die("answer does not match request $syncroot/$dto->[0] - $synctree".substr($ans, 32, $namelen)."\n") if "$syncroot/$dto->[0]" ne $synctree.substr($ans, 32, $namelen);
2895 $ans = substr($ans, 32 + $namelen);
2897 if ($type eq 'GONE' || $type eq 'NODR') {
2898 $ans = finishreq(*S, $ans, undef, $id);
2903 $extralen = 12 + 16 if $type eq 'RPM ';
2905 die("answer is too short\n") if $anssize < $extralen;
2906 my $ctx = Digest::MD5->new;
2910 $ans = tolength(*S, $ans, $extralen);
2911 $extra = substr($ans, 0, $extralen);
2912 die("illegal extra block\n") if $extra =~ /[^a-f0-9]/;
2913 if ($type eq 'RPM ') {
2914 $ndrpm = hex(substr($extra, 12, 8));
2915 $nrpm = hex(substr($extra, 12 + 8, 8));
2916 die("more than one rpm?\n") if $nrpm > 1;
2918 $extralen += $ndrpm * (12 + 32 * 3 + 8);
2919 $ans = tolength(*S, $ans, $extralen);
2920 $extra = substr($ans, 0, $extralen);
2921 die("illegal extra block\n") if $extra =~ /[^a-f0-9]/;
2924 $ans = substr($ans, $extralen);
2925 $anssize -= $extralen;
2929 die("unexpected type $type\n") if $rextract && $type ne 'RPM ';
2931 if ($type eq 'FILZ') {
2932 die("cannot uncompress\n") unless $have_zlib;
2933 $ans = tolength(*S, $ans, $anssize);
2934 my $data = substr($ans, 0, $anssize);
2936 $ans = finishreq(*S, substr($ans, $anssize), $ctx, $id);
2937 $data = Compress::Zlib::uncompress($data);
2938 my $datamd5 = Digest::MD5::md5_hex($data);
2939 if ($dto->[2] =~ /^2/) {
2940 symlink($data, $tmpnam) || die("symlink: $!\n");
2942 open(FD, '>', $tmpnam) || die("$tmpnam: $!\n");
2943 die("$tmpnam: write error\n") if (syswrite(FD, $data) || 0) != length($data);
2944 close(FD) || die("$tmpnam: $!\n");
2945 fixmodetime($tmpnam, substr($extra, 0, 12));
2947 my @s = lstat($tmpnam);
2948 die("$tmpnam: $!\n") unless @s;
2949 if ($dto->[2] =~ /^2/) {
2950 $d = [ $dto->[0], "$s[9]/$s[7]/$s[1]", sprintf("2%03x%08x", ($s[2] & 07777), $s[9]), linkinfo($tmpnam) ];
2952 $d = [ $dto->[0], "$s[9]/$s[7]/$s[1]", sprintf("1%03x%08x", ($s[2] & 07777), $s[9]), $datamd5 ];
2954 return ('FILZ', $d);
2955 } elsif ($type eq 'FILE') {
2956 if ($dto->[2] =~ /^2/) {
2957 $ans = tolength(*S, $ans, $anssize);
2958 $ctx->add(substr($ans, 0, $anssize));
2959 symlink(substr($ans, 0, $anssize), $tmpnam) || die("symlink: $!\n");
2960 $ans = substr($ans, $anssize);
2962 $ans = copytofile(*S, $tmpnam, $ans, $anssize, $ctx);
2964 $ans = finishreq(*S, $ans, $ctx, $id);
2965 fixmodetime($tmpnam, substr($extra, 0, 12)) if $dto->[2] !~ /^2/;
2966 my @s = lstat($tmpnam);
2967 die("$tmpnam: $!\n") unless @s;
2968 if ($dto->[2] =~ /^2/) {
2969 $d = [ $dto->[0], "$s[9]/$s[7]/$s[1]", sprintf("2%03x%08x", ($s[2] & 07777), $s[9]), linkinfo($tmpnam) ];
2971 $d = [ $dto->[0], "$s[9]/$s[7]/$s[1]", sprintf("1%03x%08x", ($s[2] & 07777), $s[9]), fileinfo($tmpnam) ];
2973 return ('FILE', $d);
2974 } elsif ($type eq 'FISO') {
2975 $ans = copytofile(*S, "$tmpnam.fiso", $ans, $anssize, $ctx);
2976 $ans = finishreq(*S, $ans, $ctx, $id);
2977 return 'FISO', [ $tmpnam, undef, substr($extra, 0, 12) ];
2978 } elsif ($type eq 'RPM ') {
2979 $sabytes -= $anssize;
2981 die("more than one rpm?\n") if $nrpm > 1;
2982 die("nothing to do?\n") if $nrpm == 0 && $ndrpm == 0;
2984 my $dextra = substr($extra, 12 + 16);
2985 while ($ndrpm > 0) {
2987 $delta =~ s/[^\/]*$//;
2988 $delta .= substr($dextra, 12, 32 * 3);
2989 # end old job if we have a delta conflict
2990 checkjob() if $runningjob && -e $delta;
2991 my $size = hex(substr($dextra, 12 + 3 * 32, 8));
2992 die("delta rpm bigger than answer? $size > $anssize\n") if $size > $anssize;
2993 $ans = copytofile(*S, $delta, $ans, $size, $ctx);
2995 fixmodetime($delta, substr($dextra, 0, 12));
2996 $dextra = substr($dextra, 12 + 32 * 3 + 8);
2997 push @deltas, $delta;
3001 $ans = copytofile_seek(*S, $tmpnam, $extractoff, $ans, $anssize, $ctx);
3002 $ans = finishreq(*S, $ans, $ctx, $id);
3003 return 'RPM ', [ $dto->[0] ], @deltas if $rextract;
3004 fixmodetime($tmpnam, substr($extra, 0, 12));
3005 my @s = stat($tmpnam);
3006 die("$tmpnam: $!\n") unless @s;
3008 $d = [ $dto->[0], "$s[9]/$s[7]/$s[1]", sprintf("1%03x%08x", ($s[2] & 07777), $s[9]), rpminfo($tmpnam) ];
3010 die("junk at end of answer\n") if $anssize;
3011 $ans = finishreq(*S, $ans, $ctx, $id);
3012 $d = [ undef, undef, substr($extra, 0, 12) ];
3014 return 'RPM ', $d, @deltas;
3016 die("received strange answer type: $type\n");
3021 #######################################################################
3023 #######################################################################
3025 sub save_or_delete_deltas {
3026 my ($bdir, $dpn, @deltas) = @_;
3028 if (!$config_keep_deltas || !$dpn) {
3029 for my $delta (@deltas) {
3030 unlink($delta) || die("unlink $delta: $!\n");
3034 my $ddir = "$bdir/drpmsync/deltas/$dpn";
3036 for my $delta (@deltas) {
3039 if (substr($dn, 0, 32) eq substr($dn, 64, 32)) {
3040 # print("detected signature-only delta\n");
3042 opendir(DDIR, "$ddir") || die("opendir $ddir: $!\n");
3043 my @dh = grep {$_ =~ /^[0-9a-f]{96}$/} readdir(DDIR);
3045 @dh = grep {substr($_, 64, 32) eq substr($dn, 64, 32)} @dh;
3046 @dh = grep {substr($_, 32, 32) ne substr($dn, 32, 32)} @dh;
3048 # recvlog_print("! $dh");
3049 my $nn = substr($dh, 0, 32).substr($dn, 32, 64);
3050 my @oldstat = stat("$ddir/$dh");
3051 die("$ddir/$dh: $!") unless @oldstat;
3052 if (system($combinedeltarpm, "$ddir/$dh", $delta, "$bdir/drpmsync/wip/$nn") || ! -f "$bdir/drpmsync/wip/$nn") {
3053 recvlog_print("! combinedeltarpm $ddir/$dh $delta $bdir/drpmsync/wip/$nn failed");
3054 unlink("$bdir/drpmsync/wip/$nn");
3057 utime($oldstat[9], $oldstat[9], "$bdir/drpmsync/wip/$nn");
3058 rename("$bdir/drpmsync/wip/$nn", "$ddir/$nn") || die("rename $bdir/drpmsync/wip/$nn $ddir/$nn: $!\n");
3059 unlink("$bdir/drpmsync/deltas/$dpn/$dh") || die("unlink $bdir/drpmsync/deltas/$dpn/$dh: $!\n");
3061 unlink($delta) || die("unlink $delta: $!\n");
3063 rename($delta, "$ddir/$dn") || die("rename $delta $ddir/$dn: $!\n");
3069 # get rpms for fiso, fill iso
3072 my ($bdir, $pn, $dto, $rights) = @_;
3075 if (!open(F, '-|', $fragiso, 'list', "$bdir/drpmsync/wip/$pn.fiso")) {
3076 unlink("$bdir/drpmsync/wip/$pn.fiso");
3080 close(F) || return undef;
3082 open(F, '>', "$bdir/drpmsync/wip/$pn") || die("$bdir/drpmsync/wip/$pn: $!\n");
3084 for my $f (@frags) {
3085 my @f = split(' ', $f, 3);
3086 update($bdir, [ $dto->[0], undef, $rights, $f[1], undef, $f[2] ], $f[0]);
3088 checkjob() if $runningjob;
3089 my ($md5, $err) = runprg(undef, undef, $fragiso, 'fill', '-m', "$bdir/drpmsync/wip/$pn.fiso", "$bdir/drpmsync/wip/$pn");
3090 unlink("$bdir/drpmsync/wip/$pn.fiso") || die("unlink $bdir/drpmsync/wip/$pn.fiso: $!\n");;
3091 my $tmpnam = "$bdir/drpmsync/wip/$pn";
3093 recvlog_print("! fragiso fill failed: $err");
3097 die("fragiso did not return md5\n") unless $md5 =~ /^[0-9a-f]{32}$/;
3098 fixmodetime($tmpnam, $rights);
3099 my @s = lstat($tmpnam);
3100 die("$tmpnam: $!\n") unless @s;
3101 $rights = sprintf("1%03x%08x", ($s[2] & 07777), $s[9]);
3102 $files{$dto->[0]} = [ $dto->[0], "$s[9]/$s[7]/$s[1]", $rights, $md5 ];
3103 rename($tmpnam, "$bdir/$dto->[0]") || die("rename $tmpnam $bdir/$dto->[0]: $!\n");
3105 for my $f (@frags) {
3106 my @f = split(' ', $f, 3);
3107 repo_add("$bdir/$dto->[0]\@$f[0]", [ "$dto->[0]\@$f[0]", "$s[9]/$s[7]/$s[1]", $rights, $f[1], undef, $f[2] ] );
3114 # called for files and rpms
3117 my ($bdir, $dto, $rextract, $play_it_safe) = @_;
3125 if ($play_it_safe && ref($play_it_safe)) {
3126 # poor mans co-routine implementation...
3127 my $job = $play_it_safe;
3131 $pdto0 = $job->{'pdto0'};
3132 $tmpnam = $job->{'tmpnam'};
3133 $extractoff = $job->{'extractoff'};
3134 @deltas = applydeltas_finish($job);
3135 goto applydeltas_finished;
3138 die("can only update files and symlinks\n") if $dto->[2] !~ /^[12]/;
3139 $pdto0 = $dto->[0]; # for recvlog_print;
3141 # hack: patch source/dest for special fiso request
3143 die("bad extract parameter\n") unless $rextract =~ /^([0-9a-fA-F]{2})([0-9a-fA-F]{8}):[0-9a-fA-F]{8}$/;
3144 $extractoff = hex($1) * 4294967296 + hex($2);
3145 die("bad extract offset\n") unless $extractoff;
3146 $pdto0 = "$dto->[0]\@$rextract ($dto->[5])";
3149 $d = $files{$dto->[0]};
3150 if ($d && !$rextract && $d->[3] eq $dto->[3]) {
3151 return if $d->[2] eq $dto->[2]; # already identical
3152 if (substr($d->[2], 0, 1) eq substr($dto->[2], 0, 1)) {
3153 return if substr($d->[2], 0, 1) eq '2'; # can't change links
3154 fixmodetime("$bdir/$d->[0]", $dto->[2]);
3155 $d->[2] = $dto->[2];
3156 my $newmtime = hex(substr($dto->[2], 4, 8));
3157 $d->[1] =~ s/^.*?\//$newmtime\//; # patch cache id
3162 # check for simple renames
3163 if (!$d && !$rextract && substr($dto->[2], 0, 1) eq '1') {
3164 # search for same md5, same mtime and removed files
3165 my @oldds = grep {@$_ > 3 && $_->[3] eq $dto->[3] && substr($_->[2], 4) eq substr($dto->[2], 4) && !$syncfiles{$_->[0]}} values %files;
3170 $tmpnam = "$bdir/drpmsync/wip/$pn";
3171 checkjob($pn) if $runningjob;
3173 if (rename("$bdir/$d->[0]", $tmpnam)) {
3174 delete $files{$d->[0]};
3175 recvlog_print("- $d->[0]");
3176 repo_del("$bdir/$d->[0]", $d) if $config_repo;
3177 my @s = stat($tmpnam);
3178 # check link count, must be 1
3179 if (!@s || $s[3] != 1) {
3180 unlink($tmpnam); # oops
3182 fixmodetime($tmpnam, $dto->[2]);
3184 die("$tmpnam: $!\n") unless @s;
3186 splice(@info, 0, 3);
3187 $files{$dto->[0]} = [ $dto->[0], "$s[9]/$s[7]/$s[1]", sprintf("1%03x%08x", ($s[2] & 07777), $s[9]), @info ];
3188 recvlog_print("M $dto->[0]");
3189 rename($tmpnam, "$bdir/$dto->[0]") || die("rename $tmpnam $bdir/$dto->[0]: $!\n");
3190 repo_add("$bdir/$dto->[0]", $files{$dto->[0]}) if $config_repo;
3191 # no need to create delta, as file was already in tree...
3199 if (!$d && @$dto > 5) {
3200 my @oldds = grep {@$_ > 5 && $_->[5] eq $dto->[5]} values %files;
3201 $d = $oldds[0] if @oldds;
3204 $md = $d; # make delta against this entry ($d may point to repo)
3209 if ($config_repo && @$dto > 5) {
3210 @repo = repo_search($dto->[5], $dto->[3]);
3211 # we must not use the repo if we need to store the deltas.
3212 # in this case we will send a delta-only request and retry the
3214 if (@repo && !$rextract && !$config_generate_deltas && $config_keep_deltas) {
3215 @repo = repo_check(@repo);
3216 $deltaonly = 1 if @repo;
3220 ##################################################################
3221 ##################################################################
3225 while (@repo && !$deltaonly) {
3229 checkjob($pn) if $runningjob;
3230 if ($repo[0]->[0] eq $dto->[3]) {
3231 # exact match, great!
3232 $tmpnam = "$bdir/drpmsync/wip/$pn";
3233 $rd = repo_cp($repo[0], $bdir, "drpmsync/wip/$pn", $extractoff);
3239 recvlog_print("R $pdto0");
3242 fixmodetime($tmpnam, $dto->[2]);
3243 my @s = stat($tmpnam);
3244 die("$tmpnam: $!\n") unless @s;
3245 my $oldd5 = $md ? substr($md->[3], 32) : undef;
3246 $files{$dto->[0]} = [ $dto->[0], "$s[9]/$s[7]/$s[1]", sprintf("1%03x%08x", ($s[2] & 07777), $s[9]), $rd->[3], $rd->[4], $rd->[5] ];
3247 if ($oldd5 && $config_generate_deltas) {
3248 recvlog_print("Rm $pdto0");
3249 @deltas = makedelta("$bdir/$md->[0]", $tmpnam, "$bdir/drpmsync/wip/$oldd5$files{$dto->[0]}->[3]");
3250 save_or_delete_deltas($bdir, $dto->[5], @deltas);
3252 recvlog_print("R $pdto0");
3254 rename($tmpnam, "$bdir/$dto->[0]") || die("rename $tmpnam $bdir/$dto->[0]: $!\n");
3255 repo_add("$bdir/$dto->[0]", $files{$dto->[0]});
3257 } elsif (substr($repo[0]->[0], 32, 32) eq substr($dto->[3], 32, 32)) {
3258 # have sign only rpm, copy right away
3259 checkjob() if $runningjob;
3260 $rd = repo_cp($repo[0], $bdir, "drpmsync/wip/repo-$pn");
3266 $d->[1] = undef; # mark as temp, don't gen/save delta
3270 @repo = repo_check(@repo) if @repo;
3274 # ok, we really need to send a request our server
3276 if (@repo && !$deltaonly && !$play_it_safe) {
3277 my @h = map {$_->[0]} @repo;
3278 unshift @h, $d->[3] if $d && @$d > 5;
3279 $reqext .= "&have=" . shift(@h);
3281 my %ha = map {substr($_, -32, 32) => 1} @h;
3282 $reqext .= "&havealso=" . join(',', keys %ha);
3284 } elsif ($d && @$d > 5 && !$play_it_safe) {
3285 $reqext .= "&have=$d->[3]";
3286 $reqext .= "&uncombined" if $config_keep_uncombined;
3287 $reqext .= "&withrpm" if $config_always_get_rpm && substr($d->[3], 32) ne substr($dto->[3], 32);
3288 $reqext .= "&deltaonly" if $deltaonly;
3289 $reqext .= "&nocomplexdelta" if (!$config_keep_deltas || $rextract) && $config_always_get_rpm;
3291 $reqext .= "&zlib" if $have_zlib;
3292 $reqext .= "&fiso" if $config_repo && !$play_it_safe && ($dto->[0] =~ /(?<!\.delta)\.iso$/i);
3297 die("no file name?\n") unless $pn ne '';
3298 checkjob($pn) if $runningjob;
3299 $tmpnam = "$bdir/drpmsync/wip/$pn";
3301 ($type, $nd, @deltas) = get_update($dto, $tmpnam, $reqext, $rextract);
3302 if ($type eq 'ERR ') {
3304 } elsif ($type eq 'NODR') {
3305 die("unexpected NODR answer\n") unless $deltaonly;
3308 } elsif ($type eq 'GONE') {
3309 warn("$dto->[0] is gone\n");
3310 recvlog_print("${repo_key}G $pdto0");
3311 if (-e "$bdir/$dto->[0]") {
3312 unlink("$bdir/$dto->[0]") || die("unlink $bdir/$dto->[0]: $!\n");
3314 delete $files{$dto->[0]};
3316 } elsif ($type eq 'FILZ') {
3317 recvlog_print("${repo_key}z $pdto0");
3318 rename($tmpnam, "$bdir/$dto->[0]") || die("rename $tmpnam $bdir/$dto->[0]: $!\n");
3319 $files{$dto->[0]} = $nd;
3320 } elsif ($type eq 'FILE') {
3321 recvlog_print("${repo_key}f $pdto0");
3322 rename($tmpnam, "$bdir/$dto->[0]") || die("rename $tmpnam $bdir/$dto->[0]: $!\n");
3323 $files{$dto->[0]} = $nd;
3324 } elsif ($type eq 'FISO') {
3325 checkjob() if $runningjob;
3326 recvlog_print("${repo_key}i $pdto0");
3327 if (!update_fiso($bdir, $pn, $dto, $nd->[2])) {
3331 } elsif ($type eq 'RPM ') {
3333 checkjob() if $runningjob;
3334 die("no deltas?") unless @deltas;
3335 undef $d if $d && (@$d <= 4 || substr($d->[3], 32, 32) ne substr($deltas[0], -96, 32));
3337 my $dmd5 = substr($deltas[0], -96, 32);
3338 my @mrepo = grep {substr($_->[0], 32, 32) eq $dmd5} @repo;
3339 for my $rd (@mrepo) {
3340 $d = repo_cp($rd, $bdir, "drpmsync/wip/repo-$pn");
3343 if (!$d && @mrepo) {
3344 recvlog_print("R! $pdto0");
3345 save_or_delete_deltas($bdir, undef, @deltas);
3346 @repo = grep {substr($_->[0], 32, 32) ne $dmd5} @repo;
3347 goto send_again; # now without bad repo entries
3349 $d->[1] = undef if $d;
3352 if (@deltas == 1 && substr($deltas[0], -96, 32) eq substr($deltas[0], -32, 32)) {
3353 recvlog_print("${repo_key}s $pdto0");
3355 recvlog_print("${repo_key}d $pdto0");
3357 die("received delta doesn't match request\n") unless $d;
3359 #######################################################################
3366 $job->{'pdto0'} = $pdto0;
3367 $job->{'tmpnam'} = $tmpnam;
3368 $job->{'extractoff'} = $extractoff;
3369 $job->{'wip'} = $pn;
3370 $job->{'finish'} = \&update;
3371 $job->{'finishargs'} = [$bdir, $dto, $rextract, $job];
3372 @deltas = applydeltas($job, "$bdir/$d->[0]", $tmpnam, $extractoff, @deltas);
3377 delete $job->{'finishargs'}; # break circ ref
3380 #######################################################################
3382 #recvlog("applying deltarpm to $d->[0]");
3383 #@deltas = applydeltas("$bdir/$d->[0]", $tmpnam, $extractoff, @deltas);
3384 applydeltas_finished:
3386 return update($bdir, $dto, $rextract, 1);
3389 fixmodetime($tmpnam, $nd->[2]);
3390 my @s = stat($tmpnam);
3391 die("$tmpnam: $!\n") unless @s;
3393 $nd = [ $dto->[0], "$s[9]/$s[7]/$s[1]", sprintf("1%03x%08x", ($s[2] & 07777), $s[9]), rpminfo($tmpnam) ];
3396 recvlog_print("${repo_key}r $pdto0") if $rextract || !(!@deltas && $md && $md->[1] && $config_generate_deltas);
3399 save_or_delete_deltas($bdir, undef, @deltas);
3400 unlink("$bdir/$d->[0]") if $d && ($d->[0] =~ m!drpmsync/wip/repo-!);
3403 if (@deltas && $d && !$d->[1]) {
3404 # deltas made against some repo rpm, always delete
3405 save_or_delete_deltas($bdir, undef, @deltas);
3408 if (!@deltas && $md && $md->[1] && $config_generate_deltas) {
3409 recvlog_print("${repo_key}m $pdto0");
3410 @deltas = makedelta("$bdir/$md->[0]", $tmpnam, "$bdir/drpmsync/wip/".substr($md->[3], 32).$nd->[3]);
3412 save_or_delete_deltas($bdir, $dto->[5], @deltas);
3414 rename($tmpnam, "$bdir/$dto->[0]") || die("rename $tmpnam $bdir/$dto->[0]: $!\n");
3415 $files{$dto->[0]} = $nd;
3416 repo_add("$bdir/$dto->[0]", $nd) if $config_repo;
3418 die("received strange answer type: $type\n");
3420 unlink("$bdir/$d->[0]") if $d && ($d->[0] =~ m!drpmsync/wip/repo-!);
3424 my ($fn, $mthex) = @_;
3425 my $mode = hex(substr($mthex, 1, 3));
3426 my $ti = hex(substr($mthex, 4, 8));
3427 chmod($mode, $fn) == 1 || die("chmod $fn: $!\n");
3428 utime($ti, $ti, $fn) == 1 || die("utime $fn: $!\n");
3434 my $cmdline_repo_add;
3435 my $cmdline_repo_validate;
3436 my $cmdline_get_filelist;
3437 my $cmdline_use_filelist;
3438 my $cmdline_norecurse;
3441 my @cmdline_filter_arch;
3444 my ($syncfilesp, $norecurse, $verbose, @sources) = @_;
3448 setup_proto('null');
3452 for my $s (@sources) {
3455 $syncproto = 'drpmsync';
3456 if ($ss =~ /^(file|drpmsync|rsync):(.*)$/) {
3457 $syncproto = lc($1);
3459 if ($syncproto ne 'file') {
3461 if ($ss =~ /^([^\/]+)\@(.*)$/) {
3464 ($syncuser, $syncpassword) = split(':', $syncuser, 2);
3468 if ($syncproto eq 'file') {
3470 $syncroot =~ s/\/\.$//;
3471 $syncroot =~ s/\/$// unless $syncroot eq '/';
3473 ($syncaddr, $syncport, $syncroot) = $ss =~ /^([^\/]+?)(?::(\d+))?(\/.*)$/;
3475 $errors{$s} = "bad url";
3478 $syncroot =~ s/\/\.$//;
3479 $syncroot =~ s/\/$// unless $syncroot eq '/';
3480 $esyncroot = aescape($syncroot);
3481 $syncport ||= $syncproto eq 'rsync' ? 873 : 80;
3482 $syncaddr = inet_aton($syncaddr);
3484 $errors{$s} = "could not resolve host";
3487 print "trying $s\n" if $verbose;
3490 setup_proto($syncproto);
3491 @$syncfilesp = get_syncfiles($norecurse);
3493 alarm(0) if $config_timeout;
3496 $errors{$s} =~ s/\n$//s;
3499 if ($syncproto ne 'file' && !$syncaddr) {
3500 if (@sources == 1) {
3501 die("could not connect to $sources[0]: $errors{$sources[0]}\n");
3503 print STDERR "could not connect to any server:\n";
3504 print STDERR " $_: $errors{$_}\n" for @sources;
3508 filelist_apply_filter($syncfilesp);
3509 filelist_apply_filter_arch($syncfilesp);
3512 sub filelist_from_file {
3513 my ($flp, $fn) = @_;
3517 open(FL, '<&STDIN') || die("STDIN dup: $!\n");
3519 open(FL, '<', $fn) || die("$fn: $!\n");
3524 die("not a drpmsync filelist\n") if read(FL, $data, 32) != 32;
3525 if (substr($data, 0, 2) eq "\037\213") {
3526 { local $/; $data .= <FL>; }
3527 $data = Compress::Zlib::memGunzip($data);
3528 die("filelist uncompress error\n") unless defined $data;
3531 die("not a drpmsync filelist\n") if (substr($data, 0, 24) ne 'DRPMSYNC0001SYNC00000000' && substr($data, 0, 24) ne 'DRPMSYNC0001SYNZ00000000');
3532 if ($is_compressed) {
3533 $fldata = substr($data, 32);
3534 $data = substr($data, 0, 32);
3536 { local $/; $fldata = <FL>; }
3539 my $md5 = substr($fldata, -32, 32);
3540 $fldata = substr($fldata, 0, -32);
3541 die("drpmsync filelist checksum error\n") if Digest::MD5::md5_hex($fldata) ne $md5;
3542 $fldata = substr($fldata, 12);
3543 if (substr($data, 16, 4) eq 'SYNZ') {
3544 die("cannot uncompress filelist\n") unless $have_zlib;
3545 $fldata = Compress::Zlib::uncompress($fldata);
3547 @$flp = drpmsync_get_syncfiles($cmdline_norecurse, $fldata);
3548 filelist_apply_filter($flp);
3549 filelist_apply_filter_arch($flp);
3553 last if $ARGV[0] !~ /^-/;
3554 my $opt = shift @ARGV;
3555 last if $opt eq '--';
3557 die("-c: argument required\n") unless @ARGV;
3558 $cmdline_cf = shift @ARGV;
3559 } elsif ($opt eq '--repo') {
3560 die("--repo: argument required\n") unless @ARGV;
3561 $cmdline_repo = shift @ARGV;
3562 } elsif ($opt eq '--repo-add') {
3563 $cmdline_repo_add = 1;
3564 } elsif ($opt eq '--repo-validate') {
3565 $cmdline_repo_validate = 1;
3566 } elsif ($opt eq '--norecurse-validate') {
3567 $cmdline_norecurse = 1;
3568 } elsif ($opt eq '--list') {
3570 $cmdline_norecurse = 1;
3571 } elsif ($opt eq '--list-recursive') {
3573 } elsif ($opt eq '--get-filelist') {
3574 die("--get-filelist: argument required\n") unless @ARGV;
3575 $cmdline_get_filelist = shift @ARGV;
3576 } elsif ($opt eq '--filelist-synctree') {
3577 $synctree = shift @ARGV;
3579 } elsif ($opt eq '--use-filelist') {
3580 die("--use-filelist: argument required\n") unless @ARGV;
3581 $cmdline_use_filelist = shift @ARGV;
3582 } elsif ($opt eq '--exclude') {
3583 die("--exclude: argument required\n") unless @ARGV;
3584 push @cmdline_filter, '-'.shift(@ARGV);
3585 } elsif ($opt eq '--include') {
3586 die("--include: argument required\n") unless @ARGV;
3587 push @cmdline_filter, '+'.shift(@ARGV);
3588 } elsif ($opt eq '--exclude-arch') {
3589 die("--exclude-arch: argument required\n") unless @ARGV;
3590 push @cmdline_filter_arch, '-'.shift(@ARGV);
3591 } elsif ($opt eq '--include-arch') {
3592 die("--include-arch: argument required\n") unless @ARGV;
3593 push @cmdline_filter_arch, '+'.shift(@ARGV);
3595 die("$opt: unknown option\n");
3599 if ($cmdline_repo_validate) {
3601 $basedir = shift @ARGV if @ARGV;
3602 die("illegal source parameter for repo operation\n") if @ARGV;
3603 if (defined($cmdline_cf) || (defined($basedir) && -e "$basedir/drpmsync/config")) {
3604 readconfig_client(defined($cmdline_cf) ? $cmdline_cf : "$basedir/drpmsync/config");
3606 $config_repo = $cmdline_repo if defined $cmdline_repo;
3607 die("--repo-validate: no repo specified\n") unless $config_repo;
3614 die("illegal source parameter for repo operation\n") if $cmdline_repo_add;
3615 $cmdline_source = shift @ARGV;
3616 $basedir = $ARGV[0];
3617 } elsif (@ARGV == 1) {
3618 if ($cmdline_list || defined($cmdline_get_filelist)) {
3619 $cmdline_source = $ARGV[0];
3621 $basedir = $ARGV[0];
3624 die("Usage: drpmsync [-c config] [source] <dir> | -s <serverconfig>\n") unless $cmdline_list && defined($cmdline_use_filelist);
3627 if (defined($basedir)) {
3629 die("$basedir: not a directory (did you forget -s?)\n");
3634 if (defined($cmdline_cf)) {
3635 readconfig_client($cmdline_cf);
3636 } elsif (defined($basedir) && (-e "$basedir/drpmsync/config")) {
3637 readconfig_client("$basedir/drpmsync/config");
3640 @config_source = $cmdline_source if defined $cmdline_source;
3641 $config_repo = $cmdline_repo if defined $cmdline_repo;
3642 @filter_comp = compile_filter(@cmdline_filter, @config_filter);
3643 @filter_arch_comp = compile_filter(@cmdline_filter_arch, @config_filter_arch);
3645 if ($config_repo && defined($basedir)) {
3646 my $nbasedir = `cd $basedir && /bin/pwd`;
3648 die("could not canonicalize $basedir\n") if !$nbasedir || !-d "$nbasedir";
3649 $basedir = $nbasedir;
3652 if ($cmdline_repo_add) {
3653 die("--repo-add: no repo specified\n") unless $config_repo;
3654 die("need a destination\n") unless defined $basedir;
3655 readcache("$basedir/drpmsync/cache");
3656 print "getting state of local tree...\n";
3657 findfiles($basedir, '');
3658 print("cache: $cachehits hits, $cachemisses misses\n");
3659 for my $d (@files) {
3660 repo_add("$basedir/$d->[0]", $d);
3665 if (defined($cmdline_get_filelist)) {
3666 die("need a source for get-filelist\n") unless @config_source;
3667 $SIG{'ALRM'} = sub {die("network timeout\n");};
3669 find_source(\@syncfiles, $cmdline_norecurse, $cmdline_get_filelist eq '-' ? 0 : 1, @config_source);
3671 filelist_from_file(\@syncfiles, $cmdline_use_filelist) if defined $cmdline_use_filelist;
3673 if ($cmdline_get_filelist eq '-') {
3674 open(FL, '>&STDOUT') || die("STDOUT dup: $!\n");
3676 open(FL, '>', $cmdline_get_filelist) || die("$cmdline_get_filelist: $!\n");
3679 $data = pack('H*', "$newstamp1$newstamp2");
3680 $data = pack("Nw/a*w/a*", scalar(@syncfiles), $synctree ne '/' ? substr($synctree, 0, -1) : '/', $data);
3681 $data = sprintf("1%03x%08x", 0644, time()).$data;
3686 $b = pack('H*', "$l[2]$l[3]$l[4]").$l[5];
3689 $b = pack('H*', $l[2])."\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
3691 $b = pack('H*', "$l[2]$l[3]");
3694 $b = pack('H*', $l[2]);
3696 $data .= pack("w/a*w/a*", $l[0], $b);
3698 $data = "DRPMSYNC0001SYNC00000000".sprintf("%08x", length($data)).$data.Digest::MD5::md5_hex($data);
3700 close(FL) || die("close: $!\n");
3704 if ($cmdline_list) {
3705 $SIG{'ALRM'} = sub {die("network timeout\n");};
3707 find_source(\@syncfiles, $cmdline_norecurse, 0, @config_source);
3709 filelist_from_file(\@syncfiles, $cmdline_use_filelist) if defined $cmdline_use_filelist;
3710 for my $f (@syncfiles) {
3711 my $p = substr($f->[2], 0, 1) eq '0' ? '/' : '';
3712 print "$f->[0]$p\n";
3719 die("need a destination\n") unless defined $basedir;
3720 mkdir_p("$basedir/drpmsync");
3721 sysopen(LOCK, "$basedir/drpmsync/lock", POSIX::O_RDWR|POSIX::O_CREAT, 0666) || die("$basedir/drpmsync/lock: $!\n");
3722 if (!flock(LOCK, LOCK_EX | LOCK_NB)) {
3724 sysread(LOCK, $lockuser, 1024);
3726 $lockuser = "somebody else\n" unless $lockuser =~ /.*[\S].*\n$/s;
3727 print "update already in progress by $lockuser";
3731 syswrite(LOCK, "drpmsync[$$]\@$synchost\n");
3733 my ($oldstamp1, $oldstamp2);
3734 if (open(STAMP, '<', "$basedir/drpmsync/timestamp")) {
3736 if ((sysread(STAMP, $s, 16) || 0) == 16 && $s !~ /[^0-9a-f]/) {
3737 $oldstamp1 = substr($s, 0, 8);
3738 $oldstamp2 = substr($s, 8, 8);
3742 $oldstamp1 ||= "00000000";
3745 if (opendir(WIP, "$basedir/drpmsync/wip")) {
3746 for (readdir(WIP)) {
3747 next if $_ eq '.' || $_ eq '..';
3748 unlink("$basedir/drpmsync/wip/$_") || die("unlink $basedir/drpmsync/wip/$_: $!\n");
3753 readcache("$basedir/drpmsync/cache");
3754 print "getting state of local tree...\n";
3755 findfiles($basedir, '', 1);
3756 print("cache: $cachehits hits, $cachemisses misses\n");
3757 writecache("$basedir/drpmsync/cache");
3759 if (!@config_source) {
3760 # just a cache update...
3761 unlink("$basedir/drpmsync/lock");
3766 mkdir_p("$basedir/drpmsync/wip");
3768 $SIG{'ALRM'} = sub {die("network timeout\n");};
3771 find_source(\@syncfiles, $cmdline_norecurse || $cmdline_use_filelist, 1, @config_source);
3772 filelist_from_file(\@syncfiles, $cmdline_use_filelist) if defined $cmdline_use_filelist;
3774 $config_recvlog = "$basedir/drpmsync/$config_recvlog" if $config_recvlog && $config_recvlog !~ /^\//;
3775 if ($config_recvlog) {
3776 open(RECVLOG, '>>', $config_recvlog) || die("$config_recvlog: $!\n");
3780 recvlog("started update from $syncurl");
3781 $SIG{'__DIE__'} = sub {
3789 if ($oldstamp1 ne '00000000' && $oldstamp1 gt $newstamp1) {
3790 if ($newstamp1 eq '00000000') {
3791 die("remote tree is incomplete\n");
3793 die("remote tree is older than local tree (last completion): ".toiso(hex($newstamp1))." < ".toiso(hex($oldstamp1))."\n");
3795 if ($oldstamp2 && $oldstamp2 gt $newstamp2) {
3796 die("remote tree is older than local tree (last start): ".toiso(hex($newstamp2))." < ".toiso(hex($oldstamp2))."\n");
3798 open(STAMP, '>', "$basedir/drpmsync/timestamp.new") || die("$basedir/drpmsync/timestamp.new: $!\n");
3799 print STAMP "$oldstamp1$newstamp2\n";
3801 rename("$basedir/drpmsync/timestamp.new", "$basedir/drpmsync/timestamp");
3803 # change all directories to at least user rwx
3805 next if $_->[2] !~ /^0/;
3806 next if (hex(substr($_->[2], 0, 4)) & 0700) == 0700;
3807 $_->[2] = sprintf("0%03x", hex(substr($_->[2], 0, 4)) | 0700).substr($_->[2], 4);
3810 printf "local: ".@files." entries\n";
3811 printf "remote: ".@syncfiles." entries\n";
3813 rsync_adapt_filelist(\@syncfiles) if $syncproto eq 'rsync';
3815 %files = map {$_->[0] => $_} @files;
3816 %syncfiles = map {$_->[0] => $_} @syncfiles;
3818 # 1) create all new directories
3819 # 2) delete all dirs that are now files
3820 # 3) get all rpms and update/delete the associated files
3821 # 4) update all other files
3822 # 5) delete all files/rpms/directories
3823 # 6) set mode/time of directories
3826 for my $dir (grep {@$_ == 3} @syncfiles) {
3827 my $d = $files{$dir->[0]};
3829 next if $d->[2] =~ /^0/;
3830 recvlog_print("- $d->[0]");
3831 unlink("$basedir/$d->[0]") || die("unlink $basedir/$d->[0]: $!\n");
3833 recvlog_print("+ $dir->[0]");
3834 mkdir("$basedir/$dir->[0]", 0755) || die("mkdir $basedir/$dir->[0]: $!\n");
3835 fixmodetime("$basedir/$dir->[0]", $dir->[2]);
3836 my @s = lstat("$basedir/$dir->[0]");
3837 die("$basedir/$dir->[0]: $!\n") unless @s;
3838 $files{$dir->[0]} = [ $dir->[0], "$s[9]/$s[7]/$s[1]", sprintf("0%03x%08x", ($s[2] & 07777), $s[9]) ];
3839 dirchanged($dir->[0]);
3843 @files = sort {$a->[0] cmp $b->[0]} values %files;
3844 for my $dir (grep {@$_ == 3} @files) {
3845 my $sd = $syncfiles{$dir->[0]};
3846 next if !$sd || $sd->[2] =~ /^0/;
3847 next unless $files{$dir->[0]};
3848 my @subf = grep {$_->[0] =~ /^\Q$dir->[0]\E\//} @files;
3849 unshift @subf, $dir;
3850 @subf = reverse @subf;
3851 for my $subf (@subf) {
3852 recvlog_print("- $subf->[0]");
3853 if ($subf->[2] =~ /^0/) {
3854 rmdir("$basedir/$subf->[0]") || die("rmdir $basedir/$subf->[0]: $!\n");
3856 unlink("$basedir/$subf->[0]") || die("unlink $basedir/$subf->[0]: $!\n");
3858 repo_del("$basedir/$subf->[0]", $subf) if $config_repo;
3859 delete $files{$subf->[0]};
3861 dirchanged($dir->[0]);
3862 @files = sort {$a->[0] cmp $b->[0]} values %files;
3866 my @syncrpms = grep {@$_ > 5} @syncfiles;
3867 # sort by rpm built date
3868 @syncrpms = sort {$a->[4] cmp $b->[4]} @syncrpms;
3869 for my $rpm (@syncrpms) {
3870 update($basedir, $rpm);
3871 # update meta file(s)
3872 my $rpmname = $rpm->[0];
3873 $rpmname =~ s/\.[sr]pm$//;
3874 for my $afn ("$rpmname.changes", "$rpmname-MD5SUMS.meta", "$rpmname-MD5SUMS.srcdir") {
3875 my $sd = $syncfiles{$afn};
3876 my $d = $files{$afn};
3877 next if !$d && !$sd;
3879 next if $d->[2] =~ /^0/;
3880 recvlog_print("- $d->[0]");
3881 unlink("$basedir/$d->[0]") || die("unlink $basedir/$d->[0]: $!\n");
3882 dirchanged($d->[0]);
3883 delete $files{$d->[0]};
3885 update($basedir, $sd);
3891 for my $file (grep {@$_ == 4} @syncfiles) {
3892 update($basedir, $file);
3895 checkjob() if $runningjob;
3900 @files = sort {$a->[0] cmp $b->[0]} values %files;
3901 for my $file (grep {!$syncfiles{$_->[0]}} reverse @files) {
3902 recvlog_print("- $file->[0]");
3903 if ($file->[2] =~ /^0/) {
3904 rmdir("$basedir/$file->[0]") || die("rmdir $basedir/$file->[0]: $!\n");
3906 unlink("$basedir/$file->[0]") || die("unlink $basedir/$file->[0]: $!\n");
3907 repo_del("$basedir/$file->[0]", $file) if $config_repo;
3909 dirchanged($file->[0]);
3910 delete $files{$file->[0]};
3914 for my $dir (grep {@$_ == 3} @syncfiles) {
3915 my $d = $files{$dir->[0]};
3916 next if !$d || $d->[2] eq $dir->[2];
3917 fixmodetime("$basedir/$dir->[0]", $dir->[2]);
3920 @files = sort {$a->[0] cmp $b->[0]} values %files;
3921 writecache("$basedir/drpmsync/cache");
3924 open(STAMP, '>', "$basedir/drpmsync/timestamp.new") || die("$basedir/drpmsync/timestamp.new: $!\n");
3925 print STAMP "$newstamp1$newstamp2\n";
3927 rename("$basedir/drpmsync/timestamp.new", "$basedir/drpmsync/timestamp");
3930 if (defined($config_delta_max_age)) {
3931 print "removing outdated deltas...\n";
3933 my $cut = time() - 24*60*60*$config_delta_max_age;
3934 if (opendir(PACKS, "$basedir/drpmsync/deltas")) {
3935 my @packs = readdir(PACKS);
3937 for my $pack (@packs) {
3938 next if $pack eq '.' || $pack eq '..';
3939 next unless opendir(DELTAS, "$basedir/drpmsync/deltas/$pack");
3940 my @deltas = readdir(DELTAS);
3942 for my $delta (@deltas) {
3943 next if $delta eq '.' || $delta eq '..';
3944 my @s = stat "$basedir/drpmsync/deltas/$pack/$delta";
3946 next if $s[9] >= $cut;
3947 unlink("$basedir/drpmsync/deltas/$pack/$delta") || die("unlink $basedir/drpmsync/deltas/$pack/$delta: $!\n");
3952 recvlog_print("removed $nold deltarpms") if $nold;
3955 $net_kbsec = int($net_recv_bytes / 1024 / $net_spent_time) if $net_spent_time;
3956 recvlog("update finished $txbytes/$rvbytes/$sabytes $net_kbsec");
3957 close(RECVLOG) if $config_recvlog;
3958 unlink("$basedir/drpmsync/lock");
3960 if ($sabytes == 0) {
3961 printf "update finished, sent %.1f K, received %.1f M\n", $txbytes / 1000, $rvbytes / 1000000;
3962 } elsif ($sabytes < 0) {
3963 printf "update finished, sent %.1f K, received %.1f M, deltarpm excess %.1f M\n", $txbytes / 1000, $rvbytes / 1000000, (-$sabytes) /1000000;
3965 printf "update finished, sent %.1f K, received %.1f M, deltarpm savings %.1f M\n", $txbytes / 1000, $rvbytes / 1000000, $sabytes /1000000;
3967 printf "network throughput %d kbyte/sec\n", $net_kbsec if $net_spent_time;
3968 exit 24 if $had_gone;