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