-
Notifications
You must be signed in to change notification settings - Fork 2
/
jaunch.c
224 lines (204 loc) Β· 7.8 KB
/
jaunch.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
/*
* This is the C portion of Jaunch, the configurable native launcher.
*
* Its primary function is to launch a non-native runtime plus main program
* in the same process, by dynamically loading the runtime library.
*
* Currently supported runtimes include Python and the Java Virtual Machine.
*
* - For Python logic, see python.h.
* - For JVM logic, see jvm.h.
*
* The C portion of Jaunch is empowered by a so-called "configurator" program,
* which is the more sophisticated portion of Jaunch. The C launcher invokes
* the configurator program in a separate process, using the function:
*
* int run_command(const char *command,
* size_t numInput, const char *input[],
* size_t *numOutput, char ***output)
*
* The C code waits for the Jaunch configurator process to complete, then
* passes the outputs given by the configurator to the appropriate `launch`
* function.
*
* In this way, the non-native runtime is launched in the same process by C,
* but in a way that is fuily customizable from the Jaunch code written in a
* high-level language.
*
* For example, a command line invocation of:
*
* fizzbuzz Hello --verbose=2 --min 100 --max 200
*
* might be translated by the configurator into a Python invocation:
*
* python -vv fizzbuzz.py Hello 100..200
*
* or a Java invocation:
*
* java -DverboseLevel=2 -Xmx128m com.fizzbuzz.FizzBuzz Hello 100..200
*
* depending on the way Jaunch is configured via its TOML config files.
*
* See the common.toml file for a walkthrough of how the configurator
* can be flexibly configured to decide how arguments are transformed.
*/
#include <unistd.h>
#include "common.h"
// -- PLATFORMS --
#ifdef __linux__
#include "linux.h"
#endif
#ifdef __APPLE__
#include "macos.h"
#endif
#ifdef WIN32
#include "win32.h"
#else
#include "posix.h"
#endif
#ifdef __x86_64__
#define OS_ARCH "x64"
#endif
#ifdef __aarch64__
#define OS_ARCH "arm64"
#endif
// -- DIRECTIVES --
#include "jvm.h"
#include "python.h"
// List of places to search for the jaunch configurator executable.
//
// NB: This list should align with the configDirs list in Jaunch.kt,
// except for the trailing "Contents/MacOS/" and NULL entries.
//
// The trailing slashes make the math simpler in the path function logic.
const char *JAUNCH_SEARCH_PATHS[] = {
"jaunch"SLASH,
".jaunch"SLASH,
"config"SLASH"jaunch"SLASH,
".config"SLASH"jaunch"SLASH,
"Contents"SLASH"MacOS"SLASH,
NULL,
};
/* result=$(dirname "$argv0")/$subdir$command */
char *path(const char *argv0, const char *subdir, const char *command) {
// Calculate string lengths.
const char *last_slash = argv0 == NULL ? NULL : strrchr(argv0, SLASH[0]);
size_t dir_len = (size_t)(last_slash == NULL ? 1 : last_slash - argv0);
size_t subdir_len = subdir == NULL ? 0 : strlen(subdir);
size_t command_len = strlen(command);
size_t result_len = dir_len + 1 + subdir_len + command_len;
// Allocate the result string.
char *result = (char *)malloc(result_len + 1);
if (result == NULL) return NULL;
// Build the result string.
if (last_slash == NULL) result[0] = '.';
else strncpy(result, argv0, dir_len);
result[dir_len] = SLASH[0];
result[dir_len + 1] = '\0';
if (subdir != NULL) strcat(result, subdir); // result += subdir
strcat(result, command); // result += command
return result;
}
int main(const int argc, const char *argv[]) {
// Enable debug mode when --debug is an argument.
for (size_t i = 0; i < argc; i++)
if (strcmp(argv[i], "--debug") == 0) debug_mode = 1;
char *command = NULL;
size_t search_path_count = sizeof(JAUNCH_SEARCH_PATHS) / sizeof(char *);
for (size_t i = 0; i < search_path_count; i++) {
// First, look for jaunch configurator with a `-<os>-<arch>` suffix.
command = path(
argc == 0 ? NULL : argv[0],
JAUNCH_SEARCH_PATHS[i],
"jaunch-" OS_NAME "-" OS_ARCH EXE_SUFFIX
);
if (file_exists(command)) break;
else debug("[JAUNCH] No configurator at %s", command);
// If not found, look for plain jaunch configurator with no suffix.
free(command);
command = path(
argc == 0 ? NULL : argv[0],
JAUNCH_SEARCH_PATHS[i],
"jaunch" EXE_SUFFIX
);
if (file_exists(command)) break;
else debug("[JAUNCH] No configurator at %s", command);
// Nothing at this search path; clean up and move on to the next one.
free(command);
command = NULL;
}
if (command == NULL) {
error("Failed to locate the jaunch configurator program.");
return ERROR_COMMAND_PATH;
}
debug("[JAUNCH] configurator command = %s", command);
// Run external command to process the command line arguments.
char **out_argv;
size_t out_argc;
int run_result = run_command((const char *)command, argc, argv, &out_argc, &out_argv);
free(command);
if (run_result != SUCCESS) return run_result;
CHECK_ARGS("JAUNCH", "out", out_argc, 2, 99999, out_argv);
// Maximum # of lines to treat as valid. ^^^^^
// We could of course leave this unbounded, but pragmatically, the value
// will probably never exceed this sizeΒ -- it is more likely that a
// programming error in the configurator yields a much-too-large argc
// value, and it is better to fail fast than to access invalid memory.
// Perform the indicated directive(s).
int exit_code = SUCCESS;
size_t index = 0;
while (index < out_argc) {
// Prepare the (argc, argv) for the next directive.
const char *directive = (const char *)(out_argv[index]);
if (index == out_argc - 1) {
error("Invalid trailing directive: %s", directive);
break;
}
const size_t dir_argc = atoi(out_argv[index + 1]);
const char **dir_argv = (const char **)(out_argv + index + 2);
CHECK_ARGS("JAUNCH", "dir", dir_argc, 0, out_argc - index, dir_argv);
index += 2 + dir_argc; // Advance index past this directive block.
// Call the directive's associated function.
if (strcmp(directive, "ABORT") == 0) {
if (dir_argc > 0) error("Ignoring %zu extra ABORT lines.", dir_argc);
const size_t extra = out_argc - index;
if (extra > 0) error("Ignoring %zu trailing output lines.", extra);
break;
}
else if (strcmp(directive, "JVM") == 0) {
exit_code = launch(launch_jvm, dir_argc, dir_argv);
if (exit_code != SUCCESS) break;
}
else if (strcmp(directive, "PYTHON") == 0) {
exit_code = launch(launch_python, dir_argc, dir_argv);
if (exit_code != SUCCESS) break;
}
else if (strcmp(directive, "INIT_THREADS") == 0) {
init_threads();
}
else if (strcmp(directive, "ERROR") == 0) {
// =======================================================================
// Parse the arguments, which must conform to the following structure:
//
// 1. Exit code to use after issuing the error message.
// 2. The error message, which may span multiple lines.
// =======================================================================
exit_code = atoi(dir_argv[0]);
if (exit_code < 20) exit_code = 20;
if (exit_code > 255) exit_code = 255;
for (size_t i = 1; i < dir_argc; i++) error(dir_argv[i]);
// TODO: show_alert(title, message);
}
else {
// Mysterious directive! Fail fast.
error("Unknown directive: %s", directive);
return ERROR_UNKNOWN_DIRECTIVE;
}
}
// Clean up.
for (size_t i = 0; i < out_argc; i++) {
free(out_argv[i]);
}
free(out_argv);
return exit_code;
}