fixpath.c revision 1789:11b31df300ae
190075Sobrien/* 2132718Skan * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved. 3169689Skan * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4169689Skan * 590075Sobrien * This code is free software; you can redistribute it and/or modify it 690075Sobrien * under the terms of the GNU General Public License version 2 only, as 7132718Skan * published by the Free Software Foundation. Oracle designates this 890075Sobrien * particular file as subject to the "Classpath" exception as provided 9132718Skan * by Oracle in the LICENSE file that accompanied this code. 10132718Skan * 11132718Skan * This code is distributed in the hope that it will be useful, but WITHOUT 12132718Skan * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 1390075Sobrien * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14132718Skan * version 2 for more details (a copy is included in the LICENSE file that 15132718Skan * accompanied this code). 16132718Skan * 17132718Skan * 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, 1990075Sobrien * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20132718Skan * 21169689Skan * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22169689Skan * or visit www.oracle.com if you need additional information or have any 2390075Sobrien * questions. 2490075Sobrien */ 2590075Sobrien 26132718Skan#include <Windows.h> 27132718Skan#include <io.h> 28132718Skan#include <stdio.h> 29132718Skan#include <string.h> 30132718Skan#include <malloc.h> 31132718Skan 32132718Skanvoid report_error(char const * msg) 33132718Skan{ 34132718Skan LPVOID lpMsgBuf; 35132718Skan DWORD dw = GetLastError(); 36132718Skan 37132718Skan FormatMessage( 38169689Skan FORMAT_MESSAGE_ALLOCATE_BUFFER | 39169689Skan FORMAT_MESSAGE_FROM_SYSTEM | 40169689Skan FORMAT_MESSAGE_IGNORE_INSERTS, 41169689Skan NULL, 42132718Skan dw, 43132718Skan MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 44132718Skan (LPTSTR) &lpMsgBuf, 45132718Skan 0, 46132718Skan NULL); 47132718Skan 48132718Skan fprintf(stderr, 49132718Skan "%s Failed with error %d: %s\n", 50132718Skan msg, dw, lpMsgBuf); 51132718Skan 52132718Skan LocalFree(lpMsgBuf); 53132718Skan} 54132718Skan 55132718Skan/* 56132718Skan * Test if pos points to /cygdrive/_/ where _ can 57132718Skan * be any character. 58169689Skan */ 59169689Skanint is_cygdrive_here(int pos, char const *in, int len) 60169689Skan{ 61169689Skan // Length of /cygdrive/c/ is 12 62169689Skan if (pos+12 > len) return 0; 63169689Skan if (in[pos+11]=='/' && 64169689Skan in[pos+9]=='/' && 65169689Skan in[pos+8]=='e' && 66169689Skan in[pos+7]=='v' && 67169689Skan in[pos+6]=='i' && 68169689Skan in[pos+5]=='r' && 69169689Skan in[pos+4]=='d' && 70169689Skan in[pos+3]=='g' && 71169689Skan in[pos+2]=='y' && 72169689Skan in[pos+1]=='c' && 73169689Skan in[pos+0]=='/') { 74169689Skan return 1; 75132718Skan } 76132718Skan return 0; 77132718Skan} 78132718Skan 79132718Skan/* 80132718Skan * Replace /cygdrive/_/ with _:/ 81132718Skan * Works in place since drive letter is always 82132718Skan * shorter than /cygdrive/ 83169689Skan */ 84169689Skanchar *replace_cygdrive_cygwin(char const *in) 85132718Skan{ 86132718Skan size_t len = strlen(in); 8790075Sobrien char *out = (char*) malloc(len+1); 8890075Sobrien int i,j; 8990075Sobrien 90169689Skan if (len < 12) { 9190075Sobrien memmove(out, in, len + 1); 9290075Sobrien return out; 9390075Sobrien } 94169689Skan 9590075Sobrien for (i = 0, j = 0; i<len;) { 9690075Sobrien if (is_cygdrive_here(i, in, len)) { 9790075Sobrien out[j++] = in[i+10]; 9890075Sobrien out[j++] = ':'; 9990075Sobrien i+=11; 10090075Sobrien } else { 10190075Sobrien out[j] = in[i]; 10290075Sobrien i++; 10390075Sobrien j++; 10490075Sobrien } 10590075Sobrien } 10690075Sobrien out[j] = '\0'; 10790075Sobrien return out; 10890075Sobrien} 109132718Skan 11090075Sobrienvoid append(char **b, size_t *bl, size_t *u, char *add, size_t addlen) 11190075Sobrien{ 112132718Skan while ((addlen+*u+1) > *bl) { 11390075Sobrien *bl *= 2; 114132718Skan *b = (char*) realloc(*b, *bl); 115132718Skan } 116132718Skan memcpy(*b+*u, add, addlen); 117132718Skan *u += addlen; 118132718Skan} 119132718Skan 120132718Skan/* 121132718Skan * Creates a new string from in where the first occurrence of sub is 122132718Skan * replaced by rep. 123132718Skan */ 124132718Skanchar *replace_substring(char *in, char *sub, char *rep) 125132718Skan{ 126132718Skan int in_len = strlen(in); 12790075Sobrien int sub_len = strlen(sub); 128169689Skan int rep_len = strlen(rep); 129169689Skan char *out = (char *) malloc(in_len - sub_len + rep_len + 1); 130169689Skan char *p; 131169689Skan 132169689Skan if (!(p = strstr(in, sub))) { 13390075Sobrien // If sub isn't a substring of in, just return in. 134169689Skan return in; 135169689Skan } 136169689Skan 137169689Skan // Copy characters from beginning of in to start of sub. 138169689Skan strncpy(out, in, p - in); 139169689Skan out[p - in] = '\0'; 140169689Skan 141169689Skan sprintf(out + (p - in), "%s%s", rep, p + sub_len); 142169689Skan 143169689Skan return out; 144169689Skan} 145169689Skan 146169689Skanchar* msys_path_list; // @-separated list of paths prefix to look for 147169689Skanchar* msys_path_list_end; // Points to last \0 in msys_path_list. 148169689Skan 149169689Skanvoid setup_msys_path_list(char const * argument) 150169689Skan{ 151169689Skan char* p; 152169689Skan char* drive_letter_pos; 153169689Skan 154169689Skan msys_path_list = strdup(&argument[2]); 155169689Skan msys_path_list_end = &msys_path_list[strlen(msys_path_list)]; 156169689Skan 157169689Skan // Convert all at-sign (@) in path list to \0. 158169689Skan // @ was chosen as separator to minimize risk of other tools messing around with it 159169689Skan p = msys_path_list; 160169689Skan do { 161169689Skan if (p[1] == ':') { 162169689Skan // msys has mangled our path list, restore it from c:/... to /c/... 163169689Skan drive_letter_pos = p+1; 164169689Skan *drive_letter_pos = *p; 165169689Skan *p = '/'; 166169689Skan } 167169689Skan 168169689Skan // Look for an @ in the list 169169689Skan p = strchr(p, '@'); 17090075Sobrien if (p != NULL) { 17190075Sobrien *p = '\0'; 17290075Sobrien p++; 173169689Skan } 17490075Sobrien } while (p != NULL); 175169689Skan} 17690075Sobrien 17790075Sobrienchar *replace_cygdrive_msys(char const *in) 17890075Sobrien{ 179169689Skan char* str; 18090075Sobrien char* prefix; 181169689Skan char* p; 18290075Sobrien 18390075Sobrien str = strdup(in); 184169689Skan 185169689Skan // For each prefix in the path list, search for it and replace /c/... with c:/... 18690075Sobrien for (prefix = msys_path_list; prefix < msys_path_list_end && prefix != NULL; prefix += strlen(prefix)+1) { 18790075Sobrien p=str; 188169689Skan while ((p = strstr(p, prefix))) { 18990075Sobrien char* drive_letter = p+1; 190169689Skan *p = *drive_letter; 19190075Sobrien *drive_letter = ':'; 19290075Sobrien p++; 193132718Skan } 19490075Sobrien } 195169689Skan 19690075Sobrien return str; 19790075Sobrien} 19890075Sobrien 19990075Sobrienchar*(*replace_cygdrive)(char const *in) = NULL; 20090075Sobrien 20190075Sobrienchar *files_to_delete[1024]; 202169689Skanint num_files_to_delete = 0; 20390075Sobrien 20490075Sobrienchar *fix_at_file(char const *in) 20590075Sobrien{ 20690075Sobrien char *tmpdir; 20790075Sobrien char name[2048]; 20890075Sobrien char *atname; 20990075Sobrien char *buffer; 21090075Sobrien size_t buflen=65536; 21190075Sobrien size_t used=0; 21290075Sobrien size_t len; 213169689Skan int rc; 21490075Sobrien FILE *atout; 215169689Skan FILE *atin; 21690075Sobrien char block[2048]; 21790075Sobrien size_t blocklen; 21890075Sobrien char *fixed; 219132718Skan 22090075Sobrien atin = fopen(in+1, "r"); 221169689Skan 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) { 228169689Skan#if _WIN64 22990075Sobrien tmpdir = "c:/cygwin64/tmp"; 23090075Sobrien#else 23190075Sobrien tmpdir = "c:/cygwin/tmp"; 23290075Sobrien#endif 23390075Sobrien } 23490075Sobrien _snprintf(name, sizeof(name), "%s\\atfile_XXXXXX", tmpdir); 23590075Sobrien 23690075Sobrien rc = _mktemp_s(name, strlen(name)+1); 23790075Sobrien if (rc) { 23890075Sobrien fprintf(stderr, "Could not create temporary file name for at file!\n"); 23990075Sobrien exit(-1); 24090075Sobrien } 24190075Sobrien 24290075Sobrien atout = fopen(name, "w"); 24390075Sobrien if (atout == NULL) { 24490075Sobrien fprintf(stderr, "Could not open temporary file for writing! %s\n", name); 24590075Sobrien exit(-1); 24690075Sobrien } 24790075Sobrien 24890075Sobrien buffer = (char*) malloc(buflen); 24990075Sobrien while ((blocklen = fread(block, 1, sizeof(block), atin)) > 0) { 25090075Sobrien append(&buffer, &buflen, &used, block, blocklen); 251132718Skan } 25290075Sobrien buffer[used] = 0; 25390075Sobrien if (getenv("DEBUG_FIXPATH") != NULL) { 25490075Sobrien fprintf(stderr, "fixpath input from @-file %s: %s\n", &in[1], buffer); 25590075Sobrien } 25690075Sobrien fixed = replace_cygdrive(buffer); 25790075Sobrien if (getenv("DEBUG_FIXPATH") != NULL) { 25890075Sobrien fprintf(stderr, "fixpath converted to @-file %s is: %s\n", name, fixed); 25990075Sobrien } 26090075Sobrien fwrite(fixed, strlen(fixed), 1, atout); 26190075Sobrien fclose(atin); 26290075Sobrien fclose(atout); 26390075Sobrien free(fixed); 26490075Sobrien free(buffer); 26590075Sobrien files_to_delete[num_files_to_delete] = (char*) malloc(strlen(name)+1); 26690075Sobrien strcpy(files_to_delete[num_files_to_delete], name); 26790075Sobrien num_files_to_delete++; 26890075Sobrien atname = (char*) malloc(strlen(name)+2); 26990075Sobrien atname[0] = '@'; 27090075Sobrien strcpy(atname+1, name); 27190075Sobrien return atname; 27290075Sobrien} 27390075Sobrien 27490075Sobrien// given an argument, convert it to the windows command line safe quoted version 27590075Sobrien// using rules from: 27690075Sobrien// http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx 277132718Skan// caller is responsible for freeing both input and output. 27890075Sobrienchar * quote_arg(char const * in_arg) { 27990075Sobrien char *quoted = NULL; 28090075Sobrien char *current = quoted; 28190075Sobrien int pass; 28290075Sobrien 28390075Sobrien if (strlen(in_arg) == 0) { 28490075Sobrien // empty string? explicitly quote it. 28590075Sobrien return _strdup("\"\""); 28690075Sobrien } 28790075Sobrien 28890075Sobrien if (strpbrk(in_arg, " \t\n\v\r\\\"") == NULL) { 28990075Sobrien return _strdup(in_arg); 29090075Sobrien } 29190075Sobrien 29290075Sobrien // process the arg twice. Once to calculate the size and then to copy it. 29390075Sobrien for (pass=1; pass<=2; pass++) { 29490075Sobrien char const *arg = in_arg; 29590075Sobrien 29690075Sobrien // initial " 29790075Sobrien if (pass == 2) { 29890075Sobrien *current = '\"'; 29990075Sobrien } 30090075Sobrien current++; 30190075Sobrien 30290075Sobrien // process string to be quoted until NUL 30390075Sobrien do { 30490075Sobrien int escapes = 0; 30590075Sobrien 30690075Sobrien while (*arg == '\\') { 30790075Sobrien // count escapes. 30890075Sobrien escapes++; 30990075Sobrien arg++; 31090075Sobrien } 311132718Skan 31290075Sobrien if (*arg == '\0') { 31390075Sobrien // escape the escapes before final " 31490075Sobrien escapes *= 2; 31590075Sobrien } else if (*arg == '"') { 31690075Sobrien // escape the escapes and the " 31790075Sobrien escapes = escapes * 2 + 1; 318132718Skan } else { 31990075Sobrien // escapes aren't special, just echo them. 32090075Sobrien } 32190075Sobrien 32290075Sobrien // emit some escapes 32390075Sobrien while (escapes > 0) { 32490075Sobrien if (pass == 2) { 32590075Sobrien *current = '\\'; 32690075Sobrien } 32790075Sobrien current++; 32890075Sobrien escapes--; 32990075Sobrien } 33090075Sobrien 33190075Sobrien // and the current char 33290075Sobrien if (pass == 2) { 33390075Sobrien *current = *arg; 33490075Sobrien } 33590075Sobrien current++; 33690075Sobrien } while (*arg++ != '\0'); 33790075Sobrien 33890075Sobrien // allocate the buffer 33990075Sobrien if (pass == 1) { 34090075Sobrien size_t alloc = (size_t) (current - quoted + (ptrdiff_t) 2); 34190075Sobrien current = quoted = (char*) calloc(alloc, sizeof(char)); 34290075Sobrien } 34390075Sobrien } 344132718Skan 34590075Sobrien // final " and \0 34690075Sobrien *(current - 1) = '"'; 34790075Sobrien *current = '\0'; 34890075Sobrien 34990075Sobrien return quoted; 35090075Sobrien} 35190075Sobrien 35290075Sobrienint main(int argc, char const ** argv) 35390075Sobrien{ 35490075Sobrien STARTUPINFO si; 35590075Sobrien PROCESS_INFORMATION pi; 35690075Sobrien unsigned short rc; 35790075Sobrien 35890075Sobrien char *line; 35990075Sobrien char *current; 36090075Sobrien int i, cmd; 36190075Sobrien DWORD exitCode = 0; 36290075Sobrien DWORD processFlags = 0; 36390075Sobrien BOOL processInheritHandles = TRUE; 36490075Sobrien BOOL waitForChild = TRUE; 36590075Sobrien 36690075Sobrien if (argc<2 || argv[1][0] != '-' || (argv[1][1] != 'c' && argv[1][1] != 'm')) { 36790075Sobrien fprintf(stderr, "Usage: fixpath -c|m<path@path@...> [--detach] /cygdrive/c/WINDOWS/notepad.exe [/cygdrive/c/x/test.txt|@/cygdrive/c/x/atfile]\n"); 36890075Sobrien exit(0); 36990075Sobrien } 37090075Sobrien 37190075Sobrien if (getenv("DEBUG_FIXPATH") != NULL) { 37290075Sobrien char const * cmdline = GetCommandLine(); 37390075Sobrien fprintf(stderr, "fixpath input line >%s<\n", strstr(cmdline, argv[1])); 37490075Sobrien } 37590075Sobrien 37690075Sobrien if (argv[1][1] == 'c' && argv[1][2] == '\0') { 37790075Sobrien if (getenv("DEBUG_FIXPATH") != NULL) { 378132718Skan fprintf(stderr, "fixpath using cygwin mode\n"); 37990075Sobrien } 38090075Sobrien replace_cygdrive = replace_cygdrive_cygwin; 38190075Sobrien } else if (argv[1][1] == 'm') { 38290075Sobrien if (getenv("DEBUG_FIXPATH") != NULL) { 38390075Sobrien fprintf(stderr, "fixpath using msys mode, with path list: %s\n", &argv[1][2]); 38490075Sobrien } 385132718Skan setup_msys_path_list(argv[1]); 38690075Sobrien replace_cygdrive = replace_cygdrive_msys; 38790075Sobrien } else { 38890075Sobrien fprintf(stderr, "fixpath Unknown mode: %s\n", argv[1]); 38990075Sobrien exit(-1); 39090075Sobrien } 39190075Sobrien 39290075Sobrien if (argv[2][0] == '-') { 39390075Sobrien if (strcmp(argv[2], "--detach") == 0) { 39490075Sobrien if (getenv("DEBUG_FIXPATH") != NULL) { 39590075Sobrien fprintf(stderr, "fixpath in detached mode\n"); 39690075Sobrien } 39790075Sobrien processFlags |= DETACHED_PROCESS; 39890075Sobrien processInheritHandles = FALSE; 39990075Sobrien waitForChild = FALSE; 40090075Sobrien } else { 40190075Sobrien fprintf(stderr, "fixpath Unknown argument: %s\n", argv[2]); 40290075Sobrien exit(-1); 40390075Sobrien } 40490075Sobrien i = 3; 40590075Sobrien } else { 40690075Sobrien i = 2; 40790075Sobrien } 40890075Sobrien 40990075Sobrien // handle assignments 41090075Sobrien while (i < argc) { 411132718Skan char const * assignment = strchr(argv[i], '='); 41290075Sobrien if (assignment != NULL && assignment != argv[i]) { 41390075Sobrien size_t var_len = (size_t) (assignment - argv[i] + (ptrdiff_t) 1); 41490075Sobrien char *var = (char *) calloc(var_len, sizeof(char)); 41590075Sobrien char *val = replace_cygdrive(assignment + 1); 41690075Sobrien memmove(var, argv[i], var_len); 41790075Sobrien var[var_len - 1] = '\0'; 41890075Sobrien strupr(var); 41990075Sobrien 42090075Sobrien if (getenv("DEBUG_FIXPATH") != NULL) { 42190075Sobrien fprintf(stderr, "fixpath setting var >%s< to >%s<\n", var, val); 42290075Sobrien } 42390075Sobrien 42490075Sobrien rc = SetEnvironmentVariable(var, val); 42590075Sobrien if (!rc) { 42690075Sobrien // Could not set var for some reason. Try to report why. 42790075Sobrien const int msg_len = 80 + var_len + strlen(val); 42890075Sobrien char * msg = (char *) alloca(msg_len); 42990075Sobrien _snprintf_s(msg, msg_len, _TRUNCATE, "Could not set environment variable [%s=%s]", var, val); 43090075Sobrien report_error(msg); 43190075Sobrien exit(1); 43290075Sobrien } 43390075Sobrien free(var); 43490075Sobrien free(val); 43590075Sobrien } else { 43690075Sobrien // no more assignments; 43790075Sobrien break; 43890075Sobrien } 43990075Sobrien i++; 44090075Sobrien } 44190075Sobrien 44290075Sobrien // remember index of the command 44390075Sobrien cmd = i; 44490075Sobrien 44590075Sobrien // handle command and it's args. 44690075Sobrien while (i < argc) { 44790075Sobrien char const *replaced = replace_cygdrive(argv[i]); 44890075Sobrien if (replaced[0] == '@') { 44990075Sobrien if (waitForChild == FALSE) { 45090075Sobrien fprintf(stderr, "fixpath Cannot use @-files in detached mode: %s\n", replaced); 45190075Sobrien exit(1); 45290075Sobrien } 45390075Sobrien // Found at-file! Fix it! 45490075Sobrien replaced = fix_at_file(replaced); 45590075Sobrien } 45690075Sobrien argv[i] = quote_arg(replaced); 45790075Sobrien i++; 45890075Sobrien } 45990075Sobrien 46090075Sobrien // determine the length of the line 46190075Sobrien line = NULL; 46290075Sobrien // args 46390075Sobrien for (i = cmd; i < argc; i++) { 46490075Sobrien line += (ptrdiff_t) strlen(argv[i]); 46590075Sobrien } 46690075Sobrien // spaces and null 46790075Sobrien line += (ptrdiff_t) (argc - cmd + 1); 46890075Sobrien // allocate 46990075Sobrien line = (char*) calloc(line - (char*) NULL, sizeof(char)); 47090075Sobrien 47190075Sobrien // copy in args. 47290075Sobrien current = line; 47390075Sobrien for (i = cmd; i < argc; i++) { 47490075Sobrien ptrdiff_t len = strlen(argv[i]); 47590075Sobrien if (i != cmd) { 47690075Sobrien *current++ = ' '; 47790075Sobrien } 47890075Sobrien memmove(current, argv[i], len); 47990075Sobrien current += len; 48090075Sobrien } 48190075Sobrien *current = '\0'; 48290075Sobrien 48390075Sobrien if (getenv("DEBUG_FIXPATH") != NULL) { 48490075Sobrien fprintf(stderr, "fixpath converted line >%s<\n", line); 48590075Sobrien } 48690075Sobrien 48790075Sobrien if (cmd == argc) { 48890075Sobrien if (getenv("DEBUG_FIXPATH") != NULL) { 48990075Sobrien fprintf(stderr, "fixpath no command provided!\n"); 49090075Sobrien } 49190075Sobrien exit(0); 49290075Sobrien } 49390075Sobrien 49490075Sobrien ZeroMemory(&si, sizeof(si)); 49590075Sobrien si.cb=sizeof(si); 49690075Sobrien ZeroMemory(&pi, sizeof(pi)); 49790075Sobrien 49890075Sobrien fflush(stderr); 49990075Sobrien fflush(stdout); 50090075Sobrien 50190075Sobrien rc = CreateProcess(NULL, 50290075Sobrien line, 50390075Sobrien 0, 50490075Sobrien 0, 50590075Sobrien processInheritHandles, 50690075Sobrien processFlags, 50790075Sobrien NULL, 50890075Sobrien NULL, 50990075Sobrien &si, 51090075Sobrien &pi); 51190075Sobrien if (!rc) { 51290075Sobrien // Could not start process for some reason. Try to report why: 51390075Sobrien report_error("Could not start process!"); 51490075Sobrien exit(126); 51590075Sobrien } 51690075Sobrien 51790075Sobrien if (waitForChild == TRUE) { 51890075Sobrien WaitForSingleObject(pi.hProcess, INFINITE); 51990075Sobrien GetExitCodeProcess(pi.hProcess, &exitCode); 52090075Sobrien 52190075Sobrien if (getenv("DEBUG_FIXPATH") != NULL) { 52290075Sobrien for (i=0; i<num_files_to_delete; ++i) { 52390075Sobrien fprintf(stderr, "fixpath Not deleting temporary file %s\n", 52490075Sobrien files_to_delete[i]); 52590075Sobrien } 52690075Sobrien } else { 52790075Sobrien for (i=0; i<num_files_to_delete; ++i) { 52890075Sobrien remove(files_to_delete[i]); 52990075Sobrien } 53090075Sobrien } 53190075Sobrien 53290075Sobrien if (exitCode != 0) { 53390075Sobrien if (getenv("DEBUG_FIXPATH") != NULL) { 53490075Sobrien fprintf(stderr, "fixpath exit code %d\n", 53590075Sobrien exitCode); 53690075Sobrien } 53790075Sobrien } 53890075Sobrien } else { 53990075Sobrien if (getenv("DEBUG_FIXPATH") != NULL) { 54090075Sobrien fprintf(stderr, "fixpath Not waiting for child process"); 54190075Sobrien } 54290075Sobrien } 54390075Sobrien 54490075Sobrien exit(exitCode); 54590075Sobrien} 54690075Sobrien