1 /* -*- Mode: C; indent-tabs-mode: t; tab-width: 4 -*-
2 // ---------------------------------------------------------------------------
4 // Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
5 // ---------------------------------------------------------------------------
6 // SquirrelJME is under the Mozilla Public License Version 2.0.
7 // See license.mkd for licensing and copyright information.
8 // -------------------------------------------------------------------------*/
13 #include "sjme/nvm/allocSizeOf.h"
14 #include "sjme/nvm/boot.h"
15 #include "sjme/debug.h"
16 #include "sjme/nvm/nvm.h"
17 #include "sjme/nvm/task.h"
18 #include "sjme/charSeq.h"
19 #include "sjme/native.h"
20 #include "sjme/cleanup.h"
21 #include "sjme/path.h"
23 #if defined(SJME_PATH_SHORT)
24 /** The name of the SquirrelJME Jar. */
25 #define SJME_JAR_NAME "sjme.jar"
27 /** The name of the SquirrelJME directory. */
28 #define SJME_DIRECTORY_NAME "sjme"
30 /** The name of the SquirrelJME Jar. */
31 #define SJME_JAR_NAME "squirreljme.jar"
33 /** The name of the SquirrelJME directory. */
34 #define SJME_DIRECTORY_NAME "squirreljme"
38 * Help parameter storage.
42 typedef struct sjme_nvm_helpParam
47 /** Description of the parameter. */
51 static const sjme_nvm_helpParam sjme_nvm_helpParams
[] =
53 {"-Xclutter:<release|debug>",
54 "If available, selects the given clutter level."},
56 "Starts debugging with the built-in debugger."},
58 "Ignored, this will always be \"nanocoat\"."},
60 "If launching a MIDlet, choose a MIDlet entry."},
61 {"-Xjdwp:[hostname]:port",
62 "Listens or connects to a JDWP debugger."},
63 {"-Xlibraries:<class:path:...>",
64 "Libraries to include in the library path, not the classpath."},
66 "Default interface to choose for ScritchUI."},
67 {"-Xsnapshot:<path-to-nps>",
68 "Write a VisualVM snapshot (.nps) to the given path."},
69 {"-Xthread:<single|coop|multi|smt>",
70 "The threading model to use."},
71 {"-Xtrace:<flag|...>",
72 "Trace flags to permanently set on by default."},
74 "Force pure interpreter, do not JIT/AOT compilation."},
75 {"-D<sysprop>=<value>",
76 "Declare system property <sysprop> and set to <value>."},
77 {"-classpath <class:path:...>",
78 "The additional classpath to use for the application."},
82 "Hopefully what you are reading currently, to StdErr."},
84 "Hopefully what you are reading currently, to StdOut."},
86 "Launch the specified Jar."},
90 "SquirrelJME version information, to StdErr."},
92 "SquirrelJME version information, to StdOut."},
99 sjme_errorCode
sjme_nvm_allocReservedPool(
100 sjme_attrInNotNull sjme_alloc_pool
* mainPool
,
101 sjme_attrOutNotNull sjme_alloc_pool
** outReservedPool
)
103 sjme_errorCode error
;
104 sjme_pointer reservedBase
;
105 sjme_alloc_pool
* reservedPool
;
106 sjme_jint reservedSize
;
108 if (mainPool
== NULL
|| outReservedPool
== NULL
)
109 return SJME_ERROR_NULL_ARGUMENTS
;
111 /* Determine how big the reserved pool should be... */
114 if (sjme_error_is(error
= sjme_alloc_sizeOf(
115 SJME_ALLOC_SIZEOF_RESERVED_POOL
, 0, &reservedSize
)))
116 return sjme_error_default(error
);
117 if (sjme_error_is(error
= sjme_alloc(mainPool
,
118 reservedSize
, (sjme_pointer
*)&reservedBase
) ||
119 reservedBase
== NULL
))
120 return sjme_error_default(error
);
122 /* Initialize a reserved pool where all of our own data structures go. */
124 if (sjme_error_is(error
= sjme_alloc_poolInitStatic(
125 &reservedPool
, reservedBase
, reservedSize
)) ||
126 reservedPool
== NULL
)
127 return sjme_error_default(error
);
129 /* Use the resultant pool. */
130 *outReservedPool
= reservedPool
;
131 return SJME_ERROR_NONE
;
134 sjme_errorCode
sjme_nvm_boot(
135 sjme_attrInNotNull sjme_alloc_pool
* mainPool
,
136 sjme_attrInNotNull sjme_alloc_pool
* reservedPool
,
137 sjme_attrInNotNull
const sjme_nvm_bootParam
* param
,
138 sjme_attrOutNotNull sjme_nvm
* outState
)
140 #define FIXED_SUITE_COUNT 16
141 sjme_errorCode error
;
142 sjme_exceptTrace
* trace
;
145 sjme_rom_suite mergeSuites
[FIXED_SUITE_COUNT
];
146 sjme_jint numMergeSuites
;
147 sjme_task_startConfig initTaskConfig
;
148 sjme_nvm_task initTask
;
149 sjme_list_sjme_rom_library
* classPath
;
151 if (param
== NULL
|| outState
== NULL
)
152 return SJME_ERROR_NULL_ARGUMENTS
;
154 /* Set up a reserved pool where all the data structures for the VM go... */
155 /* But only if one does not exist. */
156 if (reservedPool
== NULL
)
157 if (sjme_error_is(error
= sjme_nvm_allocReservedPool(mainPool
,
158 &reservedPool
)) || reservedPool
== NULL
)
159 goto fail_reservedPoolAlloc
;
161 /* Allocate resultant state. */
163 if (sjme_error_is(error
= sjme_alloc_weakNew(reservedPool
,
164 sizeof(*result
), sjme_nvm_enqueueHandler
, SJME_NVM_ENQUEUE_IDENTITY
,
165 (sjme_pointer
*)&result
, NULL
)) || result
== NULL
)
166 goto fail_resultAlloc
;
169 if (sjme_error_is(error
= sjme_nvm_objectInit(
170 SJME_AS_COMMON(result
),
171 SJME_NVM_STRUCTTYPE_STATE
)))
172 goto fail_resultInit
;
174 /* Make a defensive copy of the boot parameters. */
175 if (sjme_error_is(error
= sjme_alloc_copyWeak(reservedPool
,
176 sizeof(sjme_nvm_bootParam
),
177 sjme_nvm_enqueueHandler
, SJME_NVM_ENQUEUE_IDENTITY
,
178 (sjme_pointer
*)&result
->bootParamCopy
, param
, NULL
)) ||
180 goto fail_bootParamCopy
;
182 /* Can only use one or the other to get the class path. */
183 if (result
->bootParamCopy
->mainClassPathById
!= NULL
&&
184 result
->bootParamCopy
->mainClassPathByName
!= NULL
)
185 goto fail_bothIdAndName
;
187 /* Set parameters accordingly. */
188 result
->allocPool
= mainPool
;
189 result
->reservedPool
= reservedPool
;
191 /* Initialize base for suite merging. */
192 memset(mergeSuites
, 0, sizeof(mergeSuites
));
195 /* Process payload suites. */
196 if (result
->bootParamCopy
->payload
!= NULL
)
198 /* Scan accordingly. */
199 if (sjme_error_is(error
= sjme_rom_suiteFromPayload(reservedPool
,
200 &mergeSuites
[numMergeSuites
],
201 result
->bootParamCopy
->payload
)))
202 goto fail_payloadRom
;
204 /* Was a suite generated? */
205 if (mergeSuites
[numMergeSuites
] != NULL
)
209 /* Is there a pre-existing boot suite to use? */
210 if (result
->bootParamCopy
->bootSuite
!= NULL
)
211 if (numMergeSuites
< FIXED_SUITE_COUNT
)
212 mergeSuites
[numMergeSuites
++] =
213 (sjme_rom_suite
)result
->bootParamCopy
->bootSuite
;
215 /* Is there a library suite to use? */
216 if (result
->bootParamCopy
->librarySuite
!= NULL
)
217 if (numMergeSuites
< FIXED_SUITE_COUNT
)
218 mergeSuites
[numMergeSuites
++] =
219 (sjme_rom_suite
)result
->bootParamCopy
->librarySuite
;
221 /* No suites at all? Running with absolutely nothing??? */
222 if (numMergeSuites
<= 0)
225 sjme_message("No suites are available, cannot run.");
230 /* Use the single suite only. */
231 else if (numMergeSuites
== 1)
232 result
->suite
= mergeSuites
[0];
234 /* Merge everything into one. */
237 /* Merge all the suites together into one. */
238 if (sjme_error_is(error
= sjme_rom_suiteFromMerge(reservedPool
,
239 &result
->suite
, mergeSuites
,
240 numMergeSuites
)) || result
->suite
== NULL
)
241 goto fail_suiteMerge
;
244 /* Resolve class path libraries. */
246 error
= SJME_ERROR_NO_SUITES
;
247 if (result
->bootParamCopy
->mainClassPathById
!= NULL
)
248 error
= sjme_rom_resolveClassPathById(result
->suite
,
249 result
->bootParamCopy
->mainClassPathById
,
251 else if (result
->bootParamCopy
->mainClassPathByName
!= NULL
)
252 error
= sjme_rom_resolveClassPathByName(result
->suite
,
253 result
->bootParamCopy
->mainClassPathByName
,
256 /* Failed to resolve? */
257 if (sjme_error_is(error
) || classPath
== NULL
)
260 sjme_message("Classpath resolve failure: %d %p %s",
262 (result
->bootParamCopy
->mainClassPathById
!= NULL
?
265 goto fail_badClassPath
;
268 /* Setup task details. */
269 initTaskConfig
.stdOut
= SJME_TASK_PIPE_REDIRECT_TYPE_TERMINAL
;
270 initTaskConfig
.stdErr
= SJME_TASK_PIPE_REDIRECT_TYPE_TERMINAL
;
271 initTaskConfig
.classPath
= classPath
;
272 initTaskConfig
.mainClass
= result
->bootParamCopy
->mainClass
;
273 initTaskConfig
.mainArgs
= result
->bootParamCopy
->mainArgs
;
274 initTaskConfig
.sysProps
= result
->bootParamCopy
->sysProps
;
276 /* Spawn initial task which uses the main arguments. */
278 if (sjme_error_is(error
= sjme_task_start(result
,
279 &initTaskConfig
, &initTask
)) || initTask
== NULL
)
282 /* Return newly created VM. */
284 return SJME_ERROR_NONE
;
286 /* Failed at specific points... */
294 if (result
!= NULL
&& result
->bootParamCopy
!= NULL
)
295 sjme_alloc_free(result
->bootParamCopy
);
300 sjme_alloc_free(result
);
302 fail_reservedPoolAlloc
:
304 /* Use whatever error code. */
305 return sjme_error_defaultOr(error
, SJME_ERROR_BOOT_FAILURE
);
308 sjme_errorCode
sjme_nvm_defaultBootSuite(
309 sjme_attrInNotNull sjme_alloc_pool
* inPool
,
310 sjme_attrInNotNull
const sjme_nal
* nal
,
311 sjme_attrOutNotNull sjme_rom_suite
* outSuite
)
313 sjme_errorCode error
;
314 sjme_cchar dataPath
[SJME_MAX_PATH
];
316 sjme_rom_suite result
;
318 if (inPool
== NULL
|| nal
== NULL
|| outSuite
== NULL
)
319 return SJME_ERROR_NULL_ARGUMENTS
;
321 /* We cannot load if filesystem access is not supported. */
322 if (nal
->fileOpen
== NULL
)
323 return SJME_ERROR_NOT_IMPLEMENTED
;
326 memset(&dataPath
, 0, sizeof(dataPath
));
328 /* Get default data directory. */
329 if (sjme_error_is(error
= sjme_nvm_defaultDir(
330 SJME_NVM_DEFAULT_DIRECTORY_DATA
, nal
,
331 dataPath
, SJME_MAX_PATH
- 1)))
332 return sjme_error_default(error
);
334 /* Use ROM from here. */
335 if (sjme_error_is(error
= sjme_path_resolveAppend(
336 dataPath
, SJME_MAX_PATH
- 1,
337 SJME_JAR_NAME
, INT32_MAX
)))
338 return sjme_error_default(error
);
340 /* Open main ROM file. */
342 if (sjme_error_is(error
= nal
->fileOpen(inPool
, dataPath
,
343 &rom
)) || rom
== NULL
)
344 return sjme_error_default(error
);
346 /* Load suite from the ZIP. */
348 if (sjme_error_is(error
= sjme_rom_suiteFromZipSeekable(inPool
,
349 &result
, rom
)) || result
== NULL
)
351 /* Make sure to close the file. */
352 sjme_closeable_close(SJME_AS_CLOSEABLE(rom
));
355 return sjme_error_default(error
);
360 return SJME_ERROR_NONE
;
363 sjme_errorCode
sjme_nvm_defaultDir(
364 sjme_attrInValue sjme_nvm_defaultDirectoryType type
,
365 sjme_attrInNotNull
const sjme_nal
* nal
,
366 sjme_attrOutNotNull sjme_lpstr outPath
,
367 sjme_attrInPositiveNonZero sjme_jint outPathLen
)
369 sjme_errorCode error
;
370 sjme_lpcstr useEnv
, insteadSub
;
374 if (nal
== NULL
|| outPath
== NULL
)
375 return SJME_ERROR_NULL_ARGUMENTS
;
377 if (type
<= SJME_NVM_DEFAULT_DIRECTORY_UNKNOWN
||
378 type
>= SJME_NVM_NUM_DEFAULT_DIRECTORY_TYPE
)
379 return SJME_ERROR_INVALID_ARGUMENT
;
382 return SJME_ERROR_INDEX_OUT_OF_BOUNDS
;
384 if (nal
->getEnv
== NULL
)
385 return sjme_error_notImplemented(0);
388 limit
= (SJME_MAX_PATH
> outPathLen
? SJME_MAX_PATH
: outPathLen
);
389 work
= sjme_alloca(sizeof(*work
) * limit
);
391 return SJME_ERROR_OUT_OF_MEMORY
;
392 memset(work
, 0, sizeof(*work
) * limit
);
394 #if defined(SJME_CONFIG_HAS_WINDOWS)
397 #elif defined(SJME_CONFIG_HAS_DOS)
401 #elif defined(SJME_CONFIG_HAS_LINUX) || \
402 defined(SJME_CONFIG_HAS_BSD) || \
403 defined(SJME_CONFIG_HAS_MACOS) || \
404 defined(SJME_CONFIG_HAS_CYGWIN)
406 /* Which are we interested in? */
411 case SJME_NVM_DEFAULT_DIRECTORY_CACHE
:
412 useEnv
= "XDG_CACHE_HOME";
413 insteadSub
= ".cache";
416 case SJME_NVM_DEFAULT_DIRECTORY_CONFIG
:
417 useEnv
= "XDG_CONFIG_HOME";
418 insteadSub
= ".config";
421 case SJME_NVM_DEFAULT_DIRECTORY_DATA
:
422 useEnv
= "XDG_DATA_HOME";
423 insteadSub
= ".local/share";
426 case SJME_NVM_DEFAULT_DIRECTORY_STATE
:
427 useEnv
= "XDG_STATE_HOME";
428 insteadSub
= ".local/state";
433 return SJME_ERROR_INVALID_ARGUMENT
;
436 /* Check if environment variable is available. */
437 if (sjme_error_is(error
= nal
->getEnv(
438 work
, limit
- 1, useEnv
)))
440 /* This is not considered an error, we just try something else. */
441 if (error
!= SJME_ERROR_NO_SUCH_ELEMENT
)
442 return sjme_error_default(error
);
444 /* Get home variable instead, to add onto. */
445 memset(work
, 0, sizeof(work
));
446 if (sjme_error_is(error
= nal
->getEnv(
447 work
, limit
- 1, "HOME")))
448 return sjme_error_default(error
);
450 /* Append subdirectory path. */
451 if (sjme_error_is(error
= sjme_path_resolveAppend(work
,
452 limit
- 1, insteadSub
, INT32_MAX
)))
453 return sjme_error_default(error
);
457 return sjme_error_notImplemented(0);
460 /* Append SquirrelJME on top. */
461 if (sjme_error_is(error
= sjme_path_resolveAppend(work
,
462 limit
- 1, SJME_DIRECTORY_NAME
, INT32_MAX
)))
463 return sjme_error_default(error
);
465 /* Is there enough room to fit? */
466 limit
= strlen(work
) + 1;
467 if (limit
> outPathLen
)
468 return SJME_ERROR_PATH_TOO_LONG
;
471 memmove(outPath
, work
, sizeof(*outPath
) * limit
);
472 return SJME_ERROR_NONE
;
475 sjme_errorCode
sjme_nvm_destroy(sjme_nvm state
, sjme_jint
* exitCode
)
478 return SJME_ERROR_NULL_ARGUMENTS
;
480 /* Free sub-structures. */
482 sjme_todo("sjme_nvm_destroy()");
484 /* Free main structure. */
486 sjme_todo("sjme_nvm_destroy()");
488 /* Set exit code, if requested. */
490 sjme_todo("sjme_nvm_destroy()");
493 sjme_todo("sjme_nvm_destroy()");
494 return SJME_ERROR_NOT_IMPLEMENTED
;
497 sjme_errorCode
sjme_nvm_parseCommandLine(
498 sjme_attrInNotNull sjme_alloc_pool
* inPool
,
499 sjme_attrInNotNull
const sjme_nal
* nal
,
500 sjme_attrInOutNotNull sjme_nvm_bootParam
* outParam
,
501 sjme_attrInPositiveNonZero sjme_jint argc
,
502 sjme_attrInNotNull sjme_lpcstr
* argv
)
504 sjme_errorCode error
;
507 sjme_jboolean jarSpecified
;
508 const sjme_nvm_helpParam
* help
;
509 sjme_nal_stdFFunc helpOut
;
511 if (inPool
== NULL
|| nal
== NULL
|| outParam
== NULL
|| argv
== NULL
)
512 return SJME_ERROR_NULL_ARGUMENTS
;
515 return SJME_ERROR_INVALID_ARGUMENT
;
517 /* Command line format is: */
518 jarSpecified
= SJME_JNI_FALSE
;
519 for (argAt
= 1; argAt
< argc
; argAt
++)
521 /* Setup sequence to wrap argument for parsing. */
522 memset(&argSeq
, 0, sizeof(argSeq
));
523 if (sjme_error_is(error
= sjme_charSeq_newUtfStatic(
524 &argSeq
, argv
[argAt
])))
525 return sjme_error_default(error
);
528 if (sjme_charSeq_equalsUtfR(&argSeq
,
530 sjme_charSeq_equalsUtfR(&argSeq
,
533 /* Where is this information going? */
534 helpOut
= nal
->stdErrF
;
535 if (sjme_charSeq_equalsUtfR(&argSeq
, "--version"))
536 helpOut
= nal
->stdOutF
;
538 /* Print version information to stdout. */
539 /* https://www.oracle.com/java/technologies/javase/ */
540 /* versioning-naming.html */
542 "java version \"1.8.0\"\n");
544 "SquirrelJME Class Library, Micro Edition (build %s)\n",
545 SQUIRRELJME_VERSION
);
547 "SquirrelJME NanoCoat VM (build %s, %s)\n",
548 SQUIRRELJME_VERSION
, SQUIRRELJME_VERSION_NANOCOAT
);
551 return SJME_ERROR_EXIT
;
555 else if (sjme_charSeq_equalsUtfR(&argSeq
,
557 sjme_charSeq_equalsUtfR(&argSeq
,
559 sjme_charSeq_equalsUtfR(&argSeq
,
561 sjme_charSeq_equalsUtfR(&argSeq
,
564 /* Where is this information going? */
565 helpOut
= nal
->stdErrF
;
566 if (sjme_charSeq_equalsUtfR(&argSeq
, "--help"))
567 helpOut
= nal
->stdOutF
;
571 "Usage: %s [Options] <MainClass> [Args...]\n", argv
[0]);
573 "Usage: %s [Options] -jar <Jar> [Args...]\n", argv
[0]);
576 /* And all the help parameters. */
577 helpOut("Options are:\n");
578 for (help
= &sjme_nvm_helpParams
[0]; help
->arg
!= NULL
; help
++)
587 return SJME_ERROR_EXIT
;
590 /* -Xclutter:(release|debug) */
591 else if (sjme_charSeq_startsWithUtfR(&argSeq
,
594 sjme_todo("Impl? %s", argv
[argAt
]);
598 else if (sjme_charSeq_equalsUtfR(&argSeq
,
601 sjme_todo("Impl? %s", argv
[argAt
]);
604 /* -Xemulator:(vm) */
605 else if (sjme_charSeq_startsWithUtfR(&argSeq
,
608 sjme_todo("Impl? %s", argv
[argAt
]);
612 else if (sjme_charSeq_startsWithUtfR(&argSeq
,
615 sjme_todo("Impl? %s", argv
[argAt
]);
618 /* -Xjdwp:[hostname]:port */
619 else if (sjme_charSeq_startsWithUtfR(&argSeq
,
622 sjme_todo("Impl? %s", argv
[argAt
]);
625 /* -Xlibraries:(class:path:...) */
626 else if (sjme_charSeq_startsWithUtfR(&argSeq
,
629 sjme_todo("Impl? %s", argv
[argAt
]);
632 /* -Xscritchui:(ui) */
633 else if (sjme_charSeq_startsWithUtfR(&argSeq
,
636 sjme_todo("Impl? %s", argv
[argAt
]);
639 /* -Xsnapshot:(path-to-nps) */
640 else if (sjme_charSeq_startsWithUtfR(&argSeq
,
643 sjme_todo("Impl? %s", argv
[argAt
]);
646 /* -Xthread:(single|coop|multi|smt) */
647 else if (sjme_charSeq_startsWithUtfR(&argSeq
,
650 sjme_todo("Impl? %s", argv
[argAt
]);
653 /* -Xtrace:(flag|...) */
654 else if (sjme_charSeq_startsWithUtfR(&argSeq
,
657 sjme_todo("Impl? %s", argv
[argAt
]);
660 /* -Dsysprop=value */
661 else if (sjme_charSeq_startsWithUtfR(&argSeq
,
664 sjme_todo("Impl? %s", argv
[argAt
]);
667 /* -classpath (class:path:...) */
668 else if (sjme_charSeq_equalsUtfR(&argSeq
,
671 sjme_todo("Impl? %s", argv
[argAt
]);
675 else if (sjme_charSeq_equalsUtfR(&argSeq
, "-zero") ||
676 sjme_charSeq_equalsUtfR(&argSeq
, "-Xint"))
678 sjme_todo("Impl? %s", argv
[argAt
]);
681 /* -client/-server. */
682 else if (sjme_charSeq_equalsUtfR(&argSeq
,
684 sjme_charSeq_equalsUtfR(&argSeq
, "-server"))
686 sjme_todo("Impl? %s", argv
[argAt
]);
690 else if (sjme_charSeq_equalsUtfR(&argSeq
, "-jar"))
692 /* We are using a Jar now. */
693 jarSpecified
= SJME_JNI_TRUE
;
695 sjme_todo("Impl? %s", argv
[argAt
]);
701 sjme_message("Invalid command line: %s",
704 return SJME_ERROR_INVALID_ARGUMENT
;
710 /* Launching a specific Jar? */
713 /* Main-class, if not -jar */
726 /* Default launching. */
729 outParam
->mainArgs
= NULL
;
730 outParam
->mainClass
= NULL
;
734 return SJME_ERROR_NONE
;