tebako 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Brewfile +0 -5
- data/CMakeLists.txt +94 -46
- data/cmake/tebako-libtfs.cmake +456 -0
- data/common.env +1 -1
- data/include/tebako/tpkg.h +575 -0
- data/lib/tebako/cli.rb +91 -0
- data/lib/tebako/cli_helpers.rb +24 -0
- data/lib/tebako/error.rb +10 -0
- data/lib/tebako/launcher_abi.rb +99 -0
- data/lib/tebako/options_manager.rb +71 -0
- data/lib/tebako/packager/patch_libraries.rb +47 -27
- data/lib/tebako/packager.rb +40 -1
- data/lib/tebako/runtime_manager.rb +347 -0
- data/lib/tebako/stitcher.rb +255 -0
- data/lib/tebako/version.rb +1 -1
- data/src/tebako-main.cpp +577 -9
- data/tools/.github/workflows/test.yml +6 -6
- data/tools/cmake-scripts/macos-environment.cmake +11 -2
- metadata +7 -4
- data/tools/ci-scripts/patch-fbthrift.sh +0 -94
- data/tools/ci-scripts/patch-folly.sh +0 -551
data/src/tebako-main.cpp
CHANGED
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
#include <sys/stat.h>
|
|
36
36
|
#include <fcntl.h>
|
|
37
37
|
#include <stdint.h>
|
|
38
|
+
#include <limits.h>
|
|
38
39
|
|
|
39
40
|
#include <string>
|
|
40
41
|
#include <cstdint>
|
|
@@ -45,6 +46,11 @@
|
|
|
45
46
|
#ifdef _WIN32
|
|
46
47
|
#include <winsock2.h>
|
|
47
48
|
#include <windows.h>
|
|
49
|
+
#include <io.h>
|
|
50
|
+
#endif
|
|
51
|
+
|
|
52
|
+
#ifdef __APPLE__
|
|
53
|
+
#include <mach-o/dyld.h>
|
|
48
54
|
#endif
|
|
49
55
|
|
|
50
56
|
#include <tebako/tebako-config.h>
|
|
@@ -55,9 +61,417 @@
|
|
|
55
61
|
#include <tebako/tebako-fs.h>
|
|
56
62
|
#include <tebako/tebako-cmdline.h>
|
|
57
63
|
|
|
64
|
+
/*
|
|
65
|
+
* tpkg manifest trailer reader (Stage 3A).
|
|
66
|
+
* Vendored copy of libtfs include/tebako/tpkg.h @ 37166d3 (PR #155) at
|
|
67
|
+
* include/tebako/tpkg.h in this repo -- keep in sync with upstream.
|
|
68
|
+
* TPKG_IMPLEMENTATION is defined in exactly this translation unit.
|
|
69
|
+
*/
|
|
70
|
+
#define TPKG_IMPLEMENTATION
|
|
71
|
+
#include <tebako/tpkg.h>
|
|
72
|
+
|
|
73
|
+
/*
|
|
74
|
+
* Launcher ABI v1 (Stage 3B) -- the bootstrap -> runtime handoff contract.
|
|
75
|
+
* A lean package's bootstrap execs the runtime as
|
|
76
|
+
* <runtime> --tebako-image <file>:<slot>:<mount-point> ...
|
|
77
|
+
* --tebako-entry <argv0> <user args...>
|
|
78
|
+
* and the runtime mounts the referenced image slots directly out of the
|
|
79
|
+
* bootstrap's file (spec 4.4). The Ruby-side constants and the full contract
|
|
80
|
+
* documentation live in lib/tebako/launcher_abi.rb -- keep the two in sync.
|
|
81
|
+
*/
|
|
82
|
+
#define TEBAKO_LAUNCHER_ABI_VERSION 1u
|
|
83
|
+
#define TEBAKO_LAUNCHER_IMAGE_ARG "--tebako-image"
|
|
84
|
+
#define TEBAKO_LAUNCHER_ENTRY_ARG "--tebako-entry"
|
|
85
|
+
#define TEBAKO_LAUNCHER_VERSION_ARG "--tebako-launcher-abi"
|
|
86
|
+
|
|
58
87
|
static int running_miniruby = 0;
|
|
59
88
|
static tebako::cmdline_args* args = nullptr;
|
|
60
89
|
static std::vector<char> package;
|
|
90
|
+
/* Owns the self-executable bytes when a tpkg trailer mount replaces the
|
|
91
|
+
incbin image; process-lifetime like the incbin section itself. */
|
|
92
|
+
static std::vector<char> self_image;
|
|
93
|
+
|
|
94
|
+
namespace {
|
|
95
|
+
|
|
96
|
+
/* Path of the running executable, UTF-8 (best effort; empty on failure) */
|
|
97
|
+
std::string self_executable_path()
|
|
98
|
+
{
|
|
99
|
+
#if defined(_WIN32)
|
|
100
|
+
wchar_t wbuf[32768];
|
|
101
|
+
DWORD n = GetModuleFileNameW(nullptr, wbuf, static_cast<DWORD>(sizeof(wbuf) / sizeof(wbuf[0])));
|
|
102
|
+
if (n == 0 || n >= sizeof(wbuf) / sizeof(wbuf[0])) {
|
|
103
|
+
return std::string();
|
|
104
|
+
}
|
|
105
|
+
int len = WideCharToMultiByte(CP_UTF8, 0, wbuf, n, nullptr, 0, nullptr, nullptr);
|
|
106
|
+
if (len <= 0) {
|
|
107
|
+
return std::string();
|
|
108
|
+
}
|
|
109
|
+
std::string out(static_cast<size_t>(len), '\0');
|
|
110
|
+
WideCharToMultiByte(CP_UTF8, 0, wbuf, n, out.data(), len, nullptr, nullptr);
|
|
111
|
+
return out;
|
|
112
|
+
#elif defined(__APPLE__)
|
|
113
|
+
uint32_t bufsize = 0;
|
|
114
|
+
_NSGetExecutablePath(nullptr, &bufsize);
|
|
115
|
+
std::vector<char> buf(bufsize);
|
|
116
|
+
if (_NSGetExecutablePath(buf.data(), &bufsize) != 0) {
|
|
117
|
+
return std::string();
|
|
118
|
+
}
|
|
119
|
+
char resolved[PATH_MAX];
|
|
120
|
+
if (realpath(buf.data(), resolved) != nullptr) {
|
|
121
|
+
return std::string(resolved);
|
|
122
|
+
}
|
|
123
|
+
return std::string(buf.data());
|
|
124
|
+
#else
|
|
125
|
+
char buf[PATH_MAX];
|
|
126
|
+
ssize_t n = readlink("/proc/self/exe", buf, sizeof(buf) - 1);
|
|
127
|
+
if (n <= 0) {
|
|
128
|
+
return std::string();
|
|
129
|
+
}
|
|
130
|
+
buf[n] = '\0';
|
|
131
|
+
return std::string(buf);
|
|
132
|
+
#endif
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* Open a file read-only from a UTF-8 path (the running executable or a
|
|
136
|
+
--tebako-image package file); returns fd or -1 */
|
|
137
|
+
int open_self_executable(const std::string& path)
|
|
138
|
+
{
|
|
139
|
+
#if defined(_WIN32)
|
|
140
|
+
int wlen = MultiByteToWideChar(CP_UTF8, 0, path.c_str(), -1, nullptr, 0);
|
|
141
|
+
if (wlen <= 0) {
|
|
142
|
+
return -1;
|
|
143
|
+
}
|
|
144
|
+
std::vector<wchar_t> wpath(static_cast<size_t>(wlen));
|
|
145
|
+
MultiByteToWideChar(CP_UTF8, 0, path.c_str(), -1, wpath.data(), wlen);
|
|
146
|
+
return _wopen(wpath.data(), _O_RDONLY | _O_BINARY);
|
|
147
|
+
#else
|
|
148
|
+
return open(path.c_str(), O_RDONLY);
|
|
149
|
+
#endif
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/*
|
|
153
|
+
* Probe the own executable for a tpkg manifest trailer.
|
|
154
|
+
*
|
|
155
|
+
* Returns 1 if a valid trailer was read into `manifest`, 0 if no trailer is
|
|
156
|
+
* present (classic incbin bundle -- caller falls back), and -1 if a corrupt
|
|
157
|
+
* trailer was detected (caller must fail startup; spec §6: never a partial
|
|
158
|
+
* mount). On return values other than 0 a message naming the binary is
|
|
159
|
+
* printed for the corrupt case.
|
|
160
|
+
*/
|
|
161
|
+
int read_self_manifest(tpkg_manifest* manifest)
|
|
162
|
+
{
|
|
163
|
+
std::string self = self_executable_path();
|
|
164
|
+
if (self.empty()) {
|
|
165
|
+
return 0; // cannot locate self -- treat as classic bundle
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
int fd = open_self_executable(self);
|
|
169
|
+
if (fd < 0) {
|
|
170
|
+
return 0;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
int rc = tpkg_read_fd(fd, manifest);
|
|
174
|
+
if (rc == 0) {
|
|
175
|
+
// Validate slot geometry against the real file size; a CRC-valid
|
|
176
|
+
// manifest pointing outside the file is still corrupt for us.
|
|
177
|
+
off_t fsize = lseek(fd, 0, SEEK_END);
|
|
178
|
+
close(fd);
|
|
179
|
+
if (fsize < 0) {
|
|
180
|
+
return 0;
|
|
181
|
+
}
|
|
182
|
+
for (uint32_t i = 0; i < manifest->slot_count; ++i) {
|
|
183
|
+
const tpkg_slot& s = manifest->slots[i];
|
|
184
|
+
if (s.offset > static_cast<uint64_t>(fsize) || s.size > static_cast<uint64_t>(fsize) - s.offset) {
|
|
185
|
+
printf(
|
|
186
|
+
"Tebako: package manifest trailer in '%s' is corrupt (slot %u outside file bounds). "
|
|
187
|
+
"Re-stitch the package to repair the manifest.\n",
|
|
188
|
+
self.c_str(), i);
|
|
189
|
+
return -1;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return 1;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
close(fd);
|
|
196
|
+
int err = tpkg_errno();
|
|
197
|
+
if (err == TPKG_ERR_NO_TRAILER) {
|
|
198
|
+
return 0; // classic bundle, not an error
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
printf(
|
|
202
|
+
"Tebako: package manifest trailer in '%s' is corrupt (%s). "
|
|
203
|
+
"Re-stitch the package to repair the manifest.\n",
|
|
204
|
+
self.c_str(), tpkg_strerror(err));
|
|
205
|
+
return -1;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/* Read the whole file behind `fd` into self_image; 0 on success */
|
|
209
|
+
int read_self_image(int fd)
|
|
210
|
+
{
|
|
211
|
+
off_t fsize = lseek(fd, 0, SEEK_END);
|
|
212
|
+
if (fsize <= 0 || lseek(fd, 0, SEEK_SET) != 0) {
|
|
213
|
+
return -1;
|
|
214
|
+
}
|
|
215
|
+
self_image.resize(static_cast<size_t>(fsize));
|
|
216
|
+
size_t got = 0;
|
|
217
|
+
while (got < static_cast<size_t>(fsize)) {
|
|
218
|
+
ssize_t r = read(fd, self_image.data() + got, static_cast<size_t>(fsize) - got);
|
|
219
|
+
if (r <= 0) {
|
|
220
|
+
self_image.clear();
|
|
221
|
+
return -1;
|
|
222
|
+
}
|
|
223
|
+
got += static_cast<size_t>(r);
|
|
224
|
+
}
|
|
225
|
+
return 0;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/*
|
|
229
|
+
* Launcher ABI v1 (Stage 3B) -- runtime side of the bootstrap handoff.
|
|
230
|
+
*/
|
|
231
|
+
|
|
232
|
+
/* One --tebako-image reference, resolved against the file's tpkg trailer */
|
|
233
|
+
struct launcher_image {
|
|
234
|
+
std::string file;
|
|
235
|
+
uint32_t slot = 0; /* index into the file's tpkg slot table */
|
|
236
|
+
std::string mount_point;
|
|
237
|
+
uint64_t size = 0; /* image length in bytes, from the trailer */
|
|
238
|
+
const char* window = nullptr; /* slot bytes, owned by launcher_image_store */
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
struct launcher_handoff {
|
|
242
|
+
bool present = false; /* any launcher ABI option seen at all */
|
|
243
|
+
bool version_seen = false; /* --tebako-launcher-abi given */
|
|
244
|
+
uint32_t version = 0;
|
|
245
|
+
std::string entry; /* package argv[0]; empty when --tebako-entry is absent */
|
|
246
|
+
std::vector<launcher_image> images;
|
|
247
|
+
std::vector<std::string> user_args; /* everything after --tebako-entry <argv0> */
|
|
248
|
+
std::string error; /* non-empty: malformed handoff */
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
/* Owns the image slot bytes read out of --tebako-image files;
|
|
252
|
+
process-lifetime like the incbin section itself. Appending moves the
|
|
253
|
+
outer vector but never the inner buffers, so window pointers stay valid. */
|
|
254
|
+
static std::vector<std::vector<char>> launcher_image_store;
|
|
255
|
+
|
|
256
|
+
bool parse_uint32(const std::string& s, uint32_t* out)
|
|
257
|
+
{
|
|
258
|
+
if (s.empty() || s.size() > 10) {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
uint64_t v = 0;
|
|
262
|
+
for (char c : s) {
|
|
263
|
+
if (c < '0' || c > '9') {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
v = v * 10 + static_cast<uint64_t>(c - '0');
|
|
267
|
+
if (v > UINT32_MAX) {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
*out = static_cast<uint32_t>(v);
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/* Split "<file>:<slot>:<mount-point>" on the last two colons; the file
|
|
276
|
+
component may itself contain colons (e.g. Windows drive prefixes). */
|
|
277
|
+
bool split_image_spec(const std::string& spec, std::string* file, uint32_t* slot, std::string* mount_point)
|
|
278
|
+
{
|
|
279
|
+
size_t last = spec.rfind(':');
|
|
280
|
+
if (last == std::string::npos || last == 0) {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
size_t prev = spec.rfind(':', last - 1);
|
|
284
|
+
if (prev == std::string::npos) {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
*file = spec.substr(0, prev);
|
|
288
|
+
*mount_point = spec.substr(last + 1);
|
|
289
|
+
if (file->empty() || mount_point->empty()) {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
return parse_uint32(spec.substr(prev + 1, last - prev - 1), slot);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/* Match one launcher ABI option, inline ("--opt=value") or bare ("--opt").
|
|
296
|
+
Returns 1 for --tebako-image, 2 for --tebako-entry, 3 for
|
|
297
|
+
--tebako-launcher-abi, 0 for anything else. For a bare match *had_inline
|
|
298
|
+
is false and *value is untouched; the caller consumes the next argument. */
|
|
299
|
+
int match_launcher_arg(const std::string& arg, bool* had_inline, std::string* value)
|
|
300
|
+
{
|
|
301
|
+
static const std::string keys[] = {TEBAKO_LAUNCHER_IMAGE_ARG, TEBAKO_LAUNCHER_ENTRY_ARG, TEBAKO_LAUNCHER_VERSION_ARG};
|
|
302
|
+
for (size_t k = 0; k < 3; ++k) {
|
|
303
|
+
if (arg == keys[k]) {
|
|
304
|
+
*had_inline = false;
|
|
305
|
+
return static_cast<int>(k) + 1;
|
|
306
|
+
}
|
|
307
|
+
if (arg.size() > keys[k].size() && arg.compare(0, keys[k].size(), keys[k]) == 0 && arg[keys[k].size()] == '=') {
|
|
308
|
+
*had_inline = true;
|
|
309
|
+
*value = arg.substr(keys[k].size() + 1);
|
|
310
|
+
return static_cast<int>(k) + 1;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return 0;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/*
|
|
317
|
+
* Scan argv for the launcher ABI handoff. The three ABI options are
|
|
318
|
+
* recognized until --tebako-entry is consumed; everything after its value is
|
|
319
|
+
* application argv. Non-ABI arguments preceding --tebako-entry are ignored
|
|
320
|
+
* (the bootstrap emits none). When no ABI option is present at all the
|
|
321
|
+
* returned handoff is !present and the caller runs the classic flow
|
|
322
|
+
* unchanged.
|
|
323
|
+
*/
|
|
324
|
+
launcher_handoff parse_launcher_handoff(int argc, char** argv)
|
|
325
|
+
{
|
|
326
|
+
launcher_handoff h;
|
|
327
|
+
for (int i = 1; i < argc; ++i) {
|
|
328
|
+
bool had_inline = false;
|
|
329
|
+
std::string value;
|
|
330
|
+
int kind = match_launcher_arg(argv[i], &had_inline, &value);
|
|
331
|
+
if (kind == 0) {
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
h.present = true;
|
|
335
|
+
if (!had_inline) {
|
|
336
|
+
if (i + 1 >= argc) {
|
|
337
|
+
h.error = std::string("Error: ") + argv[i] +
|
|
338
|
+
(kind == 1 ? " shall be followed by <file>:<slot>:<mount-point>"
|
|
339
|
+
: (kind == 2 ? " shall be followed by the package argv[0]"
|
|
340
|
+
: " shall be followed by an ABI version number"));
|
|
341
|
+
return h;
|
|
342
|
+
}
|
|
343
|
+
value = argv[++i];
|
|
344
|
+
}
|
|
345
|
+
if (kind == 1) {
|
|
346
|
+
launcher_image img;
|
|
347
|
+
if (!split_image_spec(value, &img.file, &img.slot, &img.mount_point)) {
|
|
348
|
+
h.error = "Error: malformed --tebako-image value '" + value + "' -- expected <file>:<slot>:<mount-point>";
|
|
349
|
+
return h;
|
|
350
|
+
}
|
|
351
|
+
h.images.push_back(std::move(img));
|
|
352
|
+
}
|
|
353
|
+
else if (kind == 2) {
|
|
354
|
+
if (value.empty()) {
|
|
355
|
+
h.error = "Error: --tebako-entry shall be followed by the package argv[0]";
|
|
356
|
+
return h;
|
|
357
|
+
}
|
|
358
|
+
h.entry = value;
|
|
359
|
+
for (++i; i < argc; ++i) {
|
|
360
|
+
h.user_args.emplace_back(argv[i]);
|
|
361
|
+
}
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
if (!parse_uint32(value, &h.version)) {
|
|
366
|
+
h.error = "Error: malformed --tebako-launcher-abi value '" + value + "' -- expected an ABI version number";
|
|
367
|
+
return h;
|
|
368
|
+
}
|
|
369
|
+
h.version_seen = true;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return h;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/* 64-bit file positioning for image slot reads (slot offsets can exceed 2GB) */
|
|
376
|
+
int seek_fd(int fd, uint64_t offset)
|
|
377
|
+
{
|
|
378
|
+
#if defined(_WIN32)
|
|
379
|
+
return _lseeki64(fd, static_cast<__int64>(offset), SEEK_SET) == -1 ? -1 : 0;
|
|
380
|
+
#else
|
|
381
|
+
return lseek(fd, static_cast<off_t>(offset), SEEK_SET) == static_cast<off_t>(-1) ? -1 : 0;
|
|
382
|
+
#endif
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
uint64_t file_size_fd(int fd)
|
|
386
|
+
{
|
|
387
|
+
#if defined(_WIN32)
|
|
388
|
+
__int64 end = _lseeki64(fd, 0, SEEK_END);
|
|
389
|
+
return end < 0 ? 0 : static_cast<uint64_t>(end);
|
|
390
|
+
#else
|
|
391
|
+
off_t end = lseek(fd, 0, SEEK_END);
|
|
392
|
+
return end == static_cast<off_t>(-1) ? 0 : static_cast<uint64_t>(end);
|
|
393
|
+
#endif
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/* Read exactly n bytes from fd at absolute offset `offset` into dest */
|
|
397
|
+
int read_region(int fd, uint64_t offset, char* dest, size_t n)
|
|
398
|
+
{
|
|
399
|
+
if (seek_fd(fd, offset) != 0) {
|
|
400
|
+
return -1;
|
|
401
|
+
}
|
|
402
|
+
size_t got = 0;
|
|
403
|
+
while (got < n) {
|
|
404
|
+
ssize_t r = read(fd, dest + got, n - got);
|
|
405
|
+
if (r <= 0) {
|
|
406
|
+
return -1;
|
|
407
|
+
}
|
|
408
|
+
got += static_cast<size_t>(r);
|
|
409
|
+
}
|
|
410
|
+
return 0;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/*
|
|
414
|
+
* Resolve every --tebako-image reference against its file's tpkg manifest
|
|
415
|
+
* trailer and read the slot's bytes out of the file (no extraction, no temp
|
|
416
|
+
* copies; spec 4.4). On failure prints a named startup error and returns
|
|
417
|
+
* false; the caller must fail startup (spec 6: never a partial mount, one
|
|
418
|
+
* bad slot aborts with the slot index).
|
|
419
|
+
*/
|
|
420
|
+
bool resolve_launcher_images(launcher_handoff* h)
|
|
421
|
+
{
|
|
422
|
+
for (auto& img : h->images) {
|
|
423
|
+
int fd = open_self_executable(img.file);
|
|
424
|
+
if (fd < 0) {
|
|
425
|
+
printf("Tebako: cannot open image file '%s': %s\n", img.file.c_str(), strerror(errno));
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
tpkg_manifest m;
|
|
429
|
+
if (tpkg_read_fd(fd, &m) != 0) {
|
|
430
|
+
int err = tpkg_errno();
|
|
431
|
+
close(fd);
|
|
432
|
+
if (err == TPKG_ERR_NO_TRAILER) {
|
|
433
|
+
printf("Tebako: image file '%s' carries no tpkg manifest trailer --\n"
|
|
434
|
+
" --tebako-image expects a three-part package file (bootstrap + image slots + trailer)\n",
|
|
435
|
+
img.file.c_str());
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
printf("Tebako: package manifest trailer in '%s' is corrupt (%s). "
|
|
439
|
+
"Re-stitch the package to repair the manifest.\n",
|
|
440
|
+
img.file.c_str(), tpkg_strerror(err));
|
|
441
|
+
}
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
if (img.slot >= m.slot_count) {
|
|
445
|
+
printf("Tebako: --tebako-image slot %u is out of range for '%s' (%u slot(s) in its manifest)\n", img.slot,
|
|
446
|
+
img.file.c_str(), m.slot_count);
|
|
447
|
+
close(fd);
|
|
448
|
+
return false;
|
|
449
|
+
}
|
|
450
|
+
const tpkg_slot& s = m.slots[img.slot];
|
|
451
|
+
uint64_t fsize = file_size_fd(fd);
|
|
452
|
+
if (s.offset > fsize || s.size > fsize - s.offset) {
|
|
453
|
+
printf("Tebako: package manifest trailer in '%s' is corrupt (slot %u outside file bounds). "
|
|
454
|
+
"Re-stitch the package to repair the manifest.\n",
|
|
455
|
+
img.file.c_str(), img.slot);
|
|
456
|
+
close(fd);
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
launcher_image_store.emplace_back(static_cast<size_t>(s.size));
|
|
460
|
+
std::vector<char>& buf = launcher_image_store.back();
|
|
461
|
+
if (read_region(fd, s.offset, buf.data(), buf.size()) != 0) {
|
|
462
|
+
printf("Tebako: failed to read image slot %u from '%s': %s\n", img.slot, img.file.c_str(), strerror(errno));
|
|
463
|
+
launcher_image_store.pop_back();
|
|
464
|
+
close(fd);
|
|
465
|
+
return false;
|
|
466
|
+
}
|
|
467
|
+
close(fd);
|
|
468
|
+
img.size = s.size;
|
|
469
|
+
img.window = buf.data();
|
|
470
|
+
}
|
|
471
|
+
return true;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
} // namespace
|
|
61
475
|
|
|
62
476
|
static void tebako_clean(void)
|
|
63
477
|
{
|
|
@@ -89,9 +503,46 @@ extern "C" int tebako_main(int* argc, char*** argv)
|
|
|
89
503
|
}
|
|
90
504
|
const void* data = &gfsData[0];
|
|
91
505
|
size_t size = gfsSize;
|
|
506
|
+
std::string root_image_offset = "auto";
|
|
507
|
+
/* Non-root image slots: (recorded mount point, image bytes, image size).
|
|
508
|
+
window points at the slot's image -- into self_image for the trailer
|
|
509
|
+
path, into launcher_image_store for the launcher ABI path. */
|
|
510
|
+
struct extra_slot {
|
|
511
|
+
std::string mount_point;
|
|
512
|
+
const char* window;
|
|
513
|
+
uint64_t size;
|
|
514
|
+
};
|
|
515
|
+
std::vector<extra_slot> extra_slots;
|
|
516
|
+
bool trailer_corrupt = false;
|
|
517
|
+
bool handoff_failed = false;
|
|
92
518
|
|
|
93
519
|
try {
|
|
94
|
-
|
|
520
|
+
/*
|
|
521
|
+
* Launcher ABI v1 (Stage 3B): a lean package's bootstrap execs the
|
|
522
|
+
* runtime with --tebako-image/--tebako-entry arguments. When present,
|
|
523
|
+
* they are consumed here -- before the classic cmdline/trailer/incbin
|
|
524
|
+
* logic -- and the interpreter is handed a synthetic argv of the
|
|
525
|
+
* package argv[0] and the application arguments.
|
|
526
|
+
*/
|
|
527
|
+
launcher_handoff handoff = parse_launcher_handoff(*argc, *argv);
|
|
528
|
+
if (!handoff.error.empty()) {
|
|
529
|
+
printf("Tebako: %s\n", handoff.error.c_str());
|
|
530
|
+
handoff_failed = true;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
int effective_argc = *argc;
|
|
534
|
+
char** effective_argv = *argv;
|
|
535
|
+
std::vector<const char*> synthetic_argv;
|
|
536
|
+
if (handoff.present && !handoff_failed) {
|
|
537
|
+
synthetic_argv.push_back(handoff.entry.empty() ? (*argv)[0] : handoff.entry.c_str());
|
|
538
|
+
for (const auto& a : handoff.user_args) {
|
|
539
|
+
synthetic_argv.push_back(a.c_str());
|
|
540
|
+
}
|
|
541
|
+
effective_argc = static_cast<int>(synthetic_argv.size());
|
|
542
|
+
effective_argv = const_cast<char**>(synthetic_argv.data());
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
args = new tebako::cmdline_args(effective_argc, (const char**)effective_argv);
|
|
95
546
|
args->parse_arguments();
|
|
96
547
|
if (args->with_application()) {
|
|
97
548
|
args->process_package();
|
|
@@ -106,14 +557,131 @@ extern "C" int tebako_main(int* argc, char*** argv)
|
|
|
106
557
|
}
|
|
107
558
|
}
|
|
108
559
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
560
|
+
if (handoff.present && !handoff_failed) {
|
|
561
|
+
/*
|
|
562
|
+
* Launcher ABI handoff (Stage 3B): mount the images the bootstrap
|
|
563
|
+
* named, directly out of its package file(s) -- no extraction.
|
|
564
|
+
*/
|
|
565
|
+
if (handoff.version_seen && handoff.version > TEBAKO_LAUNCHER_ABI_VERSION) {
|
|
566
|
+
printf(
|
|
567
|
+
"Tebako: launcher ABI mismatch -- the bootstrap speaks ABI %u but this runtime supports ABI %u.\n"
|
|
568
|
+
" Refresh the runtime via tebako cache, or re-bundle with a matching tebako-bootstrap.\n",
|
|
569
|
+
handoff.version, TEBAKO_LAUNCHER_ABI_VERSION);
|
|
570
|
+
handoff_failed = true;
|
|
571
|
+
}
|
|
572
|
+
else if (handoff.images.empty()) {
|
|
573
|
+
printf("Tebako: launcher ABI handoff without --tebako-image -- nothing to mount\n");
|
|
574
|
+
handoff_failed = true;
|
|
575
|
+
}
|
|
576
|
+
else if (!resolve_launcher_images(&handoff)) {
|
|
577
|
+
handoff_failed = true; // resolve_launcher_images printed the reason
|
|
578
|
+
}
|
|
579
|
+
else if (data == &gfsData[0]) {
|
|
580
|
+
/* Root image: the one handed over for the package mount point;
|
|
581
|
+
fall back to the first image (mounted at the compiled-in root). */
|
|
582
|
+
size_t root_image = 0;
|
|
583
|
+
for (size_t i = 0; i < handoff.images.size(); ++i) {
|
|
584
|
+
if (mount_point == handoff.images[i].mount_point) {
|
|
585
|
+
root_image = i;
|
|
586
|
+
break;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
data = handoff.images[root_image].window;
|
|
590
|
+
size = static_cast<size_t>(handoff.images[root_image].size);
|
|
591
|
+
root_image_offset = "0";
|
|
592
|
+
for (size_t i = 0; i < handoff.images.size(); ++i) {
|
|
593
|
+
if (i != root_image) {
|
|
594
|
+
extra_slots.push_back({handoff.images[i].mount_point, handoff.images[i].window, handoff.images[i].size});
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
else {
|
|
599
|
+
/* --tebako-run owns the root; every handed-over image mounts extra */
|
|
600
|
+
for (const auto& img : handoff.images) {
|
|
601
|
+
extra_slots.push_back({img.mount_point, img.window, img.size});
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
else if (data == &gfsData[0]) {
|
|
606
|
+
/*
|
|
607
|
+
* Incbin bundle startup: probe the own executable for a tpkg
|
|
608
|
+
* manifest trailer (Stage 3A, spec §4.3).
|
|
609
|
+
* - trailer present: mount each slot at its recorded mount point via
|
|
610
|
+
* the legacy memfs path with the slot's explicit offset
|
|
611
|
+
* - no trailer: classic incbin mount below, unchanged
|
|
612
|
+
* - corrupt trailer: clean startup error (spec §6), no mount at all
|
|
613
|
+
*/
|
|
614
|
+
tpkg_manifest manifest;
|
|
615
|
+
int probe = read_self_manifest(&manifest);
|
|
616
|
+
if (probe < 0) {
|
|
617
|
+
trailer_corrupt = true;
|
|
618
|
+
}
|
|
619
|
+
else if (probe > 0) {
|
|
620
|
+
std::string self = self_executable_path();
|
|
621
|
+
int fd = open_self_executable(self);
|
|
622
|
+
if (fd >= 0 && read_self_image(fd) == 0) {
|
|
623
|
+
close(fd);
|
|
624
|
+
/* Root slot: the one recorded at the package mount point;
|
|
625
|
+
fall back to slot 0 (mounted at the compiled-in root). */
|
|
626
|
+
uint32_t root_slot = 0;
|
|
627
|
+
for (uint32_t i = 0; i < manifest.slot_count; ++i) {
|
|
628
|
+
if (mount_point == manifest.slots[i].mount_point) {
|
|
629
|
+
root_slot = i;
|
|
630
|
+
break;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
/*
|
|
634
|
+
* Mount each slot through a memory window covering exactly the
|
|
635
|
+
* slot's image: the legacy memfs API takes an image offset but
|
|
636
|
+
* no image size, and DwarFS parses up to the end of the view --
|
|
637
|
+
* a whole-file view would let it run into the trailer. The
|
|
638
|
+
* slot's explicit file offset selects the window; the image
|
|
639
|
+
* sits at offset 0 within it.
|
|
640
|
+
*/
|
|
641
|
+
data = self_image.data() + manifest.slots[root_slot].offset;
|
|
642
|
+
size = static_cast<size_t>(manifest.slots[root_slot].size);
|
|
643
|
+
root_image_offset = "0";
|
|
644
|
+
for (uint32_t i = 0; i < manifest.slot_count; ++i) {
|
|
645
|
+
if (i != root_slot) {
|
|
646
|
+
extra_slots.push_back({std::string(manifest.slots[i].mount_point),
|
|
647
|
+
self_image.data() + manifest.slots[i].offset, manifest.slots[i].size});
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
else {
|
|
652
|
+
if (fd >= 0) {
|
|
653
|
+
close(fd);
|
|
654
|
+
}
|
|
655
|
+
printf("Tebako: failed to read package image from '%s': %s\n", self.c_str(), strerror(errno));
|
|
656
|
+
trailer_corrupt = true;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
if (!trailer_corrupt && !handoff_failed) {
|
|
662
|
+
fsret = mount_root_memfs(data, size, tebako::fs_log_level, nullptr, nullptr, nullptr, nullptr,
|
|
663
|
+
root_image_offset.c_str());
|
|
664
|
+
if (fsret == 0) {
|
|
665
|
+
for (const auto& slot : extra_slots) {
|
|
666
|
+
// mount_memfs_at_root returns the memfs table index on success,
|
|
667
|
+
// -1 on failure
|
|
668
|
+
if (mount_memfs_at_root(slot.window, static_cast<unsigned int>(slot.size), "0",
|
|
669
|
+
slot.mount_point.c_str()) == -1) {
|
|
670
|
+
printf("Tebako: failed to mount package slot at '%s'\n", slot.mount_point.c_str());
|
|
671
|
+
unmount_root_memfs(); // spec §6: never a partial mount
|
|
672
|
+
fsret = -1;
|
|
673
|
+
break;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
if (fsret == 0) {
|
|
678
|
+
args->process_mountpoints();
|
|
679
|
+
args->build_arguments(mount_point.c_str(), entry_point.c_str());
|
|
680
|
+
*argc = args->get_argc();
|
|
681
|
+
*argv = args->get_argv();
|
|
682
|
+
ret = 0;
|
|
683
|
+
atexit(tebako_clean);
|
|
684
|
+
}
|
|
117
685
|
}
|
|
118
686
|
}
|
|
119
687
|
catch (std::exception e) {
|
|
@@ -48,7 +48,7 @@ jobs:
|
|
|
48
48
|
strategy:
|
|
49
49
|
fail-fast: false
|
|
50
50
|
matrix:
|
|
51
|
-
os: [ macos-
|
|
51
|
+
os: [ macos-14 ]
|
|
52
52
|
steps:
|
|
53
53
|
- name: Checkout
|
|
54
54
|
uses: actions/checkout@v4
|
|
@@ -136,7 +136,7 @@ jobs:
|
|
|
136
136
|
strategy:
|
|
137
137
|
fail-fast: false
|
|
138
138
|
matrix:
|
|
139
|
-
os: [ macos-
|
|
139
|
+
os: [ macos-14 ]
|
|
140
140
|
steps:
|
|
141
141
|
- name: Checkout
|
|
142
142
|
uses: actions/checkout@v4
|
|
@@ -193,7 +193,7 @@ jobs:
|
|
|
193
193
|
strategy:
|
|
194
194
|
fail-fast: false
|
|
195
195
|
matrix:
|
|
196
|
-
os: [ macos-
|
|
196
|
+
os: [ macos-14 ]
|
|
197
197
|
steps:
|
|
198
198
|
- name: Checkout
|
|
199
199
|
uses: actions/checkout@v4
|
|
@@ -245,7 +245,7 @@ jobs:
|
|
|
245
245
|
strategy:
|
|
246
246
|
fail-fast: false
|
|
247
247
|
matrix:
|
|
248
|
-
os: [ macos-
|
|
248
|
+
os: [ macos-14 ]
|
|
249
249
|
steps:
|
|
250
250
|
- name: Checkout
|
|
251
251
|
uses: actions/checkout@v4
|
|
@@ -291,7 +291,7 @@ jobs:
|
|
|
291
291
|
|
|
292
292
|
test-cross-brew-install:
|
|
293
293
|
name: test arm-brew-setup/install
|
|
294
|
-
runs-on: macos-
|
|
294
|
+
runs-on: macos-14
|
|
295
295
|
continue-on-error: true
|
|
296
296
|
steps:
|
|
297
297
|
- name: Checkout
|
|
@@ -315,7 +315,7 @@ jobs:
|
|
|
315
315
|
strategy:
|
|
316
316
|
fail-fast: false
|
|
317
317
|
matrix:
|
|
318
|
-
os: [ macos-
|
|
318
|
+
os: [ macos-14 ]
|
|
319
319
|
|
|
320
320
|
steps:
|
|
321
321
|
- name: Checkout
|
|
@@ -59,9 +59,18 @@ if (CMAKE_HOST_SYSTEM_NAME MATCHES "Darwin")
|
|
|
59
59
|
set(OPENSSL_VERSION "3")
|
|
60
60
|
endif()
|
|
61
61
|
set(OPENSSL_ROOT_DIR "${BREW_PREFIX}/opt/openssl@${OPENSSL_VERSION}")
|
|
62
|
+
|
|
63
|
+
# IMPORTANT: Set CMAKE_PREFIX_PATH but do NOT add Homebrew include directories
|
|
64
|
+
# globally. vcpkg provides our dependencies and its includes must take precedence.
|
|
65
|
+
# Homebrew's include paths would conflict with vcpkg packages (e.g., dwarfs).
|
|
66
|
+
# Only add specific Homebrew package includes when explicitly needed via target_*
|
|
67
|
+
# functions, not global include_directories().
|
|
68
|
+
#
|
|
69
|
+
# Previously this file had:
|
|
70
|
+
# include_directories("${OPENSSL_ROOT_DIR}/include")
|
|
71
|
+
# include_directories("${BREW_PREFIX}/include")
|
|
72
|
+
# These caused Homebrew's dwarfs headers to shadow vcpkg's newer dwarfs headers.
|
|
62
73
|
set(CMAKE_PREFIX_PATH "${BREW_PREFIX}")
|
|
63
|
-
include_directories("${OPENSSL_ROOT_DIR}/include")
|
|
64
|
-
include_directories("${BREW_PREFIX}/include")
|
|
65
74
|
|
|
66
75
|
# https://stackoverflow.com/questions/53877344/cannot-configure-cmake-to-look-for-homebrew-installed-version-of-bison
|
|
67
76
|
find_and_set_homebrew_prefix("bison" "BISON" "BISON_EXECUTABLE")
|