1 /*
   2  * Copyright (c) 2011, 2020, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 #include <Windows.h>
  27 #include <stdbool.h>
  28 #include <io.h>
  29 #include <stdio.h>
  30 #include <string.h>
  31 #include <malloc.h>
  32 
  33 void report_error(char const * msg)
  34 {
  35   LPTSTR lpMsgBuf = NULL;
  36   DWORD dw = GetLastError();
  37 
  38   FormatMessage(
  39       FORMAT_MESSAGE_ALLOCATE_BUFFER |
  40       FORMAT_MESSAGE_FROM_SYSTEM |
  41       FORMAT_MESSAGE_IGNORE_INSERTS,
  42       NULL,
  43       dw,
  44       MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
  45       &lpMsgBuf,
  46       0,
  47       NULL);
  48 
  49   fprintf(stderr,
  50           "%s  Failed with error %d: %s\n",
  51           msg, dw, lpMsgBuf);
  52 
  53   LocalFree(lpMsgBuf);
  54 }
  55 
  56 /*
  57  * Test if pos points to /prefix/_/ where _ can
  58  * be any character.
  59  */
  60 int is_prefix_here(int pos, char const *in, int len, const char* prefix)
  61 {
  62   // Length of c/ is 2
  63   int prefix_size = strlen(prefix);
  64   if (pos+prefix_size+2 > len) return 0;
  65   if (in[pos+prefix_size+1]=='/') {
  66     return strncmp(in + pos, prefix, prefix_size) == 0;
  67   }
  68   return 0;
  69 }
  70 
  71 /*
  72  * Replace /cygdrive/_/ with _:/
  73  * Works in place since drive letter is always
  74  * shorter than /cygdrive/
  75  */
  76 char *replace_cygdrive_cygwin(char const *in)
  77 {
  78   size_t len = strlen(in);
  79   char *out = (char*) malloc(len+1);
  80   int i,j;
  81 
  82   if (len < 12) {
  83     memmove(out, in, len + 1);
  84     return out;
  85   }
  86 
  87   for (i = 0, j = 0; i<len;) {
  88     if (is_prefix_here(i, in, len, "/cygdrive/")) {
  89       out[j++] = in[i+10];
  90       out[j++] = ':';
  91       i+=11;
  92     } else {
  93       out[j] = in[i];
  94       i++;
  95       j++;
  96     }
  97   }
  98   out[j] = '\0';
  99   return out;
 100 }
 101 
 102 void append(char **b, size_t *bl, size_t *u, char *add, size_t addlen)
 103 {
 104   while ((addlen+*u+1) > *bl) {
 105     *bl *= 2;
 106     *b = (char*) realloc(*b, *bl);
 107   }
 108   memcpy(*b+*u, add, addlen);
 109   *u += addlen;
 110 }
 111 
 112 /*
 113  * Creates a new string from in where the first occurrence of sub is
 114  * replaced by rep.
 115  */
 116 char *replace_substring(char *in, char *sub, char *rep)
 117 {
 118   int in_len = strlen(in);
 119   int sub_len = strlen(sub);
 120   int rep_len = strlen(rep);
 121   char *out = (char *) malloc(in_len - sub_len + rep_len + 1);
 122   char *p;
 123 
 124   if (!(p = strstr(in, sub))) {
 125     // If sub isn't a substring of in, just return in.
 126     return in;
 127   }
 128 
 129   // Copy characters from beginning of in to start of sub.
 130   strncpy(out, in, p - in);
 131   out[p - in] = '\0';
 132 
 133   sprintf(out + (p - in), "%s%s", rep, p + sub_len);
 134 
 135   return out;
 136 }
 137 
 138 char* msys_path_list; // @-separated list of paths prefix to look for
 139 char* msys_path_list_end; // Points to last \0 in msys_path_list.
 140 
 141 void setup_msys_path_list(char const * argument)
 142 {
 143   char* p;
 144   char* drive_letter_pos;
 145 
 146   msys_path_list = strdup(&argument[2]);
 147   msys_path_list_end = &msys_path_list[strlen(msys_path_list)];
 148 
 149   // Convert all at-sign (@) in path list to \0.
 150   // @ was chosen as separator to minimize risk of other tools messing around with it
 151   p = msys_path_list;
 152   do {
 153     if (p[1] == ':') {
 154       // msys has mangled our path list, restore it from c:/... to /c/...
 155       drive_letter_pos = p+1;
 156       *drive_letter_pos = *p;
 157       *p = '/';
 158     }
 159 
 160     // Look for an @ in the list
 161     p = strchr(p, '@');
 162     if (p != NULL) {
 163       *p = '\0';
 164       p++;
 165     }
 166   } while (p != NULL);
 167 }
 168 
 169 char *replace_cygdrive_msys(char const *in)
 170 {
 171   char* str;
 172   char* prefix;
 173   char* p;
 174 
 175   str = strdup(in);
 176 
 177   // For each prefix in the path list, search for it and replace /c/... with c:/...
 178   for (prefix = msys_path_list; prefix < msys_path_list_end && prefix != NULL; prefix += strlen(prefix)+1) {
 179     p=str;
 180     while ((p = strstr(p, prefix))) {
 181       char* drive_letter = p+1;
 182       *p = *drive_letter;
 183       *drive_letter = ':';
 184       p++;
 185     }
 186   }
 187 
 188   return str;
 189 }
 190 
 191 /*
 192  * Replace /mnt/_/ with _:/
 193  * Works in place since drive letter is always
 194  * shorter than /mnt/
 195  */
 196 char *replace_cygdrive_wsl(char const *in)
 197 {
 198   size_t len = strlen(in);
 199   char *out = (char*) malloc(len+1);
 200   int i,j;
 201 
 202   if (len < 7) {
 203     memmove(out, in, len + 1);
 204     return out;
 205   }
 206 
 207   for (i = 0, j = 0; i<len;) {
 208     if (is_prefix_here(i, in, len, "/mnt/")) {
 209       out[j++] = in[i+5];
 210       out[j++] = ':';
 211       i+=6;
 212     } else {
 213       out[j] = in[i];
 214       i++;
 215       j++;
 216     }
 217   }
 218   out[j] = '\0';
 219   return out;
 220 }
 221 
 222 char*(*replace_cygdrive)(char const *in) = NULL;
 223 bool debug_fixpath = false;
 224 
 225 char *files_to_delete[1024];
 226 int num_files_to_delete = 0;
 227 
 228 char *fix_at_file(char const *in)
 229 {
 230   char *tmpdir;
 231   char name[2048];
 232   char *atname;
 233   char *buffer;
 234   size_t buflen=65536;
 235   size_t used=0;
 236   size_t len;
 237   int rc;
 238   FILE *atout;
 239   FILE *atin;
 240   char block[2048];
 241   size_t blocklen;
 242   char *fixed;
 243 
 244   atin = fopen(in+1, "r");
 245   if (atin == NULL) {
 246     fprintf(stderr, "Could not read at file %s\n", in+1);
 247     exit(-1);
 248   }
 249 
 250   tmpdir = getenv("TEMP");
 251   if (tmpdir == NULL) {
 252 #if _WIN64
 253     tmpdir = "c:/cygwin64/tmp";
 254 #else
 255     tmpdir = "c:/cygwin/tmp";
 256 #endif
 257   }
 258   _snprintf(name, sizeof(name), "%s\\atfile_XXXXXX", tmpdir);
 259 
 260   rc = _mktemp_s(name, strlen(name)+1);
 261   if (rc) {
 262     fprintf(stderr, "Could not create temporary file name for at file!\n");
 263     exit(-1);
 264   }
 265 
 266   atout = fopen(name, "w");
 267   if (atout == NULL) {
 268     fprintf(stderr, "Could not open temporary file for writing! %s\n", name);
 269     exit(-1);
 270   }
 271 
 272   buffer = (char*) malloc(buflen);
 273   while ((blocklen = fread(block, 1, sizeof(block), atin)) > 0) {
 274     append(&buffer, &buflen, &used, block, blocklen);
 275   }
 276   buffer[used] = 0;
 277   if (debug_fixpath) {
 278     fprintf(stderr, "fixpath input from @-file %s: %s\n", &in[1], buffer);
 279   }
 280   fixed = replace_cygdrive(buffer);
 281   if (debug_fixpath) {
 282     fprintf(stderr, "fixpath converted to @-file %s is: %s\n", name, fixed);
 283   }
 284   fwrite(fixed, strlen(fixed), 1, atout);
 285   fclose(atin);
 286   fclose(atout);
 287   free(fixed);
 288   free(buffer);
 289   files_to_delete[num_files_to_delete] = (char*) malloc(strlen(name)+1);
 290   strcpy(files_to_delete[num_files_to_delete], name);
 291   num_files_to_delete++;
 292   atname = (char*) malloc(strlen(name)+2);
 293   atname[0] = '@';
 294   strcpy(atname+1, name);
 295   return atname;
 296 }
 297 
 298 // given an argument, convert it to the windows command line safe quoted version
 299 // using rules from:
 300 // http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx
 301 // caller is responsible for freeing both input and output.
 302 char * quote_arg(char const * in_arg) {
 303   char *quoted = NULL;
 304   char *current = quoted;
 305   int pass;
 306 
 307   if (strlen(in_arg) == 0) {
 308      // empty string? explicitly quote it.
 309      return _strdup("\"\"");
 310   }
 311 
 312   if (strpbrk(in_arg, " \t\n\v\r\\\"") == NULL) {
 313      return _strdup(in_arg);
 314   }
 315 
 316   // process the arg twice. Once to calculate the size and then to copy it.
 317   for (pass=1; pass<=2; pass++) {
 318     char const *arg = in_arg;
 319 
 320     // initial "
 321     if (pass == 2) {
 322       *current = '\"';
 323     }
 324     current++;
 325 
 326     // process string to be quoted until NUL
 327     do {
 328       int escapes = 0;
 329 
 330       while (*arg == '\\') {
 331         // count escapes.
 332         escapes++;
 333         arg++;
 334       }
 335 
 336       if (*arg == '\0') {
 337          // escape the escapes before final "
 338          escapes *= 2;
 339       } else if (*arg == '"') {
 340         // escape the escapes and the "
 341         escapes = escapes * 2 + 1;
 342       } else {
 343          // escapes aren't special, just echo them.
 344       }
 345 
 346       // emit some escapes
 347       while (escapes > 0) {
 348         if (pass == 2) {
 349           *current = '\\';
 350         }
 351         current++;
 352         escapes--;
 353       }
 354 
 355       // and the current char
 356       if (pass == 2) {
 357         *current = *arg;
 358       }
 359       current++;
 360     } while (*arg++ != '\0');
 361 
 362     // allocate the buffer
 363     if (pass == 1) {
 364       size_t alloc = (size_t) (current - quoted + (ptrdiff_t) 2);
 365       current = quoted = (char*) calloc(alloc, sizeof(char));
 366     }
 367   }
 368 
 369   // final " and \0
 370   *(current - 1) = '"';
 371   *current = '\0';
 372 
 373   return quoted;
 374 }
 375 
 376 int main(int argc, char const ** argv)
 377 {
 378     STARTUPINFO si;
 379     PROCESS_INFORMATION pi;
 380     unsigned short rc;
 381 
 382     char *line;
 383     char *current;
 384     int i, cmd;
 385     DWORD exitCode = 0;
 386     DWORD processFlags = 0;
 387     BOOL processInheritHandles = TRUE;
 388     BOOL waitForChild = TRUE;
 389     char* fixpathPath;
 390 
 391     debug_fixpath = (getenv("DEBUG_FIXPATH") != NULL);
 392 
 393     if (argc<2 || argv[1][0] != '-' || (argv[1][1] != 'c' && argv[1][1] != 'm' && argv[1][1] != 'w')) {
 394         fprintf(stderr, "Usage: fixpath -c|m|w<path@path@...> [--detach] /cygdrive/c/WINDOWS/notepad.exe [/cygdrive/c/x/test.txt|@/cygdrive/c/x/atfile]\n");
 395         exit(0);
 396     }
 397 
 398     if (debug_fixpath) {
 399       char const * cmdline = GetCommandLine();
 400       fprintf(stderr, "fixpath input line >%s<\n", strstr(cmdline, argv[1]));
 401     }
 402 
 403     if (argv[1][1] == 'c' && argv[1][2] == '\0') {
 404       if (debug_fixpath) {
 405         fprintf(stderr, "fixpath using cygwin mode\n");
 406       }
 407       replace_cygdrive = replace_cygdrive_cygwin;
 408     } else if (argv[1][1] == 'm') {
 409       if (debug_fixpath) {
 410         fprintf(stderr, "fixpath using msys mode, with path list: %s\n", &argv[1][2]);
 411       }
 412       setup_msys_path_list(argv[1]);
 413       replace_cygdrive = replace_cygdrive_msys;
 414     } else if (argv[1][1] == 'w') {
 415       if (debug_fixpath) {
 416         fprintf(stderr, "fixpath using wsl mode, with path list: %s\n", &argv[1][2]);
 417       }
 418       replace_cygdrive = replace_cygdrive_wsl;
 419     } else {
 420       fprintf(stderr, "fixpath Unknown mode: %s\n", argv[1]);
 421       exit(-1);
 422     }
 423 
 424     if (argv[2][0] == '-') {
 425       if (strcmp(argv[2], "--detach") == 0) {
 426         if (debug_fixpath) {
 427           fprintf(stderr, "fixpath in detached mode\n");
 428         }
 429         processFlags |= DETACHED_PROCESS;
 430         processInheritHandles = FALSE;
 431         waitForChild = FALSE;
 432       } else {
 433         fprintf(stderr, "fixpath Unknown argument: %s\n", argv[2]);
 434         exit(-1);
 435       }
 436       i = 3;
 437     } else {
 438       i = 2;
 439     }
 440 
 441     // handle assignments
 442     while (i < argc) {
 443       char const * assignment = strchr(argv[i], '=');
 444       if (assignment != NULL && assignment != argv[i]) {
 445         size_t var_len = (size_t) (assignment - argv[i] + (ptrdiff_t) 1);
 446         char *var = (char *) calloc(var_len, sizeof(char));
 447         char *val = replace_cygdrive(assignment + 1);
 448         memmove(var, argv[i], var_len);
 449         var[var_len - 1] = '\0';
 450         strupr(var);
 451 
 452         if (debug_fixpath) {
 453           fprintf(stderr, "fixpath setting var >%s< to >%s<\n", var, val);
 454         }
 455 
 456         rc = SetEnvironmentVariable(var, val);
 457         if (!rc) {
 458           // Could not set var for some reason.  Try to report why.
 459           const int msg_len = 80 + var_len + strlen(val);
 460           char * msg = (char *) alloca(msg_len);
 461           _snprintf_s(msg, msg_len, _TRUNCATE, "Could not set environment variable [%s=%s]", var, val);
 462           report_error(msg);
 463           exit(1);
 464         }
 465         free(var);
 466         free(val);
 467       } else {
 468         // no more assignments;
 469         break;
 470       }
 471       i++;
 472     }
 473 
 474     // remember index of the command
 475     cmd = i;
 476 
 477     // handle command and it's args.
 478     while (i < argc) {
 479       char const *replaced = replace_cygdrive(argv[i]);
 480       if (replaced[0] == '@') {
 481         if (waitForChild == FALSE) {
 482           fprintf(stderr, "fixpath Cannot use @-files in detached mode: %s\n", replaced);
 483           exit(1);
 484         }
 485         // Found at-file! Fix it!
 486         replaced = fix_at_file(replaced);
 487       }
 488       argv[i] = quote_arg(replaced);
 489       i++;
 490     }
 491 
 492     // determine the length of the line
 493     line = NULL;
 494     // args
 495     for (i = cmd; i < argc; i++) {
 496       line += (ptrdiff_t) strlen(argv[i]);
 497     }
 498     // spaces and null
 499     line += (ptrdiff_t) (argc - cmd + 1);
 500     // allocate
 501     line = (char*) calloc(line - (char*) NULL, sizeof(char));
 502 
 503     // copy in args.
 504     current = line;
 505     for (i = cmd; i < argc; i++) {
 506       ptrdiff_t len = strlen(argv[i]);
 507       if (i != cmd) {
 508         *current++ = ' ';
 509       }
 510       memmove(current, argv[i], len);
 511       current += len;
 512     }
 513     *current = '\0';
 514 
 515     if (debug_fixpath) {
 516       fprintf(stderr, "fixpath converted line >%s<\n", line);
 517     }
 518 
 519     if (cmd == argc) {
 520       if (debug_fixpath) {
 521         fprintf(stderr, "fixpath no command provided!\n");
 522       }
 523       exit(0);
 524     }
 525 
 526     ZeroMemory(&si, sizeof(si));
 527     si.cb=sizeof(si);
 528     ZeroMemory(&pi, sizeof(pi));
 529 
 530     fflush(stderr);
 531     fflush(stdout);
 532 
 533     fixpathPath = calloc(32767, sizeof(char));
 534     rc = GetEnvironmentVariable("FIXPATH_PATH", fixpathPath, 32767);
 535     if (rc) {
 536       if (debug_fixpath) {
 537         fprintf(stderr, "Setting Path to FIXPATH_PATH: %s\n", fixpathPath);
 538       }
 539       rc = SetEnvironmentVariable("Path", fixpathPath);
 540       if (!rc) {
 541         // Could not set Path for some reason.  Try to report why.
 542         const int msg_len = 80 + strlen(fixpathPath);
 543         char * msg = (char *)alloca(msg_len);
 544         _snprintf_s(msg, msg_len, _TRUNCATE, "Could not set environment variable [Path=%s]", fixpathPath);
 545         report_error(msg);
 546         exit(1);
 547       }
 548     }
 549 
 550     rc = CreateProcess(NULL,
 551                        line,
 552                        0,
 553                        0,
 554                        processInheritHandles,
 555                        processFlags,
 556                        NULL,
 557                        NULL,
 558                        &si,
 559                        &pi);
 560     if (!rc) {
 561       // Could not start process for some reason.  Try to report why:
 562       report_error("Could not start process!");
 563       exit(126);
 564     }
 565 
 566     if (waitForChild == TRUE) {
 567       WaitForSingleObject(pi.hProcess, INFINITE);
 568       GetExitCodeProcess(pi.hProcess, &exitCode);
 569 
 570       if (debug_fixpath) {
 571         for (i=0; i<num_files_to_delete; ++i) {
 572           fprintf(stderr, "fixpath Not deleting temporary file %s\n",
 573                   files_to_delete[i]);
 574         }
 575       } else {
 576         for (i=0; i<num_files_to_delete; ++i) {
 577           remove(files_to_delete[i]);
 578         }
 579       }
 580 
 581       if (exitCode != 0) {
 582         if (debug_fixpath) {
 583           fprintf(stderr, "fixpath exit code %d\n",
 584                   exitCode);
 585         }
 586       }
 587     } else {
 588       if (debug_fixpath) {
 589         fprintf(stderr, "fixpath Not waiting for child process");
 590       }
 591     }
 592 
 593     exit(exitCode);
 594 }