225b59ddce9f26b8224e992d4a8233fa36cd2d44
[maemian] / nokia-lintian / lib / Util.pm
1 # Hey emacs! This is a -*- Perl -*- script!
2 # Util -- Perl utility functions for lintian
3
4 # Copyright (C) 1998 Christian Schwarz
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program.  If not, you can find it on the World Wide
18 # Web at http://www.gnu.org/copyleft/gpl.html, or write to the Free
19 # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
20 # MA 02110-1301, USA.
21
22 package Util;
23 use strict;
24
25 use Exporter;
26 our @ISA = qw(Exporter);
27 our @EXPORT = qw(parse_dpkg_control
28         read_dpkg_control
29         get_deb_info
30         get_dsc_info
31         slurp_entire_file
32         get_file_checksum
33         file_is_encoded_in_non_utf8
34         fail
35         system_env
36         perm2oct);
37
38 use FileHandle;
39 use Pipeline;
40 use Digest::MD5;
41
42 # general function to read dpkg control files
43 # this function can parse output of `dpkg-deb -f', .dsc,
44 # and .changes files (and probably all similar formats)
45 # arguments:
46 #    $filehandle 
47 #    $debconf_flag (true if the file is a debconf template file)
48 # output:
49 #    list of hashes
50 #    (a hash contains one sections,
51 #    keys in hash are lower case letters of control fields)
52 sub parse_dpkg_control {
53     my ($CONTROL, $debconf_flag) = @_;
54
55     my @data;
56     my $cur_section = 0;
57     my $open_section = 0;
58     my $last_tag;
59
60     while (<$CONTROL>) {
61         chomp;
62
63         # tabs at the beginning are illegal, but handle them anyways
64         s/^\t/ \t/o;
65         next if /^#/; #comment line?
66
67         # empty line?
68         if ((!$debconf_flag && m/^\s*$/) or 
69             ($debconf_flag && m/^$/)) {
70             if ($open_section) { # end of current section
71                 $cur_section++;
72                 $open_section = 0;
73             }
74         }
75         # pgp sig?
76         elsif (m/^-----BEGIN PGP SIGNATURE/) { # skip until end of signature
77             while (<$CONTROL>) {
78                 last if m/^-----END PGP SIGNATURE/o;
79             }
80         }
81         # other pgp control?
82         elsif (m/^-----BEGIN PGP/) { # skip until the next blank line
83             while (<$CONTROL>) {
84                 last if /^\s*$/o;
85             }
86         }
87         # new empty field?
88         elsif (m/^(\S+):\s*$/o) {
89             $open_section = 1;
90
91             my ($tag) = (lc $1);
92             $data[$cur_section]->{$tag} = '';
93
94             $last_tag = $tag;
95         }
96         # new field?
97         elsif (m/^(\S+):\s*(.*)$/o) {
98             $open_section = 1;
99
100             my ($tag,$value) = (lc $1,$2);
101             $data[$cur_section]->{$tag} = $value;
102
103             $last_tag = $tag;
104         }
105         # continued field?
106         elsif (m/^ (.*)$/o) {
107             $open_section or fail("syntax error in section $cur_section after the tag $last_tag: $_");
108
109             $data[$cur_section]->{$last_tag} .= "\n".$1;
110         }
111     }
112
113     return @data;
114 }
115
116 sub read_dpkg_control {
117     my ($file, $debconf_flag) = @_;
118
119     if (not _ensure_file_is_sane($file)) {
120         return undef;
121     }
122
123     my $CONTROL = FileHandle->new;
124     open($CONTROL, '<', $file)
125         or fail("cannot open control file $file for reading: $!");
126     my @data = parse_dpkg_control($CONTROL, $debconf_flag);
127     close($CONTROL)
128         or fail("pipe for control file $file exited with status: $?");
129     return @data;
130 }
131
132 sub get_deb_info {
133     my ($file) = @_;
134
135     if (not _ensure_file_is_sane($file)) {
136         return undef;
137     }
138
139     # `dpkg-deb -f $file' is very slow. Instead, we use ar and tar.
140     my $CONTROL = FileHandle->new;
141     pipeline_open($CONTROL,
142                   (sub { exec 'ar', 'p', $file, 'control.tar.gz' }),
143                   (sub { exec 'tar', '--wildcards', '-xzO', '-f', '-', '*control' }))
144         or fail("cannot fork to unpack $file: $!\n");
145     my @data = parse_dpkg_control($CONTROL);
146     close($CONTROL) or fail("broken input pipe for unpacking $file: $!");
147     return $data[0];
148 }
149
150 sub get_dsc_info {
151     my ($file) = @_;
152
153     if (not _ensure_file_is_sane($file)) {
154         return undef;
155     }
156
157     my @data = read_dpkg_control($file);
158     return $data[0];
159 }
160
161 sub _ensure_file_is_sane {
162     my ($file) = @_;
163
164     # if file exists and is not 0 bytes
165     if (-f $file and -s $file) {
166         return 1;
167     }
168     return 0;
169 }
170
171 sub slurp_entire_file {
172     my $file = shift;
173     open(C, '<', $file)
174         or fail("cannot open file $file for reading: $!");
175     my $save = $/;
176     undef $/;
177     local $_ = <C>;
178     $/ = $save;
179     close(C);
180     return $_;
181 }
182
183 sub get_file_checksum {
184         my ($alg, $file) = @_;
185         open (FILE, '<', $file) or fail("Couldn't open $file");
186         my $digest;
187         if ($alg eq 'md5') {
188             $digest = Digest::MD5->new;
189         } elsif ($alg =~ /sha(\d+)/) {
190             require Digest::SHA;
191             $digest = Digest::SHA->new($1);
192         }
193         $digest->addfile(*FILE);
194         close FILE or fail("Couldn't close $file");
195         return $digest->hexdigest;
196 }
197
198 sub file_is_encoded_in_non_utf8 {
199         my ($file, $type, $pkg) = @_;
200         my $non_utf8 = 0;
201
202         open (ICONV, '-|', "env LANG=C iconv -f utf8 -t utf8 $file 2>&1")
203             or fail("failure while checking encoding of $file for $type package $pkg");
204         my $line = 1;
205         while (<ICONV>) {
206                 if (m/iconv: illegal input sequence at position \d+$/) {
207                         $non_utf8 = 1;
208                         last;
209                 }
210                 $line++
211         }
212         close ICONV;
213
214         return $line if $non_utf8;
215         return 0;
216 }
217
218 # Just like system, except cleanses the environment first to avoid any strange
219 # side effects due to the user's environment.
220 sub system_env {
221     my @whitelist = qw(PATH INTLTOOL_EXTRACT);
222     my %newenv = map { exists $ENV{$_} ? ($_ => $ENV{$_}) : () } @whitelist;
223     my $pid = fork;
224     if (not defined $pid) {
225         return -1;
226     } elsif ($pid == 0) {
227         %ENV = %newenv;
228         exec @_ or die("exec of $_[0] failed: $!\n");
229     } else {
230         waitpid $pid, 0;
231         return $?;
232     }
233 }
234
235 # Translate permission strings like `-rwxrwxrwx' into an octal number.
236 sub perm2oct {
237     my ($t) = @_;
238
239     my $o = 0;
240
241     $t =~ m/^.(.)(.)(.)(.)(.)(.)(.)(.)(.)/o;
242
243     $o += 00400 if $1 eq 'r';   # owner read
244     $o += 00200 if $2 eq 'w';   # owner write
245     $o += 00100 if $3 eq 'x';   # owner execute
246     $o += 04000 if $3 eq 'S';   # setuid
247     $o += 04100 if $3 eq 's';   # setuid + owner execute
248     $o += 00040 if $4 eq 'r';   # group read
249     $o += 00020 if $5 eq 'w';   # group write
250     $o += 00010 if $6 eq 'x';   # group execute
251     $o += 02000 if $6 eq 'S';   # setgid
252     $o += 02010 if $6 eq 's';   # setgid + group execute
253     $o += 00004 if $7 eq 'r';   # other read
254     $o += 00002 if $8 eq 'w';   # other write
255     $o += 00001 if $9 eq 'x';   # other execute
256     $o += 01000 if $9 eq 'T';   # stickybit
257     $o += 01001 if $9 eq 't';   # stickybit + other execute
258
259     return $o;
260 }
261
262 # ------------------------
263
264 sub fail {
265     my $str = "internal error";
266     if (@_) {
267         $str .= ": ".join( "\n", @_)."\n";
268     } elsif ($!) {
269         $str .= ": $!\n";
270     } else {
271         $str .= ".\n";
272     }
273     $! = 2; # set return code outside eval()
274     die $str;
275
276 }
277
278 1;