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