1 ###########################################################################
3 # Project ___| | | | _ \| |
5 # | (__| |_| | _ <| |___
6 # \___|\___/|_| \_\_____|
8 # Copyright (C) Evgeny Grin (Karlson2k), <k2k@narod.ru>.
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.
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.
18 # This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 # KIND, either express or implied.
21 # SPDX-License-Identifier: curl
23 ###########################################################################
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
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.
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.
59 use base qw(Exporter);
65 sys_native_current_path
69 drives_mounted_on_cygdrive
74 #######################################################################
75 # Block for cached static variables
78 # Cached static variable, Perl 5.0-compatible.
79 my $is_win = $^O eq 'MSWin32'
83 # Returns boolean true if OS is any form of Windows.
88 # Cached static variable, Perl 5.0-compatible.
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;
99 my $use_cygpath; # Only for Win32:
101 # 0 - do not use cygpath
104 # Returns boolean true if 'cygpath' utility should be used for path conversion.
105 sub should_use_cygpath {
106 return $use_cygpath if defined $use_cygpath;
108 $use_cygpath = (qx{cygpath -u '.\\' 2>/dev/null} eq "./\n" && $? == 0);
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".
125 #######################################################################
126 # Returns current working directory in Win32 format on Windows.
128 sub sys_native_current_path {
129 return Cwd::getcwd() if !os_is_win();
133 # MSys shell has built-in command.
134 chomp($cur_dir = `bash -c 'pwd -W'`);
136 warn "Can't determine Win32 current directory.\n";
139 # Add final slash if required.
140 $cur_dir .= '/' if length($cur_dir) > 3;
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";
149 # Remove both '\r' and '\n'.
150 $cur_dir =~ s{\n|\r}{}g;
152 # Replace back slashes with forward slashes.
153 $cur_dir =~ s{\\}{/}g;
158 #######################################################################
159 # Returns Win32 current drive letter with colon.
161 sub 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";
170 return substr($drive_letter, 0, 2);
173 # Internal function. Converts path by using Msys's built-in transformation.
174 # Returned path may contain duplicated and back slashes.
175 sub do_msys_transform;
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.
180 sub get_abs_path_on_win32_drive;
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.
187 sub do_dumb_guessed_transform;
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.
194 sub sys_native_path {
197 # Return untouched on non-Windows platforms.
198 return $path if (!os_is_win());
200 # Do not process empty path.
201 return $path if ($path eq '');
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'.
207 # Be careful, this relative path can be accidentally transformed
208 # into wrong absolute path by adding to it some '/dirname' with
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:..\../.\')
221 my $first_char = ucfirst(substr($path, 0, 1));
223 # Replace any back and duplicated slashes with single forward slashes.
224 $path =~ s{[\\/]+}{/}g;
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);
231 elsif(should_use_cygpath()) {
232 # 'cygpath' is available - use it.
234 # Remove leading duplicated forward and back slashes, as they may
235 # prevent transforming and may be not processed.
236 $path =~ s{^([\\/])[\\/]+}{$1}g;
238 my $has_final_slash = ($path =~ m{[/\\]$});
240 # Use 'cygpath', '-m' means Win32 path with forward slashes.
241 chomp($path = `cygpath -m '$path'`);
243 warn "Can't convert path by \"cygpath\".\n";
247 # 'cygpath' may remove last slash for existing directories.
248 $path .= '/' if($has_final_slash);
250 # Remove any duplicated forward slashes (added by 'cygpath' for root
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.
260 $path = do_msys_transform($path);
261 return undef if !defined $path;
263 # Capitalize drive letter for Win32 paths.
264 $path =~ s{^([a-z]:)}{\u$1};
266 # Replace any back and duplicated slashes with single forward slashes.
267 $path =~ s{[\\/]+}{/}g;
270 elsif($path =~ s{^([a-zA-Z]):[/\\]}{\u$1:/}) {
271 # Path is already in Win32 form. ('C:\path')
273 # Replace any back and duplicated slashes with single forward slashes.
274 $path =~ s{[\\/]+}{/}g;
277 elsif($path !~ m{^/}) {
278 # Path is in relative form. ('path/name', './path' or '../path')
280 # Replace any back and duplicated slashes with single forward slashes.
281 $path =~ s{[\\/]+}{/}g;
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);
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.
295 sub sys_native_abs_path {
299 # Convert path to absolute form.
300 $path = Cwd::abs_path($path);
302 # Do not process further on non-Windows platforms.
306 if($path =~ m{^([a-zA-Z]):($|[^/\\].*$)}) {
307 # Path is single drive with colon or relative path on Win32 drive.
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);
314 # Path is empty string. Return current directory.
315 # Empty string processed correctly by 'cygpath'.
317 return sys_native_current_path();
319 elsif(should_use_cygpath()) {
320 # 'cygpath' is available - use it.
322 my $has_final_slash = ($path =~ m{[\\/]$});
324 # Remove leading duplicated forward and back slashes, as they may
325 # prevent transforming and may be not processed.
326 $path =~ s{^([\\/])[\\/]+}{$1}g;
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'`);
333 warn "Can't resolve path by usung \"cygpath\".\n";
337 # 'cygpath' may remove last slash for existing directories.
338 $path .= '/' if($has_final_slash);
340 # Remove any duplicated forward slashes (added by 'cygpath' for root
346 elsif($path =~ s{^([a-zA-Z]):[/\\]}{\u$1:/}) {
347 # Path is already in Win32 form. ('C:\path')
349 # Replace any possible back slashes with forward slashes,
350 # remove any duplicated slashes, resolve relative dirs.
351 return normalize_path($path);
353 elsif(substr($path, 0, 1) eq '\\' ) {
354 # Path is directory or filename on Win32 current drive. ('\Windows')
356 my $w32drive = get_win32_current_drive();
357 return undef if !defined $w32drive;
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);
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.
371 # MSys shell has built-in command.
373 $cur_dir = `bash -c 'pwd -L'`;
379 warn "Can't determine current working directory.\n";
384 $path = $cur_dir . '/' . $path;
387 # Resolve relative dirs.
388 $path = normalize_path($path);
389 return undef unless defined $path;
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;
397 # Replace any back and duplicated slashes with single forward slashes.
398 $path =~ s{[\\/]+}{/}g;
401 # OS is Windows, but not Msys, path is absolute, path is not in Win32
402 # form and 'cygpath' is not available.
404 return do_dumb_guessed_transform($path);
407 # Internal function. Converts given Unix-style absolute path to Win32 format.
408 sub simple_transform_win32_to_unix;
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.
415 sub build_sys_abs_path {
419 # Convert path to absolute form.
420 $path = Cwd::abs_path($path);
422 # Do not process further on non-Windows platforms.
426 if($path =~ m{^([a-zA-Z]):($|[^/\\].*$)}) {
427 # Path is single drive with colon or relative path on Win32 drive.
429 # This kind of relative path is not processed correctly by 'cygpath'.
430 # Get specified drive letter
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;
439 return simple_transform_win32_to_unix($path);
442 # Path is empty string. Return current directory.
443 # Empty string processed correctly by 'cygpath'.
445 # MSys shell has built-in command.
447 chomp($path = `bash -c 'pwd -L'`);
450 chomp($path = `pwd -L`);
453 warn "Can't determine Unix-style current working directory.\n";
457 # Add final slash if not at root dir.
458 $path .= '/' if length($path) > 2;
461 elsif(should_use_cygpath()) {
462 # 'cygpath' is available - use it.
464 my $has_final_slash = ($path =~ m{[\\/]$});
466 # Resolve relative directories, as they may be not resolved for
468 # Remove duplicated slashes, as they may be not processed.
469 $path = normalize_path($path);
470 return undef if !defined $path;
472 # Use 'cygpath', '-u' means Unix-stile path,
473 # '-a' means absolute path
474 chomp($path = `cygpath -u -a '$path'`);
476 warn "Can't resolve path by usung \"cygpath\".\n";
480 # 'cygpath' removes last slash if path is root dir on Win32 drive.
482 $path .= '/' if($has_final_slash &&
483 substr($path, length($path) - 1, 1) ne '/');
487 elsif($path =~ m{^[a-zA-Z]:[/\\]}) {
488 # Path is already in Win32 form. ('C:\path')
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;
497 return simple_transform_win32_to_unix($path);
499 elsif(substr($path, 0, 1) eq '\\') {
500 # Path is directory or filename on Win32 current drive. ('\Windows')
502 my $w32drive = get_win32_current_drive();
503 return undef if !defined $w32drive;
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;
513 return simple_transform_win32_to_unix($path);
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.
523 # MSys shell has built-in command.
525 $cur_dir = `bash -c 'pwd -L'`;
531 warn "Can't determine current working directory.\n";
536 $path = $cur_dir . '/' . $path;
539 return normalize_path($path);
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".
553 # Don't process empty paths.
554 return $path if $path eq '';
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);
569 # Check whether path starts from Win32 drive. ('C:path' or 'C:\path')
570 if($path =~ m{^([a-zA-Z]:(/|\\)?)(.*$)}) {
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;
579 if($path =~ m{^(\/|\\)}) {
586 @arr = split(m{\/|\\}, $path);
593 if(length($el) == 0 || $el eq '.') {
596 elsif($el eq '..' && @res > 0 && $res[-1] ne '..') {
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";
608 my $ret = $prefix . join('/', @res);
609 $ret .= '/' if($path =~ m{\\$|/$} && scalar @res > 0);
614 # Internal function. Converts path by using Msys's built-in
616 sub do_msys_transform {
618 return undef if $^O ne 'msys';
619 return $path if $path eq '';
621 # Remove leading double forward slashes, as they turn off Msys
623 $path =~ s{^/[/\\]+}{/};
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'`;
630 warn "Can't transform path into Win32 form by using Msys" .
631 "internal transformation.\n";
635 # Remove double quotes, they are added for paths with spaces,
636 # remove both '\r' and '\n'.
637 $path =~ s{^\"|\"$|\"\r|\n|\r}{}g;
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.
645 sub get_abs_path_on_win32_drive {
646 my ($drv, $rel_path) = @_;
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:%`;
653 warn "Can't determine Win32 current directory on drive $drv:.\n";
657 if($cur_dir_on_drv =~ m{^[%]}) {
658 # Current directory on drive is not set, default is
661 $res = ucfirst($drv) . ':/';
664 # Current directory on drive was set.
665 # Remove both '\r' and '\n'.
666 $cur_dir_on_drv =~ s{\n|\r}{}g;
668 # Append relative path part.
669 $res = $cur_dir_on_drv . '/';
671 $res .= $rel_path if defined $rel_path;
673 # Replace any possible back slashes with forward slashes,
674 # remove any duplicated slashes, resolve relative dirs.
675 return normalize_path($res);
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.
683 sub do_dumb_guessed_transform {
686 # Replace any possible back slashes and duplicated forward slashes
687 # with single forward slashes.
688 $path =~ s{[/\\]+}{/}g;
690 # Empty path is not valid.
691 return undef if (length($path) == 0);
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])($|/.*$)};
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;
705 # This may be some custom mapped path. ('/mymount/path')
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;
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'.
720 # Replace all back slashes with forward slashes.
723 if(length($path_tail) > 0) {
724 return $res . $path_tail;
727 $res =~ s{/$}{} if $check_path !~ m{/$};
732 if($check_path =~ m{(^.*/)([^/]+/*)}) {
734 $path_tail = $2 . $path_tail;
737 # Shouldn't happens as root '/' directory should always
739 warn "Can't determine Win32 directory for path \"$path\".\n";
746 # Internal function. Converts given Unix-style absolute path to Win32 format.
747 sub simple_transform_win32_to_unix {
750 if(should_use_cygpath()) {
751 # 'cygpath' gives precise result.
753 chomp($res = `cygpath -a -u '$path'`);
755 warn "Can't determine Unix-style directory for Win32 " .
756 "directory \"$path\".\n";
760 # 'cygpath' removes last slash if path is root dir on Win32 drive.
761 $res .= '/' if(substr($res, length($res) - 1, 1) ne '/' &&
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";
773 $path = '/cygdrive' . $path if(drives_mounted_on_cygdrive());
777 #***************************************************************************
778 # Return file extension for executable files on this operating system
781 my ($component, @arr) = @_;
782 if ($ENV{'CURL_TEST_EXE_EXT'}) {
783 return $ENV{'CURL_TEST_EXE_EXT'};
785 if ($ENV{'CURL_TEST_EXE_EXT_'.$component}) {
786 return $ENV{'CURL_TEST_EXE_EXT_'.$component};
788 if ($^O eq 'MSWin32' || $^O eq 'cygwin' || $^O eq 'msys' ||
789 $^O eq 'dos' || $^O eq 'os2') {