1 /* 2 * Copyright (c) 2015, 2018, 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 <stdlib.h> 27 #include <stdio.h> 28 #include <assert.h> 29 #include <sys/stat.h> 30 #include <ctype.h> 31 32 #ifdef DEBUG_ARGFILE 33 #ifndef NO_JNI 34 #define NO_JNI 35 #endif 36 #define JLI_ReportMessage(...) printf(__VA_ARGS__) 37 #define JDK_JAVA_OPTIONS "JDK_JAVA_OPTIONS" 38 int IsWhiteSpaceOption(const char* name) { return 1; } 39 #else 40 #include "java.h" 41 #include "jni.h" 42 #endif 43 44 #include "jli_util.h" 45 #include "emessages.h" 46 47 #define MAX_ARGF_SIZE 0x7fffffffL 48 49 static char* clone_substring(const char *begin, size_t len) { 50 char *rv = (char *) JLI_MemAlloc(len + 1); 51 memcpy(rv, begin, len); 52 rv[len] = '\0'; 53 return rv; 54 } 55 56 enum STATE { 57 FIND_NEXT, 58 IN_COMMENT, 59 IN_QUOTE, 60 IN_ESCAPE, 61 SKIP_LEAD_WS, 62 IN_TOKEN 63 }; 64 65 typedef struct { 66 enum STATE state; 67 const char* cptr; 68 const char* eob; 69 char quote_char; 70 JLI_List parts; 71 } __ctx_args; 72 73 #define NOT_FOUND -1 74 static int firstAppArgIndex = NOT_FOUND; 75 76 static jboolean expectingNoDashArg = JNI_FALSE; 77 // Initialize to 1, as the first argument is the app name and not preprocessed 78 static size_t argsCount = 1; 79 static jboolean stopExpansion = JNI_FALSE; 80 static jboolean relaunch = JNI_FALSE; 81 82 JNIEXPORT void JNICALL 83 JLI_InitArgProcessing(jboolean hasJavaArgs, jboolean disableArgFile) { 84 // No expansion for relaunch 85 if (argsCount != 1) { 86 relaunch = JNI_TRUE; 87 stopExpansion = JNI_TRUE; 88 argsCount = 1; 89 } else { 90 stopExpansion = disableArgFile; 91 } 92 93 expectingNoDashArg = JNI_FALSE; 94 95 // for tools, this value remains 0 all the time. 96 firstAppArgIndex = hasJavaArgs ? 0: NOT_FOUND; 97 } 98 99 JNIEXPORT int JNICALL 100 JLI_GetAppArgIndex() { 101 // Will be 0 for tools 102 return firstAppArgIndex; 103 } 104 105 static void checkArg(const char *arg) { 106 size_t idx = 0; 107 argsCount++; 108 109 // All arguments arrive here must be a launcher argument, 110 // ie. by now, all argfile expansions must have been performed. 111 if (*arg == '-') { 112 expectingNoDashArg = JNI_FALSE; 113 if (IsWhiteSpaceOption(arg)) { 114 // expect an argument 115 expectingNoDashArg = JNI_TRUE; 116 117 if (JLI_StrCmp(arg, "-jar") == 0 || 118 JLI_StrCmp(arg, "--module") == 0 || 119 JLI_StrCmp(arg, "-m") == 0) { 120 // This is tricky, we do expect NoDashArg 121 // But that is considered main class to stop expansion 122 expectingNoDashArg = JNI_FALSE; 123 // We can not just update the idx here because if -jar @file 124 // still need expansion of @file to get the argument for -jar 125 } 126 } else if (JLI_StrCmp(arg, "--disable-@files") == 0) { 127 stopExpansion = JNI_TRUE; 128 } 129 } else { 130 if (!expectingNoDashArg) { 131 // this is main class, argsCount is index to next arg 132 idx = argsCount; 133 } 134 expectingNoDashArg = JNI_FALSE; 135 } 136 // only update on java mode and not yet found main class 137 if (firstAppArgIndex == NOT_FOUND && idx != 0) { 138 firstAppArgIndex = (int) idx; 139 } 140 } 141 142 /* 143 [\n\r] +------------+ +------------+ [\n\r] 144 +---------+ IN_COMMENT +<------+ | IN_ESCAPE +---------+ 145 | +------------+ | +------------+ | 146 | [#] ^ |[#] ^ | | 147 | +----------+ | [\\]| |[^\n\r] | 148 v | | | v | 149 +------------+ [^ \t\n\r\f] +------------+['"]> +------------+ | 150 | FIND_NEXT +-------------->+ IN_TOKEN +-----------+ IN_QUOTE + | 151 +------------+ +------------+ <[quote]+------------+ | 152 | ^ | | ^ ^ | 153 | | [ \t\n\r\f]| [\n\r]| | |[^ \t\n\r\f]v 154 | +--------------------------+-----------------------+ | +--------------+ 155 | ['"] | | SKIP_LEAD_WS | 156 +---------------------------------------------------------+ +--------------+ 157 */ 158 static char* nextToken(__ctx_args *pctx) { 159 const char* nextc = pctx->cptr; 160 const char* const eob = pctx->eob; 161 const char* anchor = nextc; 162 char *token; 163 164 for (; nextc < eob; nextc++) { 165 register char ch = *nextc; 166 167 // Skip white space characters 168 if (pctx->state == FIND_NEXT || pctx->state == SKIP_LEAD_WS) { 169 while (ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t' || ch == '\f') { 170 nextc++; 171 if (nextc >= eob) { 172 return NULL; 173 } 174 ch = *nextc; 175 } 176 pctx->state = (pctx->state == FIND_NEXT) ? IN_TOKEN : IN_QUOTE; 177 anchor = nextc; 178 // Deal with escape sequences 179 } else if (pctx->state == IN_ESCAPE) { 180 // concatenation directive 181 if (ch == '\n' || ch == '\r') { 182 pctx->state = SKIP_LEAD_WS; 183 } else { 184 // escaped character 185 char* escaped = (char*) JLI_MemAlloc(2 * sizeof(char)); 186 escaped[1] = '\0'; 187 switch (ch) { 188 case 'n': 189 escaped[0] = '\n'; 190 break; 191 case 'r': 192 escaped[0] = '\r'; 193 break; 194 case 't': 195 escaped[0] = '\t'; 196 break; 197 case 'f': 198 escaped[0] = '\f'; 199 break; 200 default: 201 escaped[0] = ch; 202 break; 203 } 204 JLI_List_add(pctx->parts, escaped); 205 pctx->state = IN_QUOTE; 206 } 207 // anchor to next character 208 anchor = nextc + 1; 209 continue; 210 // ignore comment to EOL 211 } else if (pctx->state == IN_COMMENT) { 212 while (ch != '\n' && ch != '\r') { 213 nextc++; 214 if (nextc > eob) { 215 return NULL; 216 } 217 ch = *nextc; 218 } 219 pctx->state = FIND_NEXT; 220 continue; 221 } 222 223 assert(pctx->state != IN_ESCAPE); 224 assert(pctx->state != FIND_NEXT); 225 assert(pctx->state != SKIP_LEAD_WS); 226 assert(pctx->state != IN_COMMENT); 227 228 switch(ch) { 229 case ' ': 230 case '\t': 231 case '\f': 232 if (pctx->state == IN_QUOTE) { 233 continue; 234 } 235 // fall through 236 case '\n': 237 case '\r': 238 if (pctx->parts->size == 0) { 239 token = clone_substring(anchor, nextc - anchor); 240 } else { 241 JLI_List_addSubstring(pctx->parts, anchor, nextc - anchor); 242 token = JLI_List_combine(pctx->parts); 243 JLI_List_free(pctx->parts); 244 pctx->parts = JLI_List_new(4); 245 } 246 pctx->cptr = nextc + 1; 247 pctx->state = FIND_NEXT; 248 return token; 249 case '#': 250 if (pctx->state == IN_QUOTE) { 251 continue; 252 } 253 pctx->state = IN_COMMENT; 254 break; 255 case '\\': 256 if (pctx->state != IN_QUOTE) { 257 continue; 258 } 259 JLI_List_addSubstring(pctx->parts, anchor, nextc - anchor); 260 pctx->state = IN_ESCAPE; 261 break; 262 case '\'': 263 case '"': 264 if (pctx->state == IN_QUOTE && pctx->quote_char != ch) { 265 // not matching quote 266 continue; 267 } 268 // partial before quote 269 if (anchor != nextc) { 270 JLI_List_addSubstring(pctx->parts, anchor, nextc - anchor); 271 } 272 // anchor after quote character 273 anchor = nextc + 1; 274 if (pctx->state == IN_TOKEN) { 275 pctx->quote_char = ch; 276 pctx->state = IN_QUOTE; 277 } else { 278 pctx->state = IN_TOKEN; 279 } 280 break; 281 default: 282 break; 283 } 284 } 285 286 assert(nextc == eob); 287 if (anchor != nextc) { 288 // not yet return until end of stream, we have part of a token. 289 JLI_List_addSubstring(pctx->parts, anchor, nextc - anchor); 290 } 291 return NULL; 292 } 293 294 static JLI_List readArgFile(FILE *file) { 295 char buf[4096]; 296 JLI_List rv; 297 __ctx_args ctx; 298 size_t size; 299 char *token; 300 301 ctx.state = FIND_NEXT; 302 ctx.parts = JLI_List_new(4); 303 304 /* arbitrarily pick 8, seems to be a reasonable number of arguments */ 305 rv = JLI_List_new(8); 306 307 while (!feof(file)) { 308 size = fread(buf, sizeof(char), sizeof(buf), file); 309 if (ferror(file)) { 310 JLI_List_free(rv); 311 return NULL; 312 } 313 314 /* nextc is next character to read from the buffer 315 * eob is the end of input 316 * token is the copied token value, NULL if no a complete token 317 */ 318 ctx.cptr = buf; 319 ctx.eob = buf + size; 320 token = nextToken(&ctx); 321 while (token != NULL) { 322 checkArg(token); 323 JLI_List_add(rv, token); 324 token = nextToken(&ctx); 325 } 326 } 327 328 // remaining partial token 329 if (ctx.state == IN_TOKEN || ctx.state == IN_QUOTE) { 330 if (ctx.parts->size != 0) { 331 JLI_List_add(rv, JLI_List_combine(ctx.parts)); 332 } 333 } 334 JLI_List_free(ctx.parts); 335 336 return rv; 337 } 338 339 /* 340 * if the arg represent a file, that is, prefix with a single '@', 341 * return a list of arguments from the file. 342 * otherwise, return NULL. 343 */ 344 static JLI_List expandArgFile(const char *arg) { 345 FILE *fptr; 346 struct stat st; 347 JLI_List rv; 348 349 /* failed to access the file */ 350 if (stat(arg, &st) != 0) { 351 JLI_ReportMessage(CFG_ERROR6, arg); 352 exit(1); 353 } 354 355 if (st.st_size > MAX_ARGF_SIZE) { 356 JLI_ReportMessage(CFG_ERROR10, MAX_ARGF_SIZE); 357 exit(1); 358 } 359 360 fptr = fopen(arg, "r"); 361 /* arg file cannot be openned */ 362 if (fptr == NULL) { 363 JLI_ReportMessage(CFG_ERROR6, arg); 364 exit(1); 365 } 366 367 rv = readArgFile(fptr); 368 fclose(fptr); 369 370 /* error occurred reading the file */ 371 if (rv == NULL) { 372 JLI_ReportMessage(DLL_ERROR4, arg); 373 exit(1); 374 } 375 376 return rv; 377 } 378 379 JNIEXPORT JLI_List JNICALL 380 JLI_PreprocessArg(const char *arg) 381 { 382 JLI_List rv; 383 384 if (firstAppArgIndex > 0) { 385 // In user application arg, no more work. 386 return NULL; 387 } 388 389 if (stopExpansion) { 390 // still looking for user application arg 391 checkArg(arg); 392 return NULL; 393 } 394 395 if (arg[0] != '@') { 396 checkArg(arg); 397 return NULL; 398 } 399 400 if (arg[1] == '\0') { 401 // @ by itself is an argument 402 checkArg(arg); 403 return NULL; 404 } 405 406 arg++; 407 if (arg[0] == '@') { 408 // escaped @argument 409 rv = JLI_List_new(1); 410 checkArg(arg); 411 JLI_List_add(rv, JLI_StringDup(arg)); 412 } else { 413 rv = expandArgFile(arg); 414 } 415 return rv; 416 } 417 418 int isTerminalOpt(char *arg) { 419 return JLI_StrCmp(arg, "-jar") == 0 || 420 JLI_StrCmp(arg, "-m") == 0 || 421 JLI_StrCmp(arg, "--module") == 0 || 422 JLI_StrCmp(arg, "--dry-run") == 0 || 423 JLI_StrCmp(arg, "-h") == 0 || 424 JLI_StrCmp(arg, "-?") == 0 || 425 JLI_StrCmp(arg, "-help") == 0 || 426 JLI_StrCmp(arg, "--help") == 0 || 427 JLI_StrCmp(arg, "-X") == 0 || 428 JLI_StrCmp(arg, "--help-extra") == 0 || 429 JLI_StrCmp(arg, "-version") == 0 || 430 JLI_StrCmp(arg, "--version") == 0 || 431 JLI_StrCmp(arg, "-fullversion") == 0 || 432 JLI_StrCmp(arg, "--full-version") == 0; 433 } 434 435 JNIEXPORT jboolean JNICALL 436 JLI_AddArgsFromEnvVar(JLI_List args, const char *var_name) { 437 char *env = getenv(var_name); 438 char *p, *arg; 439 char quote; 440 JLI_List argsInFile; 441 442 if (firstAppArgIndex == 0) { 443 // Not 'java', return 444 return JNI_FALSE; 445 } 446 447 if (relaunch) { 448 return JNI_FALSE; 449 } 450 451 if (NULL == env) { 452 return JNI_FALSE; 453 } 454 455 JLI_ReportMessage(ARG_INFO_ENVVAR, var_name, env); 456 457 // This is retained until the process terminates as it is saved as the args 458 p = JLI_MemAlloc(JLI_StrLen(env) + 1); 459 while (*env != '\0') { 460 while (*env != '\0' && isspace(*env)) { 461 env++; 462 } 463 464 // Trailing space 465 if (*env == '\0') { 466 break; 467 } 468 469 arg = p; 470 while (*env != '\0' && !isspace(*env)) { 471 if (*env == '"' || *env == '\'') { 472 quote = *env++; 473 while (*env != quote && *env != '\0') { 474 *p++ = *env++; 475 } 476 477 if (*env == '\0') { 478 JLI_ReportMessage(ARG_ERROR8, var_name); 479 exit(1); 480 } 481 env++; 482 } else { 483 *p++ = *env++; 484 } 485 } 486 487 *p++ = '\0'; 488 489 argsInFile = JLI_PreprocessArg(arg); 490 491 if (NULL == argsInFile) { 492 if (isTerminalOpt(arg)) { 493 JLI_ReportMessage(ARG_ERROR9, arg, var_name); 494 exit(1); 495 } 496 JLI_List_add(args, arg); 497 } else { 498 size_t cnt, idx; 499 char *argFile = arg; 500 cnt = argsInFile->size; 501 for (idx = 0; idx < cnt; idx++) { 502 arg = argsInFile->elements[idx]; 503 if (isTerminalOpt(arg)) { 504 JLI_ReportMessage(ARG_ERROR10, arg, argFile, var_name); 505 exit(1); 506 } 507 JLI_List_add(args, arg); 508 } 509 // Shallow free, we reuse the string to avoid copy 510 JLI_MemFree(argsInFile->elements); 511 JLI_MemFree(argsInFile); 512 } 513 /* 514 * Check if main-class is specified after argument being checked. It 515 * must always appear after expansion, as a main-class could be specified 516 * indirectly into environment variable via an @argfile, and it must be 517 * caught now. 518 */ 519 if (firstAppArgIndex != NOT_FOUND) { 520 JLI_ReportMessage(ARG_ERROR11, var_name); 521 exit(1); 522 } 523 524 assert (*env == '\0' || isspace(*env)); 525 } 526 527 return JNI_TRUE; 528 } 529 530 #ifdef DEBUG_ARGFILE 531 /* 532 * Stand-alone sanity test, build with following command line 533 * $ CC -DDEBUG_ARGFILE -DNO_JNI -g args.c jli_util.c 534 */ 535 536 void fail(char *expected, char *actual, size_t idx) { 537 printf("FAILED: Token[%lu] expected to be <%s>, got <%s>\n", idx, expected, actual); 538 exit(1); 539 } 540 541 void test_case(char *case_data, char **tokens, size_t cnt_tokens) { 542 size_t actual_cnt; 543 char *token; 544 __ctx_args ctx; 545 546 actual_cnt = 0; 547 548 ctx.state = FIND_NEXT; 549 ctx.parts = JLI_List_new(4); 550 ctx.cptr = case_data; 551 ctx.eob = case_data + strlen(case_data); 552 553 printf("Test case: <%s>, expected %lu tokens.\n", case_data, cnt_tokens); 554 555 for (token = nextToken(&ctx); token != NULL; token = nextToken(&ctx)) { 556 // should not have more tokens than expected 557 if (actual_cnt >= cnt_tokens) { 558 printf("FAILED: Extra token detected: <%s>\n", token); 559 exit(2); 560 } 561 if (JLI_StrCmp(token, tokens[actual_cnt]) != 0) { 562 fail(tokens[actual_cnt], token, actual_cnt); 563 } 564 actual_cnt++; 565 } 566 567 char* last = NULL; 568 if (ctx.parts->size != 0) { 569 last = JLI_List_combine(ctx.parts); 570 } 571 JLI_List_free(ctx.parts); 572 573 if (actual_cnt >= cnt_tokens) { 574 // same number of tokens, should have nothing left to parse 575 if (last != NULL) { 576 if (*last != '#') { 577 printf("Leftover detected: %s", last); 578 exit(2); 579 } 580 } 581 } else { 582 if (JLI_StrCmp(last, tokens[actual_cnt]) != 0) { 583 fail(tokens[actual_cnt], last, actual_cnt); 584 } 585 actual_cnt++; 586 } 587 if (actual_cnt != cnt_tokens) { 588 printf("FAILED: Number of tokens not match, expected %lu, got %lu\n", 589 cnt_tokens, actual_cnt); 590 exit(3); 591 } 592 593 printf("PASS\n"); 594 } 595 596 #define DO_CASE(name) \ 597 test_case(name[0], name + 1, sizeof(name)/sizeof(char*) - 1) 598 599 int main(int argc, char** argv) { 600 size_t i, j; 601 602 char* case1[] = { "-version -cp \"c:\\\\java libs\\\\one.jar\" \n", 603 "-version", "-cp", "c:\\java libs\\one.jar" }; 604 DO_CASE(case1); 605 606 // note the open quote at the end 607 char* case2[] = { "com.foo.Panda \"Furious 5\"\fand\t'Shi Fu' \"escape\tprison", 608 "com.foo.Panda", "Furious 5", "and", "Shi Fu", "escape\tprison"}; 609 DO_CASE(case2); 610 611 char* escaped_chars[] = { "escaped chars testing \"\\a\\b\\c\\f\\n\\r\\t\\v\\9\\6\\23\\82\\28\\377\\477\\278\\287\"", 612 "escaped", "chars", "testing", "abc\f\n\r\tv96238228377477278287"}; 613 DO_CASE(escaped_chars); 614 615 char* mixed_quote[] = { "\"mix 'single quote' in double\" 'mix \"double quote\" in single' partial\"quote me\"this", 616 "mix 'single quote' in double", "mix \"double quote\" in single", "partialquote methis"}; 617 DO_CASE(mixed_quote); 618 619 char* comments[] = { "line one #comment\n'line #2' #rest are comment\r\n#comment on line 3\nline 4 #comment to eof", 620 "line", "one", "line #2", "line", "4"}; 621 DO_CASE(comments); 622 623 char* open_quote[] = { "This is an \"open quote \n across line\n\t, note for WS.", 624 "This", "is", "an", "open quote ", "across", "line", ",", "note", "for", "WS." }; 625 DO_CASE(open_quote); 626 627 char* escape_in_open_quote[] = { "Try \"this \\\\\\\\ escape\\n double quote \\\" in open quote", 628 "Try", "this \\\\ escape\n double quote \" in open quote" }; 629 DO_CASE(escape_in_open_quote); 630 631 char* quote[] = { "'-Dmy.quote.single'='Property in single quote. Here a double quote\" Add some slashes \\\\/'", 632 "-Dmy.quote.single=Property in single quote. Here a double quote\" Add some slashes \\/" }; 633 DO_CASE(quote); 634 635 char* multi[] = { "\"Open quote to \n new \"line \\\n\r third\\\n\r\\\tand\ffourth\"", 636 "Open quote to ", "new", "line third\tand\ffourth" }; 637 DO_CASE(multi); 638 639 char* escape_quote[] = { "c:\\\"partial quote\"\\lib", 640 "c:\\partial quote\\lib" }; 641 DO_CASE(escape_quote); 642 643 if (argc > 1) { 644 for (i = 0; i < argc; i++) { 645 JLI_List tokens = JLI_PreprocessArg(argv[i]); 646 if (NULL != tokens) { 647 for (j = 0; j < tokens->size; j++) { 648 printf("Token[%lu]: <%s>\n", (unsigned long) j, tokens->elements[j]); 649 } 650 } 651 } 652 } 653 } 654 655 #endif // DEBUG_ARGFILE