File: | Dpkg/Path.pm |
Coverage: | 74.6% |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | # Copyright © 2007-2011 Raphaël Hertzog <hertzog@debian.org> | ||||||
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 <https://www.gnu.org/licenses/>. | ||||||
16 | |||||||
17 | =encoding utf8 | ||||||
18 | |||||||
19 - 27 | =head1 NAME Dpkg::Path - some common path handling functions =head1 DESCRIPTION It provides some functions to handle various path. =cut | ||||||
28 | |||||||
29 | package Dpkg::Path 1.05; | ||||||
30 | |||||||
31 | 225 225 225 | 610 152 3951 | use strict; | ||||
32 | 225 225 225 | 385 151 8972 | use warnings; | ||||
33 | |||||||
34 | our @EXPORT_OK = qw( | ||||||
35 | canonpath | ||||||
36 | resolve_symlink | ||||||
37 | check_files_are_the_same | ||||||
38 | check_directory_traversal | ||||||
39 | find_command | ||||||
40 | find_build_file | ||||||
41 | get_control_path | ||||||
42 | get_pkg_root_dir | ||||||
43 | guess_pkg_root_dir | ||||||
44 | relative_to_pkg_root | ||||||
45 | ); | ||||||
46 | |||||||
47 | 225 225 225 | 394 217 3350 | use Exporter qw(import); | ||||
48 | 225 225 225 | 29223 142862 14876 | use Errno qw(ENOENT); | ||||
49 | 225 225 225 | 688 225 3454 | use File::Spec; | ||||
50 | 225 225 225 | 508 150 5315 | use File::Find; | ||||
51 | 225 225 225 | 520 83 4795 | use Cwd qw(realpath); | ||||
52 | |||||||
53 | 225 225 225 | 373 152 7355 | use Dpkg::ErrorHandling; | ||||
54 | 225 225 225 | 514 287 6233 | use Dpkg::Gettext; | ||||
55 | 225 225 225 | 42963 302 9842 | use Dpkg::Arch qw(get_host_arch debarch_to_debtuple); | ||||
56 | 225 225 225 | 40665 231 158635 | use Dpkg::IPC; | ||||
57 | |||||||
58 - 70 | =head1 FUNCTIONS =over 8 =item get_pkg_root_dir($file) This function will scan upwards the hierarchy of directory to find out the directory which contains the "DEBIAN" sub-directory and it will return its path. This directory is the root directory of a package being built. If no DEBIAN subdirectory is found, it will return undef. =cut | ||||||
71 | |||||||
72 | sub get_pkg_root_dir($) { | ||||||
73 | 18 | 1 | 16 | my $file = shift; | |||
74 | 18 | 50 | $file =~ s{/+$}{}; | ||||
75 | 18 | 88 | $file =~ s{/+[^/]+$}{} if not -d $file; | ||||
76 | 18 | 28 | while ($file) { | ||||
77 | 63 | 228 | return $file if -d "$file/DEBIAN"; | ||||
78 | 54 | 67 | last if $file !~ m{/}; | ||||
79 | 45 | 97 | $file =~ s{/+[^/]+$}{}; | ||||
80 | } | ||||||
81 | 9 | 14 | return; | ||||
82 | } | ||||||
83 | |||||||
84 - 88 | =item relative_to_pkg_root($file) Returns the filename relative to get_pkg_root_dir($file). =cut | ||||||
89 | |||||||
90 | sub relative_to_pkg_root($) { | ||||||
91 | 6 | 1 | 7 | my $file = shift; | |||
92 | 6 | 8 | my $pkg_root = get_pkg_root_dir($file); | ||||
93 | 6 | 9 | if (defined $pkg_root) { | ||||
94 | 3 | 3 | $pkg_root .= '/'; | ||||
95 | 3 | 44 | return $file if ($file =~ s/^\Q$pkg_root\E//); | ||||
96 | } | ||||||
97 | 3 | 8 | return; | ||||
98 | } | ||||||
99 | |||||||
100 - 110 | =item guess_pkg_root_dir($file) This function tries to guess the root directory of the package build tree. It will first use get_pkg_root_dir(), but it will fallback to a more imprecise check: namely it will use the parent directory that is a sub-directory of the debian directory. It can still return undef if a file outside of the debian sub-directory is provided. =cut | ||||||
111 | |||||||
112 | sub guess_pkg_root_dir($) { | ||||||
113 | 6 | 1 | 9 | my $file = shift; | |||
114 | 6 | 10 | my $root = get_pkg_root_dir($file); | ||||
115 | 6 | 15 | return $root if defined $root; | ||||
116 | |||||||
117 | 3 | 10 | $file =~ s{/+$}{}; | ||||
118 | 3 | 11 | $file =~ s{/+[^/]+$}{} if not -d $file; | ||||
119 | 3 | 8 | my $parent = $file; | ||||
120 | 3 | 6 | while ($file) { | ||||
121 | 9 | 28 | $parent =~ s{/+[^/]+$}{}; | ||||
122 | 9 | 52 | last if not -d $parent; | ||||
123 | 9 | 19 | return $file if check_files_are_the_same('debian', $parent); | ||||
124 | 6 | 10 | $file = $parent; | ||||
125 | 6 | 21 | last if $file !~ m{/}; | ||||
126 | } | ||||||
127 | 0 | 0 | return; | ||||
128 | } | ||||||
129 | |||||||
130 - 136 | =item check_files_are_the_same($file1, $file2, $resolve_symlink) This function verifies that both files are the same by checking that the device numbers and the inode numbers returned by stat()/lstat() are the same. If $resolve_symlink is true then stat() is used, otherwise lstat() is used. =cut | ||||||
137 | |||||||
138 | sub check_files_are_the_same($$;$) { | ||||||
139 | 15 | 1 | 27 | my ($file1, $file2, $resolve_symlink) = @_; | |||
140 | |||||||
141 | 15 | 42 | return 1 if $file1 eq $file2; | ||||
142 | 12 | 100 | return 0 if ((! -e $file1) || (! -e $file2)); | ||||
143 | 12 | 16 | my (@stat1, @stat2); | ||||
144 | 12 | 18 | if ($resolve_symlink) { | ||||
145 | 0 | 0 | @stat1 = stat($file1); | ||||
146 | 0 | 0 | @stat2 = stat($file2); | ||||
147 | } else { | ||||||
148 | 12 | 35 | @stat1 = lstat($file1); | ||||
149 | 12 | 70 | @stat2 = lstat($file2); | ||||
150 | } | ||||||
151 | 12 | 55 | my $result = ($stat1[0] == $stat2[0]) && ($stat1[1] == $stat2[1]); | ||||
152 | 12 | 44 | return $result; | ||||
153 | } | ||||||
154 | |||||||
155 | |||||||
156 - 163 | =item canonpath($file) This function returns a cleaned path. It simplifies double //, and remove /./ and /../ intelligently. For /../ it simplifies the path only if the previous element is not a symlink. Thus it should only be used on real filenames. =cut | ||||||
164 | |||||||
165 | sub canonpath($) { | ||||||
166 | 21 | 1 | 23 | my $path = shift; | |||
167 | 21 | 43 | $path = File::Spec->canonpath($path); | ||||
168 | 21 | 137 | my ($v, $dirs, $file) = File::Spec->splitpath($path); | ||||
169 | 21 | 64 | my @dirs = File::Spec->splitdir($dirs); | ||||
170 | 21 | 15 | my @new; | ||||
171 | 21 | 20 | foreach my $d (@dirs) { | ||||
172 | 114 | 83 | if ($d eq '..') { | ||||
173 | 15 | 44 | if (scalar(@new) > 0 and $new[-1] ne '..') { | ||||
174 | 15 | 20 | next if $new[-1] eq ''; # Root directory has no parent | ||||
175 | 15 | 67 | my $parent = File::Spec->catpath($v, | ||||
176 | File::Spec->catdir(@new), ''); | ||||||
177 | 15 | 62 | if (not -l $parent) { | ||||
178 | 12 | 14 | pop @new; | ||||
179 | } else { | ||||||
180 | 3 | 6 | push @new, $d; | ||||
181 | } | ||||||
182 | } else { | ||||||
183 | 0 | 0 | push @new, $d; | ||||
184 | } | ||||||
185 | } else { | ||||||
186 | 99 | 84 | push @new, $d; | ||||
187 | } | ||||||
188 | } | ||||||
189 | 21 | 176 | return File::Spec->catpath($v, File::Spec->catdir(@new), $file); | ||||
190 | } | ||||||
191 | |||||||
192 - 197 | =item $newpath = resolve_symlink($symlink) Return the filename of the file pointed by the symlink. The new name is canonicalized by canonpath(). =cut | ||||||
198 | |||||||
199 | sub resolve_symlink($) { | ||||||
200 | 9 | 1 | 8 | my $symlink = shift; | |||
201 | 9 | 47 | my $content = readlink($symlink); | ||||
202 | 9 | 15 | return unless defined $content; | ||||
203 | 9 | 32 | if (File::Spec->file_name_is_absolute($content)) { | ||||
204 | 3 | 5 | return canonpath($content); | ||||
205 | } else { | ||||||
206 | 6 | 38 | my ($link_v, $link_d, $link_f) = File::Spec->splitpath($symlink); | ||||
207 | 6 | 23 | my ($cont_v, $cont_d, $cont_f) = File::Spec->splitpath($content); | ||||
208 | 6 | 23 | my $new = File::Spec->catpath($link_v, $link_d . '/' . $cont_d, $cont_f); | ||||
209 | 6 | 6 | return canonpath($new); | ||||
210 | } | ||||||
211 | } | ||||||
212 | |||||||
213 - 218 | =item check_directory_traversal($basedir, $dir) This function verifies that the directory $dir does not contain any symlink that goes beyond $basedir (which should be either equal or a parent of $dir). =cut | ||||||
219 | |||||||
220 | sub check_directory_traversal { | ||||||
221 | 54 | 1 | 81 | my ($basedir, $dir) = @_; | |||
222 | |||||||
223 | 54 | 454 | my $canon_basedir = realpath($basedir); | ||||
224 | # On Solaris /dev/null points to /devices/pseudo/mm@0:null. | ||||||
225 | 54 | 203 | my $canon_devnull = realpath('/dev/null'); | ||||
226 | my $check_symlinks = sub { | ||||||
227 | 231 | 2511 | my $canon_pathname = realpath($_); | ||||
228 | 231 | 240 | if (not defined $canon_pathname) { | ||||
229 | 3 | 12 | return if $! == ENOENT; | ||||
230 | |||||||
231 | 3 | 6 | syserr(g_("pathname '%s' cannot be canonicalized"), $_); | ||||
232 | } | ||||||
233 | 228 | 200 | return if $canon_pathname eq $canon_devnull; | ||||
234 | 225 | 192 | return if $canon_pathname eq $canon_basedir; | ||||
235 | 225 | 4405 | return if $canon_pathname =~ m{^\Q$canon_basedir/\E}; | ||||
236 | |||||||
237 | 30 | 71 | error(g_("pathname '%s' points outside source root (to '%s')"), | ||||
238 | $_, $canon_pathname); | ||||||
239 | 54 | 207 | }; | ||||
240 | |||||||
241 | 54 | 3211 | find({ | ||||
242 | wanted => $check_symlinks, | ||||||
243 | no_chdir => 1, | ||||||
244 | follow => 1, | ||||||
245 | follow_skip => 2, | ||||||
246 | }, $dir); | ||||||
247 | |||||||
248 | 21 | 121 | return; | ||||
249 | } | ||||||
250 | |||||||
251 - 256 | =item $cmdpath = find_command($command) Return the path of the command if defined and available on an absolute or relative path or on the $PATH, undef otherwise. =cut | ||||||
257 | |||||||
258 | sub find_command($) { | ||||||
259 | 1359 | 1 | 2057 | my $cmd = shift; | |||
260 | |||||||
261 | 1359 | 2219 | return if not $cmd; | ||||
262 | 1359 | 4405 | if ($cmd =~ m{/}) { | ||||
263 | 0 | 0 | return "$cmd" if -x "$cmd"; | ||||
264 | } else { | ||||||
265 | 1359 | 7394 | foreach my $dir (split(/:/, $ENV{PATH})) { | ||||
266 | 9513 | 64318 | return "$dir/$cmd" if -x "$dir/$cmd"; | ||||
267 | } | ||||||
268 | } | ||||||
269 | 0 | return; | |||||
270 | } | ||||||
271 | |||||||
272 - 281 | =item $control_file = get_control_path($pkg, $filetype) Return the path of the control file of type $filetype for the given package. =item @control_files = get_control_path($pkg) Return the path of all available control files for the given package. =cut | ||||||
282 | |||||||
283 | sub get_control_path($;$) { | ||||||
284 | 0 | 1 | my ($pkg, $filetype) = @_; | ||||
285 | 0 | my $control_file; | |||||
286 | 0 | my @exec = ('dpkg-query', '--control-path', $pkg); | |||||
287 | 0 | push @exec, $filetype if defined $filetype; | |||||
288 | 0 | spawn(exec => \@exec, wait_child => 1, to_string => \$control_file); | |||||
289 | 0 | chomp($control_file); | |||||
290 | 0 | if (defined $filetype) { | |||||
291 | 0 | return if $control_file eq ''; | |||||
292 | 0 | return $control_file; | |||||
293 | } | ||||||
294 | 0 | return () if $control_file eq ''; | |||||
295 | 0 | return split(/\n/, $control_file); | |||||
296 | } | ||||||
297 | |||||||
298 - 310 | =item $file = find_build_file($basename) Selects the right variant of the given file: the arch-specific variant ("$basename.$arch") has priority over the OS-specific variant ("$basename.$os") which has priority over the default variant ("$basename"). If none of the files exists, then it returns undef. =item @files = find_build_file($basename) Return the available variants of the given file. Returns an empty list if none of the files exists. =cut | ||||||
311 | |||||||
312 | sub find_build_file($) { | ||||||
313 | 0 | 1 | my $base = shift; | ||||
314 | 0 | my $host_arch = get_host_arch(); | |||||
315 | 0 | my ($abi, $libc, $host_os, $cpu) = debarch_to_debtuple($host_arch); | |||||
316 | 0 | my @files; | |||||
317 | 0 | foreach my $fn ("$base.$host_arch", "$base.$host_os", "$base") { | |||||
318 | 0 | push @files, $fn if -f $fn; | |||||
319 | } | ||||||
320 | 0 | return @files if wantarray; | |||||
321 | 0 | return $files[0] if scalar @files; | |||||
322 | 0 | return; | |||||
323 | } | ||||||
324 | |||||||
325 | =back | ||||||
326 | |||||||
327 - 353 | =head1 CHANGES =head2 Version 1.05 (dpkg 1.20.4) New function: check_directory_traversal(). =head2 Version 1.04 (dpkg 1.17.11) Update semantics: find_command() now handles an empty or undef argument. =head2 Version 1.03 (dpkg 1.16.1) New function: find_build_file() =head2 Version 1.02 (dpkg 1.16.0) New function: get_control_path() =head2 Version 1.01 (dpkg 1.15.8) New function: find_command() =head2 Version 1.00 (dpkg 1.15.6) Mark the module as public. =cut | ||||||
354 | |||||||
355 | 1; |