1#!/bin/bash 2# 3# Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. 4# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 5# 6# This code is free software; you can redistribute it and/or modify it 7# under the terms of the GNU General Public License version 2 only, as 8# published by the Free Software Foundation. Oracle designates this 9# particular file as subject to the "Classpath" exception as provided 10# by Oracle in the LICENSE file that accompanied this code. 11# 12# This code is distributed in the hope that it will be useful, but WITHOUT 13# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 15# version 2 for more details (a copy is included in the LICENSE file that 16# accompanied this code). 17# 18# You should have received a copy of the GNU General Public License version 19# 2 along with this work; if not, write to the Free Software Foundation, 20# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 21# 22# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 23# or visit www.oracle.com if you need additional information or have any 24# questions. 25# 26 27# Setup the environment fixpath assumes. Read from command line options if 28# available, or extract values automatically from the environment if missing. 29# This is robust, but slower. 30function setup() { 31 while getopts "e:p:r:t:c:qmi" opt; do 32 case "$opt" in 33 e) PATHTOOL="$OPTARG" ;; 34 p) DRIVEPREFIX="$OPTARG" ;; 35 r) ENVROOT="$OPTARG" ;; 36 t) WINTEMP="$OPTARG" ;; 37 c) CMD="$OPTARG" ;; 38 q) QUIET=true ;; 39 m) MIXEDMODE=true ;; 40 i) IGNOREFAILURES=true ;; 41 ?) 42 # optargs found argument error 43 exit 2 44 ;; 45 esac 46 done 47 48 shift $((OPTIND-1)) 49 ACTION="$1" 50 51 # Locate variables ourself if not giving from caller 52 if [[ -z ${PATHTOOL+x} ]]; then 53 PATHTOOL="$(type -p cygpath)" 54 if [[ $PATHTOOL == "" ]]; then 55 PATHTOOL="$(type -p wslpath)" 56 if [[ $PATHTOOL == "" ]]; then 57 if [[ $QUIET != true ]]; then 58 echo fixpath: failure: Cannot locate cygpath or wslpath >&2 59 fi 60 exit 2 61 fi 62 fi 63 fi 64 65 if [[ -z ${DRIVEPREFIX+x} ]]; then 66 winroot="$($PATHTOOL -u c:/)" 67 DRIVEPREFIX="${winroot%/c/}" 68 else 69 if [[ $DRIVEPREFIX == "NONE" ]]; then 70 DRIVEPREFIX="" 71 fi 72 fi 73 74 if [[ -z ${ENVROOT+x} ]]; then 75 unixroot="$($PATHTOOL -w / 2> /dev/null)" 76 # Remove trailing backslash 77 ENVROOT="${unixroot%\\}" 78 elif [[ "$ENVROOT" == "[unavailable]" ]]; then 79 ENVROOT="" 80 fi 81 82 if [[ -z ${CMD+x} ]]; then 83 CMD="$DRIVEPREFIX/c/windows/system32/cmd.exe" 84 fi 85 86 if [[ -z ${WINTEMP+x} ]]; then 87 wintemp_win="$($CMD /q /c echo %TEMP% 2>/dev/null | tr -d \\n\\r)" 88 WINTEMP="$($PATHTOOL -u "$wintemp_win")" 89 fi 90 91 # Make regexp tests case insensitive 92 shopt -s nocasematch 93 # Prohibit msys2 from meddling with paths 94 export MSYS2_ARG_CONV_EXCL="*" 95 # Make sure WSL gets a copy of the path 96 export WSLENV=PATH/l 97} 98 99# Cleanup handling 100TEMPDIRS="" 101trap "cleanup" EXIT 102function cleanup() { 103 if [[ "$TEMPDIRS" != "" ]]; then 104 rm -rf $TEMPDIRS 105 fi 106} 107 108# Import a single path 109# Result: imported path returned in $result 110function import_path() { 111 path="$1" 112 # Strip trailing and leading space 113 path="${path#"${path%%[![:space:]]*}"}" 114 path="${path%"${path##*[![:space:]]}"}" 115 116 if [[ $path =~ ^.:[/\\].*$ ]] || [[ "$path" =~ ^"$ENVROOT"\\.*$ ]] ; then 117 # We got a Windows path as input; use pathtool to convert to unix path 118 path="$($PATHTOOL -u "$path")" 119 # Path will now be absolute 120 else 121 # Make path absolute, and resolve embedded '..' in path 122 dirpart="$(dirname "$path")" 123 dirpart="$(cd "$dirpart" 2>&1 > /dev/null && pwd)" 124 if [[ $? -ne 0 ]]; then 125 if [[ $QUIET != true ]]; then 126 echo fixpath: failure: Directory containing path "'"$path"'" does not exist >&2 127 fi 128 if [[ $IGNOREFAILURES != true ]]; then 129 exit 1 130 else 131 path="" 132 fi 133 else 134 basepart="$(basename "$path")" 135 if [[ $dirpart == / ]]; then 136 # Avoid double leading / 137 dirpart="" 138 fi 139 if [[ $basepart == / ]]; then 140 # Avoid trailing / 141 basepart="" 142 fi 143 path="$dirpart/$basepart" 144 fi 145 fi 146 147 if [[ "$path" != "" ]]; then 148 # Now turn it into a windows path 149 winpath="$($PATHTOOL -w "$path" 2>/dev/null)" 150 # If it fails, try again with an added .exe (needed on WSL) 151 if [[ $? -ne 0 ]]; then 152 winpath="$($PATHTOOL -w "$path.exe" 2>/dev/null)" 153 fi 154 if [[ $? -eq 0 ]]; then 155 if [[ ! "$winpath" =~ ^"$ENVROOT"\\.*$ ]] ; then 156 # If it is not in envroot, it's a generic windows path 157 if [[ ! $winpath =~ ^[-_.:\\a-zA-Z0-9]*$ ]] ; then 158 # Path has forbidden characters, rewrite as short name 159 # This monster of a command uses the %~s support from cmd.exe to 160 # reliably convert to short paths on all winenvs. 161 shortpath="$($CMD /q /c for %I in \( "$winpath" \) do echo %~sI 2>/dev/null | tr -d \\n\\r)" 162 path="$($PATHTOOL -u "$shortpath")" 163 # Path is now unix style, based on short name 164 fi 165 # Make it lower case 166 path="$(echo "$path" | tr [:upper:] [:lower:])" 167 fi 168 else 169 # On WSL1, PATHTOOL will fail for files in envroot. If the unix path 170 # exists, we assume that $path is a valid unix path. 171 172 if [[ ! -e $path ]]; then 173 if [[ -e $path.exe ]]; then 174 path="$path.exe" 175 else 176 if [[ $QUIET != true ]]; then 177 echo fixpath: warning: Path "'"$path"'" does not exist >&2 178 fi 179 # This is not a fatal error, maybe the path will be created later on 180 fi 181 fi 182 fi 183 fi 184 185 if [[ "$path" =~ " " ]]; then 186 # Our conversion attempts failed. Perhaps the path did not exists, and thus 187 # we could not convert it to short name. 188 if [[ $QUIET != true ]]; then 189 echo fixpath: failure: Path "'"$path"'" contains space >&2 190 fi 191 if [[ $IGNOREFAILURES != true ]]; then 192 exit 1 193 else 194 path="" 195 fi 196 fi 197 198 result="$path" 199} 200 201# Import a single path, or a pathlist in Windows style (i.e. ; separated) 202# Incoming paths can be in Windows or unix style. 203# Returns in $result a converted path or path list 204function import_command_line() { 205 imported="" 206 207 old_ifs="$IFS" 208 IFS=";" 209 for arg in $1; do 210 if ! [[ $arg =~ ^" "+$ ]]; then 211 import_path "$arg" 212 213 if [[ "$result" != "" && "$imported" = "" ]]; then 214 imported="$result" 215 else 216 imported="$imported:$result" 217 fi 218 fi 219 done 220 IFS="$old_ifs" 221 222 result="$imported" 223} 224 225# If argument seems to be colon separated path list, and all elements 226# are possible to convert to paths, make a windows path list 227# Return 0 if successful with converted path list in $result, or 228# 1 if it was not a path list. 229function convert_pathlist() { 230 converted_list="" 231 pathlist_args="$1" 232 233 IFS=':' read -r -a arg_array <<< "$pathlist_args" 234 for arg in "${arg_array[@]}"; do 235 winpath="" 236 # Start looking for drive prefix 237 if [[ $arg =~ ^($DRIVEPREFIX/)([a-z])(/[^/]+.*$) ]] ; then 238 winpath="${BASH_REMATCH[2]}:${BASH_REMATCH[3]}" 239 # Change slash to backslash (or vice versa if mixed mode) 240 if [[ $MIXEDMODE != true ]]; then 241 winpath="${winpath//'/'/'\'}" 242 else 243 winpath="${winpath//'\'/'/'}" 244 fi 245 elif [[ $arg =~ ^(/[-_.*a-zA-Z0-9]+(/[-_.*a-zA-Z0-9]+)+.*$) ]] ; then 246 # This looks like a unix path, like /foo/bar 247 pathmatch="${BASH_REMATCH[1]}" 248 if [[ $ENVROOT == "" ]]; then 249 if [[ $QUIET != true ]]; then 250 echo fixpath: failure: Path "'"$pathmatch"'" cannot be converted to Windows path >&2 251 fi 252 exit 1 253 fi 254 winpath="$ENVROOT$pathmatch" 255 # Change slash to backslash (or vice versa if mixed mode) 256 if [[ $MIXEDMODE != true ]]; then 257 winpath="${winpath//'/'/'\'}" 258 else 259 winpath="${winpath//'\'/'/'}" 260 fi 261 else 262 # This does not look like a path, so assume this is not a proper pathlist. 263 # Flag this to caller. 264 result="" 265 return 1 266 fi 267 268 if [[ "$converted_list" = "" ]]; then 269 converted_list="$winpath" 270 else 271 converted_list="$converted_list;$winpath" 272 fi 273 done 274 275 result="$converted_list" 276 return 0 277} 278 279# The central conversion function. Convert a single argument, so that any 280# contained paths are converted to Windows style paths. Result is returned 281# in $result. If it is a path list, convert it as one. 282function convert_path() { 283 if [[ $1 =~ : ]]; then 284 convert_pathlist "$1" 285 if [[ $? -eq 0 ]]; then 286 return 0 287 fi 288 # Not all elements was possible to convert to Windows paths, so we 289 # presume it is not a pathlist. Continue using normal conversion. 290 fi 291 292 arg="$1" 293 winpath="" 294 # Start looking for drive prefix. Also allow /xxxx prefixes (typically options 295 # for Visual Studio tools), and embedded file:// URIs. 296 if [[ $arg =~ ^([^/]*|-[^:=]*[:=]|.*file://|/[a-zA-Z:]{1,3}:?)($DRIVEPREFIX/)([a-z])(/[^/]+.*$) ]] ; then 297 prefix="${BASH_REMATCH[1]}" 298 winpath="${BASH_REMATCH[3]}:${BASH_REMATCH[4]}" 299 # Change slash to backslash (or vice versa if mixed mode) 300 if [[ $MIXEDMODE != true ]]; then 301 winpath="${winpath//'/'/'\'}" 302 else 303 winpath="${winpath//'\'/'/'}" 304 fi 305 elif [[ $arg =~ ^([^/]*|-[^:=]*[:=]|(.*file://))(/([-_.+a-zA-Z0-9]+)(/[-_.+a-zA-Z0-9]+)+)(.*)?$ ]] ; then 306 # This looks like a unix path, like /foo/bar. Also embedded file:// URIs. 307 prefix="${BASH_REMATCH[1]}" 308 pathmatch="${BASH_REMATCH[3]}" 309 firstdir="${BASH_REMATCH[4]}" 310 suffix="${BASH_REMATCH[6]}" 311 312 # We only believe this is a path if the first part is an existing directory 313 if [[ -d "/$firstdir" ]]; then 314 if [[ $ENVROOT == "" ]]; then 315 if [[ $QUIET != true ]]; then 316 echo fixpath: failure: Path "'"$pathmatch"'" cannot be converted to Windows path >&2 317 fi 318 exit 1 319 fi 320 winpath="$ENVROOT$pathmatch" 321 # Change slash to backslash (or vice versa if mixed mode) 322 if [[ $MIXEDMODE != true ]]; then 323 winpath="${winpath//'/'/'\'}" 324 else 325 winpath="${winpath//'\'/'/'}" 326 fi 327 winpath="$winpath$suffix" 328 fi 329 fi 330 331 if [[ $winpath != "" ]]; then 332 result="$prefix$winpath" 333 else 334 # Return the arg unchanged 335 result="$arg" 336 fi 337} 338 339# Treat $1 as name of a file containg paths. Convert those paths to Windows style, 340# in a new temporary file, and return a string "@<temp file>" pointing to that 341# new file. 342function convert_at_file() { 343 infile="$1" 344 if [[ -e $infile ]] ; then 345 tempdir=$(mktemp -dt fixpath.XXXXXX -p "$WINTEMP") 346 TEMPDIRS="$TEMPDIRS $tempdir" 347 348 while read line; do 349 convert_path "$line" 350 echo "$result" >> $tempdir/atfile 351 done < $infile 352 convert_path "$tempdir/atfile" 353 result="@$result" 354 else 355 result="@$infile" 356 fi 357} 358 359# Convert an entire command line, replacing all unix paths with Windows paths, 360# and all unix-style path lists (colon separated) with Windows-style (semicolon 361# separated). 362function print_command_line() { 363 converted_args="" 364 for arg in "$@" ; do 365 if [[ $arg =~ ^@(.*$) ]] ; then 366 # This is an @-file with paths that need converting 367 convert_at_file "${BASH_REMATCH[1]}" 368 else 369 convert_path "$arg" 370 fi 371 converted_args="$converted_args$result " 372 done 373 result="${converted_args% }" 374} 375 376# Check if the winenv will allow us to start a Windows program when we are 377# standing in the current directory 378function verify_current_dir() { 379 arg="$PWD" 380 if [[ $arg =~ ^($DRIVEPREFIX/)([a-z])(/[^/]+.*$) ]] ; then 381 return 0 382 elif [[ $arg =~ ^(/[^/]+.*$) ]] ; then 383 if [[ $ENVROOT == "" || $ENVROOT =~ ^\\\\.* ]]; then 384 # This is a WSL1 or WSL2 environment 385 return 1 386 fi 387 return 0 388 fi 389 # This should not happen 390 return 1 391} 392 393# The core functionality of fixpath. Take the given command line, and convert 394# it and execute it, so that all paths are converted to Windows style. 395# The return code is the return code of the executed command. 396function exec_command_line() { 397 # Check that Windows can handle our current directory (only an issue for WSL) 398 verify_current_dir 399 400 if [[ $? -ne 0 ]]; then 401 # WSL1 will just forcefully put us in C:\Windows\System32 if we execute this from 402 # a unix directory. WSL2 will do the same, and print a warning. In both cases, 403 # we prefer to take control. 404 cd "$WINTEMP" 405 if [[ $QUIET != true ]]; then 406 echo fixpath: warning: Changing directory to $WINTEMP >&2 407 fi 408 fi 409 410 collected_args=() 411 command="" 412 for arg in "$@" ; do 413 if [[ $command == "" ]]; then 414 # We have not yet located the command to run 415 if [[ $arg =~ ^(.*)=(.*)$ ]]; then 416 # It's a leading env variable assignment (FOO=bar) 417 key="${BASH_REMATCH[1]}" 418 arg="${BASH_REMATCH[2]}" 419 convert_path "$arg" 420 # Set the variable to the converted result 421 export $key="$result" 422 # While this is only needed on WSL, it does not hurt to do everywhere 423 export WSLENV=$WSLENV:$key/w 424 else 425 # The actual command will be executed by bash, so don't convert it 426 command="$arg" 427 fi 428 else 429 # Now we are collecting arguments; they all need converting 430 if [[ $arg =~ ^@(.*$) ]] ; then 431 # This is an @-file with paths that need converting 432 convert_at_file "${BASH_REMATCH[1]}" 433 else 434 convert_path "$arg" 435 fi 436 collected_args=("${collected_args[@]}" "$result") 437 fi 438 done 439 440 # Now execute it 441 if [[ -v DEBUG_FIXPATH ]]; then 442 echo fixpath: debug: input: "$@" >&2 443 echo fixpath: debug: output: "$command" "${collected_args[@]}" >&2 444 fi 445 446 if [[ ! -e "$command" ]]; then 447 if [[ -e "$command.exe" ]]; then 448 command="$command.exe" 449 fi 450 fi 451 452 if [[ $ENVROOT != "" || ! -x /bin/grep ]]; then 453 "$command" "${collected_args[@]}" 454 else 455 # For WSL1, automatically strip away warnings from WSLENV=PATH/l 456 "$command" "${collected_args[@]}" 2> >(/bin/grep -v "ERROR: UtilTranslatePathList" 1>&2) 457 fi 458} 459 460# Check that the input represents a path that is reachable from Windows 461function verify_command_line() { 462 arg="$1" 463 if [[ $arg =~ ^($DRIVEPREFIX/)([a-z])(/[^/]+.*$) ]] ; then 464 return 0 465 elif [[ $arg =~ ^(/[^/]+/[^/]+.*$) ]] ; then 466 if [[ $ENVROOT != "" ]]; then 467 return 0 468 fi 469 fi 470 return 1 471} 472 473#### MAIN FUNCTION 474 475setup "$@" 476# Shift away the options processed in setup 477shift $((OPTIND)) 478 479if [[ "$ACTION" == "import" ]] ; then 480 import_command_line "$@" 481 echo "$result" 482elif [[ "$ACTION" == "print" ]] ; then 483 print_command_line "$@" 484 echo "$result" 485elif [[ "$ACTION" == "exec" ]] ; then 486 exec_command_line "$@" 487 # Propagate exit code 488 exit $? 489elif [[ "$ACTION" == "verify" ]] ; then 490 verify_command_line "$@" 491 exit $? 492else 493 if [[ $QUIET != true ]]; then 494 echo Unknown operation: "$ACTION" >&2 495 echo Supported operations: import print exec verify >&2 496 fi 497 exit 2 498fi 499