File Coverage

blib/lib/Dpkg/Path.pm
Criterion Covered Total %
statement 110 136 80.8
branch 40 70 57.1
condition 5 9 55.5
subroutine 20 22 90.9
pod 10 10 100.0
total 185 247 74.9


line stmt bran cond sub pod time code
1             # Copyright © 2007-2011 Raphaël Hertzog
2             # Copyright © 2011 Linaro Limited
3             #
4             # This program is free software; you can redistribute it and/or modify
5             # it under the terms of the GNU General Public License as published by
6             # the Free Software Foundation; either version 2 of the License, or
7             # (at your option) any later version.
8             #
9             # This program is distributed in the hope that it will be useful,
10             # but WITHOUT ANY WARRANTY; without even the implied warranty of
11             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12             # GNU General Public License for more details.
13             #
14             # You should have received a copy of the GNU General Public License
15             # along with this program. If not, see .
16              
17             package Dpkg::Path;
18              
19 522     522   261737 use strict;
  522         1093  
  522         15154  
20 522     522   3130 use warnings;
  522         1045  
  522         51518  
21              
22             our $VERSION = '1.05';
23             our @EXPORT_OK = qw(
24             canonpath
25             resolve_symlink
26             check_files_are_the_same
27             check_directory_traversal
28             find_command
29             find_build_file
30             get_control_path
31             get_pkg_root_dir
32             guess_pkg_root_dir
33             relative_to_pkg_root
34             );
35              
36 522     522   3677 use Exporter qw(import);
  522         1056  
  522         14627  
37 522     522   275657 use Errno qw(ENOENT);
  522         701160  
  522         47667  
38 522     522   5723 use File::Spec;
  522         528  
  522         11482  
39 522     522   3126 use File::Find;
  522         1062  
  522         44234  
40 522     522   5715 use Cwd qw(realpath);
  522         1046  
  522         21472  
41              
42 522     522   3993 use Dpkg::ErrorHandling;
  522         1045  
  522         45869  
43 522     522   3653 use Dpkg::Gettext;
  522         527  
  522         35421  
44 522     522   234729 use Dpkg::Arch qw(get_host_arch debarch_to_debtuple);
  522         1045  
  522         37081  
45 522     522   6078 use Dpkg::IPC;
  522         527  
  522         776047  
