1 /*
   2  * Copyright (c) 2011, 2014, 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 <io.h>
  28 #include <stdio.h>
  29 #include <string.h>
  30 #include <malloc.h>
  31 
  32 void report_error(char const * msg)
  33 {
  34   LPVOID lpMsgBuf;
  35   DWORD dw = GetLastError();
  36 
  37   FormatMessage(
  38       FORMAT_MESSAGE_ALLOCATE_BUFFER |
  39       FORMAT_MESSAGE_FROM_SYSTEM |
  40       FORMAT_MESSAGE_IGNORE_INSERTS,
  41       NULL,
  42       dw,
  43       MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
  44       (LPTSTR) &lpMsgBuf,
  45       0,
  46       NULL);
  47 
  48   fprintf(stderr,
  49           "%s  Failed with error %d: %s\n",
  50           msg, dw, lpMsgBuf);
  51 
  52   LocalFree(lpMsgBuf);
  53 }
  54 
  55 /*
  56  * Test if pos points to /cygdrive/_/ where _ can
  57  * be any character.
  58  */
  59 int is_cygdrive_here(int pos, char const *in, int len)
  60 {
  61   // Length of /cygdrive/c/ is 12
  62   if (pos+12 > len) return 0;
  63   if (in[pos+11]=='/' &&
  64       in[pos+9]=='/' &&
  65       in[pos+8]=='e' &&
  66       in[pos+7]=='v' &&
  67       in[pos+6]=='i' &&
  68       in[pos+5]=='r' &&
  69       in[pos+4]=='d' &&
  70       in[pos+3]=='g' &&
  71       in[pos+2]=='y' &&
  72       in[pos+1]=='c' &&
  73       in[pos+0]=='/') {
  74     return 1;
  75   }
  76   return 0;
  77 }
  78 
  79 /*
  80  * Replace /cygdrive/_/ with _:/
  81  * Works in place since drive letter is always
  82  * shorter than /cygdrive/
  83  */
  84 char *replace_cygdrive_cygwin(char const *in)
  85 {
  86   size_t len = strlen(in);
  87   char *out = (char*) malloc(len+1);
  88   int i,j;
  89 
  90   if (len < 12) {
  91     memmove(out, in, len + 1);
  92     return out;
  93   }
  94 
  95   for (i = 0, j = 0; i<len;) {
  96     if (is_cygdrive_here(i, in, len)) {
  97       out[j++] = in[i+10];
  98       out[j++] = ':';
  99       i+=11;
 100     } else {
 101       out[j] = in[i];
 102       i++;
 103       j++;
 104     }
 105   }
 106   out[j] = '\0';
 107   return out;
 108 }
 109 
 110 void append(char **b, size_t *bl, size_t *u, char *add, size_t addlen)
 111 {
 112   while ( (addlen+*u+1) > *bl) {
 113     *bl *= 2;
 114     *b = (char*) realloc(*b, *bl);
 115   }
 116   memcpy(*b+*u, add, addlen);
 117   *u += addlen;
 118 }
 119 
 120 /*
 121  * Creates a new string from in where the first occurance of sub is
 122  * replaced by rep.
 123  */
 124 char *replace_substring(char *in, char *sub, char *rep)
 125 {
 126   int in_len = strlen(in);
 127   int sub_len = strlen(sub);
 128   int rep_len = strlen(rep);
 129   char *out = (char *) malloc(in_len - sub_len + rep_len + 1);
 130   char *p;
 131 
 132   if (!(p = strstr(in, sub))) {
 133     // If sub isn't a substring of in, just return in.
 134     return in;
 135   }
 136 
 137   // Copy characters from beginning of in to start of sub.
 138   strncpy(out, in, p - in);
 139   out[p - in] = '\0';
 140 
 141   sprintf(out + (p - in), "%s%s", rep, p + sub_len);
 142 
 143   return out;
 144 }
 145 
 146 char* msys_path_list; // @-separated list of paths prefix to look for
 147 char* msys_path_list_end; // Points to last \0 in msys_path_list.
 148 
 149 void setup_msys_path_list(char const * argument)
 150 {
 151   char* p;
 152   char* drive_letter_pos;
 153 
 154   msys_path_list = strdup(&argument[2]);
 155   msys_path_list_end = &msys_path_list[strlen(msys_path_list)];
 156 
 157   // Convert all at-sign (@) in path list to \0.
 158   // @ was chosen as separator to minimize risk of other tools messing around with it
 159   p = msys_path_list;
 160   do {
 161     if (p[1] == ':') {
 162       // msys has mangled our path list, restore it from c:/... to /c/...
 163       drive_letter_pos = p+1;
 164       *drive_letter_pos = *p;
 165       *p = '/';
 166     }
 167 
 168     // Look for an @ in the list
 169     p = strchr(p, '@');
 170     if (p != NULL) {
 171       *p = '\0';
 172       p++;
 173     }
 174   } while (p != NULL);
 175 }
 176 
 177 char *replace_cygdrive_msys(char const *in)
 178 {
 179   char* str;
 180   char* prefix;
 181   char* p;
 182 
 183   str = strdup(in);
 184 
 185   // For each prefix in the path list, search for it and replace /c/... with c:/...
 186   for (prefix = msys_path_list; prefix < msys_path_list_end && prefix != NULL; prefix += strlen(prefix)+1) {
 187     p=str;
 188     while ((p = strstr(p, prefix))) {
 189       char* drive_letter = p+1;
 190       *p = *drive_letter;
 191       *drive_letter = ':';
 192       p++;
 193     }
 194   }
 195 
 196   return str;
 197 }
 198 
 199 char*(*replace_cygdrive)(char const *in) = NULL;
 200 
 201 char *files_to_delete[1024];
 202 int num_files_to_delete = 0;
 203 
 204 char *fix_at_file(char const *in)
 205 {
 206   char *tmpdir;
 207   char name[2048];
 208   char *atname;
 209   char *buffer;
 210   size_t buflen=65536;
 211   size_t used=0;
 212   size_t len;
 213   int rc;
 214   FILE *atout;
 215   FILE *atin;
 216   char block[2048];
 217   size_t blocklen;
 218   char *fixed;
 219 
 220   atin = fopen(in+1, "r");
 221   if (atin == NULL) {
 222     fprintf(stderr, "Could not read at file %s\n", in+1);
 223     exit(-1);
 224   }
 225 
 226   tmpdir = getenv("TEMP");
 227   if (tmpdir == NULL) {
 228 #if _WIN64
 229     tmpdir = "c:/cygwin64/tmp";
 230 #else
 231     tmpdir = "c:/cygwin/tmp";
 232 #endif
 233   }
 234   _snprintf(name, sizeof(name), "%s\\atfile_XXXXXX", tmpdir);
 235 
 236   rc = _mktemp_s(name, strlen(name)+1);
 237   if (rc) {
 238     fprintf(stderr, "Could not create temporary file name for at file!\n");
 239     exit(-1);
 240   }
 241 
 242   atout = fopen(name, "w");
 243   if (atout == NULL) {
 244     fprintf(stderr, "Could not open temporary file for writing! %s\n", name);
 245     exit(-1);
 246   }
 247 
 248   buffer = (char*) malloc(buflen);
 249   while((blocklen = fread(block,1,sizeof(block),atin)) > 0) {
 250     append(&buffer, &buflen, &used, block, blocklen);
 251   }
 252   buffer[used] = 0;
 253   if (getenv("DEBUG_FIXPATH") != NULL) {
 254     fprintf(stderr, "fixpath input from @-file %s: %s\n", &in[1], buffer);
 255   }
 256   fixed = replace_cygdrive(buffer);
 257   if (getenv("DEBUG_FIXPATH") != NULL) {
 258     fprintf(stderr, "fixpath converted to @-file %s is: %s\n", name, fixed);
 259   }
 260   fwrite(fixed, strlen(fixed), 1, atout);
 261   fclose(atin);
 262   fclose(atout);
 263   free(fixed);
 264   free(buffer);
 265   files_to_delete[num_files_to_delete] = (char*) malloc(strlen(name)+1);
 266   strcpy(files_to_delete[num_files_to_delete], name);
 267   num_files_to_delete++;
 268   atname = (char*) malloc(strlen(name)+2);
 269   atname[0] = '@';
 270   strcpy(atname+1, name);
 271   return atname;
 272 }
 273 
 274 // given an argument, convert it to the windows command line safe quoted version
 275 // using rules from:
 276 // http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx
 277 // caller is responsible for freeing both input and output.
 278 char * quote_arg(char const * in_arg) {
 279   char *quoted = NULL;
 280   char *current = quoted;
 281   int pass;
 282 
 283   if(strpbrk(in_arg, " \t\n\v\r\\\"") == NULL) {
 284      return _strdup(in_arg);
 285   }
 286 
 287   // process the arg twice. Once to calculate the size and then to copy it.
 288   for(pass=1; pass<=2; pass++) {
 289     char const *arg = in_arg;
 290 
 291     // initial "
 292     if(pass == 2) {
 293       *current = '\"';
 294     }
 295     current++;
 296 
 297     // process string to be quoted until NUL
 298     do {
 299       int escapes = 0;
 300 
 301       while (*arg == '\\') {
 302         // count escapes.
 303         escapes++;
 304         arg++;
 305       }
 306 
 307       if (*arg == '\0') {
 308          // escape the escapes before final "
 309          escapes *= 2;
 310       } else if (*arg == '"') {
 311         // escape the escapes and the "
 312         escapes = escapes * 2 + 1;
 313       } else {
 314          // escapes aren't special, just echo them.
 315       }
 316 
 317       // emit some escapes
 318       while (escapes > 0) {
 319         if (pass == 2) {
 320           *current = '\\';
 321         }
 322         current++;
 323         escapes--;
 324       }
 325 
 326       // and the current char
 327       if (pass == 2) {
 328         *current = *arg;
 329       }
 330       current++;
 331     } while( *arg++ != '\0');
 332 
 333     // allocate the buffer
 334     if (pass == 1) {
 335       size_t alloc = (size_t) (current - quoted + (ptrdiff_t) 2);
 336       current = quoted = (char*) calloc(alloc, sizeof(char));
 337     }
 338   }
 339 
 340   // final " and \0
 341   *(current - 1) = '"';
 342   *current = '\0';
 343 
 344   return quoted;
 345 }
 346 
 347 int main(int argc, char const ** argv)
 348 {
 349     STARTUPINFO si;
 350     PROCESS_INFORMATION pi;
 351     unsigned short rc;
 352 
 353     char *line;
 354     char *current;
 355     int i, cmd;
 356     DWORD exitCode;
 357 
 358     if (argc<2 || argv[1][0] != '-' || (argv[1][1] != 'c' && argv[1][1] != 'm')) {
 359         fprintf(stderr, "Usage: fixpath -c|m<path@path@...> /cygdrive/c/WINDOWS/notepad.exe [/cygdrive/c/x/test.txt|@/cygdrive/c/x/atfile]\n");
 360         exit(0);
 361     }
 362 
 363     if (getenv("DEBUG_FIXPATH") != NULL) {
 364       char const * cmdline = GetCommandLine();
 365       fprintf(stderr, "fixpath input line >%s<\n", strstr( cmdline , argv[1]));
 366     }
 367 
 368     if (argv[1][1] == 'c' && argv[1][2] == '\0') {
 369       if (getenv("DEBUG_FIXPATH") != NULL) {
 370         fprintf(stderr, "using cygwin mode\n");
 371       }
 372       replace_cygdrive = replace_cygdrive_cygwin;
 373     } else if (argv[1][1] == 'm') {
 374       if (getenv("DEBUG_FIXPATH") != NULL) {
 375         fprintf(stderr, "using msys mode, with path list: %s\n", &argv[1][2]);
 376       }
 377       setup_msys_path_list(argv[1]);
 378       replace_cygdrive = replace_cygdrive_msys;
 379     } else {
 380       fprintf(stderr, "Unknown mode: %s\n", argv[1]);
 381       exit(-1);
 382     }
 383 
 384     i = 2;
 385 
 386     // handle assignments
 387     while (i < argc) {
 388       char const * assignment = strchr(argv[i], '=');
 389       if (assignment != NULL && assignment != argv[i]) {
 390         size_t var_len = (size_t) (assignment - argv[i] + (ptrdiff_t) 1);
 391         size_t val_len = (size_t) (&argv[i][strlen(argv[i])] - assignment);
 392         char *var = (char *) calloc(var_len, sizeof(char));
 393         char *val = (char *) calloc(val_len, sizeof(char));
 394         memmove(var, argv[i], var_len);
 395                 var[var_len - 1] = '\0';
 396                 strupr(var);
 397         memmove(val, assignment + 1, val_len);
 398 
 399         rc = SetEnvironmentVariable(var, replace_cygdrive(val));
 400         if(!rc) {
 401           // Could not set var for some reason.  Try to report why:
 402           report_error("Could not set environment variable");
 403           exit(1);
 404         }
 405       } else {
 406         // no more assignments;
 407         break;
 408       }
 409       i++;
 410     }
 411 
 412     // remember index of the command
 413     cmd = i;
 414 
 415     // handle command and it's args.
 416     while (i < argc) {
 417       char const *replaced = replace_cygdrive(argv[i]);
 418       if(replaced[0] == '@') {
 419         // Found at-file! Fix it!
 420         replaced = fix_at_file(replaced);
 421       } 
 422       argv[i] = quote_arg(replaced);
 423           i++;
 424     }
 425 
 426     // determine the length of the line
 427     line = NULL;
 428     // args
 429     for(i = cmd; i < argc; i++) {
 430       line += (ptrdiff_t) strlen(argv[i]);
 431     }
 432     // spaces and null
 433     line += (ptrdiff_t) (argc - cmd);
 434     // allocate
 435     line = (char*) calloc(line - (char*) NULL, sizeof(char));
 436 
 437     // copy in args.
 438     current = line;
 439     for(i = cmd; i < argc; i++) {
 440       ptrdiff_t len = strlen(argv[i]);
 441       if (i != cmd) {
 442         *current++ = ' ';
 443       }
 444       memmove(current, argv[i], len);
 445       current += len;
 446     }
 447         *current = '\0';
 448 
 449     if (getenv("DEBUG_FIXPATH") != NULL) {
 450       fprintf(stderr, "fixpath converted line >%s<\n", line);
 451     }
 452 
 453     ZeroMemory(&si,sizeof(si));
 454     si.cb=sizeof(si);
 455     ZeroMemory(&pi,sizeof(pi));
 456 
 457     rc = CreateProcess(NULL,
 458                        line,
 459                        0,
 460                        0,
 461                        TRUE,
 462                        0,
 463                        NULL,
 464                        NULL,
 465                        &si,
 466                        &pi);
 467     if(!rc) {
 468       // Could not start process for some reason.  Try to report why:
 469       report_error("Could not start process!");
 470       exit(126);
 471     }
 472 
 473     WaitForSingleObject(pi.hProcess,INFINITE);
 474     GetExitCodeProcess(pi.hProcess,&exitCode);
 475 
 476     if (getenv("DEBUG_FIXPATH") != NULL) {
 477       for (i=0; i<num_files_to_delete; ++i) {
 478         fprintf(stderr, "Not deleting temporary fixpath file %s\n",
 479                 files_to_delete[i]);
 480       }
 481     } else {
 482       for (i=0; i<num_files_to_delete; ++i) {
 483         remove(files_to_delete[i]);
 484       }
 485     }
 486 
 487     if (exitCode != 0) {
 488       if (getenv("DEBUG_FIXPATH") != NULL) {
 489         fprintf(stderr, "fixpath exit code %d\n",
 490                 exitCode);
 491       }
 492     }
 493 
 494     exit(exitCode);
 495 }