fixpath.c revision 1023:6005df19ef83
118334Speter/*
2169689Skan * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved.
3169689Skan * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
418334Speter *
590075Sobrien * This code is free software; you can redistribute it and/or modify it
618334Speter * under the terms of the GNU General Public License version 2 only, as
790075Sobrien * published by the Free Software Foundation.  Oracle designates this
890075Sobrien * particular file as subject to the "Classpath" exception as provided
990075Sobrien * by Oracle in the LICENSE file that accompanied this code.
1090075Sobrien *
1118334Speter * This code is distributed in the hope that it will be useful, but WITHOUT
1290075Sobrien * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
1390075Sobrien * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
1490075Sobrien * version 2 for more details (a copy is included in the LICENSE file that
1590075Sobrien * accompanied this code).
1618334Speter *
1718334Speter * You should have received a copy of the GNU General Public License version
1890075Sobrien * 2 along with this work; if not, write to the Free Software Foundation,
19169689Skan * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20169689Skan *
2118334Speter * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2218334Speter * or visit www.oracle.com if you need additional information or have any
2350397Sobrien * questions.
24132718Skan */
25132718Skan
2650397Sobrien#include <Windows.h>
2718334Speter#include <io.h>
2818334Speter#include <stdio.h>
2990075Sobrien#include <string.h>
3018334Speter#include <malloc.h>
3150397Sobrien
3250397Sobrienvoid report_error(char const * msg)
3390075Sobrien{
3496263Sobrien  LPVOID lpMsgBuf;
3590075Sobrien  DWORD dw = GetLastError();
3690075Sobrien
37169689Skan  FormatMessage(
38169689Skan      FORMAT_MESSAGE_ALLOCATE_BUFFER |
39169689Skan      FORMAT_MESSAGE_FROM_SYSTEM |
40169689Skan      FORMAT_MESSAGE_IGNORE_INSERTS,
4118334Speter      NULL,
42169689Skan      dw,
43169689Skan      MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
44169689Skan      (LPTSTR) &lpMsgBuf,
45169689Skan      0,
4618334Speter      NULL);
47117395Skan
4852284Sobrien  fprintf(stderr,
49169689Skan          "%s  Failed with error %d: %s\n",
50169689Skan          msg, dw, lpMsgBuf);
5152284Sobrien
5252284Sobrien  LocalFree(lpMsgBuf);
5318334Speter}
54117395Skan
5552284Sobrien/*
56117395Skan * Test if pos points to /cygdrive/_/ where _ can
57132718Skan * be any character.
58117395Skan */
59117395Skanint is_cygdrive_here(int pos, char const *in, int len)
6090075Sobrien{
61169689Skan  // Length of /cygdrive/c/ is 12
62169689Skan  if (pos+12 > len) return 0;
6390075Sobrien  if (in[pos+11]=='/' &&
6490075Sobrien      in[pos+9]=='/' &&
6596263Sobrien      in[pos+8]=='e' &&
66169689Skan      in[pos+7]=='v' &&
67169689Skan      in[pos+6]=='i' &&
68169689Skan      in[pos+5]=='r' &&
6952284Sobrien      in[pos+4]=='d' &&
70132718Skan      in[pos+3]=='g' &&
71132718Skan      in[pos+2]=='y' &&
7290075Sobrien      in[pos+1]=='c' &&
7352284Sobrien      in[pos+0]=='/') {
7490075Sobrien    return 1;
75132718Skan  }
7652284Sobrien  return 0;
77169689Skan}
7852284Sobrien
79169689Skan/*
8052284Sobrien * Replace /cygdrive/_/ with _:/
81169689Skan * Works in place since drive letter is always
82169689Skan * shorter than /cygdrive/
83169689Skan */
8452284Sobrienchar *replace_cygdrive_cygwin(char const *in)
85169689Skan{
86169689Skan  size_t len = strlen(in);
87169689Skan  char *out = (char*) malloc(len+1);
88169689Skan  int i,j;
89169689Skan
90169689Skan  if (len < 12) {
91169689Skan    memmove(out, in, len + 1);
92169689Skan    return out;
93169689Skan  }
9452284Sobrien
9552284Sobrien  for (i = 0, j = 0; i<len;) {
9652284Sobrien    if (is_cygdrive_here(i, in, len)) {
9790075Sobrien      out[j++] = in[i+10];
98132718Skan      out[j++] = ':';
9952284Sobrien      i+=11;
10052284Sobrien    } else {
101169689Skan      out[j] = in[i];
10252284Sobrien      i++;
103169689Skan      j++;
10452284Sobrien    }
10552284Sobrien  }
10652284Sobrien  out[j] = '\0';
10752284Sobrien  return out;
10852284Sobrien}
10952284Sobrien
11052284Sobrienvoid append(char **b, size_t *bl, size_t *u, char *add, size_t addlen)
11152284Sobrien{
11252284Sobrien  while ((addlen+*u+1) > *bl) {
11352284Sobrien    *bl *= 2;
11452284Sobrien    *b = (char*) realloc(*b, *bl);
11552284Sobrien  }
116169689Skan  memcpy(*b+*u, add, addlen);
117169689Skan  *u += addlen;
11852284Sobrien}
11952284Sobrien
12052284Sobrien/*
121169689Skan * Creates a new string from in where the first occurrence of sub is
12252284Sobrien * replaced by rep.
123169689Skan */
12452284Sobrienchar *replace_substring(char *in, char *sub, char *rep)
125169689Skan{
12652284Sobrien  int in_len = strlen(in);
12790075Sobrien  int sub_len = strlen(sub);
12890075Sobrien  int rep_len = strlen(rep);
12990075Sobrien  char *out = (char *) malloc(in_len - sub_len + rep_len + 1);
130169689Skan  char *p;
13190075Sobrien
132169689Skan  if (!(p = strstr(in, sub))) {
13352284Sobrien    // If sub isn't a substring of in, just return in.
13452284Sobrien    return in;
13590075Sobrien  }
13690075Sobrien
137169689Skan  // Copy characters from beginning of in to start of sub.
138169689Skan  strncpy(out, in, p - in);
13990075Sobrien  out[p - in] = '\0';
140169689Skan
14190075Sobrien  sprintf(out + (p - in), "%s%s", rep, p + sub_len);
14290075Sobrien
14390075Sobrien  return out;
14490075Sobrien}
145169689Skan
14618334Speterchar* msys_path_list; // @-separated list of paths prefix to look for
14790075Sobrienchar* msys_path_list_end; // Points to last \0 in msys_path_list.
14890075Sobrien
14990075Sobrienvoid setup_msys_path_list(char const * argument)
15090075Sobrien{
15118334Speter  char* p;
152169689Skan  char* drive_letter_pos;
153169689Skan
15490075Sobrien  msys_path_list = strdup(&argument[2]);
155169689Skan  msys_path_list_end = &msys_path_list[strlen(msys_path_list)];
15690075Sobrien
15718334Speter  // Convert all at-sign (@) in path list to \0.
15890075Sobrien  // @ was chosen as separator to minimize risk of other tools messing around with it
159169689Skan  p = msys_path_list;
16018334Speter  do {
16190075Sobrien    if (p[1] == ':') {
16252284Sobrien      // msys has mangled our path list, restore it from c:/... to /c/...
163169689Skan      drive_letter_pos = p+1;
164169689Skan      *drive_letter_pos = *p;
16590075Sobrien      *p = '/';
16690075Sobrien    }
167169689Skan
168169689Skan    // Look for an @ in the list
16952284Sobrien    p = strchr(p, '@');
17090075Sobrien    if (p != NULL) {
17118334Speter      *p = '\0';
172169689Skan      p++;
173169689Skan    }
17490075Sobrien  } while (p != NULL);
175169689Skan}
17690075Sobrien
17718334Speterchar *replace_cygdrive_msys(char const *in)
17890075Sobrien{
17990075Sobrien  char* str;
18090075Sobrien  char* prefix;
18190075Sobrien  char* p;
18290075Sobrien
18352284Sobrien  str = strdup(in);
184169689Skan
18518334Speter  // For each prefix in the path list, search for it and replace /c/... with c:/...
186169689Skan  for (prefix = msys_path_list; prefix < msys_path_list_end && prefix != NULL; prefix += strlen(prefix)+1) {
18752284Sobrien    p=str;
188169689Skan    while ((p = strstr(p, prefix))) {
189169689Skan      char* drive_letter = p+1;
19052284Sobrien      *p = *drive_letter;
19190075Sobrien      *drive_letter = ':';
19252284Sobrien      p++;
193169689Skan    }
19490075Sobrien  }
195169689Skan
196169689Skan  return str;
197169689Skan}
198169689Skan
199169689Skanchar*(*replace_cygdrive)(char const *in) = NULL;
20090075Sobrien
201169689Skanchar *files_to_delete[1024];
202169689Skanint num_files_to_delete = 0;
20318334Speter
20418334Speterchar *fix_at_file(char const *in)
20590075Sobrien{
20690075Sobrien  char *tmpdir;
20790075Sobrien  char name[2048];
20890075Sobrien  char *atname;
20990075Sobrien  char *buffer;
210169689Skan  size_t buflen=65536;
21152284Sobrien  size_t used=0;
212169689Skan  size_t len;
213169689Skan  int rc;
21452284Sobrien  FILE *atout;
215169689Skan  FILE *atin;
216169689Skan  char block[2048];
217169689Skan  size_t blocklen;
21890075Sobrien  char *fixed;
21990075Sobrien
22090075Sobrien  atin = fopen(in+1, "r");
22190075Sobrien  if (atin == NULL) {
22290075Sobrien    fprintf(stderr, "Could not read at file %s\n", in+1);
22390075Sobrien    exit(-1);
22490075Sobrien  }
22590075Sobrien
22690075Sobrien  tmpdir = getenv("TEMP");
22790075Sobrien  if (tmpdir == NULL) {
22890075Sobrien#if _WIN64
229169689Skan    tmpdir = "c:/cygwin64/tmp";
230169689Skan#else
231169689Skan    tmpdir = "c:/cygwin/tmp";
232169689Skan#endif
233169689Skan  }
234169689Skan  _snprintf(name, sizeof(name), "%s\\atfile_XXXXXX", tmpdir);
23590075Sobrien
23690075Sobrien  rc = _mktemp_s(name, strlen(name)+1);
23790075Sobrien  if (rc) {
23818334Speter    fprintf(stderr, "Could not create temporary file name for at file!\n");
23990075Sobrien    exit(-1);
24090075Sobrien  }
24190075Sobrien
24290075Sobrien  atout = fopen(name, "w");
243169689Skan  if (atout == NULL) {
24490075Sobrien    fprintf(stderr, "Could not open temporary file for writing! %s\n", name);
24590075Sobrien    exit(-1);
24690075Sobrien  }
24718334Speter
248117395Skan  buffer = (char*) malloc(buflen);
249117395Skan  while ((blocklen = fread(block, 1, sizeof(block), atin)) > 0) {
25090075Sobrien    append(&buffer, &buflen, &used, block, blocklen);
251132718Skan  }
252132718Skan  buffer[used] = 0;
25352284Sobrien  if (getenv("DEBUG_FIXPATH") != NULL) {
25496263Sobrien    fprintf(stderr, "fixpath input from @-file %s: %s\n", &in[1], buffer);
255132718Skan  }
25696263Sobrien  fixed = replace_cygdrive(buffer);
25796263Sobrien  if (getenv("DEBUG_FIXPATH") != NULL) {
258102780Skan    fprintf(stderr, "fixpath converted to @-file %s is: %s\n", name, fixed);
259102780Skan  }
260102780Skan  fwrite(fixed, strlen(fixed), 1, atout);
261102780Skan  fclose(atin);
262102780Skan  fclose(atout);
263102780Skan  free(fixed);
264102780Skan  free(buffer);
265102780Skan  files_to_delete[num_files_to_delete] = (char*) malloc(strlen(name)+1);
26696263Sobrien  strcpy(files_to_delete[num_files_to_delete], name);
267132718Skan  num_files_to_delete++;
26896263Sobrien  atname = (char*) malloc(strlen(name)+2);
269169689Skan  atname[0] = '@';
270169689Skan  strcpy(atname+1, name);
27196263Sobrien  return atname;
27296263Sobrien}
27396263Sobrien
27496263Sobrien// given an argument, convert it to the windows command line safe quoted version
27596263Sobrien// using rules from:
276132718Skan// http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx
27796263Sobrien// caller is responsible for freeing both input and output.
27896263Sobrienchar * quote_arg(char const * in_arg) {
27996263Sobrien  char *quoted = NULL;
280119256Skan  char *current = quoted;
281119256Skan  int pass;
282119256Skan
283119256Skan  if (strlen(in_arg) == 0) {
28496263Sobrien     // empty string? explicitly quote it.
285119256Skan     return _strdup("\"\"");
286119256Skan  }
287119256Skan
288119256Skan  if (strpbrk(in_arg, " \t\n\v\r\\\"") == NULL) {
289119256Skan     return _strdup(in_arg);
290119256Skan  }
291169689Skan
292119256Skan  // process the arg twice. Once to calculate the size and then to copy it.
293119256Skan  for (pass=1; pass<=2; pass++) {
29496263Sobrien    char const *arg = in_arg;
295119256Skan
296119256Skan    // initial "
29796263Sobrien    if (pass == 2) {
29896263Sobrien      *current = '\"';
29996263Sobrien    }
30096263Sobrien    current++;
30196263Sobrien
30296263Sobrien    // process string to be quoted until NUL
30396263Sobrien    do {
30496263Sobrien      int escapes = 0;
30596263Sobrien
306169689Skan      while (*arg == '\\') {
307169689Skan        // count escapes.
308169689Skan        escapes++;
309169689Skan        arg++;
310169689Skan      }
311169689Skan
312169689Skan      if (*arg == '\0') {
313169689Skan         // escape the escapes before final "
314169689Skan         escapes *= 2;
315169689Skan      } else if (*arg == '"') {
316169689Skan        // escape the escapes and the "
317169689Skan        escapes = escapes * 2 + 1;
318169689Skan      } else {
319169689Skan         // escapes aren't special, just echo them.
320169689Skan      }
321169689Skan
322169689Skan      // emit some escapes
323169689Skan      while (escapes > 0) {
324169689Skan        if (pass == 2) {
325169689Skan          *current = '\\';
326169689Skan        }
327169689Skan        current++;
328169689Skan        escapes--;
329169689Skan      }
330169689Skan
331169689Skan      // and the current char
332169689Skan      if (pass == 2) {
33390075Sobrien        *current = *arg;
33490075Sobrien      }
335169689Skan      current++;
33690075Sobrien    } while (*arg++ != '\0');
33796263Sobrien
33890075Sobrien    // allocate the buffer
33952284Sobrien    if (pass == 1) {
34090075Sobrien      size_t alloc = (size_t) (current - quoted + (ptrdiff_t) 2);
34152284Sobrien      current = quoted = (char*) calloc(alloc, sizeof(char));
342169689Skan    }
34390075Sobrien  }
344169689Skan
34590075Sobrien  // final " and \0
34690075Sobrien  *(current - 1) = '"';
347169689Skan  *current = '\0';
34890075Sobrien
349169689Skan  return quoted;
35090075Sobrien}
35190075Sobrien
352169689Skanint main(int argc, char const ** argv)
35352284Sobrien{
35496263Sobrien    STARTUPINFO si;
355169689Skan    PROCESS_INFORMATION pi;
356102780Skan    unsigned short rc;
357102780Skan
358102780Skan    char *line;
359102780Skan    char *current;
360102780Skan    int i, cmd;
36196263Sobrien    DWORD exitCode;
36296263Sobrien
36390075Sobrien    if (argc<2 || argv[1][0] != '-' || (argv[1][1] != 'c' && argv[1][1] != 'm')) {
36496263Sobrien        fprintf(stderr, "Usage: fixpath -c|m<path@path@...> /cygdrive/c/WINDOWS/notepad.exe [/cygdrive/c/x/test.txt|@/cygdrive/c/x/atfile]\n");
36596263Sobrien        exit(0);
366169689Skan    }
36796263Sobrien
36896263Sobrien    if (getenv("DEBUG_FIXPATH") != NULL) {
369169689Skan      char const * cmdline = GetCommandLine();
370169689Skan      fprintf(stderr, "fixpath input line >%s<\n", strstr(cmdline, argv[1]));
371169689Skan    }
372169689Skan
373169689Skan    if (argv[1][1] == 'c' && argv[1][2] == '\0') {
37496263Sobrien      if (getenv("DEBUG_FIXPATH") != NULL) {
37596263Sobrien        fprintf(stderr, "fixpath using cygwin mode\n");
376169689Skan      }
377169689Skan      replace_cygdrive = replace_cygdrive_cygwin;
378169689Skan    } else if (argv[1][1] == 'm') {
379169689Skan      if (getenv("DEBUG_FIXPATH") != NULL) {
380169689Skan        fprintf(stderr, "fixpath using msys mode, with path list: %s\n", &argv[1][2]);
381169689Skan      }
382169689Skan      setup_msys_path_list(argv[1]);
383169689Skan      replace_cygdrive = replace_cygdrive_msys;
384169689Skan    } else {
385169689Skan      fprintf(stderr, "fixpath Unknown mode: %s\n", argv[1]);
386169689Skan      exit(-1);
387169689Skan    }
388169689Skan
389169689Skan    i = 2;
390169689Skan
391169689Skan    // handle assignments
392169689Skan    while (i < argc) {
393169689Skan      char const * assignment = strchr(argv[i], '=');
394169689Skan      if (assignment != NULL && assignment != argv[i]) {
395169689Skan        size_t var_len = (size_t) (assignment - argv[i] + (ptrdiff_t) 1);
396169689Skan        char *var = (char *) calloc(var_len, sizeof(char));
397169689Skan        char *val = replace_cygdrive(assignment + 1);
398169689Skan        memmove(var, argv[i], var_len);
399169689Skan        var[var_len - 1] = '\0';
400169689Skan        strupr(var);
401169689Skan
402169689Skan        if (getenv("DEBUG_FIXPATH") != NULL) {
403169689Skan          fprintf(stderr, "fixpath setting var >%s< to >%s<\n", var, val);
404169689Skan        }
405169689Skan
406169689Skan        rc = SetEnvironmentVariable(var, val);
407169689Skan        if (!rc) {
408117395Skan          // Could not set var for some reason.  Try to report why.
409117395Skan          const int msg_len = 80 + var_len + strlen(val);
410132718Skan          char * msg = (char *) alloca(msg_len);
41196263Sobrien          _snprintf_s(msg, msg_len, _TRUNCATE, "Could not set environment variable [%s=%s]", var, val);
412169689Skan          report_error(msg);
41396263Sobrien          exit(1);
414169689Skan        }
41596263Sobrien        free(var);
41696263Sobrien        free(val);
41796263Sobrien      } else {
41896263Sobrien        // no more assignments;
419169689Skan        break;
420169689Skan      }
421169689Skan      i++;
422169689Skan    }
423169689Skan
424169689Skan    // remember index of the command
425169689Skan    cmd = i;
426169689Skan
427169689Skan    // handle command and it's args.
42896263Sobrien    while (i < argc) {
429169689Skan      char const *replaced = replace_cygdrive(argv[i]);
430169689Skan      if (replaced[0] == '@') {
431169689Skan        // Found at-file! Fix it!
43296263Sobrien        replaced = fix_at_file(replaced);
43396263Sobrien      }
43496263Sobrien      argv[i] = quote_arg(replaced);
43596263Sobrien      i++;
436169689Skan    }
437169689Skan
438169689Skan    // determine the length of the line
439169689Skan    line = NULL;
440169689Skan    // args
44196263Sobrien    for (i = cmd; i < argc; i++) {
442169689Skan      line += (ptrdiff_t) strlen(argv[i]);
443169689Skan    }
444169689Skan    // spaces and null
445169689Skan    line += (ptrdiff_t) (argc - cmd + 1);
446169689Skan    // allocate
447169689Skan    line = (char*) calloc(line - (char*) NULL, sizeof(char));
448169689Skan
449169689Skan    // copy in args.
450169689Skan    current = line;
451169689Skan    for (i = cmd; i < argc; i++) {
452169689Skan      ptrdiff_t len = strlen(argv[i]);
45396263Sobrien      if (i != cmd) {
45496263Sobrien        *current++ = ' ';
455169689Skan      }
456169689Skan      memmove(current, argv[i], len);
457169689Skan      current += len;
458169689Skan    }
459169689Skan    *current = '\0';
460169689Skan
46196263Sobrien    if (getenv("DEBUG_FIXPATH") != NULL) {
46252284Sobrien      fprintf(stderr, "fixpath converted line >%s<\n", line);
463169689Skan    }
464117395Skan
465132718Skan    if (cmd == argc) {
466117395Skan       if (getenv("DEBUG_FIXPATH") != NULL) {
467169689Skan         fprintf(stderr, "fixpath no command provided!\n");
468169689Skan       }
469169689Skan       exit(0);
470169689Skan    }
471169689Skan
472169689Skan    ZeroMemory(&si, sizeof(si));
473169689Skan    si.cb=sizeof(si);
474169689Skan    ZeroMemory(&pi, sizeof(pi));
475169689Skan
476117395Skan    fflush(stderr);
477117395Skan    fflush(stdout);
478117395Skan
479117395Skan    rc = CreateProcess(NULL,
480117395Skan                       line,
481117395Skan                       0,
48296263Sobrien                       0,
48396263Sobrien                       TRUE,
484169689Skan                       0,
48596263Sobrien                       NULL,
48696263Sobrien                       NULL,
48796263Sobrien                       &si,
48896263Sobrien                       &pi);
489169689Skan    if (!rc) {
490169689Skan      // Could not start process for some reason.  Try to report why:
491169689Skan      report_error("Could not start process!");
49296263Sobrien      exit(126);
493169689Skan    }
49496263Sobrien
495169689Skan    WaitForSingleObject(pi.hProcess, INFINITE);
496169689Skan    GetExitCodeProcess(pi.hProcess, &exitCode);
497169689Skan
498169689Skan    if (getenv("DEBUG_FIXPATH") != NULL) {
499169689Skan      for (i=0; i<num_files_to_delete; ++i) {
500169689Skan        fprintf(stderr, "fixpath Not deleting temporary file %s\n",
50196263Sobrien                files_to_delete[i]);
50296263Sobrien      }
503132718Skan    } else {
50496263Sobrien      for (i=0; i<num_files_to_delete; ++i) {
50596263Sobrien        remove(files_to_delete[i]);
50696263Sobrien      }
507132718Skan    }
50896263Sobrien
509169689Skan    if (exitCode != 0) {
51096263Sobrien      if (getenv("DEBUG_FIXPATH") != NULL) {
511169689Skan        fprintf(stderr, "fixpath exit code %d\n",
512169689Skan                exitCode);
513169689Skan      }
514169689Skan    }
515169689Skan
51696263Sobrien    exit(exitCode);
51796263Sobrien}
518169689Skan