46              
47             =encoding utf8
48              
49             =head1 NAME
50              
51             Dpkg::Path - some common path handling functions
52              
53             =head1 DESCRIPTION
54              
55             It provides some functions to handle various path.
56              
57             =head1 FUNCTIONS
58              
59             =over 8
60              
61             =item get_pkg_root_dir($file)
62              
63             This function will scan upwards the hierarchy of directory to find out
64             the directory which contains the "DEBIAN" sub-directory and it will return
65             its path. This directory is the root directory of a package being built.
66              
67             If no DEBIAN subdirectory is found, it will return undef.
68              
69             =cut
70              
71             sub get_pkg_root_dir($) {
72 6     6 1 10 my $file = shift;
73 6         29 $file =~ s{/+$}{};
74 6 50       104 $file =~ s{/+[^/]+$}{} if not -d $file;
75 6         19 while ($file) {
76 21 100       251 return $file if -d "$file/DEBIAN";
77 18 100       71 last if $file !~ m{/};
78 15         81 $file =~ s{/+[^/]+$}{};
79             }
80 3         11 return;
81             }
82              
83             =item relative_to_pkg_root($file)
84              
85             Returns the filename relative to get_pkg_root_dir($file).
86              
87             =cut
88              
89             sub relative_to_pkg_root($) {
90 2     2 1 7 my $file = shift;
91 2         6 my $pkg_root = get_pkg_root_dir($file);
92 2 100       6 if (defined $pkg_root) {
93 1         3 $pkg_root .= '/';
94 1 50       24 return $file if ($file =~ s/^\Q$pkg_root\E//);
95             }
96 1         6 return;
97             }
98              
99             =item guess_pkg_root_dir($file)
100              
101             This function tries to guess the root directory of the package build tree.
102             It will first use get_pkg_root_dir(), but it will fallback to a more
103             imprecise check: namely it will use the parent directory that is a
104             sub-directory of the debian directory.
105              
106             It can still return undef if a file outside of the debian sub-directory is
107             provided.
108              
109             =cut
110              
111             sub guess_pkg_root_dir($) {
112 2     2 1 5 my $file = shift;
113 2         6 my $root = get_pkg_root_dir($file);
114 2 100       14 return $root if defined $root;
115              
116 1         5 $file =~ s{/+$}{};
117 1 50       13 $file =~ s{/+[^/]+$}{} if not -d $file;
118 1         3 my $parent = $file;
119 1         3 while ($file) {
120 3         14 $parent =~ s{/+[^/]+$}{};
121 3 50       35 last if not -d $parent;
122 3 100       8 return $file if check_files_are_the_same('debian', $parent);
123 2         5 $file = $parent;
124 2 50       8 last if $file !~ m{/};
125             }
126 0         0 return;
127             }
128              
129             =item check_files_are_the_same($file1, $file2, $resolve_symlink)
130              
131             This function verifies that both files are the same by checking that the device
132             numbers and the inode numbers returned by stat()/lstat() are the same. If
133             $resolve_symlink is true then stat() is used, otherwise lstat() is used.
134              
135             =cut
136              
137             sub check_files_are_the_same($$;$) {
138 5     5 1 13 my ($file1, $file2, $resolve_symlink) = @_;
139              
140 5 100       21 return 1 if $file1 eq $file2;
141 4 50 33     102 return 0 if ((! -e $file1) || (! -e $file2));
142 4         11 my (@stat1, @stat2);
143 4 50       10 if ($resolve_symlink) {
144 0         0 @stat1 = stat($file1);
145 0         0 @stat2 = stat($file2);
146             } else {
147 4         48 @stat1 = lstat($file1);
148 4         52 @stat2 = lstat($file2);
149             }
150 4   100     21 my $result = ($stat1[0] == $stat2[0]) && ($stat1[1] == $stat2[1]);
151 4         20 return $result;
152             }
153              
154              
155             =item canonpath($file)
156              
157             This function returns a cleaned path. It simplifies double //, and remove
158             /./ and /../ intelligently. For /../ it simplifies the path only if the
159             previous element is not a symlink. Thus it should only be used on real
160             filenames.
161              
162             =cut
163              
164             sub canonpath($) {
165 7     7 1 1042 my $path = shift;
166 7         31 $path = File::Spec->canonpath($path);
167 7         79 my ($v, $dirs, $file) = File::Spec->splitpath($path);
168 7         39 my @dirs = File::Spec->splitdir($dirs);
169 7         12 my @new;
170 7         12 foreach my $d (@dirs) {
171 38 100       65 if ($d eq '..') {
172 5 50 33     25 if (scalar(@new) > 0 and $new[-1] ne '..') {
173 5 50       11 next if $new[-1] eq ''; # Root directory has no parent
174 5         47 my $parent = File::Spec->catpath($v,
175             File::Spec->catdir(@new), '');
176 5 100       86 if (not -l $parent) {
177 4         14 pop @new;
178             } else {
179 1         5 push @new, $d;
180             }
181             } else {
182 0         0 push @new, $d;
183             }
184             } else {
185 33         53 push @new, $d;
186             }
187             }
188 7         118 return File::Spec->catpath($v, File::Spec->catdir(@new), $file);
189             }
190              
191             =item $newpath = resolve_symlink($symlink)
192              
193             Return the filename of the file pointed by the symlink. The new name is
194             canonicalized by canonpath().
195              
196             =cut
197              
198             sub resolve_symlink($) {
199 3     3 1 7 my $symlink = shift;
200 3         43 my $content = readlink($symlink);
201 3 50       11 return unless defined $content;
202 3 100       25 if (File::Spec->file_name_is_absolute($content)) {
203 1         3 return canonpath($content);
204             } else {
205 2         23 my ($link_v, $link_d, $link_f) = File::Spec->splitpath($symlink);
206 2         15 my ($cont_v, $cont_d, $cont_f) = File::Spec->splitpath($content);
207 2         16 my $new = File::Spec->catpath($link_v, $link_d . '/' . $cont_d, $cont_f);
208 2         6 return canonpath($new);
209             }
210             }
211              
212             =item check_directory_traversal($basedir, $dir)
213              
214             This function verifies that the directory $dir does not contain any symlink
215             that goes beyond $basedir (which should be either equal or a parent of $dir).
216              
217             =cut
218              
219             sub check_directory_traversal {
220 18     18 1 47 my ($basedir, $dir) = @_;
221              
222 18         599 my $canon_basedir = realpath($basedir);
223             my $check_symlinks = sub {
224 85     85   5226 my $canon_pathname = realpath($_);
225 85 100       245 if (not defined $canon_pathname) {
226 1 50       11 return if $! == ENOENT;
227              
228 1         4 syserr(g_("pathname '%s' cannot be canonicalized"), $_);
229             }
230 84 100       193 return if $canon_pathname eq '/dev/null';
231 83 50       134 return if $canon_pathname eq $canon_basedir;
232 83 100       5249 return if $canon_pathname =~ m{^\Q$canon_basedir/\E};
233              
234 10         41 error(g_("pathname '%s' points outside source root (to '%s')"),
235             $_, $canon_pathname);
236 18         113 };
237              
238 18         2114 find({
239             wanted => $check_symlinks,
240             no_chdir => 1,
241             follow => 1,
242             follow_skip => 2,
243             }, $dir);
244              
245 7         57 return;
246             }
247              
248             =item $cmdpath = find_command($command)
249              
250             Return the path of the command if defined and available on an absolute or
251             relative path or on the $PATH, undef otherwise.
252              
253             =cut
254              
255             sub find_command($) {
256 520     520 1 700332 my $cmd = shift;
257              
258 520 50       2599 return if not $cmd;
259 520 50       2630 if ($cmd =~ m{/}) {
260 0 0       0 return "$cmd" if -x "$cmd";
261             } else {
262 520         4188 foreach my $dir (split(/:/, $ENV{PATH})) {
263 3120 100       76406 return "$dir/$cmd" if -x "$dir/$cmd";
264             }
265             }
266 0           return;
267             }
268              
269             =item $control_file = get_control_path($pkg, $filetype)
270              
271             Return the path of the control file of type $filetype for the given
272             package.
273              
274             =item @control_files = get_control_path($pkg)
275              
276             Return the path of all available control files for the given package.
277              
278             =cut
279              
280             sub get_control_path($;$) {
281 0     0 1   my ($pkg, $filetype) = @_;
282 0           my $control_file;
283 0           my @exec = ('dpkg-query', '--control-path', $pkg);
284 0 0         push @exec, $filetype if defined $filetype;
285 0           spawn(exec => \@exec, wait_child => 1, to_string => \$control_file);
286 0           chomp($control_file);
287 0 0         if (defined $filetype) {
288 0 0         return if $control_file eq '';
289 0           return $control_file;
290             }
291 0 0         return () if $control_file eq '';
292 0           return split(/\n/, $control_file);
293             }
294              
295             =item $file = find_build_file($basename)
296              
297             Selects the right variant of the given file: the arch-specific variant
298             ("$basename.$arch") has priority over the OS-specific variant
299             ("$basename.$os") which has priority over the default variant
300             ("$basename"). If none of the files exists, then it returns undef.
301              
302             =item @files = find_build_file($basename)
303              
304             Return the available variants of the given file. Returns an empty
305             list if none of the files exists.
306              
307             =cut
308              
309             sub find_build_file($) {
310 0     0 1   my $base = shift;
311 0           my $host_arch = get_host_arch();
312 0           my ($abi, $libc, $host_os, $cpu) = debarch_to_debtuple($host_arch);
313 0           my @files;
314 0           foreach my $f ("$base.$host_arch", "$base.$host_os", "$base") {
315 0 0         push @files, $f if -f $f;
316             }
317 0 0         return @files if wantarray;
318 0 0         return $files[0] if scalar @files;
319 0           return;
320             }
321              
322             =back
323              
324             =head1 CHANGES
325              
326             =head2 Version 1.05 (dpkg 1.20.4)
327              
328             New function: check_directory_traversal().
329              
330             =head2 Version 1.04 (dpkg 1.17.11)
331              
332             Update semantics: find_command() now handles an empty or undef argument.
333              
334             =head2 Version 1.03 (dpkg 1.16.1)
335              
336             New function: find_build_file()
337              
338             =head2 Version 1.02 (dpkg 1.16.0)
339              
340             New function: get_control_path()
341              
342             =head2 Version 1.01 (dpkg 1.15.8)
343              
344             New function: find_command()
345              
346             =head2 Version 1.00 (dpkg 1.15.6)
347              
348             Mark the module as public.
349              
350             =cut
351              
352             1;