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