| 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; | ||||||