File Coverage

File:Dpkg/Path.pm
Coverage:74.6%

linestmtbrancondsubpodtimecode
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
29package Dpkg::Path 1.05;
30
31
225
225
225
610
152
3951
use strict;
32
225
225
225
385
151
8972
use warnings;
33
34our @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
72sub 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
90sub 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
112sub 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
138sub 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
165sub 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
199sub 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
220sub 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
258sub 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
283sub 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
312sub 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
3551;