1###########################################################################
2#                                  _   _ ____  _
3#  Project                     ___| | | |  _ \| |
4#                             / __| | | | |_) | |
5#                            | (__| |_| |  _ <| |___
6#                             \___|\___/|_| \_\_____|
7#
8# Copyright (C) 2016 - 2020, 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        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.
406sub 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#
413sub 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 available - 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#
534sub 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.
600sub 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.
629sub 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.
667sub 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.
731sub 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
7611;    # End of module
762