Path: blob/master/src/java.base/share/native/libjli/args.c
67707 views
/*1* Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425#include <stdlib.h>26#include <stdio.h>27#include <assert.h>28#include <sys/stat.h>29#include <ctype.h>3031#ifdef DEBUG_ARGFILE32#ifndef NO_JNI33#define NO_JNI34#endif35#define JLI_ReportMessage(...) printf(__VA_ARGS__)36#define JDK_JAVA_OPTIONS "JDK_JAVA_OPTIONS"37int IsWhiteSpaceOption(const char* name) { return 1; }38#else39#include "java.h"40#include "jni.h"41#endif4243#include "jli_util.h"44#include "emessages.h"4546#define MAX_ARGF_SIZE 0x7fffffffL4748static char* clone_substring(const char *begin, size_t len) {49char *rv = (char *) JLI_MemAlloc(len + 1);50memcpy(rv, begin, len);51rv[len] = '\0';52return rv;53}5455enum STATE {56FIND_NEXT,57IN_COMMENT,58IN_QUOTE,59IN_ESCAPE,60SKIP_LEAD_WS,61IN_TOKEN62};6364typedef struct {65enum STATE state;66const char* cptr;67const char* eob;68char quote_char;69JLI_List parts;70} __ctx_args;7172#define NOT_FOUND -173static int firstAppArgIndex = NOT_FOUND;7475static jboolean expectingNoDashArg = JNI_FALSE;76// Initialize to 1, as the first argument is the app name and not preprocessed77static size_t argsCount = 1;78static jboolean stopExpansion = JNI_FALSE;79static jboolean relaunch = JNI_FALSE;8081/*82* Prototypes for internal functions.83*/84static jboolean expand(JLI_List args, const char *str, const char *var_name);8586JNIEXPORT void JNICALL87JLI_InitArgProcessing(jboolean hasJavaArgs, jboolean disableArgFile) {88// No expansion for relaunch89if (argsCount != 1) {90relaunch = JNI_TRUE;91stopExpansion = JNI_TRUE;92argsCount = 1;93} else {94stopExpansion = disableArgFile;95}9697expectingNoDashArg = JNI_FALSE;9899// for tools, this value remains 0 all the time.100firstAppArgIndex = hasJavaArgs ? 0: NOT_FOUND;101}102103JNIEXPORT int JNICALL104JLI_GetAppArgIndex() {105// Will be 0 for tools106return firstAppArgIndex;107}108109static void checkArg(const char *arg) {110size_t idx = 0;111argsCount++;112113// All arguments arrive here must be a launcher argument,114// ie. by now, all argfile expansions must have been performed.115if (*arg == '-') {116expectingNoDashArg = JNI_FALSE;117if (IsWhiteSpaceOption(arg)) {118// expect an argument119expectingNoDashArg = JNI_TRUE;120121if (JLI_StrCmp(arg, "-jar") == 0 ||122JLI_StrCmp(arg, "--module") == 0 ||123JLI_StrCmp(arg, "-m") == 0) {124// This is tricky, we do expect NoDashArg125// But that is considered main class to stop expansion126expectingNoDashArg = JNI_FALSE;127// We can not just update the idx here because if -jar @file128// still need expansion of @file to get the argument for -jar129}130} else if (JLI_StrCmp(arg, "--disable-@files") == 0) {131stopExpansion = JNI_TRUE;132} else if (JLI_StrCCmp(arg, "--module=") == 0) {133idx = argsCount;134}135} else {136if (!expectingNoDashArg) {137// this is main class, argsCount is index to next arg138idx = argsCount;139}140expectingNoDashArg = JNI_FALSE;141}142// only update on java mode and not yet found main class143if (firstAppArgIndex == NOT_FOUND && idx != 0) {144firstAppArgIndex = (int) idx;145}146}147148/*149[\n\r] +------------+ +------------+ [\n\r]150+---------+ IN_COMMENT +<------+ | IN_ESCAPE +---------+151| +------------+ | +------------+ |152| [#] ^ |[#] ^ | |153| +----------+ | [\\]| |[^\n\r] |154v | | | v |155+------------+ [^ \t\n\r\f] +------------+['"]> +------------+ |156| FIND_NEXT +-------------->+ IN_TOKEN +-----------+ IN_QUOTE + |157+------------+ +------------+ <[quote]+------------+ |158| ^ | | ^ ^ |159| | [ \t\n\r\f]| [\n\r]| | |[^ \t\n\r\f]v160| +--------------------------+-----------------------+ | +--------------+161| ['"] | | SKIP_LEAD_WS |162+---------------------------------------------------------+ +--------------+163*/164static char* nextToken(__ctx_args *pctx) {165const char* nextc = pctx->cptr;166const char* const eob = pctx->eob;167const char* anchor = nextc;168char *token;169170for (; nextc < eob; nextc++) {171register char ch = *nextc;172173// Skip white space characters174if (pctx->state == FIND_NEXT || pctx->state == SKIP_LEAD_WS) {175while (ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t' || ch == '\f') {176nextc++;177if (nextc >= eob) {178return NULL;179}180ch = *nextc;181}182pctx->state = (pctx->state == FIND_NEXT) ? IN_TOKEN : IN_QUOTE;183anchor = nextc;184// Deal with escape sequences185} else if (pctx->state == IN_ESCAPE) {186// concatenation directive187if (ch == '\n' || ch == '\r') {188pctx->state = SKIP_LEAD_WS;189} else {190// escaped character191char* escaped = (char*) JLI_MemAlloc(2 * sizeof(char));192escaped[1] = '\0';193switch (ch) {194case 'n':195escaped[0] = '\n';196break;197case 'r':198escaped[0] = '\r';199break;200case 't':201escaped[0] = '\t';202break;203case 'f':204escaped[0] = '\f';205break;206default:207escaped[0] = ch;208break;209}210JLI_List_add(pctx->parts, escaped);211pctx->state = IN_QUOTE;212}213// anchor to next character214anchor = nextc + 1;215continue;216// ignore comment to EOL217} else if (pctx->state == IN_COMMENT) {218while (ch != '\n' && ch != '\r') {219nextc++;220if (nextc >= eob) {221return NULL;222}223ch = *nextc;224}225anchor = nextc + 1;226pctx->state = FIND_NEXT;227continue;228}229230assert(pctx->state != IN_ESCAPE);231assert(pctx->state != FIND_NEXT);232assert(pctx->state != SKIP_LEAD_WS);233assert(pctx->state != IN_COMMENT);234235switch(ch) {236case ' ':237case '\t':238case '\f':239if (pctx->state == IN_QUOTE) {240continue;241}242// fall through243case '\n':244case '\r':245if (pctx->parts->size == 0) {246token = clone_substring(anchor, nextc - anchor);247} else {248JLI_List_addSubstring(pctx->parts, anchor, nextc - anchor);249token = JLI_List_combine(pctx->parts);250JLI_List_free(pctx->parts);251pctx->parts = JLI_List_new(4);252}253pctx->cptr = nextc + 1;254pctx->state = FIND_NEXT;255return token;256case '#':257if (pctx->state == IN_QUOTE) {258continue;259}260pctx->state = IN_COMMENT;261anchor = nextc + 1;262break;263case '\\':264if (pctx->state != IN_QUOTE) {265continue;266}267JLI_List_addSubstring(pctx->parts, anchor, nextc - anchor);268pctx->state = IN_ESCAPE;269// anchor after backslash character270anchor = nextc + 1;271break;272case '\'':273case '"':274if (pctx->state == IN_QUOTE && pctx->quote_char != ch) {275// not matching quote276continue;277}278// partial before quote279if (anchor != nextc) {280JLI_List_addSubstring(pctx->parts, anchor, nextc - anchor);281}282// anchor after quote character283anchor = nextc + 1;284if (pctx->state == IN_TOKEN) {285pctx->quote_char = ch;286pctx->state = IN_QUOTE;287} else {288pctx->state = IN_TOKEN;289}290break;291default:292break;293}294}295296assert(nextc == eob);297// Only need partial token, not comment or whitespaces298if (pctx->state == IN_TOKEN || pctx->state == IN_QUOTE) {299if (anchor < nextc) {300// not yet return until end of stream, we have part of a token.301JLI_List_addSubstring(pctx->parts, anchor, nextc - anchor);302}303}304return NULL;305}306307static JLI_List readArgFile(FILE *file) {308char buf[4096];309JLI_List rv;310__ctx_args ctx;311size_t size;312char *token;313314ctx.state = FIND_NEXT;315ctx.parts = JLI_List_new(4);316// initialize to avoid -Werror=maybe-uninitialized issues from gcc 7.3 onwards.317ctx.quote_char = '"';318319/* arbitrarily pick 8, seems to be a reasonable number of arguments */320rv = JLI_List_new(8);321322while (!feof(file)) {323size = fread(buf, sizeof(char), sizeof(buf), file);324if (ferror(file)) {325JLI_List_free(rv);326return NULL;327}328329/* nextc is next character to read from the buffer330* eob is the end of input331* token is the copied token value, NULL if no a complete token332*/333ctx.cptr = buf;334ctx.eob = buf + size;335token = nextToken(&ctx);336while (token != NULL) {337checkArg(token);338JLI_List_add(rv, token);339token = nextToken(&ctx);340}341}342343// remaining partial token344if (ctx.state == IN_TOKEN || ctx.state == IN_QUOTE) {345if (ctx.parts->size != 0) {346token = JLI_List_combine(ctx.parts);347checkArg(token);348JLI_List_add(rv, token);349}350}351JLI_List_free(ctx.parts);352353return rv;354}355356/*357* if the arg represent a file, that is, prefix with a single '@',358* return a list of arguments from the file.359* otherwise, return NULL.360*/361static JLI_List expandArgFile(const char *arg) {362JLI_List rv;363struct stat st;364FILE *fptr = fopen(arg, "r");365366/* arg file cannot be openned */367if (fptr == NULL || fstat(fileno(fptr), &st) != 0) {368JLI_ReportMessage(CFG_ERROR6, arg);369exit(1);370} else {371if (st.st_size > MAX_ARGF_SIZE) {372JLI_ReportMessage(CFG_ERROR10, MAX_ARGF_SIZE);373exit(1);374}375}376377rv = readArgFile(fptr);378379/* error occurred reading the file */380if (rv == NULL) {381JLI_ReportMessage(DLL_ERROR4, arg);382exit(1);383}384fclose(fptr);385386return rv;387}388389/*390* expand a string into a list of words separated by whitespace.391*/392static JLI_List expandArg(const char *arg) {393JLI_List rv;394395/* arbitrarily pick 8, seems to be a reasonable number of arguments */396rv = JLI_List_new(8);397398expand(rv, arg, NULL);399400return rv;401}402403JNIEXPORT JLI_List JNICALL404JLI_PreprocessArg(const char *arg, jboolean expandSourceOpt) {405JLI_List rv;406407if (firstAppArgIndex > 0) {408// In user application arg, no more work.409return NULL;410}411412if (stopExpansion) {413// still looking for user application arg414checkArg(arg);415return NULL;416}417418if (expandSourceOpt419&& JLI_StrCCmp(arg, "--source") == 0420&& JLI_StrChr(arg, ' ') != NULL) {421return expandArg(arg);422}423424if (arg[0] != '@') {425checkArg(arg);426return NULL;427}428429if (arg[1] == '\0') {430// @ by itself is an argument431checkArg(arg);432return NULL;433}434435arg++;436if (arg[0] == '@') {437// escaped @argument438rv = JLI_List_new(1);439checkArg(arg);440JLI_List_add(rv, JLI_StringDup(arg));441} else {442rv = expandArgFile(arg);443}444return rv;445}446447int isTerminalOpt(char *arg) {448return JLI_StrCmp(arg, "-jar") == 0 ||449JLI_StrCmp(arg, "-m") == 0 ||450JLI_StrCmp(arg, "--module") == 0 ||451JLI_StrCCmp(arg, "--module=") == 0 ||452JLI_StrCmp(arg, "--dry-run") == 0 ||453JLI_StrCmp(arg, "-h") == 0 ||454JLI_StrCmp(arg, "-?") == 0 ||455JLI_StrCmp(arg, "-help") == 0 ||456JLI_StrCmp(arg, "--help") == 0 ||457JLI_StrCmp(arg, "-X") == 0 ||458JLI_StrCmp(arg, "--help-extra") == 0 ||459JLI_StrCmp(arg, "-version") == 0 ||460JLI_StrCmp(arg, "--version") == 0 ||461JLI_StrCmp(arg, "-fullversion") == 0 ||462JLI_StrCmp(arg, "--full-version") == 0;463}464465JNIEXPORT jboolean JNICALL466JLI_AddArgsFromEnvVar(JLI_List args, const char *var_name) {467char *env = getenv(var_name);468469if (firstAppArgIndex == 0) {470// Not 'java', return471return JNI_FALSE;472}473474if (relaunch) {475return JNI_FALSE;476}477478if (NULL == env) {479return JNI_FALSE;480}481482JLI_ReportMessage(ARG_INFO_ENVVAR, var_name, env);483return expand(args, env, var_name);484}485486/*487* Expand a string into a list of args.488* If the string is the result of looking up an environment variable,489* var_name should be set to the name of that environment variable,490* for use if needed in error messages.491*/492493static jboolean expand(JLI_List args, const char *str, const char *var_name) {494jboolean inEnvVar = (var_name != NULL);495496char *p, *arg;497char quote;498JLI_List argsInFile;499500// This is retained until the process terminates as it is saved as the args501p = JLI_MemAlloc(JLI_StrLen(str) + 1);502while (*str != '\0') {503while (*str != '\0' && isspace(*str)) {504str++;505}506507// Trailing space508if (*str == '\0') {509break;510}511512arg = p;513while (*str != '\0' && !isspace(*str)) {514if (inEnvVar && (*str == '"' || *str == '\'')) {515quote = *str++;516while (*str != quote && *str != '\0') {517*p++ = *str++;518}519520if (*str == '\0') {521JLI_ReportMessage(ARG_ERROR8, var_name);522exit(1);523}524str++;525} else {526*p++ = *str++;527}528}529530*p++ = '\0';531532argsInFile = JLI_PreprocessArg(arg, JNI_FALSE);533534if (NULL == argsInFile) {535if (isTerminalOpt(arg)) {536if (inEnvVar) {537JLI_ReportMessage(ARG_ERROR9, arg, var_name);538} else {539JLI_ReportMessage(ARG_ERROR15, arg);540}541exit(1);542}543JLI_List_add(args, arg);544} else {545size_t cnt, idx;546char *argFile = arg;547cnt = argsInFile->size;548for (idx = 0; idx < cnt; idx++) {549arg = argsInFile->elements[idx];550if (isTerminalOpt(arg)) {551if (inEnvVar) {552JLI_ReportMessage(ARG_ERROR10, arg, argFile, var_name);553} else {554JLI_ReportMessage(ARG_ERROR16, arg, argFile);555}556exit(1);557}558JLI_List_add(args, arg);559}560// Shallow free, we reuse the string to avoid copy561JLI_MemFree(argsInFile->elements);562JLI_MemFree(argsInFile);563}564/*565* Check if main-class is specified after argument being checked. It566* must always appear after expansion, as a main-class could be specified567* indirectly into environment variable via an @argfile, and it must be568* caught now.569*/570if (firstAppArgIndex != NOT_FOUND) {571if (inEnvVar) {572JLI_ReportMessage(ARG_ERROR11, var_name);573} else {574JLI_ReportMessage(ARG_ERROR17);575}576exit(1);577}578579assert (*str == '\0' || isspace(*str));580}581582return JNI_TRUE;583}584585#ifdef DEBUG_ARGFILE586/*587* Stand-alone sanity test, build with following command line588* $ CC -DDEBUG_ARGFILE -DNO_JNI -g args.c jli_util.c589*/590591void fail(char *expected, char *actual, size_t idx) {592printf("FAILED: Token[%lu] expected to be <%s>, got <%s>\n", idx, expected, actual);593exit(1);594}595596void test_case(char *case_data, char **tokens, size_t cnt_tokens) {597size_t actual_cnt;598char *token;599__ctx_args ctx;600601actual_cnt = 0;602603ctx.state = FIND_NEXT;604ctx.parts = JLI_List_new(4);605ctx.cptr = case_data;606ctx.eob = case_data + strlen(case_data);607608printf("Test case: <%s>, expected %lu tokens.\n", case_data, cnt_tokens);609610for (token = nextToken(&ctx); token != NULL; token = nextToken(&ctx)) {611// should not have more tokens than expected612if (actual_cnt >= cnt_tokens) {613printf("FAILED: Extra token detected: <%s>\n", token);614exit(2);615}616if (JLI_StrCmp(token, tokens[actual_cnt]) != 0) {617fail(tokens[actual_cnt], token, actual_cnt);618}619actual_cnt++;620}621622char* last = NULL;623if (ctx.parts->size != 0) {624last = JLI_List_combine(ctx.parts);625}626JLI_List_free(ctx.parts);627628if (actual_cnt >= cnt_tokens) {629// same number of tokens, should have nothing left to parse630if (last != NULL) {631if (*last != '#') {632printf("Leftover detected: %s", last);633exit(2);634}635}636} else {637if (JLI_StrCmp(last, tokens[actual_cnt]) != 0) {638fail(tokens[actual_cnt], last, actual_cnt);639}640actual_cnt++;641}642if (actual_cnt != cnt_tokens) {643printf("FAILED: Number of tokens not match, expected %lu, got %lu\n",644cnt_tokens, actual_cnt);645exit(3);646}647648printf("PASS\n");649}650651#define DO_CASE(name) \652test_case(name[0], name + 1, sizeof(name)/sizeof(char*) - 1)653654int main(int argc, char** argv) {655size_t i, j;656657char* case1[] = { "-version -cp \"c:\\\\java libs\\\\one.jar\" \n",658"-version", "-cp", "c:\\java libs\\one.jar" };659DO_CASE(case1);660661// note the open quote at the end662char* case2[] = { "com.foo.Panda \"Furious 5\"\fand\t'Shi Fu' \"escape\tprison",663"com.foo.Panda", "Furious 5", "and", "Shi Fu", "escape\tprison"};664DO_CASE(case2);665666char* escaped_chars[] = { "escaped chars testing \"\\a\\b\\c\\f\\n\\r\\t\\v\\9\\6\\23\\82\\28\\377\\477\\278\\287\"",667"escaped", "chars", "testing", "abc\f\n\r\tv96238228377477278287"};668DO_CASE(escaped_chars);669670char* mixed_quote[] = { "\"mix 'single quote' in double\" 'mix \"double quote\" in single' partial\"quote me\"this",671"mix 'single quote' in double", "mix \"double quote\" in single", "partialquote methis"};672DO_CASE(mixed_quote);673674char* comments[] = { "line one #comment\n'line #2' #rest are comment\r\n#comment on line 3\nline 4 #comment to eof",675"line", "one", "line #2", "line", "4"};676DO_CASE(comments);677678char* open_quote[] = { "This is an \"open quote \n across line\n\t, note for WS.",679"This", "is", "an", "open quote ", "across", "line", ",", "note", "for", "WS." };680DO_CASE(open_quote);681682char* escape_in_open_quote[] = { "Try \"this \\\\\\\\ escape\\n double quote \\\" in open quote",683"Try", "this \\\\ escape\n double quote \" in open quote" };684DO_CASE(escape_in_open_quote);685686char* quote[] = { "'-Dmy.quote.single'='Property in single quote. Here a double quote\" Add some slashes \\\\/'",687"-Dmy.quote.single=Property in single quote. Here a double quote\" Add some slashes \\/" };688DO_CASE(quote);689690char* multi[] = { "\"Open quote to \n new \"line \\\n\r third\\\n\r\\\tand\ffourth\"",691"Open quote to ", "new", "line third\tand\ffourth" };692DO_CASE(multi);693694char* escape_quote[] = { "c:\\\"partial quote\"\\lib",695"c:\\partial quote\\lib" };696DO_CASE(escape_quote);697698if (argc > 1) {699for (i = 0; i < argc; i++) {700JLI_List tokens = JLI_PreprocessArg(argv[i], JNI_FALSE);701if (NULL != tokens) {702for (j = 0; j < tokens->size; j++) {703printf("Token[%lu]: <%s>\n", (unsigned long) j, tokens->elements[j]);704}705}706}707}708}709710#endif // DEBUG_ARGFILE711712713