]> Sergey Matveev's repositories - public-inbox.git/blob - lib/PublicInbox/GitCatFile.pm
dd95d5f3a0f94f5cd259cdffea4b98a6052d73e7
[public-inbox.git] / lib / PublicInbox / GitCatFile.pm
1 # Copyright (C) 2014-2015 all contributors <meta@public-inbox.org>
2 # License: GPLv2 or later (https://www.gnu.org/licenses/gpl-2.0.txt)
3 #
4 # Used to read files from a git repository without excessive forking.
5 # Used in our web interfaces as well as our -nntpd server.
6 # This is based on code in Git.pm which is GPLv2, but modified to avoid
7 # dependence on environment variables for compatibility with mod_perl.
8 # There are also API changes to simplify our usage and data set.
9 package PublicInbox::GitCatFile;
10 use strict;
11 use warnings;
12 use POSIX qw(dup2);
13 require IO::Handle;
14
15 sub new {
16         my ($class, $git_dir) = @_;
17         bless { git_dir => $git_dir }, $class
18 }
19
20 sub _cat_file_begin {
21         my ($self) = @_;
22         return if $self->{pid};
23         my ($in_r, $in_w, $out_r, $out_w);
24
25         pipe($in_r, $in_w) or die "pipe failed: $!\n";
26         pipe($out_r, $out_w) or die "pipe failed: $!\n";
27
28         my @cmd = ('git', "--git-dir=$self->{git_dir}", qw(cat-file --batch));
29         my $pid = fork;
30         defined $pid or die "fork failed: $!\n";
31         if ($pid == 0) {
32                 dup2(fileno($out_r), 0) or die "redirect stdin failed: $!\n";
33                 dup2(fileno($in_w), 1) or die "redirect stdout failed: $!\n";
34                 exec(@cmd) or die 'exec `' . join(' '). "' failed: $!\n";
35         }
36         close $out_r or die "close failed: $!\n";
37         close $in_w or die "close failed: $!\n";
38         $out_w->autoflush(1);
39
40         $self->{in} = $in_r;
41         $self->{out} = $out_w;
42         $self->{pid} = $pid;
43 }
44
45 sub cat_file {
46         my ($self, $object, $sizeref) = @_;
47
48         $self->_cat_file_begin;
49         print { $self->{out} } $object, "\n" or die "pipe write error: $!\n";
50
51         my $in = $self->{in};
52         my $head = <$in>;
53         $head =~ / missing$/ and return undef;
54         $head =~ /^[0-9a-f]{40} \S+ (\d+)$/ or
55                 die "Unexpected result from git cat-file: $head\n";
56
57         my $size = $1;
58         $$sizeref = $size if $sizeref;
59         my $bytes_left = $size;
60         my $offset = 0;
61         my $rv = '';
62
63         while ($bytes_left) {
64                 my $read = read($in, $rv, $bytes_left, $offset);
65                 defined($read) or die "sysread pipe failed: $!\n";
66                 $bytes_left -= $read;
67                 $offset += $read;
68         }
69
70         my $read = read($in, my $buf, 1);
71         defined($read) or die "read pipe failed: $!\n";
72         if ($read != 1 || $buf ne "\n") {
73                 die "newline missing after blob\n";
74         }
75         \$rv;
76 }
77
78 sub DESTROY {
79         my ($self) = @_;
80         my $pid = $self->{pid} or return;
81         $self->{pid} = undef;
82         foreach my $f (qw(in out)) {
83                 my $fh = $self->{$f};
84                 defined $fh or next;
85                 close $fh;
86                 $self->{$f} = undef;
87         }
88         waitpid $pid, 0;
89 }
90
91 1;