xref: /curl/tests/pathhelp.pm (revision 64936919)
1###########################################################################
2#                                  _   _ ____  _
3#  Project                     ___| | | |  _ \| |
4#                             / __| | | | |_) | |
5#                            | (__| |_| |  _ <| |___
6#                             \___|\___/|_| \_\_____|
7#
8# Copyright (C) Evgeny Grin (Karlson2k), <k2k@narod.ru>.
9#
10# This software is licensed as described in the file COPYING, which
11# you should have received as part of this distribution. The terms
12# are also available at https://curl.se/docs/copyright.html.
13#
14# You may opt to use, copy, modify, merge, publish, distribute and/or sell
15# copies of the Software, and permit persons to whom the Software is
16# furnished to do so, under the terms of the COPYING file.
17#
18# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19# KIND, either express or implied.
20#
21# SPDX-License-Identifier: curl
22#
23###########################################################################
24
25# This Perl package helps with path transforming when running curl tests on
26# Win32 platform with Msys or Cygwin.
27# Three main functions 'sys_native_abs_path', 'sys_native_path' and
28# 'build_sys_abs_path' autodetect format of given pathnames. Following formats
29# are supported:
30#  (1) /some/path   - absolute path in Unix-style
31#  (2) D:/some/path - absolute path in Win32-style
32#  (3) some/path    - relative path
33#  (4) D:some/path  - path relative to current directory on Win32 drive (paths
34#                     like 'D:' are treated as 'D:./') (*)
35#  (5) \some/path   - path from root directory on current Win32 drive (*)
36# All forward '/' and back '\' slashes are treated identically except leading
37# slash in forms (1) and (5).
38# Forward slashes are simpler processed in Perl, do not require extra escaping
39# for shell (unlike back slashes) and accepted by Win32 native programs, so
40# all functions return paths with only forward slashes except
41# 'sys_native_path' which returns paths with first forward slash for form (5).
42# All returned paths don't contain any duplicated slashes, only single slashes
43# are used as directory separators on output.
44# On non-Windows platforms functions acts as transparent wrappers for similar
45# Perl's functions or return unmodified string (depending on functionality),
46# so all functions can be unconditionally used on all platforms.
47#
48# (*) CAUTION! Forms (4) and (5) are not recommended to use as they can be
49#     interpreted incorrectly in Perl and Msys/Cygwin environment have low
50#     control on Win32 current drive and Win32 current path on specific drive.
51
52package pathhelp;
53
54use strict;
55use warnings;
56use Cwd 'abs_path';
57
58BEGIN {
59    use base qw(Exporter);
60
61    our @EXPORT_OK = qw(
62        os_is_win
63        exe_ext
64        sys_native_abs_path
65        sys_native_current_path
66        build_sys_abs_path
67        normalize_path
68        should_use_cygpath
69        drives_mounted_on_cygdrive
70    );
71}
72
73
74#######################################################################
75# Block for cached static variables
76#
77{
78    # Cached static variable, Perl 5.0-compatible.
79    my $is_win = $^O eq 'MSWin32'
80              || $^O eq 'cygwin'
81              || $^O eq 'msys';
82
83    # Returns boolean true if OS is any form of Windows.
84    sub os_is_win {
85        return $is_win;
86    }
87
88    # Cached static variable, Perl 5.0-compatible.
89    my $cygdrive_present;
90
91    # Returns boolean true if Win32 drives mounted with '/cygdrive/' prefix.
92    sub drives_mounted_on_cygdrive {
93        return $cygdrive_present if defined $cygdrive_present;
94        $cygdrive_present = ((-e '/cygdrive/') && (-d '/cygdrive/')) ? 1 : 0;
95        return $cygdrive_present;
96    }
97}
98
99my $use_cygpath;     # Only for Win32:
100                     #  undef - autodetect
101                     #      0 - do not use cygpath
102                     #      1 - use cygpath
103
104# Returns boolean true if 'cygpath' utility should be used for path conversion.
105sub should_use_cygpath {
106    return $use_cygpath if defined $use_cygpath;
107    if(os_is_win()) {
108        $use_cygpath = (qx{cygpath -u '.\\' 2>/dev/null} eq "./\n" && $? == 0);
109    } else {
110        $use_cygpath = 0;
111    }
112    return $use_cygpath;
113}
114
115#######################################################################
116# Performs path "normalization": all slashes converted to forward
117# slashes (except leading slash), all duplicated slashes are replaced
118# with single slashes, all relative directories ('./' and '../') are
119# resolved if possible.
120# Path processed as string, directories are not checked for presence so
121# path for not yet existing directory can be "normalized".
122#
123sub normalize_path;
124
125#######################################################################
126# Returns current working directory in Win32 format on Windows.
127#
128sub sys_native_current_path {
129    return Cwd::getcwd() if !os_is_win();
130
131    my $cur_dir;
132    if($^O eq 'msys') {
133        # MSys shell has built-in command.
134        chomp($cur_dir = `bash -c 'pwd -W'`);
135        if($? != 0) {
136            warn "Can't determine Win32 current directory.\n";
137            return undef;
138        }
139        # Add final slash if required.
140        $cur_dir .= '/' if length($cur_dir) > 3;
141    }
142    else {
143        # Do not use 'cygpath' - it falsely succeed on paths like '/cygdrive'.
144        $cur_dir = `cmd "/c;" echo %__CD__%`;
145        if($? != 0 || substr($cur_dir, 0, 1) eq '%') {
146            warn "Can't determine Win32 current directory.\n";
147            return undef;
148        }
149        # Remove both '\r' and '\n'.
150        $cur_dir =~ s{\n|\r}{}g;
151
152        # Replace back slashes with forward slashes.
153        $cur_dir =~ s{\\}{/}g;
154    }
155    return $cur_dir;
156}
157
158#######################################################################
159# Returns Win32 current drive letter with colon.
160#
161sub get_win32_current_drive {
162    # Notice parameter "/c;" - it's required to turn off Msys's
163    # transformation of '/c' and compatible with Cygwin.
164    my $drive_letter = `cmd "/c;" echo %__CD__:~0,2%`;
165    if($? != 0 || substr($drive_letter, 1, 1) ne ':') {
166        warn "Can't determine current Win32 drive letter.\n";
167        return undef;
168    }
169
170    return substr($drive_letter, 0, 2);
171}
172
173# Internal function. Converts path by using Msys's built-in transformation.
174# Returned path may contain duplicated and back slashes.
175sub do_msys_transform;
176
177# Internal function. Gets two parameters: first parameter must be single
178# drive letter ('c'), second optional parameter is path relative to drive's
179# current working directory. Returns Win32 absolute normalized path.
180sub get_abs_path_on_win32_drive;
181
182# Internal function. Tries to find or guess Win32 version of given
183# absolute Unix-style path. Other types of paths are not supported.
184# Returned paths contain only single forward slashes (no back and
185# duplicated slashes).
186# Last resort. Used only when other transformations are not available.
187sub do_dumb_guessed_transform;
188
189#######################################################################
190# Converts given path to system native format, i.e. to Win32 format on
191# Windows platform. Relative paths converted to relative, absolute
192# paths converted to absolute.
193#
194sub sys_native_path {
195    my ($path) = @_;
196
197    # Return untouched on non-Windows platforms.
198    return $path if (!os_is_win());
199
200    # Do not process empty path.
201    return $path if ($path eq '');
202
203    if($path =~ s{^([a-zA-Z]):$}{\u$1:}) {
204        # Path is single drive with colon. (C:)
205        # This type of paths is not processed correctly by 'cygpath'.
206        # WARNING!
207        # Be careful, this relative path can be accidentally transformed
208        # into wrong absolute path by adding to it some '/dirname' with
209        # slash at font.
210        return $path;
211    }
212    elsif($path =~ m{^\\} || $path =~ m{^[a-zA-Z]:[^/\\]}) {
213        # Path is a directory or filename on Win32 current drive or relative
214        # path on current directory on specific Win32 drive.
215        # ('\path' or 'D:path')
216        # First type of paths is not processed by Msys transformation and
217        # resolved to absolute path by 'cygpath'.
218        # Second type is not processed by Msys transformation and may be
219        # incorrectly processed by 'cygpath' (for paths like 'D:..\../.\')
220
221        my $first_char = ucfirst(substr($path, 0, 1));
222
223        # Replace any back and duplicated slashes with single forward slashes.
224        $path =~ s{[\\/]+}{/}g;
225
226        # Convert leading slash back to forward slash to indicate
227        # directory on Win32 current drive or capitalize drive letter.
228        substr($path, 0, 1, $first_char);
229        return $path;
230    }
231    elsif(should_use_cygpath()) {
232        # 'cygpath' is available - use it.
233
234        # Remove leading duplicated forward and back slashes, as they may
235        # prevent transforming and may be not processed.
236        $path =~ s{^([\\/])[\\/]+}{$1}g;
237
238        my $has_final_slash = ($path =~ m{[/\\]$});
239
240        # Use 'cygpath', '-m' means Win32 path with forward slashes.
241        chomp($path = `cygpath -m '$path'`);
242        if ($? != 0) {
243            warn "Can't convert path by \"cygpath\".\n";
244            return undef;
245        }
246
247        # 'cygpath' may remove last slash for existing directories.
248        $path .= '/' if($has_final_slash);
249
250        # Remove any duplicated forward slashes (added by 'cygpath' for root
251        # directories)
252        $path =~ s{//+}{/}g;
253
254        return $path;
255    }
256    elsif($^O eq 'msys') {
257        # Msys transforms automatically path to Windows native form in staring
258        # program parameters if program is not Msys-based.
259
260        $path = do_msys_transform($path);
261        return undef if !defined $path;
262
263        # Capitalize drive letter for Win32 paths.
264        $path =~ s{^([a-z]:)}{\u$1};
265
266        # Replace any back and duplicated slashes with single forward slashes.
267        $path =~ s{[\\/]+}{/}g;
268        return $path;
269    }
270    elsif($path =~ s{^([a-zA-Z]):[/\\]}{\u$1:/}) {
271        # Path is already in Win32 form. ('C:\path')
272
273        # Replace any back and duplicated slashes with single forward slashes.
274        $path =~ s{[\\/]+}{/}g;
275        return $path;
276    }
277    elsif($path !~ m{^/}) {
278        # Path is in relative form. ('path/name', './path' or '../path')
279
280        # Replace any back and duplicated slashes with single forward slashes.
281        $path =~ s{[\\/]+}{/}g;
282        return $path;
283    }
284
285    # OS is Windows, but not Msys, path is absolute, path is not in Win32
286    # form and 'cygpath' is not available.
287    return do_dumb_guessed_transform($path);
288}
289
290#######################################################################
291# Converts given path to system native absolute path, i.e. to Win32
292# absolute format on Windows platform. Both relative and absolute
293# formats are supported for input.
294#
295sub sys_native_abs_path {
296    my ($path) = @_;
297
298    if(!os_is_win()) {
299        # Convert path to absolute form.
300        $path = Cwd::abs_path($path);
301
302        # Do not process further on non-Windows platforms.
303        return $path;
304    }
305
306    if($path =~ m{^([a-zA-Z]):($|[^/\\].*$)}) {
307        # Path is single drive with colon or relative path on Win32 drive.
308        # ('C:' or 'C:path')
309        # This kind of relative path is not processed correctly by 'cygpath'.
310        # Get specified drive letter
311        return get_abs_path_on_win32_drive($1, $2);
312    }
313    elsif($path eq '') {
314        # Path is empty string. Return current directory.
315        # Empty string processed correctly by 'cygpath'.
316
317        return sys_native_current_path();
318    }
319    elsif(should_use_cygpath()) {
320        # 'cygpath' is available - use it.
321
322        my $has_final_slash = ($path =~ m{[\\/]$});
323
324        # Remove leading duplicated forward and back slashes, as they may
325        # prevent transforming and may be not processed.
326        $path =~ s{^([\\/])[\\/]+}{$1}g;
327
328        print "Inter result: \"$path\"\n";
329        # Use 'cygpath', '-m' means Win32 path with forward slashes,
330        # '-a' means absolute path
331        chomp($path = `cygpath -m -a '$path'`);
332        if($? != 0) {
333            warn "Can't resolve path by usung \"cygpath\".\n";
334            return undef;
335        }
336
337        # 'cygpath' may remove last slash for existing directories.
338        $path .= '/' if($has_final_slash);
339
340        # Remove any duplicated forward slashes (added by 'cygpath' for root
341        # directories)
342        $path =~ s{//+}{/}g;
343
344        return $path
345    }
346    elsif($path =~ s{^([a-zA-Z]):[/\\]}{\u$1:/}) {
347        # Path is already in Win32 form. ('C:\path')
348
349        # Replace any possible back slashes with forward slashes,
350        # remove any duplicated slashes, resolve relative dirs.
351        return normalize_path($path);
352    }
353    elsif(substr($path, 0, 1) eq '\\' ) {
354        # Path is directory or filename on Win32 current drive. ('\Windows')
355
356        my $w32drive = get_win32_current_drive();
357        return undef if !defined $w32drive;
358
359        # Combine drive and path.
360        # Replace any possible back slashes with forward slashes,
361        # remove any duplicated slashes, resolve relative dirs.
362        return normalize_path($w32drive . $path);
363    }
364
365    if(substr($path, 0, 1) ne '/') {
366        # Path is in relative form. Resolve relative directories in Unix form
367        # *BEFORE* converting to Win32 form otherwise paths like
368        # '../../../cygdrive/c/windows' will not be resolved.
369
370        my $cur_dir;
371        # MSys shell has built-in command.
372        if($^O eq 'msys') {
373            $cur_dir = `bash -c 'pwd -L'`;
374        }
375        else {
376            $cur_dir = `pwd -L`;
377        }
378        if($? != 0) {
379            warn "Can't determine current working directory.\n";
380            return undef;
381        }
382        chomp($cur_dir);
383
384        $path = $cur_dir . '/' . $path;
385    }
386
387    # Resolve relative dirs.
388    $path = normalize_path($path);
389    return undef unless defined $path;
390
391    if($^O eq 'msys') {
392        # Msys transforms automatically path to Windows native form in staring
393        # program parameters if program is not Msys-based.
394        $path = do_msys_transform($path);
395        return undef if !defined $path;
396
397        # Replace any back and duplicated slashes with single forward slashes.
398        $path =~ s{[\\/]+}{/}g;
399        return $path;
400    }
401    # OS is Windows, but not Msys, path is absolute, path is not in Win32
402    # form and 'cygpath' is not available.
403
404    return do_dumb_guessed_transform($path);
405}
406
407# Internal function. Converts given Unix-style absolute path to Win32 format.
408sub simple_transform_win32_to_unix;
409
410#######################################################################
411# Converts given path to build system format absolute path, i.e. to
412# Msys/Cygwin Unix-style absolute format on Windows platform. Both
413# relative and absolute formats are supported for input.
414#
415sub build_sys_abs_path {
416    my ($path) = @_;
417
418    if(!os_is_win()) {
419        # Convert path to absolute form.
420        $path = Cwd::abs_path($path);
421
422        # Do not process further on non-Windows platforms.
423        return $path;
424    }
425
426    if($path =~ m{^([a-zA-Z]):($|[^/\\].*$)}) {
427        # Path is single drive with colon or relative path on Win32 drive.
428        # ('C:' or 'C:path')
429        # This kind of relative path is not processed correctly by 'cygpath'.
430        # Get specified drive letter
431
432        # Resolve relative dirs in Win32-style path or paths like 'D:/../c/'
433        # will be resolved incorrectly.
434        # Replace any possible back slashes with forward slashes,
435        # remove any duplicated slashes.
436        $path = get_abs_path_on_win32_drive($1, $2);
437        return undef if !defined $path;
438
439        return simple_transform_win32_to_unix($path);
440    }
441    elsif($path eq '') {
442        # Path is empty string. Return current directory.
443        # Empty string processed correctly by 'cygpath'.
444
445        # MSys shell has built-in command.
446        if($^O eq 'msys') {
447            chomp($path = `bash -c 'pwd -L'`);
448        }
449        else {
450            chomp($path = `pwd -L`);
451        }
452        if($? != 0) {
453            warn "Can't determine Unix-style current working directory.\n";
454            return undef;
455        }
456
457        # Add final slash if not at root dir.
458        $path .= '/' if length($path) > 2;
459        return $path;
460    }
461    elsif(should_use_cygpath()) {
462        # 'cygpath' is available - use it.
463
464        my $has_final_slash = ($path =~ m{[\\/]$});
465
466        # Resolve relative directories, as they may be not resolved for
467        # Unix-style paths.
468        # Remove duplicated slashes, as they may be not processed.
469        $path = normalize_path($path);
470        return undef if !defined $path;
471
472        # Use 'cygpath', '-u' means Unix-stile path,
473        # '-a' means absolute path
474        chomp($path = `cygpath -u -a '$path'`);
475        if($? != 0) {
476            warn "Can't resolve path by usung \"cygpath\".\n";
477            return undef;
478        }
479
480        # 'cygpath' removes last slash if path is root dir on Win32 drive.
481        # Restore it.
482        $path .= '/' if($has_final_slash &&
483                        substr($path, length($path) - 1, 1) ne '/');
484
485        return $path
486    }
487    elsif($path =~ m{^[a-zA-Z]:[/\\]}) {
488        # Path is already in Win32 form. ('C:\path')
489
490        # Resolve relative dirs in Win32-style path otherwise paths
491        # like 'D:/../c/' will be resolved incorrectly.
492        # Replace any possible back slashes with forward slashes,
493        # remove any duplicated slashes.
494        $path = normalize_path($path);
495        return undef if !defined $path;
496
497        return simple_transform_win32_to_unix($path);
498    }
499    elsif(substr($path, 0, 1) eq '\\') {
500        # Path is directory or filename on Win32 current drive. ('\Windows')
501
502        my $w32drive = get_win32_current_drive();
503        return undef if !defined $w32drive;
504
505        # Combine drive and path.
506        # Resolve relative dirs in Win32-style path or paths like 'D:/../c/'
507        # will be resolved incorrectly.
508        # Replace any possible back slashes with forward slashes,
509        # remove any duplicated slashes.
510        $path = normalize_path($w32drive . $path);
511        return undef if !defined $path;
512
513        return simple_transform_win32_to_unix($path);
514    }
515
516    # Path is not in any Win32 form.
517    if(substr($path, 0, 1) ne '/') {
518        # Path in relative form. Resolve relative directories in Unix form
519        # *BEFORE* converting to Win32 form otherwise paths like
520        # '../../../cygdrive/c/windows' will not be resolved.
521
522        my $cur_dir;
523        # MSys shell has built-in command.
524        if($^O eq 'msys') {
525            $cur_dir = `bash -c 'pwd -L'`;
526        }
527        else {
528            $cur_dir = `pwd -L`;
529        }
530        if($? != 0) {
531            warn "Can't determine current working directory.\n";
532            return undef;
533        }
534        chomp($cur_dir);
535
536        $path = $cur_dir . '/' . $path;
537    }
538
539    return normalize_path($path);
540}
541
542#######################################################################
543# Performs path "normalization": all slashes converted to forward
544# slashes (except leading slash), all duplicated slashes are replaced
545# with single slashes, all relative directories ('./' and '../') are
546# resolved if possible.
547# Path processed as string, directories are not checked for presence so
548# path for not yet existing directory can be "normalized".
549#
550sub normalize_path {
551    my ($path) = @_;
552
553    # Don't process empty paths.
554    return $path if $path eq '';
555
556    if($path !~ m{(?:^|\\|/)\.{1,2}(?:\\|/|$)}) {
557        # Speed up processing of simple paths.
558        my $first_char = substr($path, 0, 1);
559        $path =~ s{[\\/]+}{/}g;
560        # Restore starting backslash if any.
561        substr($path, 0, 1, $first_char);
562        return $path;
563    }
564
565    my @arr;
566    my $prefix;
567    my $have_root = 0;
568
569    # Check whether path starts from Win32 drive. ('C:path' or 'C:\path')
570    if($path =~ m{^([a-zA-Z]:(/|\\)?)(.*$)}) {
571        $prefix = $1;
572        $have_root = 1 if defined $2;
573        # Process path separately from drive letter.
574        @arr = split(m{\/|\\}, $3);
575        # Replace backslash with forward slash if required.
576        substr($prefix, 2, 1, '/') if $have_root;
577    }
578    else {
579        if($path =~ m{^(\/|\\)}) {
580            $have_root = 1;
581            $prefix = $1;
582        }
583        else {
584            $prefix = '';
585        }
586        @arr = split(m{\/|\\}, $path);
587    }
588
589    my $p = 0;
590    my @res;
591
592    for my $el (@arr) {
593        if(length($el) == 0 || $el eq '.') {
594            next;
595        }
596        elsif($el eq '..' && @res > 0 && $res[-1] ne '..') {
597            pop @res;
598            next;
599        }
600        push @res, $el;
601    }
602    if($have_root && @res > 0 && $res[0] eq '..') {
603        warn "Error processing path \"$path\": " .
604             "Parent directory of root directory does not exist!\n";
605        return undef;
606    }
607
608    my $ret = $prefix . join('/', @res);
609    $ret .= '/' if($path =~ m{\\$|/$} && scalar @res > 0);
610
611    return $ret;
612}
613
614# Internal function. Converts path by using Msys's built-in
615# transformation.
616sub do_msys_transform {
617    my ($path) = @_;
618    return undef if $^O ne 'msys';
619    return $path if $path eq '';
620
621    # Remove leading double forward slashes, as they turn off Msys
622    # transforming.
623    $path =~ s{^/[/\\]+}{/};
624
625    # Msys transforms automatically path to Windows native form in staring
626    # program parameters if program is not Msys-based.
627    # Note: already checked that $path is non-empty.
628    $path = `cmd //c echo '$path'`;
629    if($? != 0) {
630        warn "Can't transform path into Win32 form by using Msys" .
631             "internal transformation.\n";
632        return undef;
633    }
634
635    # Remove double quotes, they are added for paths with spaces,
636    # remove both '\r' and '\n'.
637    $path =~ s{^\"|\"$|\"\r|\n|\r}{}g;
638
639    return $path;
640}
641
642# Internal function. Gets two parameters: first parameter must be single
643# drive letter ('c'), second optional parameter is path relative to drive's
644# current working directory. Returns Win32 absolute normalized path.
645sub get_abs_path_on_win32_drive {
646    my ($drv, $rel_path) = @_;
647    my $res;
648
649    # Get current directory on specified drive.
650    # "/c;" is compatible with both Msys and Cygwin.
651    my $cur_dir_on_drv = `cmd "/c;" echo %=$drv:%`;
652    if($? != 0) {
653        warn "Can't determine Win32 current directory on drive $drv:.\n";
654        return undef;
655    }
656
657    if($cur_dir_on_drv =~ m{^[%]}) {
658        # Current directory on drive is not set, default is
659        # root directory.
660
661        $res = ucfirst($drv) . ':/';
662    }
663    else {
664        # Current directory on drive was set.
665        # Remove both '\r' and '\n'.
666        $cur_dir_on_drv =~ s{\n|\r}{}g;
667
668        # Append relative path part.
669        $res = $cur_dir_on_drv . '/';
670    }
671    $res .= $rel_path if defined $rel_path;
672
673    # Replace any possible back slashes with forward slashes,
674    # remove any duplicated slashes, resolve relative dirs.
675    return normalize_path($res);
676}
677
678# Internal function. Tries to find or guess Win32 version of given
679# absolute Unix-style path. Other types of paths are not supported.
680# Returned paths contain only single forward slashes (no back and
681# duplicated slashes).
682# Last resort. Used only when other transformations are not available.
683sub do_dumb_guessed_transform {
684    my ($path) = @_;
685
686    # Replace any possible back slashes and duplicated forward slashes
687    # with single forward slashes.
688    $path =~ s{[/\\]+}{/}g;
689
690    # Empty path is not valid.
691    return undef if (length($path) == 0);
692
693    # RE to find Win32 drive letter
694    my $drv_ltr_re = drives_mounted_on_cygdrive() ?
695                        qr{^/cygdrive/([a-zA-Z])($|/.*$)} :
696                        qr{^/([a-zA-Z])($|/.*$)};
697
698    # Check path whether path is Win32 directly mapped drive and try to
699    # transform it assuming that drive letter is matched to Win32 drive letter.
700    if($path =~ m{$drv_ltr_re}) {
701        return ucfirst($1) . ':/' if(length($2) == 0);
702        return ucfirst($1) . ':' . $2;
703    }
704
705    # This may be some custom mapped path. ('/mymount/path')
706
707    # Must check longest possible path component as subdir can be mapped to
708    # different directory. For example '/usr/bin/' can be mapped to '/bin/' or
709    # '/bin/' can be mapped to '/usr/bin/'.
710    my $check_path = $path;
711    my $path_tail = '';
712    while(1) {
713        if(-d $check_path) {
714            my $res =
715                `(cd "$check_path" && cmd /c "echo %__CD__%") 2>/dev/null`;
716            if($? == 0 && substr($path, 0, 1) ne '%') {
717                # Remove both '\r' and '\n'.
718                $res =~ s{\n|\r}{}g;
719
720                # Replace all back slashes with forward slashes.
721                $res =~ s{\\}{/}g;
722
723                if(length($path_tail) > 0) {
724                    return $res . $path_tail;
725                }
726                else {
727                    $res =~ s{/$}{} if $check_path !~ m{/$};
728                    return $res;
729                }
730            }
731        }
732        if($check_path =~ m{(^.*/)([^/]+/*)}) {
733            $check_path = $1;
734            $path_tail = $2 . $path_tail;
735        }
736        else {
737            # Shouldn't happens as root '/' directory should always
738            # be resolvable.
739            warn "Can't determine Win32 directory for path \"$path\".\n";
740            return undef;
741        }
742    }
743}
744
745
746# Internal function. Converts given Unix-style absolute path to Win32 format.
747sub simple_transform_win32_to_unix {
748    my ($path) = @_;
749
750    if(should_use_cygpath()) {
751        # 'cygpath' gives precise result.
752        my $res;
753        chomp($res = `cygpath -a -u '$path'`);
754        if($? != 0) {
755            warn "Can't determine Unix-style directory for Win32 " .
756                 "directory \"$path\".\n";
757            return undef;
758        }
759
760        # 'cygpath' removes last slash if path is root dir on Win32 drive.
761        $res .= '/' if(substr($res, length($res) - 1, 1) ne '/' &&
762                       $path =~ m{[/\\]$});
763        return $res;
764    }
765
766    # 'cygpath' is not available, use guessed transformation.
767    if($path !~ s{^([a-zA-Z]):(?:/|\\)}{/\l$1/}) {
768        warn "Can't determine Unix-style directory for Win32 " .
769             "directory \"$path\".\n";
770        return undef;
771    }
772
773    $path = '/cygdrive' . $path if(drives_mounted_on_cygdrive());
774    return $path;
775}
776#
777#***************************************************************************
778# Return file extension for executable files on this operating system
779#
780sub exe_ext {
781    my ($component, @arr) = @_;
782    if ($ENV{'CURL_TEST_EXE_EXT'}) {
783        return $ENV{'CURL_TEST_EXE_EXT'};
784    }
785    if ($ENV{'CURL_TEST_EXE_EXT_'.$component}) {
786        return $ENV{'CURL_TEST_EXE_EXT_'.$component};
787    }
788    if ($^O eq 'MSWin32' || $^O eq 'cygwin' || $^O eq 'msys' ||
789        $^O eq 'dos' || $^O eq 'os2') {
790        return '.exe';
791    }
792    return '';
793}
794
7951;    # End of module
796