• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1###########################################################################
2#                                  _   _ ____  _
3#  Project                     ___| | | |  _ \| |
4#                             / __| | | | |_) | |
5#                            | (__| |_| |  _ <| |___
6#                             \___|\___/|_| \_\_____|
7#
8# Copyright (C) 2016 - 2021, 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###########################################################################
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
51package pathhelp;
52use strict;
53use warnings;
54use Cwd 'abs_path';
55
56BEGIN {
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
103our $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.
109sub 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#
129sub normalize_path;
130
131#######################################################################
132# Returns current working directory in Win32 format on Windows.
133#
134sub 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#
167sub 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.
181sub 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.
186sub 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.
193sub 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#
200sub 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#
301sub 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
376        my $cur_dir;
377        # MSys shell has built-in command.
378        if($^O eq 'msys') {
379            $cur_dir = `bash -c 'pwd -L'`;
380        }
381        else {
382            $cur_dir = `pwd -L`;
383        }
384        if($? != 0) {
385            warn "Can't determine current working directory.\n";
386            return undef;
387        }
388        chomp($cur_dir);
389
390        $path = $cur_dir . '/' . $path;
391    }
392
393    # Resolve relative dirs.
394    $path = normalize_path($path);
395    return undef unless defined $path;
396
397    if($^O eq 'msys') {
398        # Msys transforms automatically path to Windows native form in staring
399        # program parameters if program is not Msys-based.
400        $path = do_msys_transform($path);
401        return undef unless defined $path;
402
403        # Replace any back and duplicated slashes with single forward slashes.
404        $path =~ s{[\\/]+}{/}g;
405        return $path;
406    }
407    # OS is Windows, but not Msys, path is absolute, path is not in Win32
408    # form and 'cygpath' is not available.
409
410    return do_dumb_guessed_transform($path);
411}
412
413# Internal function. Converts given Unix-style absolute path to Win32 format.
414sub simple_transform_win32_to_unix;
415
416#######################################################################
417# Converts given path to build system format absolute path, i.e. to
418# Msys/Cygwin Unix-style absolute format on Windows platform. Both
419# relative and absolute formats are supported for input.
420#
421sub build_sys_abs_path {
422    my ($path) = @_;
423
424    unless(os_is_win()) {
425        # Convert path to absolute form.
426        $path = Cwd::abs_path($path);
427
428        # Do not process further on non-Windows platforms.
429        return $path;
430    }
431
432    if($path =~ m{^([a-zA-Z]):($|[^/\\].*$)}) {
433        # Path is single drive with colon or relative path on Win32 drive.
434        # ('C:' or 'C:path')
435        # This kind of relative path is not processed correctly by 'cygpath'.
436        # Get specified drive letter
437
438        # Resolve relative dirs in Win32-style path or paths like 'D:/../c/'
439        # will be resolved incorrectly.
440        # Replace any possible back slashes with forward slashes,
441        # remove any duplicated slashes.
442        $path = get_abs_path_on_win32_drive($1, $2);
443        return undef unless defined $path;
444
445        return simple_transform_win32_to_unix($path);
446    }
447    elsif($path eq '') {
448        # Path is empty string. Return current directory.
449        # Empty string processed correctly by 'cygpath'.
450
451        # MSys shell has built-in command.
452        if($^O eq 'msys') {
453            chomp($path = `bash -c 'pwd -L'`);
454        }
455        else {
456            chomp($path = `pwd -L`);
457        }
458        if($? != 0) {
459            warn "Can't determine Unix-style current working directory.\n";
460            return undef;
461        }
462
463        # Add final slash if not at root dir.
464        $path .= '/' if length($path) > 2;
465        return $path;
466    }
467    elsif(should_use_cygpath()) {
468        # 'cygpath' is available - use it.
469
470        my $has_final_slash = ($path =~ m{[\\/]$});
471
472        # Resolve relative directories, as they may be not resolved for
473        # Unix-style paths.
474        # Remove duplicated slashes, as they may be not processed.
475        $path = normalize_path($path);
476        return undef unless defined $path;
477
478        # Use 'cygpath', '-u' means Unix-stile path,
479        # '-a' means absolute path
480        chomp($path = `cygpath -u -a '$path'`);
481        if($? != 0) {
482            warn "Can't resolve path by usung \"cygpath\".\n";
483            return undef;
484        }
485
486        # 'cygpath' removes last slash if path is root dir on Win32 drive.
487        # Restore it.
488        $path .= '/' if($has_final_slash &&
489                        substr($path, length($path) - 1, 1) ne '/');
490
491        return $path
492    }
493    elsif($path =~ m{^[a-zA-Z]:[/\\]}) {
494        # Path is already in Win32 form. ('C:\path')
495
496        # Resolve relative dirs in Win32-style path otherwise paths
497        # like 'D:/../c/' will be resolved incorrectly.
498        # Replace any possible back slashes with forward slashes,
499        # remove any duplicated slashes.
500        $path = normalize_path($path);
501        return undef unless defined $path;
502
503        return simple_transform_win32_to_unix($path);
504    }
505    elsif(substr($path, 0, 1) eq '\\') {
506        # Path is directory or filename on Win32 current drive. ('\Windows')
507
508        my $w32drive = get_win32_current_drive();
509        return undef unless defined $w32drive;
510
511        # Combine drive and path.
512        # Resolve relative dirs in Win32-style path or paths like 'D:/../c/'
513        # will be resolved incorrectly.
514        # Replace any possible back slashes with forward slashes,
515        # remove any duplicated slashes.
516        $path = normalize_path($w32drive . $path);
517        return undef unless defined $path;
518
519        return simple_transform_win32_to_unix($path);
520    }
521
522    # Path is not in any Win32 form.
523    unless (substr($path, 0, 1) eq '/') {
524        # Path in relative form. Resolve relative directories in Unix form
525        # *BEFORE* converting to Win32 form otherwise paths like
526        # '../../../cygdrive/c/windows' will not be resolved.
527
528        my $cur_dir;
529        # MSys shell has built-in command.
530        if($^O eq 'msys') {
531            $cur_dir = `bash -c 'pwd -L'`;
532        }
533        else {
534            $cur_dir = `pwd -L`;
535        }
536        if($? != 0) {
537            warn "Can't determine current working directory.\n";
538            return undef;
539        }
540        chomp($cur_dir);
541
542        $path = $cur_dir . '/' . $path;
543    }
544
545    return normalize_path($path);
546}
547
548#######################################################################
549# Performs path "normalization": all slashes converted to forward
550# slashes (except leading slash), all duplicated slashes are replaced
551# with single slashes, all relative directories ('./' and '../') are
552# resolved if possible.
553# Path processed as string, directories are not checked for presence so
554# path for not yet existing directory can be "normalized".
555#
556sub normalize_path {
557    my ($path) = @_;
558
559    # Don't process empty paths.
560    return $path if $path eq '';
561
562    unless($path =~ m{(?:^|\\|/)\.{1,2}(?:\\|/|$)}) {
563        # Speed up processing of simple paths.
564        my $first_char = substr($path, 0, 1);
565        $path =~ s{[\\/]+}{/}g;
566        # Restore starting backslash if any.
567        substr($path, 0, 1) = $first_char;
568        return $path;
569    }
570
571    my @arr;
572    my $prefix;
573    my $have_root = 0;
574
575    # Check whether path starts from Win32 drive. ('C:path' or 'C:\path')
576    if($path =~ m{^([a-zA-Z]:(/|\\)?)(.*$)}) {
577        $prefix = $1;
578        $have_root = 1 if defined $2;
579        # Process path separately from drive letter.
580        @arr = split(m{\/|\\}, $3);
581        # Replace backslash with forward slash if required.
582        substr($prefix, 2, 1) = '/' if $have_root;
583    }
584    else {
585        if($path =~ m{^(\/|\\)}) {
586            $have_root = 1;
587            $prefix = $1;
588        }
589        else {
590            $prefix = '';
591        }
592        @arr = split(m{\/|\\}, $path);
593    }
594
595    my $p = 0;
596    my @res;
597
598    for my $el (@arr) {
599        if(length($el) == 0 || $el eq '.') {
600            next;
601        }
602        elsif($el eq '..' && @res > 0 && $res[$#res] ne '..') {
603            pop @res;
604            next;
605        }
606        push @res, $el;
607    }
608    if($have_root && @res > 0 && $res[0] eq '..') {
609        warn "Error processing path \"$path\": " .
610             "Parent directory of root directory does not exist!\n";
611        return undef;
612    }
613
614    my $ret = $prefix . join('/', @res);
615    $ret .= '/' if($path =~ m{\\$|/$} && scalar @res > 0);
616
617    return $ret;
618}
619
620# Internal function. Converts path by using Msys's built-in
621# transformation.
622sub do_msys_transform {
623    my ($path) = @_;
624    return undef if $^O ne 'msys';
625    return $path if $path eq '';
626
627    # Remove leading double forward slashes, as they turn off Msys
628    # transforming.
629    $path =~ s{^/[/\\]+}{/};
630
631    # Msys transforms automatically path to Windows native form in staring
632    # program parameters if program is not Msys-based.
633    # Note: already checked that $path is non-empty.
634    $path = `cmd //c echo '$path'`;
635    if($? != 0) {
636        warn "Can't transform path into Win32 form by using Msys" .
637             "internal transformation.\n";
638        return undef;
639    }
640
641    # Remove double quotes, they are added for paths with spaces,
642    # remove both '\r' and '\n'.
643    $path =~ s{^\"|\"$|\"\r|\n|\r}{}g;
644
645    return $path;
646}
647
648# Internal function. Gets two parameters: first parameter must be single
649# drive letter ('c'), second optional parameter is path relative to drive's
650# current working directory. Returns Win32 absolute normalized path.
651sub get_abs_path_on_win32_drive {
652    my ($drv, $rel_path) = @_;
653    my $res;
654
655    # Get current directory on specified drive.
656    # "/c;" is compatible with both Msys and Cygwin.
657    my $cur_dir_on_drv = `cmd "/c;" echo %=$drv:%`;
658    if($? != 0) {
659        warn "Can't determine Win32 current directory on drive $drv:.\n";
660        return undef;
661    }
662
663    if($cur_dir_on_drv =~ m{^[%]}) {
664        # Current directory on drive is not set, default is
665        # root directory.
666
667        $res = ucfirst($drv) . ':/';
668    }
669    else {
670        # Current directory on drive was set.
671        # Remove both '\r' and '\n'.
672        $cur_dir_on_drv =~ s{\n|\r}{}g;
673
674        # Append relative path part.
675        $res = $cur_dir_on_drv . '/';
676    }
677    $res .= $rel_path if defined $rel_path;
678
679    # Replace any possible back slashes with forward slashes,
680    # remove any duplicated slashes, resolve relative dirs.
681    return normalize_path($res);
682}
683
684# Internal function. Tries to find or guess Win32 version of given
685# absolute Unix-style path. Other types of paths are not supported.
686# Returned paths contain only single forward slashes (no back and
687# duplicated slashes).
688# Last resort. Used only when other transformations are not available.
689sub do_dumb_guessed_transform {
690    my ($path) = @_;
691
692    # Replace any possible back slashes and duplicated forward slashes
693    # with single forward slashes.
694    $path =~ s{[/\\]+}{/}g;
695
696    # Empty path is not valid.
697    return undef if (length($path) == 0);
698
699    # RE to find Win32 drive letter
700    my $drv_ltr_re = drives_mounted_on_cygdrive() ?
701                        qr{^/cygdrive/([a-zA-Z])($|/.*$)} :
702                        qr{^/([a-zA-Z])($|/.*$)};
703
704    # Check path whether path is Win32 directly mapped drive and try to
705    # transform it assuming that drive letter is matched to Win32 drive letter.
706    if($path =~ m{$drv_ltr_re}) {
707        return ucfirst($1) . ':/' if(length($2) == 0);
708        return ucfirst($1) . ':' . $2;
709    }
710
711    # This may be some custom mapped path. ('/mymount/path')
712
713    # Must check longest possible path component as subdir can be mapped to
714    # different directory. For example '/usr/bin/' can be mapped to '/bin/' or
715    # '/bin/' can be mapped to '/usr/bin/'.
716    my $check_path = $path;
717    my $path_tail = '';
718    do {
719        if(-d $check_path) {
720            my $res =
721                `(cd "$check_path" && cmd /c "echo %__CD__%") 2>/dev/null`;
722            if($? == 0 && substr($path, 0, 1) ne '%') {
723                # Remove both '\r' and '\n'.
724                $res =~ s{\n|\r}{}g;
725
726                # Replace all back slashes with forward slashes.
727                $res =~ s{\\}{/}g;
728
729                if(length($path_tail) > 0) {
730                    return $res . $path_tail;
731                }
732                else {
733                    $res =~ s{/$}{} unless $check_path =~ m{/$};
734                    return $res;
735                }
736            }
737        }
738        if($check_path =~ m{(^.*/)([^/]+/*)}) {
739            $check_path = $1;
740            $path_tail = $2 . $path_tail;
741        }
742        else {
743            # Shouldn't happens as root '/' directory should always
744            # be resolvable.
745            warn "Can't determine Win32 directory for path \"$path\".\n";
746            return undef;
747        }
748    } while(1);
749}
750
751
752# Internal function. Converts given Unix-style absolute path to Win32 format.
753sub simple_transform_win32_to_unix {
754    my ($path) = @_;
755
756    if(should_use_cygpath()) {
757        # 'cygpath' gives precise result.
758        my $res;
759        chomp($res = `cygpath -a -u '$path'`);
760        if($? != 0) {
761            warn "Can't determine Unix-style directory for Win32 " .
762                 "directory \"$path\".\n";
763            return undef;
764        }
765
766        # 'cygpath' removes last slash if path is root dir on Win32 drive.
767        $res .= '/' if(substr($res, length($res) - 1, 1) ne '/' &&
768                       $path =~ m{[/\\]$});
769        return $res;
770    }
771
772    # 'cygpath' is not available, use guessed transformation.
773    unless($path =~ s{^([a-zA-Z]):(?:/|\\)}{/\l$1/}) {
774        warn "Can't determine Unix-style directory for Win32 " .
775             "directory \"$path\".\n";
776        return undef;
777    }
778
779    $path = '/cygdrive' . $path if(drives_mounted_on_cygdrive());
780    return $path;
781}
782
7831;    # End of module
784