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
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file tpkg.h
|
|
3
|
+
* @brief tebako package manifest (tpkg) — single-header C99 mini-lib
|
|
4
|
+
*
|
|
5
|
+
* Copyright (c) 2026 [Ribose Inc](https://www.ribose.com).
|
|
6
|
+
* All rights reserved.
|
|
7
|
+
* This file is a part of the Tebako project (libtfs).
|
|
8
|
+
*
|
|
9
|
+
* Redistribution and use in source and binary forms, with or without
|
|
10
|
+
* modification, are permitted provided that the following conditions
|
|
11
|
+
* are met:
|
|
12
|
+
* 1. Redistributions of source code must retain the above copyright
|
|
13
|
+
* notice, this list of conditions and the following disclaimer.
|
|
14
|
+
* 2. Redistributions in binary form must reproduce the above copyright
|
|
15
|
+
* notice, this list of conditions and the following disclaimer in the
|
|
16
|
+
* documentation and/or other materials provided with the distribution.
|
|
17
|
+
*
|
|
18
|
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
19
|
+
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
|
20
|
+
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
21
|
+
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
|
|
22
|
+
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
23
|
+
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
24
|
+
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
|
25
|
+
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
26
|
+
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
|
27
|
+
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
|
28
|
+
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
29
|
+
*
|
|
30
|
+
* ============================================================================
|
|
31
|
+
* Manifest trailer v1 — wire layout (all integers little-endian):
|
|
32
|
+
*
|
|
33
|
+
* [payload][slot 0 .. slot n-1 records][trailer header — fixed size, at EOF]
|
|
34
|
+
*
|
|
35
|
+
* trailer header (TPKG_HEADER_SIZE = 166 bytes):
|
|
36
|
+
* offset size field
|
|
37
|
+
* 0 10 magic "TEBAKOTFS\0" (10 bytes, NUL-terminated)
|
|
38
|
+
* 10 4 u32 version (TPKG_VERSION = 1)
|
|
39
|
+
* 14 4 u32 package_flags (bit 0: TPKG_FLAG_LEAN — bootstrap+images
|
|
40
|
+
* only, runtime resolved at run time)
|
|
41
|
+
* 18 4 u32 slot_count (1..TPKG_MAX_SLOTS)
|
|
42
|
+
* 22 8 u64 slot_table_offset (absolute file offset of slot 0 record)
|
|
43
|
+
* 30 128 char runtime_ref[128] (UTF-8, NUL-padded; empty = classic bundle)
|
|
44
|
+
* 158 4 u32 launcher_abi
|
|
45
|
+
* 162 4 u32 header_crc32 — tpkg_crc32() over header bytes [0, 162)
|
|
46
|
+
*
|
|
47
|
+
* slot record (TPKG_SLOT_SIZE = 280 bytes):
|
|
48
|
+
* offset size field
|
|
49
|
+
* 0 8 u64 offset (image start, absolute file offset)
|
|
50
|
+
* 8 8 u64 size (image length in bytes)
|
|
51
|
+
* 16 4 u32 format_id (0=auto(magic), 1=dwarfs, 2=squashfs, 3=zip)
|
|
52
|
+
* 20 4 u32 flags
|
|
53
|
+
* 24 256 char mount_point[256] (UTF-8, NUL-padded)
|
|
54
|
+
*
|
|
55
|
+
* Reader algorithm: read the fixed-size header at EOF - TPKG_HEADER_SIZE,
|
|
56
|
+
* check the magic, verify header_crc32, then read slot_count slot records at
|
|
57
|
+
* slot_table_offset. The table (and therefore the payload) may sit at any —
|
|
58
|
+
* including odd — file offset.
|
|
59
|
+
*
|
|
60
|
+
* Absent vs. corrupt: a file whose last-166-byte window does not start with
|
|
61
|
+
* the 4-byte prefix "TEBA" is reported as TPKG_ERR_NO_TRAILER (a classic
|
|
62
|
+
* bundle without a manifest — not an error condition per se; callers fall
|
|
63
|
+
* back to offset auto-detection). A matching prefix with a mismatching full
|
|
64
|
+
* magic is TPKG_ERR_MAGIC (corrupt trailer); a magic-valid header with a bad
|
|
65
|
+
* crc is TPKG_ERR_CRC.
|
|
66
|
+
*
|
|
67
|
+
* Error handling: all tpkg_* functions (except tpkg_crc32, tpkg_errno and
|
|
68
|
+
* tpkg_strerror) return 0 on success and -1 on failure, with a TPKG_ERR_*
|
|
69
|
+
* code available from tpkg_errno(). The error state is a process-global
|
|
70
|
+
* (ISO C99 has no portable TLS); it is reset at the entry of every public
|
|
71
|
+
* function and is NOT thread-safe.
|
|
72
|
+
*
|
|
73
|
+
* Usage: in exactly one translation unit, #define TPKG_IMPLEMENTATION before
|
|
74
|
+
* #include <tebako/tpkg.h>; every other TU includes it plain. On POSIX the
|
|
75
|
+
* implementation section defines _POSIX_C_SOURCE 200809L if it is not
|
|
76
|
+
* defined yet, so include tpkg.h before any system header in that TU.
|
|
77
|
+
* No dependencies beyond libc.
|
|
78
|
+
* ============================================================================
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
#ifndef TEBAKO_TPKG_H
|
|
82
|
+
#define TEBAKO_TPKG_H
|
|
83
|
+
|
|
84
|
+
#include <stddef.h>
|
|
85
|
+
#include <stdint.h>
|
|
86
|
+
|
|
87
|
+
#ifdef __cplusplus
|
|
88
|
+
extern "C" {
|
|
89
|
+
#endif
|
|
90
|
+
|
|
91
|
+
/* ---- format constants ---------------------------------------------------- */
|
|
92
|
+
|
|
93
|
+
#define TPKG_VERSION 1u
|
|
94
|
+
#define TPKG_MAX_SLOTS 8u
|
|
95
|
+
#define TPKG_HEADER_SIZE 166u
|
|
96
|
+
#define TPKG_SLOT_SIZE 280u
|
|
97
|
+
#define TPKG_MOUNT_POINT_LEN 256u
|
|
98
|
+
#define TPKG_RUNTIME_REF_LEN 128u
|
|
99
|
+
#define TPKG_MAGIC "TEBAKOTFS"
|
|
100
|
+
#define TPKG_MAGIC_LEN 10u /* including the terminating NUL */
|
|
101
|
+
#define TPKG_MAGIC_PREFIX_LEN 4u /* "TEBA": absent-vs-corrupt discriminator */
|
|
102
|
+
|
|
103
|
+
/* package_flags */
|
|
104
|
+
#define TPKG_FLAG_LEAN 0x1u
|
|
105
|
+
|
|
106
|
+
/* format_id */
|
|
107
|
+
#define TPKG_FORMAT_AUTO 0u
|
|
108
|
+
#define TPKG_FORMAT_DWARFS 1u
|
|
109
|
+
#define TPKG_FORMAT_SQUASHFS 2u
|
|
110
|
+
#define TPKG_FORMAT_ZIP 3u
|
|
111
|
+
|
|
112
|
+
/* ---- error codes (returned by tpkg_errno) -------------------------------- */
|
|
113
|
+
|
|
114
|
+
enum {
|
|
115
|
+
TPKG_OK = 0, /* success */
|
|
116
|
+
TPKG_ERR_NO_TRAILER = 1, /* no manifest trailer present (absent — not an error per se) */
|
|
117
|
+
TPKG_ERR_MAGIC = 2, /* magic prefix present but full magic mismatch (corrupt) */
|
|
118
|
+
TPKG_ERR_CRC = 3, /* header_crc32 mismatch (corrupt) */
|
|
119
|
+
TPKG_ERR_IO = 4, /* underlying i/o failure */
|
|
120
|
+
TPKG_ERR_BOUNDS = 5, /* slot table outside file bounds */
|
|
121
|
+
TPKG_ERR_SLOTS = 6, /* slot_count == 0 or > TPKG_MAX_SLOTS */
|
|
122
|
+
TPKG_ERR_INVALID = 7, /* structural validation failure */
|
|
123
|
+
TPKG_ERR_ARG = 8, /* invalid argument (NULL, bad fd) */
|
|
124
|
+
TPKG_ERR_VERSION = 9 /* unsupported manifest version */
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/* ---- manifest structures -------------------------------------------------- */
|
|
128
|
+
|
|
129
|
+
typedef struct tpkg_slot {
|
|
130
|
+
uint64_t offset;
|
|
131
|
+
uint64_t size;
|
|
132
|
+
uint32_t format_id;
|
|
133
|
+
uint32_t flags;
|
|
134
|
+
char mount_point[TPKG_MOUNT_POINT_LEN];
|
|
135
|
+
} tpkg_slot;
|
|
136
|
+
|
|
137
|
+
typedef struct tpkg_manifest {
|
|
138
|
+
uint32_t version;
|
|
139
|
+
uint32_t package_flags;
|
|
140
|
+
uint32_t slot_count;
|
|
141
|
+
uint32_t launcher_abi;
|
|
142
|
+
char runtime_ref[TPKG_RUNTIME_REF_LEN];
|
|
143
|
+
tpkg_slot slots[TPKG_MAX_SLOTS];
|
|
144
|
+
} tpkg_manifest;
|
|
145
|
+
|
|
146
|
+
/* ---- API ------------------------------------------------------------------ */
|
|
147
|
+
|
|
148
|
+
/* Read the manifest trailer of an open binary (seeks to EOF; the file offset
|
|
149
|
+
* is left unspecified). */
|
|
150
|
+
int tpkg_read_fd(int fd, tpkg_manifest* out);
|
|
151
|
+
|
|
152
|
+
/* Read the manifest trailer from an in-memory image of the binary. */
|
|
153
|
+
int tpkg_read_mem(const void* data, size_t size, tpkg_manifest* out);
|
|
154
|
+
|
|
155
|
+
/* Append the slot table + trailer header to an open binary (at current EOF).
|
|
156
|
+
* The manifest is validated first; a rejected manifest appends nothing. */
|
|
157
|
+
int tpkg_write_fd(int fd, const tpkg_manifest* m);
|
|
158
|
+
|
|
159
|
+
/* Magic-independent structural checks: version supported, slot_count in
|
|
160
|
+
* 1..TPKG_MAX_SLOTS, offset+size non-overflowing, format_id <= TPKG_FORMAT_ZIP,
|
|
161
|
+
* runtime_ref and mount_points NUL-terminated within their fixed fields. */
|
|
162
|
+
int tpkg_validate(const tpkg_manifest* m);
|
|
163
|
+
|
|
164
|
+
/* CRC-32 (zlib polynomial 0xEDB88320, init/xorout 0xFFFFFFFF) — exposed for
|
|
165
|
+
* header_crc32 computation and verification. Pure; does not touch tpkg_errno. */
|
|
166
|
+
uint32_t tpkg_crc32(const void* data, size_t n);
|
|
167
|
+
|
|
168
|
+
/* Code of the last failed (or succeeded) tpkg operation on this process. */
|
|
169
|
+
int tpkg_errno(void);
|
|
170
|
+
|
|
171
|
+
/* Static string for a TPKG_ERR_* code (never NULL). */
|
|
172
|
+
const char* tpkg_strerror(int err);
|
|
173
|
+
|
|
174
|
+
#ifdef __cplusplus
|
|
175
|
+
}
|
|
176
|
+
#endif
|
|
177
|
+
|
|
178
|
+
#endif /* TEBAKO_TPKG_H */
|
|
179
|
+
|
|
180
|
+
/* ~~~~~~~~~~~~~~~~~~~~~~~~~ implementation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
|
181
|
+
|
|
182
|
+
#if defined(TPKG_IMPLEMENTATION) && !defined(TPKG_IMPLEMENTATION_DONE)
|
|
183
|
+
#define TPKG_IMPLEMENTATION_DONE
|
|
184
|
+
|
|
185
|
+
#include <stdio.h> /* SEEK_SET / SEEK_END */
|
|
186
|
+
#include <string.h>
|
|
187
|
+
|
|
188
|
+
#if defined(_WIN32)
|
|
189
|
+
#include <io.h>
|
|
190
|
+
typedef __int64 tpkg__off_t;
|
|
191
|
+
typedef int tpkg__ssize_t;
|
|
192
|
+
typedef unsigned int tpkg__io_count_t;
|
|
193
|
+
#define tpkg__lseek _lseeki64
|
|
194
|
+
#define tpkg__read _read
|
|
195
|
+
#define tpkg__write _write
|
|
196
|
+
#else
|
|
197
|
+
#if !defined(_POSIX_C_SOURCE)
|
|
198
|
+
#define _POSIX_C_SOURCE 200809L
|
|
199
|
+
#endif
|
|
200
|
+
#include <sys/types.h>
|
|
201
|
+
#include <unistd.h>
|
|
202
|
+
typedef off_t tpkg__off_t;
|
|
203
|
+
typedef ssize_t tpkg__ssize_t;
|
|
204
|
+
typedef size_t tpkg__io_count_t;
|
|
205
|
+
#define tpkg__lseek lseek
|
|
206
|
+
#define tpkg__read read
|
|
207
|
+
#define tpkg__write write
|
|
208
|
+
#endif
|
|
209
|
+
|
|
210
|
+
/* header field offsets */
|
|
211
|
+
enum {
|
|
212
|
+
TPKG__OFF_MAGIC = 0,
|
|
213
|
+
TPKG__OFF_VERSION = 10,
|
|
214
|
+
TPKG__OFF_PACKAGE_FLAGS = 14,
|
|
215
|
+
TPKG__OFF_SLOT_COUNT = 18,
|
|
216
|
+
TPKG__OFF_TABLE = 22,
|
|
217
|
+
TPKG__OFF_RUNTIME_REF = 30,
|
|
218
|
+
TPKG__OFF_LAUNCHER_ABI = 158,
|
|
219
|
+
TPKG__OFF_CRC32 = 162
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
/* slot record field offsets */
|
|
223
|
+
enum {
|
|
224
|
+
TPKG__REC_OFFSET = 0,
|
|
225
|
+
TPKG__REC_SIZE = 8,
|
|
226
|
+
TPKG__REC_FORMAT = 16,
|
|
227
|
+
TPKG__REC_FLAGS = 20,
|
|
228
|
+
TPKG__REC_MOUNT = 24
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
/* wire sizes are fixed by the format; catch exotic padding at compile time */
|
|
232
|
+
typedef char tpkg__assert_slot_size[(sizeof(tpkg_slot) == TPKG_SLOT_SIZE) ? 1 : -1];
|
|
233
|
+
typedef char tpkg__assert_manifest_size[(sizeof(tpkg_manifest) ==
|
|
234
|
+
4 * sizeof(uint32_t) + TPKG_RUNTIME_REF_LEN +
|
|
235
|
+
TPKG_MAX_SLOTS * sizeof(tpkg_slot))
|
|
236
|
+
? 1
|
|
237
|
+
: -1];
|
|
238
|
+
|
|
239
|
+
/* ---- error state ---------------------------------------------------------- */
|
|
240
|
+
|
|
241
|
+
static int tpkg__last_err = TPKG_OK;
|
|
242
|
+
|
|
243
|
+
int tpkg_errno(void)
|
|
244
|
+
{
|
|
245
|
+
return tpkg__last_err;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
static int tpkg__fail(int code)
|
|
249
|
+
{
|
|
250
|
+
tpkg__last_err = code;
|
|
251
|
+
return -1;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const char* tpkg_strerror(int err)
|
|
255
|
+
{
|
|
256
|
+
switch (err) {
|
|
257
|
+
case TPKG_OK:
|
|
258
|
+
return "success";
|
|
259
|
+
case TPKG_ERR_NO_TRAILER:
|
|
260
|
+
return "no tpkg manifest trailer present";
|
|
261
|
+
case TPKG_ERR_MAGIC:
|
|
262
|
+
return "corrupt tpkg trailer magic";
|
|
263
|
+
case TPKG_ERR_CRC:
|
|
264
|
+
return "tpkg trailer header crc32 mismatch";
|
|
265
|
+
case TPKG_ERR_IO:
|
|
266
|
+
return "tpkg i/o error";
|
|
267
|
+
case TPKG_ERR_BOUNDS:
|
|
268
|
+
return "tpkg slot table out of file bounds";
|
|
269
|
+
case TPKG_ERR_SLOTS:
|
|
270
|
+
return "tpkg slot count out of range (1..TPKG_MAX_SLOTS)";
|
|
271
|
+
case TPKG_ERR_INVALID:
|
|
272
|
+
return "invalid tpkg manifest structure";
|
|
273
|
+
case TPKG_ERR_ARG:
|
|
274
|
+
return "invalid tpkg argument";
|
|
275
|
+
case TPKG_ERR_VERSION:
|
|
276
|
+
return "unsupported tpkg manifest version";
|
|
277
|
+
default:
|
|
278
|
+
return "unknown tpkg error";
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/* ---- CRC-32 (zlib polynomial) -------------------------------------------- */
|
|
283
|
+
|
|
284
|
+
uint32_t tpkg_crc32(const void* data, size_t n)
|
|
285
|
+
{
|
|
286
|
+
const uint8_t* p = (const uint8_t*)data;
|
|
287
|
+
uint32_t crc = 0xFFFFFFFFu;
|
|
288
|
+
size_t i;
|
|
289
|
+
int k;
|
|
290
|
+
for (i = 0; i < n; i++) {
|
|
291
|
+
crc ^= p[i];
|
|
292
|
+
for (k = 0; k < 8; k++) {
|
|
293
|
+
crc = (crc >> 1) ^ (0xEDB88320u & (0u - (crc & 1u)));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return crc ^ 0xFFFFFFFFu;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/* ---- little-endian codec -------------------------------------------------- */
|
|
300
|
+
|
|
301
|
+
static void tpkg__put32(uint8_t* p, uint32_t v)
|
|
302
|
+
{
|
|
303
|
+
p[0] = (uint8_t)(v & 0xFFu);
|
|
304
|
+
p[1] = (uint8_t)((v >> 8) & 0xFFu);
|
|
305
|
+
p[2] = (uint8_t)((v >> 16) & 0xFFu);
|
|
306
|
+
p[3] = (uint8_t)((v >> 24) & 0xFFu);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
static void tpkg__put64(uint8_t* p, uint64_t v)
|
|
310
|
+
{
|
|
311
|
+
int i;
|
|
312
|
+
for (i = 0; i < 8; i++) {
|
|
313
|
+
p[i] = (uint8_t)((v >> (8 * i)) & 0xFFu);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
static uint32_t tpkg__get32(const uint8_t* p)
|
|
318
|
+
{
|
|
319
|
+
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
static uint64_t tpkg__get64(const uint8_t* p)
|
|
323
|
+
{
|
|
324
|
+
uint64_t v = 0;
|
|
325
|
+
int i;
|
|
326
|
+
for (i = 0; i < 8; i++) {
|
|
327
|
+
v |= (uint64_t)p[i] << (8 * i);
|
|
328
|
+
}
|
|
329
|
+
return v;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/* ---- helpers -------------------------------------------------------------- */
|
|
333
|
+
|
|
334
|
+
static size_t tpkg__strnlen(const char* s, size_t max)
|
|
335
|
+
{
|
|
336
|
+
const char* nul = (const char*)memchr(s, '\0', max);
|
|
337
|
+
return nul ? (size_t)(nul - s) : max;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/* copy a C string into a fixed-width field, zero-padding the remainder;
|
|
341
|
+
* callers must have validated NUL-termination within width */
|
|
342
|
+
static void tpkg__put_str(uint8_t* p, const char* s, size_t width)
|
|
343
|
+
{
|
|
344
|
+
size_t len = tpkg__strnlen(s, width);
|
|
345
|
+
memcpy(p, s, len);
|
|
346
|
+
memset(p + len, 0, width - len);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
static int tpkg__read_full(int fd, void* buf, size_t n)
|
|
350
|
+
{
|
|
351
|
+
uint8_t* p = (uint8_t*)buf;
|
|
352
|
+
while (n > 0) {
|
|
353
|
+
tpkg__ssize_t r = tpkg__read(fd, p, (tpkg__io_count_t)n);
|
|
354
|
+
if (r <= 0) {
|
|
355
|
+
return -1;
|
|
356
|
+
}
|
|
357
|
+
p += (size_t)r;
|
|
358
|
+
n -= (size_t)r;
|
|
359
|
+
}
|
|
360
|
+
return 0;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
static int tpkg__write_full(int fd, const void* buf, size_t n)
|
|
364
|
+
{
|
|
365
|
+
const uint8_t* p = (const uint8_t*)buf;
|
|
366
|
+
while (n > 0) {
|
|
367
|
+
tpkg__ssize_t r = tpkg__write(fd, p, (tpkg__io_count_t)n);
|
|
368
|
+
if (r <= 0) {
|
|
369
|
+
return -1;
|
|
370
|
+
}
|
|
371
|
+
p += (size_t)r;
|
|
372
|
+
n -= (size_t)r;
|
|
373
|
+
}
|
|
374
|
+
return 0;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/* ---- validation ------------------------------------------------------------ */
|
|
378
|
+
|
|
379
|
+
int tpkg_validate(const tpkg_manifest* m)
|
|
380
|
+
{
|
|
381
|
+
uint32_t i;
|
|
382
|
+
tpkg__last_err = TPKG_OK;
|
|
383
|
+
if (m == NULL) {
|
|
384
|
+
return tpkg__fail(TPKG_ERR_ARG);
|
|
385
|
+
}
|
|
386
|
+
if (m->version != TPKG_VERSION) {
|
|
387
|
+
return tpkg__fail(TPKG_ERR_VERSION);
|
|
388
|
+
}
|
|
389
|
+
if (m->slot_count == 0 || m->slot_count > TPKG_MAX_SLOTS) {
|
|
390
|
+
return tpkg__fail(TPKG_ERR_SLOTS);
|
|
391
|
+
}
|
|
392
|
+
if (tpkg__strnlen(m->runtime_ref, TPKG_RUNTIME_REF_LEN) == TPKG_RUNTIME_REF_LEN) {
|
|
393
|
+
return tpkg__fail(TPKG_ERR_INVALID);
|
|
394
|
+
}
|
|
395
|
+
for (i = 0; i < m->slot_count; i++) {
|
|
396
|
+
const tpkg_slot* s = &m->slots[i];
|
|
397
|
+
if (s->size > UINT64_MAX - s->offset) {
|
|
398
|
+
return tpkg__fail(TPKG_ERR_INVALID);
|
|
399
|
+
}
|
|
400
|
+
if (s->format_id > TPKG_FORMAT_ZIP) {
|
|
401
|
+
return tpkg__fail(TPKG_ERR_INVALID);
|
|
402
|
+
}
|
|
403
|
+
if (tpkg__strnlen(s->mount_point, TPKG_MOUNT_POINT_LEN) == TPKG_MOUNT_POINT_LEN) {
|
|
404
|
+
return tpkg__fail(TPKG_ERR_INVALID);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return 0;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/* ---- reader core ------------------------------------------------------------ */
|
|
411
|
+
|
|
412
|
+
/* read exactly n bytes at absolute offset off (off+n guaranteed in bounds) */
|
|
413
|
+
typedef int (*tpkg__readat_fn)(void* ctx, uint64_t off, void* buf, size_t n);
|
|
414
|
+
|
|
415
|
+
static int tpkg__mem_readat(void* ctx, uint64_t off, void* buf, size_t n)
|
|
416
|
+
{
|
|
417
|
+
memcpy(buf, (const uint8_t*)ctx + off, n);
|
|
418
|
+
return 0;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
static int tpkg__fd_readat(void* ctx, uint64_t off, void* buf, size_t n)
|
|
422
|
+
{
|
|
423
|
+
int fd = *(int*)ctx;
|
|
424
|
+
if (tpkg__lseek(fd, (tpkg__off_t)off, SEEK_SET) == (tpkg__off_t)-1) {
|
|
425
|
+
return -1;
|
|
426
|
+
}
|
|
427
|
+
return tpkg__read_full(fd, buf, n);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
static int tpkg__read_core(tpkg__readat_fn readat, void* ctx, uint64_t size, tpkg_manifest* out)
|
|
431
|
+
{
|
|
432
|
+
uint8_t hdr[TPKG_HEADER_SIZE];
|
|
433
|
+
uint8_t table[TPKG_MAX_SLOTS * TPKG_SLOT_SIZE];
|
|
434
|
+
uint64_t table_off;
|
|
435
|
+
uint64_t avail;
|
|
436
|
+
uint32_t version;
|
|
437
|
+
uint32_t slot_count;
|
|
438
|
+
uint32_t i;
|
|
439
|
+
|
|
440
|
+
if (size < TPKG_HEADER_SIZE) {
|
|
441
|
+
return tpkg__fail(TPKG_ERR_NO_TRAILER);
|
|
442
|
+
}
|
|
443
|
+
if (readat(ctx, size - TPKG_HEADER_SIZE, hdr, TPKG_HEADER_SIZE) != 0) {
|
|
444
|
+
return tpkg__fail(TPKG_ERR_IO);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/* absent vs corrupt: no "TEBA" prefix -> classic bundle, no trailer */
|
|
448
|
+
if (memcmp(hdr + TPKG__OFF_MAGIC, TPKG_MAGIC, TPKG_MAGIC_PREFIX_LEN) != 0) {
|
|
449
|
+
return tpkg__fail(TPKG_ERR_NO_TRAILER);
|
|
450
|
+
}
|
|
451
|
+
if (memcmp(hdr + TPKG__OFF_MAGIC, TPKG_MAGIC, TPKG_MAGIC_LEN) != 0) {
|
|
452
|
+
return tpkg__fail(TPKG_ERR_MAGIC);
|
|
453
|
+
}
|
|
454
|
+
if (tpkg_crc32(hdr, TPKG__OFF_CRC32) != tpkg__get32(hdr + TPKG__OFF_CRC32)) {
|
|
455
|
+
return tpkg__fail(TPKG_ERR_CRC);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
version = tpkg__get32(hdr + TPKG__OFF_VERSION);
|
|
459
|
+
if (version != TPKG_VERSION) {
|
|
460
|
+
return tpkg__fail(TPKG_ERR_VERSION);
|
|
461
|
+
}
|
|
462
|
+
slot_count = tpkg__get32(hdr + TPKG__OFF_SLOT_COUNT);
|
|
463
|
+
if (slot_count == 0 || slot_count > TPKG_MAX_SLOTS) {
|
|
464
|
+
return tpkg__fail(TPKG_ERR_SLOTS);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
table_off = tpkg__get64(hdr + TPKG__OFF_TABLE);
|
|
468
|
+
avail = size - TPKG_HEADER_SIZE; /* bytes preceding the header */
|
|
469
|
+
/* overflow-free: table must fit entirely before the header */
|
|
470
|
+
if (table_off > avail || (uint64_t)slot_count > (avail - table_off) / TPKG_SLOT_SIZE) {
|
|
471
|
+
return tpkg__fail(TPKG_ERR_BOUNDS);
|
|
472
|
+
}
|
|
473
|
+
if (readat(ctx, table_off, table, (size_t)slot_count * TPKG_SLOT_SIZE) != 0) {
|
|
474
|
+
return tpkg__fail(TPKG_ERR_IO);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
memset(out, 0, sizeof *out);
|
|
478
|
+
out->version = version;
|
|
479
|
+
out->package_flags = tpkg__get32(hdr + TPKG__OFF_PACKAGE_FLAGS);
|
|
480
|
+
out->slot_count = slot_count;
|
|
481
|
+
out->launcher_abi = tpkg__get32(hdr + TPKG__OFF_LAUNCHER_ABI);
|
|
482
|
+
memcpy(out->runtime_ref, hdr + TPKG__OFF_RUNTIME_REF, TPKG_RUNTIME_REF_LEN);
|
|
483
|
+
for (i = 0; i < slot_count; i++) {
|
|
484
|
+
const uint8_t* rec = table + (size_t)i * TPKG_SLOT_SIZE;
|
|
485
|
+
tpkg_slot* s = &out->slots[i];
|
|
486
|
+
s->offset = tpkg__get64(rec + TPKG__REC_OFFSET);
|
|
487
|
+
s->size = tpkg__get64(rec + TPKG__REC_SIZE);
|
|
488
|
+
s->format_id = tpkg__get32(rec + TPKG__REC_FORMAT);
|
|
489
|
+
s->flags = tpkg__get32(rec + TPKG__REC_FLAGS);
|
|
490
|
+
memcpy(s->mount_point, rec + TPKG__REC_MOUNT, TPKG_MOUNT_POINT_LEN);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/* structural sanity of the parsed manifest (a valid crc does not imply
|
|
494
|
+
* well-formed fields when the trailer was not written by us) */
|
|
495
|
+
return tpkg_validate(out);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
int tpkg_read_fd(int fd, tpkg_manifest* out)
|
|
499
|
+
{
|
|
500
|
+
tpkg__off_t end;
|
|
501
|
+
tpkg__last_err = TPKG_OK;
|
|
502
|
+
if (fd < 0 || out == NULL) {
|
|
503
|
+
return tpkg__fail(TPKG_ERR_ARG);
|
|
504
|
+
}
|
|
505
|
+
end = tpkg__lseek(fd, 0, SEEK_END);
|
|
506
|
+
if (end == (tpkg__off_t)-1) {
|
|
507
|
+
return tpkg__fail(TPKG_ERR_IO);
|
|
508
|
+
}
|
|
509
|
+
return tpkg__read_core(tpkg__fd_readat, &fd, (uint64_t)end, out);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
int tpkg_read_mem(const void* data, size_t size, tpkg_manifest* out)
|
|
513
|
+
{
|
|
514
|
+
tpkg__last_err = TPKG_OK;
|
|
515
|
+
if (data == NULL || out == NULL) {
|
|
516
|
+
return tpkg__fail(TPKG_ERR_ARG);
|
|
517
|
+
}
|
|
518
|
+
return tpkg__read_core(tpkg__mem_readat, (void*)data, (uint64_t)size, out);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/* ---- writer ----------------------------------------------------------------- */
|
|
522
|
+
|
|
523
|
+
int tpkg_write_fd(int fd, const tpkg_manifest* m)
|
|
524
|
+
{
|
|
525
|
+
uint8_t buf[TPKG_MAX_SLOTS * TPKG_SLOT_SIZE + TPKG_HEADER_SIZE];
|
|
526
|
+
uint8_t* p = buf;
|
|
527
|
+
uint8_t* hdr;
|
|
528
|
+
tpkg__off_t end;
|
|
529
|
+
size_t total;
|
|
530
|
+
uint32_t i;
|
|
531
|
+
|
|
532
|
+
tpkg__last_err = TPKG_OK;
|
|
533
|
+
if (fd < 0 || m == NULL) {
|
|
534
|
+
return tpkg__fail(TPKG_ERR_ARG);
|
|
535
|
+
}
|
|
536
|
+
if (tpkg_validate(m) != 0) {
|
|
537
|
+
return -1; /* tpkg_errno set by tpkg_validate; nothing appended */
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
end = tpkg__lseek(fd, 0, SEEK_END);
|
|
541
|
+
if (end == (tpkg__off_t)-1) {
|
|
542
|
+
return tpkg__fail(TPKG_ERR_IO);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/* slot table */
|
|
546
|
+
for (i = 0; i < m->slot_count; i++) {
|
|
547
|
+
const tpkg_slot* s = &m->slots[i];
|
|
548
|
+
tpkg__put64(p + TPKG__REC_OFFSET, s->offset);
|
|
549
|
+
tpkg__put64(p + TPKG__REC_SIZE, s->size);
|
|
550
|
+
tpkg__put32(p + TPKG__REC_FORMAT, s->format_id);
|
|
551
|
+
tpkg__put32(p + TPKG__REC_FLAGS, s->flags);
|
|
552
|
+
tpkg__put_str(p + TPKG__REC_MOUNT, s->mount_point, TPKG_MOUNT_POINT_LEN);
|
|
553
|
+
p += TPKG_SLOT_SIZE;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/* trailer header */
|
|
557
|
+
hdr = p;
|
|
558
|
+
memcpy(hdr + TPKG__OFF_MAGIC, TPKG_MAGIC, TPKG_MAGIC_LEN);
|
|
559
|
+
tpkg__put32(hdr + TPKG__OFF_VERSION, m->version);
|
|
560
|
+
tpkg__put32(hdr + TPKG__OFF_PACKAGE_FLAGS, m->package_flags);
|
|
561
|
+
tpkg__put32(hdr + TPKG__OFF_SLOT_COUNT, m->slot_count);
|
|
562
|
+
tpkg__put64(hdr + TPKG__OFF_TABLE, (uint64_t)end);
|
|
563
|
+
tpkg__put_str(hdr + TPKG__OFF_RUNTIME_REF, m->runtime_ref, TPKG_RUNTIME_REF_LEN);
|
|
564
|
+
tpkg__put32(hdr + TPKG__OFF_LAUNCHER_ABI, m->launcher_abi);
|
|
565
|
+
tpkg__put32(hdr + TPKG__OFF_CRC32, tpkg_crc32(hdr, TPKG__OFF_CRC32));
|
|
566
|
+
p += TPKG_HEADER_SIZE;
|
|
567
|
+
|
|
568
|
+
total = (size_t)(p - buf);
|
|
569
|
+
if (tpkg__write_full(fd, buf, total) != 0) {
|
|
570
|
+
return tpkg__fail(TPKG_ERR_IO);
|
|
571
|
+
}
|
|
572
|
+
return 0;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
#endif /* TPKG_IMPLEMENTATION */
|
data/lib/tebako/cli.rb
CHANGED
|
@@ -38,6 +38,7 @@ require_relative "cache_manager"
|
|
|
38
38
|
require_relative "cli_helpers"
|
|
39
39
|
require_relative "error"
|
|
40
40
|
require_relative "ruby_version"
|
|
41
|
+
require_relative "runtime_manager"
|
|
41
42
|
require_relative "scenario_manager"
|
|
42
43
|
require_relative "version"
|
|
43
44
|
|
|
@@ -45,6 +46,78 @@ require_relative "version"
|
|
|
45
46
|
# Implementation of tebako command-line interface
|
|
46
47
|
module Tebako
|
|
47
48
|
DEFAULT_TEBAFILE = ".tebako.yml"
|
|
49
|
+
|
|
50
|
+
# 'tebako cache' subcommands: machine-wide prebuilt runtime package cache
|
|
51
|
+
class CacheCli < Thor
|
|
52
|
+
package_name "tebako cache"
|
|
53
|
+
|
|
54
|
+
desc "list", "List cached tebako runtime packages with sizes and ages"
|
|
55
|
+
def list
|
|
56
|
+
entries = runtime_manager.entries
|
|
57
|
+
return puts empty_message if entries.empty?
|
|
58
|
+
|
|
59
|
+
entries.each { |entry| puts entry_line(entry) }
|
|
60
|
+
puts total_line(entries)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
desc "prune", "Remove cached tebako runtime packages"
|
|
64
|
+
method_option :all, type: :boolean, default: false, desc: "Remove all cached runtime packages"
|
|
65
|
+
method_option :"older-than", type: :string,
|
|
66
|
+
desc: "Remove runtime packages installed more than N days ago (e.g. 30d)"
|
|
67
|
+
def prune
|
|
68
|
+
removed = do_prune
|
|
69
|
+
return if removed.nil?
|
|
70
|
+
|
|
71
|
+
removed.each { |name| puts "Removed #{name}" }
|
|
72
|
+
puts "#{removed.size} cached runtime package(s) removed"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
no_commands do
|
|
76
|
+
def runtime_manager
|
|
77
|
+
Tebako::RuntimeManager.new
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def empty_message
|
|
81
|
+
"Runtime package cache is empty (#{File.join(runtime_manager.cache_root, "runtimes")})"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def entry_line(entry)
|
|
85
|
+
format("%<name>-44s %<size>9s %<age>s", name: entry[:name], size: human_size(entry[:size_bytes]),
|
|
86
|
+
age: human_age(entry[:installed_at]))
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def total_line(entries)
|
|
90
|
+
format("%<label>-44s %<size>9s", label: "Total (#{entries.size} package(s))",
|
|
91
|
+
size: human_size(entries.sum { |entry| entry[:size_bytes] }))
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
no_commands do
|
|
96
|
+
def do_prune
|
|
97
|
+
return runtime_manager.prune(all: true) if options[:all]
|
|
98
|
+
|
|
99
|
+
match = /\A(?<days>\d+)d?\z/.match(options[:"older-than"].to_s)
|
|
100
|
+
return runtime_manager.prune(older_than_days: match[:days].to_i) if match
|
|
101
|
+
|
|
102
|
+
puts "Nothing to do: pass --all or --older-than Nd"
|
|
103
|
+
nil
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def human_size(bytes)
|
|
107
|
+
format("%.1f MB", bytes / (1024.0 * 1024))
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def human_age(installed_at)
|
|
111
|
+
age = Time.now - installed_at
|
|
112
|
+
if age < 3600 then "#{(age / 60).floor}m ago"
|
|
113
|
+
elsif age < 86_400 then "#{(age / 3600).floor}h ago"
|
|
114
|
+
else
|
|
115
|
+
"#{(age / 86_400).floor}d ago"
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
48
121
|
# Tebako packager front-end
|
|
49
122
|
class Cli < Thor # rubocop:disable Metrics/ClassLength
|
|
50
123
|
package_name "Tebako"
|
|
@@ -82,6 +155,9 @@ module Tebako
|
|
|
82
155
|
print Digest::SHA256.hexdigest [File.read(File.join(source, "CMakeLists.txt")), Tebako::VERSION].join
|
|
83
156
|
end
|
|
84
157
|
|
|
158
|
+
desc "cache SUBCOMMAND", "Manage the machine-wide cache of prebuilt tebako runtime packages"
|
|
159
|
+
subcommand "cache", Tebako::CacheCli
|
|
160
|
+
|
|
85
161
|
CWD_DESCRIPTION = <<~DESC
|
|
86
162
|
Current working directory for packaged application. This directory shall be specified relative to root.
|
|
87
163
|
#{" " * 65}# If this parameter is not set, the application will start in the current directory of the host file system.
|
|
@@ -97,6 +173,17 @@ module Tebako
|
|
|
97
173
|
#{" " * 65}# If this parameter is not set, the application will start in the current directory of the host file system.
|
|
98
174
|
DESC
|
|
99
175
|
|
|
176
|
+
RUNTIME_DESCRIPTION = <<~DESC
|
|
177
|
+
Runtime provenance: 'prebuilt' resolves/downloads a prebuilt tebako runtime package and stitches the
|
|
178
|
+
#{" " * 65}# application image onto it (default for the 'bundle' mode); 'source' keeps the Stage-2 source build.
|
|
179
|
+
#{" " * 65}# Modes other than 'bundle' always build from source.
|
|
180
|
+
DESC
|
|
181
|
+
|
|
182
|
+
IMAGE_DESCRIPTION = <<~DESC
|
|
183
|
+
Additional image to stitch into the package, '<path>:<mount-point>'; repeatable, mount points
|
|
184
|
+
#{" " * 65}# must be distinct. Prebuilt runtime only.
|
|
185
|
+
DESC
|
|
186
|
+
|
|
100
187
|
desc "press", "Press tebako image"
|
|
101
188
|
method_option :cwd, type: :string, aliases: "-c", required: false, desc: CWD_DESCRIPTION
|
|
102
189
|
method_option :"log-level", type: :string, aliases: "-l", required: false, enum: %w[error warn debug trace],
|
|
@@ -113,6 +200,10 @@ module Tebako
|
|
|
113
200
|
method_option :mode, type: :string, aliases: "-m", required: false, enum: %w[bundle both runtime application],
|
|
114
201
|
desc: "Tebako press mode, 'bundle' by default"
|
|
115
202
|
method_option :ref, type: :string, aliases: "-u", required: false, desc: REF_DESCRIPTION
|
|
203
|
+
method_option :runtime, type: :string, required: false, enum: %w[prebuilt source], desc: RUNTIME_DESCRIPTION
|
|
204
|
+
method_option :"build-runtime", type: :boolean, required: false,
|
|
205
|
+
desc: "Build the runtime from source (alias for '--runtime source')"
|
|
206
|
+
method_option :image, type: :array, required: false, desc: IMAGE_DESCRIPTION
|
|
116
207
|
|
|
117
208
|
def press
|
|
118
209
|
validate_press_options
|