initial commit
This commit is contained in:
commit
7571cfbca7
5
.editorconfig
Normal file
5
.editorconfig
Normal file
@ -0,0 +1,5 @@
|
||||
[*.?ts]
|
||||
end_of_line = lf
|
||||
indent_style = tab
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/*
|
||||
!/lib
|
||||
!/mod
|
||||
!/src
|
||||
!/.editorconfig
|
||||
!/.gitignore
|
||||
!/Makefile
|
||||
!/LICENSE
|
15
LICENSE
Normal file
15
LICENSE
Normal file
@ -0,0 +1,15 @@
|
||||
Slimpack
|
||||
Copyright (C) 2025 TopchetoEU
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
Makefile
Normal file
16
Makefile
Normal file
@ -0,0 +1,16 @@
|
||||
lua_sources = $(wildcard src/*.lua) $(wildcard src/cli/*.lua) $(wildcard src/formats/*.lua) $(wildcard src/util/*.lua)
|
||||
c_sources = lib/main.c lib/http.c lib/fmt.c lib/zlib.c
|
||||
target = dst/slimpack
|
||||
lua_target = dst/slimpack.h
|
||||
|
||||
.PHONY: build
|
||||
|
||||
build: $(target)
|
||||
|
||||
./dst:
|
||||
mkdir -p $@
|
||||
|
||||
$(lua_target): $(lua_sources) ./dst
|
||||
lua lib/build.lua src $@ $(lua_sources)
|
||||
$(target): $(c_sources) $(lua_target) ./dst
|
||||
gcc -g -Wall -include $(lua_target) $(c_sources) -o $@ -llua -lcurl -lz -Wall
|
50
lib/build.lua
Normal file
50
lib/build.lua
Normal file
@ -0,0 +1,50 @@
|
||||
local function unresolve_require(paths, f)
|
||||
for _, pattern in ipairs(paths) do
|
||||
local path = f:match(pattern);
|
||||
if path then
|
||||
return path:gsub("%/", ".");
|
||||
end
|
||||
end
|
||||
|
||||
error("path " .. f .. " couldn't be resolved to any valid module");
|
||||
end
|
||||
|
||||
local function all_loader(paths, ...)
|
||||
local files = { ... };
|
||||
local function coro_entry()
|
||||
for _, f in ipairs(files) do
|
||||
local name = unresolve_require(paths, f);
|
||||
local bytecode = string.dump(assert(load(io.lines(f, 1024), "@" .. f, "t", nil)));
|
||||
|
||||
coroutine.yield(("package.preload[%q] = load(%q, %q, 'b');"):format(name, bytecode, f));
|
||||
end
|
||||
coroutine.yield("require 'init' (...);");
|
||||
end
|
||||
|
||||
return coroutine.wrap(coro_entry);
|
||||
end
|
||||
|
||||
local function main(root, out, ...)
|
||||
local paths = { root .. "/(.+).lua", root .. "/(.+)/init.lua" };
|
||||
|
||||
for el in package.path:gmatch "[^;]+" do
|
||||
paths[#paths + 1] = el:gsub("?", "(.+)");
|
||||
end
|
||||
|
||||
local res = string.dump(assert(load(all_loader(paths, ...), "@<entry>", "t", nil)));
|
||||
|
||||
local f = assert(io.open(out, "w"));
|
||||
f:write "#include <stdint.h>\n";
|
||||
f:write "#define BYTECODE entry_bytecode\n";
|
||||
f:write "static const uint8_t entry_bytecode[] = { ";
|
||||
|
||||
for i = 1, #res do
|
||||
f:write(string.byte(res, i), ", ");
|
||||
end
|
||||
|
||||
f:write "};";
|
||||
|
||||
assert(f:close());
|
||||
end
|
||||
|
||||
main(...);
|
771
lib/fmt.c
Normal file
771
lib/fmt.c
Normal file
@ -0,0 +1,771 @@
|
||||
// Similar in concept to Lua's pack and unpack
|
||||
// Main differences are:
|
||||
// - Some formats are reinterpreted to mean the same thing across architectures
|
||||
// - Default endianness is big-endian (but that can easily be changed, of course)
|
||||
// - Formats are parsed once and used many times, to reduce overhead
|
||||
// - Polyfills will store packers and unpackers in weak tables
|
||||
// - Default alignment is no alignment
|
||||
// - The maximum size of an integer is 64 bits (instead of 128)
|
||||
|
||||
#include <lauxlib.h>
|
||||
#include <lua.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "lib.h"
|
||||
|
||||
#define FMT_UDATA_NAME "fmt.meta"
|
||||
|
||||
typedef enum {
|
||||
FMT_INT8,
|
||||
FMT_UINT8,
|
||||
FMT_INT16,
|
||||
FMT_UINT16,
|
||||
FMT_INT32,
|
||||
FMT_UINT32,
|
||||
FMT_INT64,
|
||||
FMT_UINT64,
|
||||
|
||||
FMT_FLOAT32,
|
||||
FMT_FLOAT64,
|
||||
|
||||
FMT_STR_FIXED,
|
||||
FMT_STR8,
|
||||
FMT_STR16,
|
||||
FMT_STR32,
|
||||
FMT_STR64,
|
||||
FMT_STR_ZERO,
|
||||
|
||||
FMT_PADDING,
|
||||
} fmt_type_t;
|
||||
|
||||
typedef enum {
|
||||
FMT_OK,
|
||||
FMT_BAD_ARGS,
|
||||
FMT_INCOMPLETE_OP,
|
||||
FMT_BAD_OP_ARG,
|
||||
FMT_BAD_OP,
|
||||
} fmt_code_t;
|
||||
|
||||
typedef struct {
|
||||
fmt_type_t type;
|
||||
size_t size, align;
|
||||
} fmt_segment_t;
|
||||
|
||||
typedef struct {
|
||||
bool little_endian;
|
||||
size_t max_align;
|
||||
size_t segments_n;
|
||||
fmt_segment_t segments[];
|
||||
} fmt_t;
|
||||
|
||||
static int fmt_parse_width(size_t *i, const char *raw, size_t raw_size) {
|
||||
if (*i < raw_size) {
|
||||
switch (raw[(*i)++]) {
|
||||
case '1': return 1;
|
||||
case '2': return 2;
|
||||
case '4': return 4;
|
||||
case '8': return 8;
|
||||
default:
|
||||
(*i)--;
|
||||
return 8;
|
||||
}
|
||||
}
|
||||
else return 8;
|
||||
}
|
||||
|
||||
static fmt_code_t fmt_parse_fmt(lua_State *ctx, const char *raw, size_t raw_size) {
|
||||
if (raw == NULL || *raw == 0) return FMT_BAD_ARGS;
|
||||
if (raw_size == -1) raw_size = strlen(raw);
|
||||
|
||||
fmt_segment_t segments[raw_size];
|
||||
size_t segments_n = 0;
|
||||
bool little_endian;
|
||||
bool max_align;
|
||||
|
||||
size_t i = 0;
|
||||
bool curr_padding = false;
|
||||
|
||||
while (true) {
|
||||
if (i >= raw_size) break;
|
||||
fmt_segment_t segment;
|
||||
|
||||
switch (raw[i++]) {
|
||||
case '<': little_endian = true; continue;
|
||||
case '>':
|
||||
case '=': little_endian = false; continue;
|
||||
case '!':
|
||||
if (i < raw_size && (raw[i] < '0' || raw[i] > '9')) {
|
||||
max_align = raw[i] - '0';
|
||||
i++;
|
||||
}
|
||||
else max_align = 1;
|
||||
|
||||
continue;
|
||||
|
||||
case 'b': segment = (fmt_segment_t){ .type = FMT_INT8, .align = 1 }; break;
|
||||
case 'B': segment = (fmt_segment_t){ .type = FMT_UINT8, .align = 1 }; break;
|
||||
case 'h': segment = (fmt_segment_t){ .type = FMT_INT16, .align = 2 }; break;
|
||||
case 'H': segment = (fmt_segment_t){ .type = FMT_UINT16, .align = 2 }; break;
|
||||
case 'j': segment = (fmt_segment_t){ .type = FMT_INT32, .align = 4 }; break;
|
||||
case 'J': segment = (fmt_segment_t){ .type = FMT_UINT32, .align = 4 }; break;
|
||||
case 'l': segment = (fmt_segment_t){ .type = FMT_INT64, .align = 8 }; break;
|
||||
case 'L':
|
||||
case 'T': segment = (fmt_segment_t){ .type = FMT_UINT64, .align = 8 }; break;
|
||||
|
||||
case 'f': segment = (fmt_segment_t){ .type = FMT_FLOAT32, .align = 4 }; break;
|
||||
case 'd':
|
||||
case 'n': segment = (fmt_segment_t){ .type = FMT_FLOAT64, .align = 8 }; break;
|
||||
|
||||
case 'i':
|
||||
switch (fmt_parse_width(&i, raw, raw_size)) {
|
||||
case 1: segment = (fmt_segment_t){ .type = FMT_INT8, .align = 1 }; break;
|
||||
case 2: segment = (fmt_segment_t){ .type = FMT_INT16, .align = 2 }; break;
|
||||
case 4: segment = (fmt_segment_t){ .type = FMT_INT32, .align = 4 }; break;
|
||||
case 8: segment = (fmt_segment_t){ .type = FMT_INT64, .align = 8 }; break;
|
||||
}
|
||||
break;
|
||||
case 'I':
|
||||
switch (fmt_parse_width(&i, raw, raw_size)) {
|
||||
case 1: segment = (fmt_segment_t){ .type = FMT_UINT8, .align = 1 }; break;
|
||||
case 2: segment = (fmt_segment_t){ .type = FMT_UINT16, .align = 2 }; break;
|
||||
case 4: segment = (fmt_segment_t){ .type = FMT_UINT32, .align = 4 }; break;
|
||||
case 8: segment = (fmt_segment_t){ .type = FMT_UINT64, .align = 8 }; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'z': segment = (fmt_segment_t){ .type = FMT_STR_ZERO, .align = 1 }; break;
|
||||
case 'c':
|
||||
bool has_size;
|
||||
size_t str_size;
|
||||
|
||||
while (i >= raw_size && raw[i] >= '0' && raw[i] <= '9') {
|
||||
str_size *= 10;
|
||||
str_size += raw[i] - '0';
|
||||
has_size = true;
|
||||
i++;
|
||||
}
|
||||
|
||||
if (!has_size) return FMT_INCOMPLETE_OP;
|
||||
|
||||
segment = (fmt_segment_t){ .type = FMT_STR_FIXED, .size = str_size, .align = 1 };
|
||||
break;
|
||||
case 's':
|
||||
switch (fmt_parse_width(&i, raw, raw_size)) {
|
||||
case 1: segment = (fmt_segment_t){ .type = FMT_STR8, .align = 1 }; break;
|
||||
case 2: segment = (fmt_segment_t){ .type = FMT_STR16, .align = 2 }; break;
|
||||
case 4: segment = (fmt_segment_t){ .type = FMT_STR32, .align = 4 }; break;
|
||||
case 8: segment = (fmt_segment_t){ .type = FMT_STR64, .align = 8 }; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'x': segment = (fmt_segment_t){ .type = FMT_PADDING, .size = 1, .align = 1 }; break;
|
||||
case 'X':
|
||||
curr_padding = true;
|
||||
continue;
|
||||
case ' ':
|
||||
break;
|
||||
|
||||
default:
|
||||
return FMT_BAD_OP;
|
||||
}
|
||||
|
||||
if (curr_padding) {
|
||||
// TODO: is this correct?
|
||||
switch (segment.type) {
|
||||
case FMT_INT8:
|
||||
case FMT_UINT8:
|
||||
case FMT_STR8:
|
||||
segment = (fmt_segment_t){ .type = FMT_PADDING, .size = 1, .align = 1 };
|
||||
break;
|
||||
case FMT_INT16:
|
||||
case FMT_UINT16:
|
||||
case FMT_STR16:
|
||||
segment = (fmt_segment_t){ .type = FMT_PADDING, .size = 2, .align = 2 };
|
||||
break;
|
||||
case FMT_INT32:
|
||||
case FMT_UINT32:
|
||||
case FMT_FLOAT32:
|
||||
case FMT_STR32:
|
||||
segment = (fmt_segment_t){ .type = FMT_PADDING, .size = 4, .align = 4 };
|
||||
break;
|
||||
case FMT_INT64:
|
||||
case FMT_UINT64:
|
||||
case FMT_FLOAT64:
|
||||
case FMT_STR64:
|
||||
segment = (fmt_segment_t){ .type = FMT_PADDING, .size = 8, .align = 8 };
|
||||
break;
|
||||
case FMT_STR_FIXED:
|
||||
segment = (fmt_segment_t){ .type = FMT_PADDING, .size = segment.size, .align = segment.size };
|
||||
break;
|
||||
default:
|
||||
return FMT_BAD_OP_ARG;
|
||||
}
|
||||
}
|
||||
|
||||
segments[segments_n++] = segment;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (curr_padding) return FMT_INCOMPLETE_OP;
|
||||
|
||||
for (size_t i = 0; i < segments_n; i++) {
|
||||
// TODO: this might not be correct...
|
||||
if (segments[i].align > max_align) segments[i].align = 8;
|
||||
if (segments[i].align > max_align) segments[i].align = 4;
|
||||
if (segments[i].align > max_align) segments[i].align = 2;
|
||||
if (segments[i].align > max_align) segments[i].align = 1;
|
||||
}
|
||||
|
||||
fmt_t *res = lua_newuserdata(ctx, sizeof *res + sizeof *segments * segments_n);
|
||||
luaL_getmetatable(ctx, FMT_UDATA_NAME);
|
||||
lua_setmetatable(ctx, -2);
|
||||
|
||||
res->little_endian = little_endian;
|
||||
res->max_align = max_align;
|
||||
res->segments_n = segments_n;
|
||||
memcpy(res->segments, segments, sizeof *segments * segments_n);
|
||||
|
||||
return FMT_OK;
|
||||
}
|
||||
|
||||
|
||||
typedef struct {
|
||||
char *arr;
|
||||
size_t cap, n;
|
||||
} write_buff_t;
|
||||
|
||||
static void write_buff_append(write_buff_t *buff, const char *data, size_t size) {
|
||||
size_t new_cap = buff->cap;
|
||||
|
||||
while (new_cap < buff->n + size) {
|
||||
if (new_cap == 0) new_cap = 16;
|
||||
else new_cap *= 2;
|
||||
}
|
||||
|
||||
if (new_cap != buff->cap) {
|
||||
if (buff->cap == 0) {
|
||||
buff->arr = malloc(new_cap);
|
||||
}
|
||||
else {
|
||||
buff->arr = realloc(buff->arr, new_cap);
|
||||
}
|
||||
buff->cap = new_cap;
|
||||
}
|
||||
|
||||
if (data == NULL) {
|
||||
memset(buff->arr + buff->n, 0, size);
|
||||
}
|
||||
else {
|
||||
memcpy(buff->arr + buff->n, data, size);
|
||||
}
|
||||
|
||||
buff->n += size;
|
||||
}
|
||||
static void write_buff_fit(write_buff_t *buff) {
|
||||
if (buff->arr == NULL) return;
|
||||
|
||||
if (buff->n == 0) {
|
||||
free(buff->arr);
|
||||
buff->cap = 0;
|
||||
}
|
||||
else {
|
||||
buff->cap = buff->n;
|
||||
buff->arr = realloc(buff->arr, buff->n);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static bool fmt_read_uint8(const uint8_t *raw, size_t *i, size_t size, uint8_t *res) {
|
||||
if ((*i) + 1 > size || *i < 0) return false;
|
||||
|
||||
*res = raw[(*i)++];
|
||||
|
||||
return true;
|
||||
}
|
||||
static bool fmt_read_int8(const uint8_t *raw, size_t *i, size_t size, int8_t *res) {
|
||||
return fmt_read_uint8(raw, i, size, (uint8_t*)res);
|
||||
}
|
||||
|
||||
static bool fmt_read_uint16(const uint8_t *raw, size_t *i, size_t size, uint16_t *res, bool little_endian) {
|
||||
if ((*i) + 2 > size || *i < 0) return false;
|
||||
|
||||
uint16_t a = raw[(*i)++];
|
||||
uint16_t b = raw[(*i)++];
|
||||
|
||||
if (little_endian) *res = a | b << 8;
|
||||
else *res = a << 8 | b;
|
||||
|
||||
return true;
|
||||
}
|
||||
static bool fmt_read_int16(const uint8_t *raw, size_t *i, size_t size, int16_t *res, bool little_endian) {
|
||||
return fmt_read_uint16(raw, i, size, (uint16_t*)res, little_endian);
|
||||
}
|
||||
|
||||
static bool fmt_read_uint32(const uint8_t *raw, size_t *i, size_t size, uint32_t *res, bool little_endian) {
|
||||
if ((*i) + 4 > size || *i < 0) return false;
|
||||
|
||||
uint8_t a = raw[(*i)++];
|
||||
uint8_t b = raw[(*i)++];
|
||||
uint8_t c = raw[(*i)++];
|
||||
uint8_t d = raw[(*i)++];
|
||||
|
||||
if (little_endian) *res = a | b << 8 | c << 16 | d << 24;
|
||||
else *res = a << 24 | b << 16 | c << 8 | d;
|
||||
|
||||
return true;
|
||||
}
|
||||
static bool fmt_read_int32(const uint8_t *raw, size_t *i, size_t size, int32_t *res, bool little_endian) {
|
||||
return fmt_read_uint32(raw, i, size, (uint32_t*)res, little_endian);
|
||||
}
|
||||
|
||||
static bool fmt_read_uint64(const uint8_t *raw, size_t *i, size_t size, uint64_t *res, bool little_endian) {
|
||||
if ((*i) + 8 > size || *i < 0) return false;
|
||||
|
||||
uint64_t a = raw[(*i)++];
|
||||
uint64_t b = raw[(*i)++];
|
||||
uint64_t c = raw[(*i)++];
|
||||
uint64_t d = raw[(*i)++];
|
||||
uint64_t e = raw[(*i)++];
|
||||
uint64_t f = raw[(*i)++];
|
||||
uint64_t g = raw[(*i)++];
|
||||
uint64_t h = raw[(*i)++];
|
||||
|
||||
if (little_endian) *res = a | b << 8 | c << 16 | d << 24 | e << 32 | f << 40 | g << 48 | h << 56;
|
||||
else *res = a << 56 | b << 48 | c << 40 | d << 32 | e << 24 | f << 16 | g << 8 | h;
|
||||
|
||||
return true;
|
||||
}
|
||||
static bool fmt_read_int64(const uint8_t *raw, size_t *i, size_t size, int64_t *res, bool little_endian) {
|
||||
return fmt_read_uint64(raw, i, size, (uint64_t*)res, little_endian);
|
||||
}
|
||||
|
||||
static bool fmt_read_float32(const uint8_t *raw, size_t *i, size_t size, float *res, bool little_endian) {
|
||||
if ((*i) + 4 > size || *i < 0) return false;
|
||||
|
||||
uint8_t a = raw[(*i)++];
|
||||
uint8_t b = raw[(*i)++];
|
||||
uint8_t c = raw[(*i)++];
|
||||
uint8_t d = raw[(*i)++];
|
||||
|
||||
// TODO: is this portable enough?
|
||||
if (little_endian) *res = *(float*)(uint8_t[]) { a, b, c, d };
|
||||
else *res = *(float*)(uint8_t[]) { d, c, b, a };
|
||||
|
||||
return true;
|
||||
}
|
||||
static bool fmt_read_float64(const uint8_t *raw, size_t *i, size_t size, double *res, bool little_endian) {
|
||||
if ((*i) + 8 > size || *i < 0) return false;
|
||||
|
||||
uint8_t a = raw[(*i)++];
|
||||
uint8_t b = raw[(*i)++];
|
||||
uint8_t c = raw[(*i)++];
|
||||
uint8_t d = raw[(*i)++];
|
||||
uint8_t e = raw[(*i)++];
|
||||
uint8_t f = raw[(*i)++];
|
||||
uint8_t g = raw[(*i)++];
|
||||
uint8_t h = raw[(*i)++];
|
||||
|
||||
// TODO: is this portable enough?
|
||||
if (little_endian) *res = *(float*)(uint8_t[]) { a, b, c, d, e, f, g, h };
|
||||
else *res = *(float*)(uint8_t[]) { h, g, f, e, d, c, b, a };
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool fmt_read_string(lua_State *ctx, fmt_segment_t segment, const uint8_t *raw, size_t *i, size_t size, bool little_endian) {
|
||||
uint64_t len;
|
||||
|
||||
switch (segment.type) {
|
||||
case FMT_STR8: {
|
||||
uint8_t len8;
|
||||
if (!fmt_read_uint8(raw, i, size, &len8)) return false;
|
||||
break;
|
||||
}
|
||||
case FMT_STR16: {
|
||||
uint16_t len16;
|
||||
if (!fmt_read_uint16(raw, i, size, &len16, little_endian)) return false;
|
||||
len = len16;
|
||||
break;
|
||||
}
|
||||
case FMT_STR32: {
|
||||
uint32_t len32;
|
||||
if (!fmt_read_uint32(raw, i, size, &len32, little_endian)) return false;
|
||||
len = len32;
|
||||
break;
|
||||
}
|
||||
case FMT_STR64:
|
||||
if (!fmt_read_uint64(raw, i, size, &len, little_endian)) return false;
|
||||
break;
|
||||
case FMT_STR_FIXED:
|
||||
len = segment.size;
|
||||
break;
|
||||
case FMT_STR_ZERO:
|
||||
len = strnlen((const char*)(raw + *i), size - *i);
|
||||
if (len >= size - *i) return false;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
fprintf(stderr, "%lu %lu", len, *i);
|
||||
if ((*i) + len > size) return false;
|
||||
if (*i < 0) return false;
|
||||
|
||||
char data[len];
|
||||
memcpy(data, raw + (*i), len);
|
||||
|
||||
lua_pushlstring(ctx, data, len);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static void fmt_write_uint8(write_buff_t *buff, uint8_t val) {
|
||||
write_buff_append(buff, (char[]){ val }, 1);
|
||||
}
|
||||
static void fmt_write_int8(write_buff_t *buff, int8_t val) {
|
||||
return fmt_write_uint8(buff, val);
|
||||
}
|
||||
|
||||
static void fmt_write_uint16(write_buff_t *buff, uint16_t val, bool little_endian) {
|
||||
uint8_t a = val & 0xFF;
|
||||
uint8_t b = val >> 8 & 0xFF;
|
||||
|
||||
if (little_endian) write_buff_append(buff, (char[]){ a, b }, 2);
|
||||
else write_buff_append(buff, (char[]){ b, a }, 2);
|
||||
}
|
||||
static void fmt_write_int16(write_buff_t *buff, uint16_t val, bool little_endian) {
|
||||
fmt_write_uint16(buff, val, little_endian);
|
||||
}
|
||||
|
||||
static void fmt_write_uint32(write_buff_t *buff, uint32_t val, bool little_endian) {
|
||||
uint8_t a = val & 0xFF;
|
||||
uint8_t b = val >> 8 & 0xFF;
|
||||
uint8_t c = val >> 16 & 0xFF;
|
||||
uint8_t d = val >> 24 & 0xFF;
|
||||
|
||||
if (little_endian) write_buff_append(buff, (char[]){ a, b, c, d }, 4);
|
||||
else write_buff_append(buff, (char[]){ d, c, b, a }, 4);
|
||||
}
|
||||
static void fmt_write_int32(write_buff_t *buff, uint32_t val, bool little_endian) {
|
||||
fmt_write_uint32(buff, val, little_endian);
|
||||
}
|
||||
|
||||
static void fmt_write_uint64(write_buff_t *buff, uint64_t val, bool little_endian) {
|
||||
uint8_t a = val & 0xFF;
|
||||
uint8_t b = val >> 8 & 0xFF;
|
||||
uint8_t c = val >> 16 & 0xFF;
|
||||
uint8_t d = val >> 24 & 0xFF;
|
||||
uint8_t e = val >> 32 & 0xFF;
|
||||
uint8_t f = val >> 40 & 0xFF;
|
||||
uint8_t g = val >> 48 & 0xFF;
|
||||
uint8_t h = val >> 56 & 0xFF;
|
||||
|
||||
if (little_endian) write_buff_append(buff, (char[]){ a, b, c, d, e, f, g, h }, 8);
|
||||
else write_buff_append(buff, (char[]){ h, g, f, e, d, c, b, a }, 8);
|
||||
}
|
||||
static void fmt_write_int64(write_buff_t *buff, uint64_t val, bool little_endian) {
|
||||
fmt_write_uint64(buff, val, little_endian);
|
||||
}
|
||||
|
||||
static void fmt_write_float32(write_buff_t *buff, float val, bool little_endian) {
|
||||
uint32_t ival = *(uint32_t*)&val;
|
||||
|
||||
uint8_t a = ival & 0xFF;
|
||||
uint8_t b = ival >> 8 & 0xFF;
|
||||
uint8_t c = ival >> 16 & 0xFF;
|
||||
uint8_t d = ival >> 24 & 0xFF;
|
||||
|
||||
if (little_endian) write_buff_append(buff, (char[]){ a, b, c, d }, 4);
|
||||
else write_buff_append(buff, (char[]){ d, c, b, a }, 4);
|
||||
}
|
||||
static void fmt_write_float64(write_buff_t *buff, double val, bool little_endian) {
|
||||
uint64_t ival = *(uint64_t*)&val;
|
||||
|
||||
uint8_t a = ival & 0xFF;
|
||||
uint8_t b = ival >> 8 & 0xFF;
|
||||
uint8_t c = ival >> 16 & 0xFF;
|
||||
uint8_t d = ival >> 24 & 0xFF;
|
||||
uint8_t e = ival >> 32 & 0xFF;
|
||||
uint8_t f = ival >> 40 & 0xFF;
|
||||
uint8_t g = ival >> 48 & 0xFF;
|
||||
uint8_t h = ival >> 56 & 0xFF;
|
||||
|
||||
if (little_endian) write_buff_append(buff, (char[]){ a, b, c, d, e, f, g, h }, 8);
|
||||
else write_buff_append(buff, (char[]){ h, g, f, e, d, c, b, a }, 8);
|
||||
}
|
||||
|
||||
static bool fmt_write_string(write_buff_t *buff, fmt_segment_t segment, const char *str, size_t len, bool little_endian) {
|
||||
switch (segment.type) {
|
||||
case FMT_STR8:
|
||||
if (len > 256) return false;
|
||||
fmt_write_uint8(buff, (uint8_t)len);
|
||||
write_buff_append(buff, str, len);
|
||||
return true;
|
||||
case FMT_STR16:
|
||||
if (len > 0x10000) return false;
|
||||
fmt_write_uint16(buff, (uint16_t)len, little_endian);
|
||||
write_buff_append(buff, str, len);
|
||||
return true;
|
||||
case FMT_STR32:
|
||||
if (len > 0x100000000) return false;
|
||||
fmt_write_uint32(buff, (uint32_t)len, little_endian);
|
||||
write_buff_append(buff, str, len);
|
||||
return true;
|
||||
case FMT_STR64:
|
||||
fmt_write_uint64(buff, len, little_endian);
|
||||
write_buff_append(buff, str, len);
|
||||
return true;
|
||||
case FMT_STR_FIXED:
|
||||
if (len > segment.size) return false;
|
||||
write_buff_append(buff, str, len);
|
||||
write_buff_append(buff, NULL, segment.size - len);
|
||||
return true;
|
||||
case FMT_STR_ZERO:
|
||||
if (strlen(str) != len) return false;
|
||||
write_buff_append(buff, str, len);
|
||||
write_buff_append(buff, (char[]) { 0x00 }, 1);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int lib_fmt_pack(lua_State *ctx) {
|
||||
fmt_t *fmt = luaL_checkudata(ctx, 1, FMT_UDATA_NAME);
|
||||
|
||||
size_t arg_i = 2;
|
||||
write_buff_t buff = { .arr = NULL, .n = 0, .cap = 0 };
|
||||
|
||||
for (size_t i = 0; i < fmt->segments_n; i++) {
|
||||
fmt_segment_t segment = fmt->segments[i];
|
||||
|
||||
size_t n_aligned = -((-buff.n / segment.align) * segment.align);
|
||||
size_t align_padding = n_aligned - buff.n;
|
||||
write_buff_append(&buff, NULL, align_padding);
|
||||
|
||||
switch (segment.type) {
|
||||
case FMT_INT8:
|
||||
fmt_write_int8(&buff, luaL_checkinteger(ctx, arg_i++));
|
||||
break;
|
||||
case FMT_UINT8:
|
||||
fmt_write_uint8(&buff, luaL_checkinteger(ctx, arg_i++));
|
||||
break;
|
||||
case FMT_INT16:
|
||||
fmt_write_int16(&buff, luaL_checkinteger(ctx, arg_i++), fmt->little_endian);
|
||||
break;
|
||||
case FMT_UINT16:
|
||||
fmt_write_uint16(&buff, luaL_checkinteger(ctx, arg_i++), fmt->little_endian);
|
||||
break;
|
||||
case FMT_INT32:
|
||||
fmt_write_int32(&buff, luaL_checkinteger(ctx, arg_i++), fmt->little_endian);
|
||||
break;
|
||||
case FMT_UINT32:
|
||||
fmt_write_uint32(&buff, luaL_checkinteger(ctx, arg_i++), fmt->little_endian);
|
||||
break;
|
||||
case FMT_INT64:
|
||||
fmt_write_int64(&buff, luaL_checkinteger(ctx, arg_i++), fmt->little_endian);
|
||||
break;
|
||||
case FMT_UINT64:
|
||||
fmt_write_uint64(&buff, luaL_checkinteger(ctx, arg_i++), fmt->little_endian);
|
||||
break;
|
||||
|
||||
case FMT_FLOAT32:
|
||||
fmt_write_float32(&buff, (float)luaL_checknumber(ctx, arg_i++), fmt->little_endian);
|
||||
break;
|
||||
case FMT_FLOAT64:
|
||||
fmt_write_float64(&buff, (double)luaL_checknumber(ctx, arg_i++), fmt->little_endian);
|
||||
break;
|
||||
|
||||
case FMT_STR_FIXED:
|
||||
case FMT_STR8:
|
||||
case FMT_STR16:
|
||||
case FMT_STR32:
|
||||
case FMT_STR64:
|
||||
case FMT_STR_ZERO: {
|
||||
size_t size;
|
||||
const char *str = luaL_checklstring(ctx, arg_i++, &size);
|
||||
if (!fmt_write_string(&buff, segment, str, size, fmt->little_endian)) {
|
||||
luaL_error(ctx, "invalid string at %d", arg_i);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case FMT_PADDING:
|
||||
// TODO: might need to remove this later...
|
||||
write_buff_append(&buff, NULL, segment.size);
|
||||
}
|
||||
}
|
||||
|
||||
write_buff_fit(&buff);
|
||||
lua_pushlstring(ctx, buff.arr, buff.n);
|
||||
free(buff.arr);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int lib_fmt_unpack(lua_State *ctx) {
|
||||
fmt_t *fmt = luaL_checkudata(ctx, 1, FMT_UDATA_NAME);
|
||||
|
||||
size_t raw_size;
|
||||
const uint8_t *raw = (const uint8_t*)luaL_checklstring(ctx, 2, &raw_size);
|
||||
|
||||
size_t read_i = 0;
|
||||
size_t res_n = 0;
|
||||
if (lua_isinteger(ctx, 3)) {
|
||||
read_i = lua_tointeger(ctx, 3) - 1;
|
||||
}
|
||||
|
||||
write_buff_t buff = { .arr = NULL, .n = 0, .cap = 0 };
|
||||
|
||||
for (size_t i = 0; i < fmt->segments_n; i++) {
|
||||
fmt_segment_t segment = fmt->segments[i];
|
||||
|
||||
size_t n_aligned = -((-buff.n / segment.align) * segment.align);
|
||||
size_t align_padding = n_aligned - buff.n;
|
||||
read_i += align_padding;
|
||||
|
||||
switch (segment.type) {
|
||||
case FMT_INT8: {
|
||||
int8_t res;
|
||||
if (!fmt_read_int8(raw, &read_i, raw_size, &res)) luaL_error(ctx, "couldn't read int8");
|
||||
lua_pushinteger(ctx, res);
|
||||
res_n++;
|
||||
break;
|
||||
}
|
||||
case FMT_UINT8: {
|
||||
uint8_t res;
|
||||
if (!fmt_read_uint8(raw, &read_i, raw_size, &res)) luaL_error(ctx, "couldn't read uint8");
|
||||
lua_pushinteger(ctx, res);
|
||||
res_n++;
|
||||
break;
|
||||
}
|
||||
case FMT_INT16: {
|
||||
int16_t res;
|
||||
if (!fmt_read_int16(raw, &read_i, raw_size, &res, fmt->little_endian)) luaL_error(ctx, "couldn't read int16");
|
||||
lua_pushinteger(ctx, res);
|
||||
res_n++;
|
||||
break;
|
||||
}
|
||||
case FMT_UINT16: {
|
||||
uint16_t res;
|
||||
if (!fmt_read_uint16(raw, &read_i, raw_size, &res, fmt->little_endian)) luaL_error(ctx, "couldn't read uint16");
|
||||
lua_pushinteger(ctx, res);
|
||||
res_n++;
|
||||
break;
|
||||
}
|
||||
case FMT_INT32: {
|
||||
int32_t res;
|
||||
if (!fmt_read_int32(raw, &read_i, raw_size, &res, fmt->little_endian)) luaL_error(ctx, "couldn't read int32");
|
||||
lua_pushinteger(ctx, res);
|
||||
res_n++;
|
||||
break;
|
||||
}
|
||||
case FMT_UINT32: {
|
||||
uint32_t res;
|
||||
if (!fmt_read_uint32(raw, &read_i, raw_size, &res, fmt->little_endian)) luaL_error(ctx, "couldn't read uint32");
|
||||
lua_pushinteger(ctx, res);
|
||||
res_n++;
|
||||
break;
|
||||
}
|
||||
case FMT_INT64: {
|
||||
int64_t res;
|
||||
if (!fmt_read_int64(raw, &read_i, raw_size, &res, fmt->little_endian)) luaL_error(ctx, "couldn't read int64");
|
||||
lua_pushinteger(ctx, res);
|
||||
res_n++;
|
||||
break;
|
||||
}
|
||||
case FMT_UINT64: {
|
||||
uint64_t res;
|
||||
if (!fmt_read_uint64(raw, &read_i, raw_size, &res, fmt->little_endian)) luaL_error(ctx, "couldn't read uint64");
|
||||
lua_pushinteger(ctx, res);
|
||||
res_n++;
|
||||
break;
|
||||
}
|
||||
|
||||
case FMT_FLOAT32: {
|
||||
float res;
|
||||
if (!fmt_read_float32(raw, &read_i, raw_size, &res, fmt->little_endian)) luaL_error(ctx, "couldn't read float32");
|
||||
lua_pushinteger(ctx, res);
|
||||
res_n++;
|
||||
break;
|
||||
}
|
||||
case FMT_FLOAT64: {
|
||||
double res;
|
||||
if (!fmt_read_float64(raw, &read_i, raw_size, &res, fmt->little_endian)) luaL_error(ctx, "couldn't read float64");
|
||||
lua_pushinteger(ctx, res);
|
||||
res_n++;
|
||||
break;
|
||||
}
|
||||
|
||||
case FMT_STR_FIXED:
|
||||
case FMT_STR8:
|
||||
case FMT_STR16:
|
||||
case FMT_STR32:
|
||||
case FMT_STR64:
|
||||
case FMT_STR_ZERO: {
|
||||
if (!fmt_read_string(ctx, segment, raw, &read_i, raw_size, fmt->little_endian)) luaL_error(ctx, "couldn't read string");
|
||||
res_n++;
|
||||
break;
|
||||
}
|
||||
|
||||
case FMT_PADDING:
|
||||
read_i += segment.size;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
lua_pushinteger(ctx, read_i + 1);
|
||||
return res_n + 1;
|
||||
}
|
||||
|
||||
|
||||
static int lib_fmt_new(lua_State *ctx) {
|
||||
size_t raw_size;
|
||||
const char *raw = luaL_checklstring(ctx, 1, &raw_size);
|
||||
fmt_code_t code = fmt_parse_fmt(ctx, raw, raw_size);
|
||||
|
||||
if (code != FMT_OK) {
|
||||
switch (code) {
|
||||
case FMT_BAD_ARGS: return luaL_error(ctx, "illegal C arguments");
|
||||
case FMT_INCOMPLETE_OP: return luaL_error(ctx, "incomplete operand in format string");
|
||||
case FMT_BAD_OP_ARG: return luaL_error(ctx, "bad operand argument in format string");
|
||||
case FMT_BAD_OP: return luaL_error(ctx, "bad operand in format string");
|
||||
default: return luaL_error(ctx, "unknown error while parsing format string");
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static void fmt_init_meta(lua_State *ctx) {
|
||||
luaL_newmetatable(ctx, FMT_UDATA_NAME);
|
||||
|
||||
luaL_newlib(ctx, ((luaL_Reg[]) {
|
||||
{ "pack", lib_fmt_pack },
|
||||
{ "unpack", lib_fmt_unpack },
|
||||
{ NULL, NULL }
|
||||
}));
|
||||
lua_setfield(ctx, -2, "__index");
|
||||
|
||||
lua_pushboolean(ctx, false);
|
||||
lua_setfield(ctx, -2, "__meta");
|
||||
|
||||
lua_pop(ctx, 1);
|
||||
}
|
||||
|
||||
int fmt_open_lib(lua_State *ctx) {
|
||||
fmt_init_meta(ctx);
|
||||
|
||||
luaL_newlib(ctx, ((luaL_Reg[]) {
|
||||
{ "new", lib_fmt_new },
|
||||
{ "pack", lib_fmt_pack },
|
||||
{ "unpack", lib_fmt_unpack },
|
||||
{ NULL, NULL },
|
||||
}));
|
||||
|
||||
return 1;
|
||||
}
|
101
lib/http.c
Normal file
101
lib/http.c
Normal file
@ -0,0 +1,101 @@
|
||||
#include <lua.h>
|
||||
#include <lualib.h>
|
||||
#include <lauxlib.h>
|
||||
#include <curl/curl.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "lib.h"
|
||||
|
||||
typedef struct {
|
||||
char **segments;
|
||||
size_t *sizes;
|
||||
size_t n, cap;
|
||||
} http_body_buff_t;
|
||||
|
||||
static size_t body_writer(char *ptr, size_t _, size_t size, void *pbuff) {
|
||||
http_body_buff_t *buff = pbuff;
|
||||
|
||||
size_t new_cap = buff->cap;
|
||||
while (buff->n >= new_cap) {
|
||||
new_cap *= 2;
|
||||
}
|
||||
|
||||
if (new_cap > buff->cap) {
|
||||
buff->segments = realloc(buff->segments, sizeof(*buff->segments) * new_cap);
|
||||
buff->sizes = realloc(buff->sizes, sizeof(*buff->sizes) * new_cap);
|
||||
buff->cap = new_cap;
|
||||
}
|
||||
|
||||
buff->segments[buff->n] = malloc(size);
|
||||
memcpy(buff->segments[buff->n], ptr, size);
|
||||
buff->sizes[buff->n] = size;
|
||||
buff->n++;
|
||||
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static char *body_compress(http_body_buff_t buff, size_t *psize) {
|
||||
size_t size = 0;
|
||||
|
||||
for (size_t i = 0; i < buff.n; i++) {
|
||||
size += buff.sizes[i];
|
||||
}
|
||||
|
||||
char *res = malloc(size + 1);
|
||||
res[size] = 0;
|
||||
|
||||
size_t offset = 0;
|
||||
|
||||
for (size_t i = 0; i < buff.n; i++) {
|
||||
memcpy(res + offset, buff.segments[i], buff.sizes[i]);
|
||||
offset += buff.sizes[i];
|
||||
free(buff.segments[i]);
|
||||
}
|
||||
|
||||
free(buff.segments);
|
||||
free(buff.sizes);
|
||||
|
||||
*psize = size;
|
||||
return res;
|
||||
}
|
||||
|
||||
static int lib_http_get(lua_State *ctx) {
|
||||
const char *url = luaL_checkstring(ctx, 1);
|
||||
|
||||
http_body_buff_t buff = {
|
||||
.segments = malloc(sizeof *buff.segments * 16),
|
||||
.sizes = malloc(sizeof *buff.sizes * 16),
|
||||
.n = 0,
|
||||
.cap = 16,
|
||||
};
|
||||
|
||||
CURL *curl = curl_easy_init();
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, body_writer);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buff);
|
||||
|
||||
CURLcode code = curl_easy_perform(curl);
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
if (code != CURLE_OK) {
|
||||
return luaL_error(ctx, "HTTP GET failed: %s", curl_easy_strerror(code));
|
||||
}
|
||||
|
||||
size_t size;
|
||||
char *body = body_compress(buff, &size);
|
||||
lua_pushlstring(ctx, body, size);
|
||||
free(body);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int http_open_lib(lua_State *ctx) {
|
||||
luaL_newlib(ctx, ((luaL_Reg[]) {
|
||||
{ "get", lib_http_get },
|
||||
{ NULL, NULL },
|
||||
}));
|
||||
|
||||
return 1;
|
||||
}
|
5
lib/lib.h
Normal file
5
lib/lib.h
Normal file
@ -0,0 +1,5 @@
|
||||
#include <lua.h>
|
||||
|
||||
int http_open_lib(lua_State *ctx);
|
||||
int fmt_open_lib(lua_State *ctx);
|
||||
int zlib_open_lib(lua_State *ctx);
|
126
lib/main.c
Normal file
126
lib/main.c
Normal file
@ -0,0 +1,126 @@
|
||||
#include <lua.h>
|
||||
#include <lualib.h>
|
||||
#include <lauxlib.h>
|
||||
#include <curl/curl.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "lib.h"
|
||||
|
||||
#ifndef BYTECODE
|
||||
static char entry_bytecode[] = {
|
||||
0x1b, 0x4c, 0x75, 0x61, 0x54, 0x00, 0x19, 0x93,
|
||||
0x0d, 0x0a, 0x1a, 0x0a, 0x04, 0x08, 0x08, 0x78,
|
||||
0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x28, 0x77, 0x40, 0x01,
|
||||
0x80, 0x80, 0x80, 0x00, 0x01, 0x02, 0x85, 0x51,
|
||||
0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x83,
|
||||
0x80, 0x00, 0x00, 0x44, 0x00, 0x02, 0x01, 0x46,
|
||||
0x00, 0x01, 0x01, 0x82, 0x04, 0x86, 0x65, 0x72,
|
||||
0x72, 0x6f, 0x72, 0x04, 0xa4, 0x70, 0x6c, 0x65,
|
||||
0x61, 0x73, 0x65, 0x2c, 0x20, 0x63, 0x6f, 0x6d,
|
||||
0x70, 0x69, 0x6c, 0x65, 0x20, 0x77, 0x69, 0x74,
|
||||
0x68, 0x20, 0x2d, 0x44, 0x42, 0x59, 0x54, 0x45,
|
||||
0x43, 0x4f, 0x44, 0x45, 0x3d, 0x2e, 0x2e, 0x2e,
|
||||
0x81, 0x01, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80,
|
||||
0x80,
|
||||
};
|
||||
#define BYTECODE entry_bytecode
|
||||
#endif
|
||||
|
||||
static char err_bytecode[] = {
|
||||
0x1b, 0x4c, 0x75, 0x61, 0x54, 0x00, 0x19, 0x93,
|
||||
0x0d, 0x0a, 0x1a, 0x0a, 0x04, 0x08, 0x08, 0x78,
|
||||
0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x28, 0x77, 0x40, 0x01,
|
||||
0x80, 0x80, 0x80, 0x00, 0x01, 0x0c, 0xc4, 0x51,
|
||||
0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x02, 0x81,
|
||||
0x80, 0x00, 0x80, 0x0b, 0x01, 0x00, 0x00, 0x0e,
|
||||
0x01, 0x02, 0x01, 0x14, 0x81, 0x02, 0x02, 0x03,
|
||||
0x82, 0x01, 0x00, 0x8b, 0x02, 0x00, 0x04, 0x00,
|
||||
0x03, 0x00, 0x00, 0xc4, 0x02, 0x02, 0x02, 0x03,
|
||||
0x83, 0x02, 0x00, 0x44, 0x01, 0x05, 0x01, 0x0b,
|
||||
0x01, 0x00, 0x06, 0x0e, 0x01, 0x02, 0x07, 0x80,
|
||||
0x01, 0x01, 0x00, 0x03, 0x02, 0x04, 0x00, 0x44,
|
||||
0x01, 0x03, 0x02, 0x3c, 0x81, 0x09, 0x00, 0x38,
|
||||
0x17, 0x00, 0x80, 0x8e, 0x01, 0x02, 0x0a, 0x0e,
|
||||
0x02, 0x02, 0x0b, 0x3c, 0x02, 0x0c, 0x00, 0xb8,
|
||||
0x00, 0x00, 0x80, 0x03, 0x82, 0x06, 0x00, 0x38,
|
||||
0x05, 0x00, 0x80, 0x94, 0x82, 0x04, 0x0e, 0x83,
|
||||
0x83, 0x07, 0x00, 0xc4, 0x02, 0x03, 0x02, 0xc2,
|
||||
0x02, 0x00, 0x00, 0xb8, 0x00, 0x00, 0x80, 0x03,
|
||||
0x02, 0x08, 0x00, 0xb8, 0x01, 0x00, 0x80, 0x83,
|
||||
0x82, 0x08, 0x00, 0x00, 0x03, 0x04, 0x00, 0xb5,
|
||||
0x02, 0x02, 0x00, 0x00, 0x02, 0x05, 0x00, 0x8e,
|
||||
0x02, 0x02, 0x12, 0xc0, 0x02, 0x7f, 0x00, 0x38,
|
||||
0x02, 0x00, 0x80, 0x80, 0x02, 0x04, 0x00, 0x03,
|
||||
0x83, 0x09, 0x00, 0x8e, 0x03, 0x02, 0x12, 0xb5,
|
||||
0x02, 0x03, 0x00, 0x00, 0x02, 0x05, 0x00, 0xbc,
|
||||
0x81, 0x09, 0x00, 0xb8, 0x04, 0x00, 0x80, 0x8b,
|
||||
0x02, 0x00, 0x00, 0x8e, 0x02, 0x05, 0x01, 0x94,
|
||||
0x82, 0x05, 0x02, 0x83, 0x03, 0x0a, 0x00, 0x00,
|
||||
0x04, 0x04, 0x00, 0x83, 0x84, 0x0a, 0x00, 0x00,
|
||||
0x05, 0x03, 0x00, 0x83, 0x85, 0x02, 0x00, 0xc4,
|
||||
0x02, 0x07, 0x01, 0x38, 0x03, 0x00, 0x80, 0x8b,
|
||||
0x02, 0x00, 0x00, 0x8e, 0x02, 0x05, 0x01, 0x94,
|
||||
0x82, 0x05, 0x02, 0x83, 0x03, 0x0a, 0x00, 0x00,
|
||||
0x04, 0x04, 0x00, 0x83, 0x84, 0x02, 0x00, 0xc4,
|
||||
0x02, 0x05, 0x01, 0x95, 0x00, 0x01, 0x80, 0xaf,
|
||||
0x00, 0x80, 0x06, 0xb8, 0xe4, 0xff, 0x7f, 0x46,
|
||||
0x00, 0x02, 0x01, 0x46, 0x01, 0x01, 0x01, 0x96,
|
||||
0x04, 0x83, 0x69, 0x6f, 0x04, 0x87, 0x73, 0x74,
|
||||
0x64, 0x65, 0x72, 0x72, 0x04, 0x86, 0x77, 0x72,
|
||||
0x69, 0x74, 0x65, 0x04, 0x92, 0x75, 0x6e, 0x68,
|
||||
0x61, 0x6e, 0x64, 0x6c, 0x65, 0x64, 0x20, 0x65,
|
||||
0x72, 0x72, 0x6f, 0x72, 0x3a, 0x20, 0x04, 0x89,
|
||||
0x74, 0x6f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67,
|
||||
0x04, 0x82, 0x0a, 0x04, 0x86, 0x64, 0x65, 0x62,
|
||||
0x75, 0x67, 0x04, 0x88, 0x67, 0x65, 0x74, 0x69,
|
||||
0x6e, 0x66, 0x6f, 0x04, 0x84, 0x53, 0x6e, 0x6c,
|
||||
0x00, 0x04, 0x85, 0x6e, 0x61, 0x6d, 0x65, 0x04,
|
||||
0x8a, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x5f, 0x73,
|
||||
0x72, 0x63, 0x04, 0x84, 0x5b, 0x43, 0x5d, 0x04,
|
||||
0x87, 0x61, 0x74, 0x20, 0x3c, 0x43, 0x3e, 0x04,
|
||||
0x85, 0x66, 0x69, 0x6e, 0x64, 0x04, 0x89, 0x25,
|
||||
0x5b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x04,
|
||||
0x8c, 0x61, 0x74, 0x20, 0x3c, 0x73, 0x74, 0x72,
|
||||
0x69, 0x6e, 0x67, 0x3e, 0x04, 0x84, 0x61, 0x74,
|
||||
0x20, 0x04, 0x8c, 0x63, 0x75, 0x72, 0x72, 0x65,
|
||||
0x6e, 0x74, 0x6c, 0x69, 0x6e, 0x65, 0x04, 0x82,
|
||||
0x3a, 0x04, 0x83, 0x20, 0x20, 0x04, 0x85, 0x20,
|
||||
0x69, 0x6e, 0x20, 0x81, 0x01, 0x00, 0x00, 0x80,
|
||||
0x80, 0x80, 0x80, 0x80,
|
||||
};
|
||||
|
||||
static void load_modules(lua_State *ctx) {
|
||||
luaL_openlibs(ctx);
|
||||
luaL_requiref(ctx, "http", http_open_lib, false);
|
||||
luaL_requiref(ctx, "fmt", fmt_open_lib, false);
|
||||
luaL_requiref(ctx, "zlib", zlib_open_lib, false);
|
||||
}
|
||||
|
||||
int main(int argc, const char **argv) {
|
||||
lua_State *ctx = luaL_newstate();
|
||||
load_modules(ctx);
|
||||
|
||||
if (luaL_loadbufferx(ctx, err_bytecode, sizeof(err_bytecode), "<main>", "b")) {
|
||||
fprintf(stderr, "error while loading lua bytecode for errfunc: %s", lua_tostring(ctx, -1));
|
||||
return 1;
|
||||
}
|
||||
int errfunc_i = lua_gettop(ctx);
|
||||
|
||||
if (luaL_loadbufferx(ctx, (char*)BYTECODE, sizeof(BYTECODE), "<main>", "b")) {
|
||||
fprintf(stderr, "error while loading lua bytecode for main: %s", lua_tostring(ctx, -1));
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (size_t i = 1; i < argc; i++) {
|
||||
lua_pushstring(ctx, argv[i]);
|
||||
}
|
||||
|
||||
if (lua_pcall(ctx, argc - 1, 0, errfunc_i)) return 1;
|
||||
|
||||
lua_close(ctx);
|
||||
return 0;
|
||||
}
|
136
lib/zlib.c
Normal file
136
lib/zlib.c
Normal file
@ -0,0 +1,136 @@
|
||||
#include <zlib.h>
|
||||
#include <lauxlib.h>
|
||||
#include <lua.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "lib.h"
|
||||
|
||||
#define DECOMPRESS_META "zlib.decompress.meta"
|
||||
|
||||
typedef struct {
|
||||
z_stream stream;
|
||||
size_t processed;
|
||||
uint8_t buff[4096];
|
||||
bool finalized;
|
||||
} lua_zlib_stream_t;
|
||||
|
||||
static int lib_decompress_iter(lua_State *ctx) {
|
||||
size_t size;
|
||||
uint8_t *data = (uint8_t*)lua_tolstring(ctx, lua_upvalueindex(1), &size);
|
||||
|
||||
lua_zlib_stream_t *stream = lua_touserdata(ctx, lua_upvalueindex(2));
|
||||
|
||||
if (stream->finalized) {
|
||||
lua_pushnil(ctx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
stream->stream.next_in = data + stream->processed;
|
||||
stream->stream.avail_in = size - stream->processed;
|
||||
|
||||
stream->stream.next_out = stream->buff;
|
||||
stream->stream.avail_out = sizeof stream->buff;
|
||||
|
||||
int res = inflate(&stream->stream, Z_NO_FLUSH);
|
||||
|
||||
switch (res) {
|
||||
case Z_STREAM_END:
|
||||
inflateEnd(&stream->stream);
|
||||
stream->finalized = true;
|
||||
case Z_OK: {
|
||||
lua_pushlstring(ctx, (const char*)stream->buff, sizeof stream->buff - stream->stream.avail_out);
|
||||
stream->processed = size - stream->stream.avail_in;
|
||||
return 1;
|
||||
}
|
||||
case Z_BUF_ERROR:
|
||||
inflateEnd(&stream->stream);
|
||||
stream->finalized = true;
|
||||
return luaL_error(ctx, "zlib error: incorrect C parameters passed");
|
||||
case Z_MEM_ERROR:
|
||||
inflateEnd(&stream->stream);
|
||||
stream->finalized = true;
|
||||
return luaL_error(ctx, "zlib error: memory ran out");
|
||||
case Z_STREAM_ERROR:
|
||||
inflateEnd(&stream->stream);
|
||||
stream->finalized = true;
|
||||
return luaL_error(ctx, "zlib error: invalid state");
|
||||
case Z_DATA_ERROR:
|
||||
inflateEnd(&stream->stream);
|
||||
stream->finalized = true;
|
||||
return luaL_error(ctx, "zlib error: %s", stream->stream.msg);
|
||||
default:
|
||||
return luaL_error(ctx, "wtf");
|
||||
}
|
||||
}
|
||||
static int lib_decompress_free(lua_State *ctx) {
|
||||
lua_zlib_stream_t *stream = lua_touserdata(ctx, 1);
|
||||
if (stream->finalized) return 0;
|
||||
|
||||
inflateEnd(&stream->stream);
|
||||
stream->finalized = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
static int lib_decompress_create(lua_State *ctx, int window_size) {
|
||||
// size_t data_size;
|
||||
// uint8_t *data = (uint8_t*)luaL_checklstring(ctx, 1, &data_size);
|
||||
|
||||
lua_pushvalue(ctx, 1);
|
||||
|
||||
lua_zlib_stream_t *stream = lua_newuserdatauv(ctx, sizeof *stream, 0);
|
||||
memset(stream, 0, sizeof *stream);
|
||||
|
||||
luaL_getmetatable(ctx, DECOMPRESS_META);
|
||||
lua_setmetatable(ctx, -2);
|
||||
|
||||
|
||||
// stream->stream.avail_out = sizeof stream->buff;
|
||||
// stream->stream.next_out = stream->buff;
|
||||
|
||||
// stream->stream.avail_in = data_size;
|
||||
// stream->stream.next_in = data;
|
||||
|
||||
if (inflateInit2(&stream->stream, window_size) != Z_OK) {
|
||||
return luaL_error(ctx, "failed to initialize inflate stream");
|
||||
}
|
||||
|
||||
lua_pushcclosure(ctx, lib_decompress_iter, 2);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int lib_inflate(lua_State *ctx) {
|
||||
return lib_decompress_create(ctx, MAX_WBITS);
|
||||
}
|
||||
static int lib_inflate_raw(lua_State *ctx) {
|
||||
return lib_decompress_create(ctx, -MAX_WBITS);
|
||||
}
|
||||
static int lib_gunzip(lua_State *ctx) {
|
||||
return lib_decompress_create(ctx, MAX_WBITS + 16);
|
||||
}
|
||||
static int lib_decompress(lua_State *ctx) {
|
||||
return lib_decompress_create(ctx, MAX_WBITS + 32);
|
||||
}
|
||||
|
||||
|
||||
int zlib_open_lib(lua_State *ctx) {
|
||||
luaL_newmetatable(ctx, DECOMPRESS_META);
|
||||
luaL_setfuncs(ctx, (luaL_Reg[]) {
|
||||
{ "__gc", lib_decompress_free },
|
||||
{ NULL, NULL },
|
||||
}, 0);
|
||||
|
||||
lua_pop(ctx, 2);
|
||||
|
||||
luaL_newlib(ctx, ((luaL_Reg[]) {
|
||||
{ "inflate", lib_inflate },
|
||||
{ "inflate_raw", lib_inflate_raw },
|
||||
{ "gunzip", lib_gunzip },
|
||||
{ "decompress", lib_decompress },
|
||||
{ NULL, NULL },
|
||||
}));
|
||||
|
||||
return 1;
|
||||
}
|
24
mod/fmt.d.lua
Normal file
24
mod/fmt.d.lua
Normal file
@ -0,0 +1,24 @@
|
||||
--- @meta fmt
|
||||
|
||||
--- @class fmt
|
||||
local fmt_proto = {};
|
||||
|
||||
--- @param ... any
|
||||
--- @return string
|
||||
function fmt_proto:pack(...) end
|
||||
|
||||
--- @param str string
|
||||
--- @param i? integer
|
||||
--- @return ...
|
||||
function fmt_proto:unpack(str, i) end
|
||||
|
||||
local fmt = {};
|
||||
|
||||
--- @param path string
|
||||
--- @return fmt fmt
|
||||
function fmt.new(path) end
|
||||
|
||||
fmt.pack = fmt_proto.pack;
|
||||
fmt.unpack = fmt_proto.unpack;
|
||||
|
||||
return fmt;
|
9
mod/http.d.lua
Normal file
9
mod/http.d.lua
Normal file
@ -0,0 +1,9 @@
|
||||
--- @meta http
|
||||
|
||||
local http = {};
|
||||
|
||||
--- @param path string
|
||||
--- @return string body
|
||||
function http.get(path) end
|
||||
|
||||
return http;
|
17
mod/zlib.d.lua
Normal file
17
mod/zlib.d.lua
Normal file
@ -0,0 +1,17 @@
|
||||
--- @meta zlib
|
||||
|
||||
local zlib = {};
|
||||
|
||||
--- @param data string
|
||||
--- @return fun(): string?
|
||||
function zlib.inflate_raw(data) end
|
||||
|
||||
--- @param data string
|
||||
--- @return fun(): string?
|
||||
function zlib.gunzip(data) end
|
||||
|
||||
--- @param data string
|
||||
--- @return fun(): string?
|
||||
function zlib.inflate(data) end
|
||||
|
||||
return zlib;
|
41
src/cli/dump.lua
Normal file
41
src/cli/dump.lua
Normal file
@ -0,0 +1,41 @@
|
||||
local ostree = require "ostree";
|
||||
-- TODO: make this an option
|
||||
local repo_url = "https://dl.flathub.org/repo";
|
||||
|
||||
return function (name, out)
|
||||
local repo = ostree.from_http(repo_url);
|
||||
local ref = repo:read_ref(name);
|
||||
local commit = repo:read_commit(ref);
|
||||
|
||||
local function dump_file(path, file)
|
||||
print("Dumping " .. path .. "...");
|
||||
|
||||
local file_obj = repo:read_file(file);
|
||||
|
||||
if file_obj.type == "file" then
|
||||
local f = assert(io.open(out .. "/" .. path, "w"));
|
||||
for seg in file_obj.get_content() do
|
||||
assert(f:write(seg));
|
||||
end
|
||||
f:close();
|
||||
else
|
||||
os.execute(("ln -s %q %q"):format(file_obj.target, out .. "/" .. path));
|
||||
end
|
||||
end
|
||||
local function dump_dir(path, dir)
|
||||
print("Dumping " .. path .. "...");
|
||||
os.execute(("mkdir %q"):format(out .. "/" .. path));
|
||||
|
||||
local dir_obj = repo:read_dir(dir);
|
||||
|
||||
for subname, subdir in pairs(dir_obj.dirs) do
|
||||
dump_dir(path .. "/" .. subname, subdir);
|
||||
end
|
||||
|
||||
for subname, subfile in pairs(dir_obj.files) do
|
||||
dump_file(path .. "/" .. subname, subfile);
|
||||
end
|
||||
end
|
||||
|
||||
dump_dir("/", commit.root);
|
||||
end
|
14
src/cli/help.lua
Normal file
14
src/cli/help.lua
Normal file
@ -0,0 +1,14 @@
|
||||
return function (action, ...)
|
||||
print "TopchetoEU's slimpack:";
|
||||
print "Minimalistic flatpak client, used to run flatpaks";
|
||||
print "with less isolation (to hell with security)";
|
||||
print "";
|
||||
print "How to use:";
|
||||
print "The CLI accepts one 'action' argument, and the rest are specific to the action.";
|
||||
print "The following actions are currently supported:";
|
||||
print "query list - lists all refs in the current repo (be warned, this will produce over 20K lines of output)";
|
||||
print "query search <term> - searches the given term in the current repo";
|
||||
print "dump <ref> [out] - dumps the raw contents of the given package in the given dirname (defaults to 'out')";
|
||||
print "help (or --help/-h/-help) - shows this message";
|
||||
os.exit();
|
||||
end
|
28
src/cli/query.lua
Normal file
28
src/cli/query.lua
Normal file
@ -0,0 +1,28 @@
|
||||
local ostree = require "ostree";
|
||||
local repo_url = "https://dl.flathub.org/repo";
|
||||
|
||||
local actions = {};
|
||||
function actions.list()
|
||||
local repo = ostree.from_http(repo_url);
|
||||
local index = repo:read_summary_index();
|
||||
local summary = repo:read_subsummary(index.refs.x86_64.checksum);
|
||||
|
||||
for ref in pairs(summary.refs) do
|
||||
print(ref);
|
||||
end
|
||||
end
|
||||
function actions.search(term)
|
||||
local repo = ostree.from_http(repo_url);
|
||||
local index = repo:read_summary_index();
|
||||
local summary = repo:read_subsummary(index.refs.x86_64.checksum);
|
||||
|
||||
for ref in pairs(summary.refs) do
|
||||
if ref:lower():match(term:lower(), 1, true) then
|
||||
print(ref);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return function (action, ...)
|
||||
actions[action](...);
|
||||
end
|
34
src/errfunc.lua
Normal file
34
src/errfunc.lua
Normal file
@ -0,0 +1,34 @@
|
||||
local err = ...;
|
||||
local i = 2;
|
||||
|
||||
io.stderr:write("unhandled error: ", tostring(err), "\n");
|
||||
|
||||
while true do
|
||||
local info = debug.getinfo(i, "Snl");
|
||||
if info == nil then break end
|
||||
|
||||
local name = info.name;
|
||||
local location = info.short_src;
|
||||
|
||||
if location == "[C]" then
|
||||
location = "at <C>";
|
||||
elseif location:find "%[string" then
|
||||
location = "at <string>";
|
||||
else
|
||||
location = "at " .. location;
|
||||
end
|
||||
|
||||
if info.currentline > 0 then
|
||||
location = location .. ":" .. info.currentline;
|
||||
end
|
||||
|
||||
if name ~= nil then
|
||||
io.stderr:write(" ", location, " in ", name, "\n");
|
||||
else
|
||||
io.stderr:write(" ", location, "\n");
|
||||
end
|
||||
|
||||
i = i + 1;
|
||||
end
|
||||
|
||||
return err;
|
401
src/formats/gvariant.lua
Normal file
401
src/formats/gvariant.lua
Normal file
@ -0,0 +1,401 @@
|
||||
-- TODO: remove string.unpack
|
||||
|
||||
local fmt = require "fmt";
|
||||
|
||||
local uint8_le_fmt = fmt.new "<! I1";
|
||||
local uint16_le_fmt = fmt.new "<! I2";
|
||||
local uint32_le_fmt = fmt.new "<! I4";
|
||||
|
||||
|
||||
local parsers = {};
|
||||
local cache = setmetatable({}, { __mode = "v" });
|
||||
|
||||
-- local indent = "";
|
||||
|
||||
local function alignof(i, n)
|
||||
return math.ceil((i - 1) / n) * n + 1;
|
||||
end
|
||||
|
||||
local function offset_reader(s, e)
|
||||
if e - s + 1 < 256 then
|
||||
return function (buf, i)
|
||||
local res = uint8_le_fmt:unpack(buf, e - i + 1);
|
||||
return res;
|
||||
end, 1;
|
||||
elseif e - s + 1 < 65536 then
|
||||
return function (buf, i)
|
||||
local res = uint16_le_fmt:unpack(buf, e - i * 2 + 1);
|
||||
return res;
|
||||
end, 2;
|
||||
else
|
||||
return function (buf, i)
|
||||
local res = uint32_le_fmt:unpack(buf, e - i * 4 + 1);
|
||||
return res;
|
||||
end, 4;
|
||||
end
|
||||
end
|
||||
|
||||
local function construct_reader_no_cache(str, i)
|
||||
local c = str:sub(i, i);
|
||||
|
||||
if c == "b" then
|
||||
return parsers.bool(), i + 1;
|
||||
elseif c == "y" then
|
||||
return parsers.uint8(), i + 1;
|
||||
elseif c == "n" then
|
||||
return parsers.int16(), i + 1;
|
||||
elseif c == "q" then
|
||||
return parsers.uint16(), i + 1;
|
||||
elseif c == "i" then
|
||||
return parsers.int32(), i + 1;
|
||||
elseif c == "u" then
|
||||
return parsers.uint32(), i + 1;
|
||||
elseif c == "x" then
|
||||
return parsers.int64(), i + 1;
|
||||
elseif c == "t" then
|
||||
return parsers.uint64(), i + 1;
|
||||
elseif c == "d" then
|
||||
return parsers.float64(), i + 1;
|
||||
elseif c == "s" or c == "o" or c == "g" then
|
||||
return parsers.string(), i + 1;
|
||||
elseif c == "r" then
|
||||
return parsers.ref(), i + 1;
|
||||
elseif c == "v" then
|
||||
return parsers.variant(), i + 1;
|
||||
elseif c == "m" then
|
||||
local reader;
|
||||
reader, i = construct_reader_no_cache(str, i + 1);
|
||||
return parsers.maybe(reader), i;
|
||||
elseif c == "a" then
|
||||
i = i + 1;
|
||||
if str:sub(i, i) == "{" then
|
||||
i = i + 1;
|
||||
|
||||
local key, val;
|
||||
key, i = construct_reader_no_cache(str, i);
|
||||
val, i = construct_reader_no_cache(str, i);
|
||||
|
||||
if str:sub(i, i) ~= "}" then
|
||||
error "malformed gvarian format";
|
||||
end
|
||||
i = i + 1;
|
||||
|
||||
return parsers.map(key, val), i;
|
||||
elseif str:sub(i, i) == "y" then
|
||||
i = i + 1;
|
||||
return parsers.buffer(), i;
|
||||
else
|
||||
local reader;
|
||||
reader, i = construct_reader_no_cache(str, i);
|
||||
return parsers.array(reader), i;
|
||||
end
|
||||
elseif c == "(" then
|
||||
i = i + 1;
|
||||
|
||||
local tuple_parsers = {};
|
||||
|
||||
while str:sub(i, i) ~= ")" do
|
||||
tuple_parsers[#tuple_parsers + 1], i = construct_reader_no_cache(str, i);
|
||||
end
|
||||
|
||||
i = i + 1;
|
||||
|
||||
return parsers.tuple(table.unpack(tuple_parsers)), i;
|
||||
elseif c == "{" then
|
||||
i = i + 1;
|
||||
|
||||
local key, val;
|
||||
key, i = construct_reader_no_cache(str, i);
|
||||
val, i = construct_reader_no_cache(str, i);
|
||||
|
||||
if str:sub(i, i) ~= "}" then
|
||||
error "malformed gvarian format";
|
||||
end
|
||||
i = i + 1;
|
||||
|
||||
return parsers.tuple(key, val), i;
|
||||
else
|
||||
error "malformed gvariant format";
|
||||
end
|
||||
end
|
||||
|
||||
local function construct_reader(str)
|
||||
if not cache[str] then
|
||||
cache[str] = { construct_reader_no_cache(str, 1) };
|
||||
end
|
||||
return cache[str][1], cache[str][2];
|
||||
end
|
||||
|
||||
local function encoded_helper(c)
|
||||
return ("%02x"):format(string.byte(c));
|
||||
end
|
||||
local function to_encoded(str)
|
||||
return str:gsub(".", encoded_helper);
|
||||
end
|
||||
|
||||
local function fmt_to_parser(format, fixed, align)
|
||||
return function ()
|
||||
return {
|
||||
fixed = fixed, align = align,
|
||||
read = function(buf, s)
|
||||
return (format:unpack(buf, s));
|
||||
end,
|
||||
};
|
||||
end
|
||||
end
|
||||
|
||||
parsers.uint8 = fmt_to_parser(fmt.new ">! I1", 1, 1);
|
||||
parsers.uint16 = fmt_to_parser(fmt.new ">! I2", 2, 2);
|
||||
parsers.uint32 = fmt_to_parser(fmt.new ">! I4", 4, 4);
|
||||
parsers.uint64 = fmt_to_parser(fmt.new ">! I8", 8, 8);
|
||||
|
||||
parsers.int8 = fmt_to_parser(fmt.new ">! i1", 1, 1);
|
||||
parsers.int16 = fmt_to_parser(fmt.new ">! i2", 2, 2);
|
||||
parsers.int32 = fmt_to_parser(fmt.new ">! i4", 4, 4);
|
||||
parsers.int64 = fmt_to_parser(fmt.new ">! i8", 8, 8);
|
||||
|
||||
parsers.float32 = fmt_to_parser(fmt.new ">! f", 4, 4);
|
||||
parsers.float64 = fmt_to_parser(fmt.new ">! d", 8, 8);
|
||||
|
||||
function parsers.string()
|
||||
return {
|
||||
fixed = nil, align = 1,
|
||||
read = function(buf, s, e)
|
||||
return buf:sub(s, e - 1);
|
||||
end,
|
||||
};
|
||||
end
|
||||
function parsers.ref()
|
||||
return {
|
||||
fixed = nil, align = 1,
|
||||
read = function(buf, s, e)
|
||||
return to_encoded(buf:sub(s, e));
|
||||
end,
|
||||
};
|
||||
end
|
||||
function parsers.buffer()
|
||||
return {
|
||||
fixed = nil, align = 1,
|
||||
read = function(buf, s, e)
|
||||
return buf:sub(s, e);
|
||||
end,
|
||||
};
|
||||
end
|
||||
|
||||
function parsers.bool()
|
||||
return {
|
||||
fixed = 1, align = 1,
|
||||
read = function(buf, s, e)
|
||||
return buf:sub(s, e) == "\1";
|
||||
end,
|
||||
};
|
||||
end
|
||||
|
||||
function parsers.maybe(val_parser)
|
||||
if val_parser.fixed then
|
||||
return {
|
||||
fixed = nil, align = val_parser.align,
|
||||
read = function(buf, s, e)
|
||||
if s > e then
|
||||
return nil;
|
||||
else
|
||||
return val_parser.read(buf, alignof(s, val_parser.align), e);
|
||||
end
|
||||
end
|
||||
};
|
||||
else
|
||||
return {
|
||||
fixed = nil, align = val_parser.align,
|
||||
read = function(buf, s, e)
|
||||
if s > e then
|
||||
return nil;
|
||||
else
|
||||
return val_parser.read(buf, alignof(s, val_parser.align), e - 1);
|
||||
end
|
||||
end
|
||||
};
|
||||
end
|
||||
end
|
||||
|
||||
function parsers.array(el_parser, as_iterable)
|
||||
return {
|
||||
fixed = nil, align = el_parser.align,
|
||||
read = function(buf, s, e)
|
||||
if s > e then
|
||||
if as_iterable then
|
||||
return function () return nil end
|
||||
else
|
||||
return {};
|
||||
end
|
||||
end
|
||||
|
||||
s = alignof(s, el_parser.align);
|
||||
|
||||
local read_offset, offset_size = offset_reader(s, e);
|
||||
local n = ((e - s + 1) - read_offset(buf, 1)) / offset_size;
|
||||
local curr_s = s;
|
||||
|
||||
-- print(indent, "ARR\t", s, e, n, read_offset(buf, 1));
|
||||
-- indent = indent .. "\t";
|
||||
|
||||
assert(not (n % 1 > 0), "offset calculation error");
|
||||
n = math.floor(n);
|
||||
|
||||
if as_iterable then
|
||||
local i = 0;
|
||||
|
||||
return function ()
|
||||
if i >= n then
|
||||
-- indent = indent:sub(1, -2);
|
||||
return nil;
|
||||
end
|
||||
i = i + 1;
|
||||
|
||||
local curr_e = s + read_offset(buf, n - i + 1) - 1;
|
||||
curr_s = alignof(curr_s, el_parser.align);
|
||||
|
||||
-- print(indent, "ARR ELEMENT", i, curr_s, curr_e);
|
||||
local res = el_parser.read(buf, curr_s, curr_e);
|
||||
|
||||
curr_s = curr_e + 1;
|
||||
return res;
|
||||
end
|
||||
else
|
||||
local res = {};
|
||||
for i = 1, n do
|
||||
local curr_e = s + read_offset(buf, n - i + 1) - 1;
|
||||
curr_s = alignof(curr_s, el_parser.align);
|
||||
|
||||
-- print(indent, "ARR ELEMENT", i, curr_s, curr_e);
|
||||
res[i] = el_parser.read(buf, curr_s, curr_e);
|
||||
|
||||
curr_s = curr_e + 1;
|
||||
end
|
||||
|
||||
-- indent = indent:sub(1, -2);
|
||||
return res;
|
||||
end
|
||||
end
|
||||
};
|
||||
end
|
||||
|
||||
function parsers.map(key, val)
|
||||
local arr_parser = parsers.array(parsers.tuple(key, val), true);
|
||||
|
||||
return {
|
||||
fixed = nil, align = arr_parser.align,
|
||||
read = function (buf, s, e)
|
||||
local res = {};
|
||||
|
||||
for el in arr_parser.read(buf, s, e) do
|
||||
res[el[1]] = el[2];
|
||||
end
|
||||
|
||||
return res;
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
function parsers.tuple(...)
|
||||
local descriptors = { ... };
|
||||
|
||||
local align = 1;
|
||||
--- @type integer | nil
|
||||
local fixed = 0;
|
||||
|
||||
for i = 1, select("#", ...) do
|
||||
assert(select(i, ...) ~= nil, "don't pass nil arguments!");
|
||||
|
||||
local parser = descriptors[i];
|
||||
|
||||
if align < parser.align then
|
||||
align = parser.align;
|
||||
end
|
||||
|
||||
if parser.fixed == nil then
|
||||
fixed = nil;
|
||||
elseif fixed ~= nil then
|
||||
fixed = alignof(fixed, parser.align) + parser.fixed;
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
fixed = fixed, align = align,
|
||||
read = function (buff, s, e)
|
||||
if s > e then return {} end
|
||||
|
||||
s = alignof(s, align);
|
||||
-- print(indent, "TUPLE\t", s, e);
|
||||
-- indent = indent .. "\t";
|
||||
|
||||
local read_offset, offset_size = offset_reader(s, e);
|
||||
local res = {};
|
||||
local curr_s = s;
|
||||
|
||||
local offset_i = 0;
|
||||
|
||||
for i = 1, #descriptors do
|
||||
curr_s = alignof(curr_s, descriptors[i].align);
|
||||
local curr_e;
|
||||
|
||||
if descriptors[i].fixed then
|
||||
curr_e = curr_s + descriptors[i].fixed - 1;
|
||||
-- print(indent, "TUPLE FIXEL", i, curr_s, curr_e);
|
||||
elseif i == #descriptors then
|
||||
curr_e = e - offset_size * offset_i;
|
||||
-- print(indent, "TUPLE LASTEL", i, curr_s, curr_e);
|
||||
else
|
||||
offset_i = offset_i + 1;
|
||||
curr_e = s + read_offset(buff, offset_i) - 1;
|
||||
-- print(indent, "TUPLE DYNEL", i, curr_s, curr_e);
|
||||
end
|
||||
|
||||
assert(curr_e - curr_s + 1 >= 0, "negative length segment");
|
||||
|
||||
res[i] = descriptors[i].read(buff, curr_s, curr_e);
|
||||
curr_s = curr_e + 1;
|
||||
end
|
||||
|
||||
-- indent = indent:sub(1, -2);
|
||||
return res;
|
||||
end
|
||||
};
|
||||
end
|
||||
|
||||
function parsers.variant()
|
||||
return {
|
||||
fixed = nil, align = 8,
|
||||
read = function (buff, s, e)
|
||||
local format = buff:sub(s, e):match "\0([^\0]+)$";
|
||||
local parser = construct_reader(format);
|
||||
-- print(indent, "VARIANT", format);
|
||||
return parser.read(buff, alignof(s, parser.align), e - #format - 1);
|
||||
end
|
||||
};
|
||||
end
|
||||
|
||||
return function (format)
|
||||
local readers = {};
|
||||
|
||||
while #format > 0 do
|
||||
local i;
|
||||
readers[#readers + 1], i = construct_reader(format);
|
||||
format = format:sub(i);
|
||||
end
|
||||
|
||||
if #readers == 0 then
|
||||
return function () end
|
||||
elseif #readers == 1 then
|
||||
local reader = readers[1];
|
||||
|
||||
return function (buff, s, e)
|
||||
return reader.read(buff, s or 1, e or #buff);
|
||||
end
|
||||
else
|
||||
local tuple_parser = parsers.tuple(table.unpack(readers));
|
||||
|
||||
return function (buff, s, e)
|
||||
return table.unpack(tuple_parser.read(buff, s or 1, e or #buff));
|
||||
end
|
||||
end
|
||||
end
|
59
src/formats/ini.lua
Normal file
59
src/formats/ini.lua
Normal file
@ -0,0 +1,59 @@
|
||||
local function parse_ini(raw, glob_group)
|
||||
local lines = {};
|
||||
|
||||
for line in raw:gmatch "[^\n]+" do
|
||||
lines[#lines + 1] = line:match "^%s+(.+)%s+$";
|
||||
end
|
||||
|
||||
local groups = {};
|
||||
local curr_group;
|
||||
|
||||
if glob_group then
|
||||
curr_group = {};
|
||||
groups[glob_group] = curr_group;
|
||||
end
|
||||
|
||||
local line_i = 0;
|
||||
|
||||
for line in raw:gmatch "[^\n]+" do
|
||||
line_i = line_i + 1;
|
||||
line = line:match "^%s*(.-)%s*$";
|
||||
|
||||
if line ~= "" then
|
||||
local group = line.match "^%[%s*(.-)%s*%]$";
|
||||
|
||||
if group ~= nil then
|
||||
curr_group = {};
|
||||
groups[group] = curr_group;
|
||||
elseif curr_group == nil then
|
||||
error("line " .. line_i .. ": Unexpected global key");
|
||||
else
|
||||
local key, value = line.match "^%s*(.-)%s*=%s*(.-)%s*$";
|
||||
if key == nil then
|
||||
error("line " .. line_i .. ": Unexpected ini syntax");
|
||||
end
|
||||
|
||||
curr_group[key] = value;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return groups;
|
||||
end
|
||||
|
||||
local function parse_ini_list(raw)
|
||||
local res = {};
|
||||
|
||||
for el in raw:gmatch "%s*([^;]-)%s*" do
|
||||
if el ~= "" then
|
||||
res[#res + 1] = el;
|
||||
end
|
||||
end
|
||||
|
||||
return res;
|
||||
end
|
||||
|
||||
return {
|
||||
parse = parse_ini,
|
||||
list = parse_ini_list,
|
||||
};
|
251
src/formats/xml.lua
Normal file
251
src/formats/xml.lua
Normal file
@ -0,0 +1,251 @@
|
||||
--- @alias xml_node_raw { tag: string, attribs: { [string]: string }, [integer]: xml_element }
|
||||
--- @alias xml_element string | xml_node
|
||||
|
||||
--- @class xml_node
|
||||
--- @field tag string
|
||||
--- @field attribs { [string]: string }
|
||||
--- @field [integer] xml_element
|
||||
local xml_node = {};
|
||||
xml_node.__index = xml_node;
|
||||
|
||||
--- @param name string
|
||||
function xml_node:get_all(name)
|
||||
--- @type xml_node[]
|
||||
local res = {};
|
||||
|
||||
for _, el in ipairs(self) do
|
||||
if type(el) ~= "string" and el.tag == name then
|
||||
res[#res + 1] = el;
|
||||
end
|
||||
end
|
||||
|
||||
return res;
|
||||
end
|
||||
|
||||
--- @param name string
|
||||
function xml_node:get(name)
|
||||
local res = self:get_all(name);
|
||||
if #res == 0 then
|
||||
error("node '" .. name .. "' not found");
|
||||
elseif #res > 1 then
|
||||
error("multiple nodes '" .. name .. "' exist");
|
||||
else
|
||||
return res[1];
|
||||
end
|
||||
end
|
||||
|
||||
function xml_node:text()
|
||||
if #self == 0 then
|
||||
return "";
|
||||
elseif #self == 1 and type(self[1]) == "string" then
|
||||
return self[1];
|
||||
else
|
||||
error "not a text-only node";
|
||||
end
|
||||
end
|
||||
|
||||
--- @param raw xml_node_raw
|
||||
--- @return xml_node
|
||||
function xml_node.new(raw)
|
||||
local res = setmetatable({
|
||||
tag = raw.tag,
|
||||
attribs = raw.attribs or {},
|
||||
}, xml_node);
|
||||
|
||||
table.move(raw, 1, #raw, 1, res);
|
||||
|
||||
return res;
|
||||
end
|
||||
|
||||
local function skip_spaces(raw, i)
|
||||
local next_i = raw:find("[^%s]", i);
|
||||
if next_i then return next_i end
|
||||
|
||||
if raw:find("^%s", i) then
|
||||
local match = raw:match("^%s*", i);
|
||||
if match then return i + #match end
|
||||
else
|
||||
return i;
|
||||
end
|
||||
end
|
||||
|
||||
local function parse_tag(raw, i)
|
||||
i = skip_spaces(raw, i);
|
||||
|
||||
local tag = raw:match("^[%w0-9%-]+", i);
|
||||
if tag == nil then error("expected tag name near '" .. raw:sub(i, i + 25) .. "'") end
|
||||
|
||||
i = i + #tag;
|
||||
|
||||
local attribs = {};
|
||||
|
||||
while true do
|
||||
i = skip_spaces(raw, i);
|
||||
|
||||
local all, key, _, val = raw:match("^(([%w0-9%-]-)%s*=%s*(['\"])(.-)%3%s*)", i);
|
||||
if all then
|
||||
attribs[key] = val;
|
||||
i = i + #all;
|
||||
else
|
||||
break;
|
||||
end
|
||||
end
|
||||
|
||||
return { tag = tag, attribs = attribs }, i;
|
||||
end
|
||||
|
||||
local function parse_part(raw, i, allow_version)
|
||||
i = skip_spaces(raw, i);
|
||||
|
||||
repeat
|
||||
local comment_end;
|
||||
|
||||
if raw:sub(i, i + 3) == "<!--" then
|
||||
i = i + 43;
|
||||
|
||||
_, comment_end = raw:find("-->", i);
|
||||
i = comment_end or #raw;
|
||||
i = skip_spaces(raw, i);
|
||||
end
|
||||
until not comment_end;
|
||||
|
||||
if i > #raw then
|
||||
return { type = "eof" }, i;
|
||||
elseif allow_version and raw:sub(i, i + 1) == "<?" then
|
||||
i = i + 2;
|
||||
|
||||
local tag;
|
||||
tag, i = parse_tag(raw, i);
|
||||
|
||||
if raw:sub(i, i + 1) ~= "?>" then
|
||||
error("malformed XML near '" .. raw:sub(i, i + 25) .. "'");
|
||||
end
|
||||
i = i + 2;
|
||||
|
||||
return { type = "version", tag = tag.tag, attribs = tag.attribs }, i;
|
||||
elseif raw:sub(i, i + 1) == "</" then
|
||||
i = i + 2;
|
||||
i = skip_spaces(raw, i);
|
||||
|
||||
local tag = raw:match("[%w0-9%-]+", i);
|
||||
if tag == nil then error("expected closing tag name near '" .. raw:sub(i, i + 25) .. "'") end
|
||||
|
||||
i = i + #tag;
|
||||
|
||||
if raw:sub(i, i) == ">" then
|
||||
i = i + 1;
|
||||
return { type = "end", tag = tag }, i;
|
||||
else
|
||||
error("malformed closing tag near '" .. raw:sub(i, i + 25) .. "'");
|
||||
end
|
||||
|
||||
elseif raw:sub(i, i) == "<" then
|
||||
i = i + 1;
|
||||
|
||||
local tag;
|
||||
tag, i = parse_tag(raw, i);
|
||||
|
||||
|
||||
if raw:sub(i, i + 1) == "/>" then
|
||||
i = i + 2;
|
||||
return { type = "small", tag = tag.tag, attribs = tag.attribs }, i;
|
||||
elseif raw:sub(i, i) == ">" then
|
||||
i = i + 1;
|
||||
return { type = "begin", tag = tag.tag, attribs = tag.attribs }, i;
|
||||
else
|
||||
error("malformed opening tag near '" .. raw:sub(i, i + 25) .. "'");
|
||||
end
|
||||
else
|
||||
local text_parts = {};
|
||||
|
||||
while i <= #raw do
|
||||
local text_end = raw:find("<", i);
|
||||
|
||||
local text_part = raw:sub(i, text_end and text_end - 1 or #raw):match "^%s*(.-)%s*$";
|
||||
if text_part ~= "" then
|
||||
text_parts[#text_parts + 1] = text_part;
|
||||
end
|
||||
|
||||
if not text_end then break end
|
||||
|
||||
i = text_end or #raw;
|
||||
|
||||
local comment_end;
|
||||
|
||||
if raw:sub(i, i + 3) == "<!--" then
|
||||
i = i + 4;
|
||||
|
||||
_, comment_end = raw:find("-->", i);
|
||||
i = comment_end and comment_end + 1 or #raw;
|
||||
i = skip_spaces(raw, i);
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if #text_parts > 0 then
|
||||
return { type = "text", text = table.concat(text_parts, " ") }, i;
|
||||
elseif i > #raw then
|
||||
return { type = "eof" }, i;
|
||||
else
|
||||
error("malformed XML near '" .. raw:sub(i, i + 25) .. "'");
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- @param raw string
|
||||
--- @return xml_element
|
||||
local function parse(raw)
|
||||
--- @type xml_node
|
||||
local curr_node = xml_node.new { tag = "document", attribs = {} };
|
||||
local stack = { curr_node };
|
||||
local i = 1;
|
||||
local first = true;
|
||||
|
||||
while true do
|
||||
local part;
|
||||
part, i = parse_part(raw, i, first);
|
||||
if part.type == "eof" then break end
|
||||
|
||||
first = false;
|
||||
|
||||
if part.type == "text" then
|
||||
if #stack == 1 then
|
||||
error "text may not appear outside a tag";
|
||||
else
|
||||
curr_node[#curr_node + 1] = part.text;
|
||||
end
|
||||
elseif part.type == "version" then
|
||||
curr_node.attribs.type = part.tag;
|
||||
curr_node.attribs.version = part.attribs.version;
|
||||
elseif part.type == "begin" then
|
||||
local new_node = xml_node.new { tag = part.tag, attribs = part.attribs };
|
||||
curr_node[#curr_node + 1] = new_node;
|
||||
curr_node = new_node;
|
||||
stack[#stack + 1] = new_node;
|
||||
elseif part.type == "end" then
|
||||
if part.tag ~= curr_node.tag then
|
||||
error("closing tag '" .. part.tag .. "' doesn't match most recent opening tag '" .. curr_node.tag .. "'");
|
||||
else
|
||||
table.remove(stack);
|
||||
curr_node = stack[#stack];
|
||||
end
|
||||
elseif part.type == "small" then
|
||||
curr_node[#curr_node + 1] = xml_node.new { tag = part.tag, attribs = part.attribs };
|
||||
else
|
||||
error "wtf";
|
||||
end
|
||||
end
|
||||
|
||||
if #stack > 1 then
|
||||
error("tag '" .. curr_node.tag .. "' was left open");
|
||||
end
|
||||
|
||||
return curr_node;
|
||||
end
|
||||
|
||||
return {
|
||||
parse = parse,
|
||||
node = xml_node.new,
|
||||
};
|
16
src/init.lua
Normal file
16
src/init.lua
Normal file
@ -0,0 +1,16 @@
|
||||
require "util.printing";
|
||||
|
||||
local actions = {
|
||||
help = require "cli.help",
|
||||
query = require "cli.query",
|
||||
dump = require "cli.dump",
|
||||
};
|
||||
|
||||
return function (action, ...)
|
||||
if action == "--help" or action == "-h" or action == "-help" then
|
||||
return actions.help();
|
||||
end
|
||||
|
||||
return actions[action](...);
|
||||
end
|
||||
|
251
src/ostree.lua
Normal file
251
src/ostree.lua
Normal file
@ -0,0 +1,251 @@
|
||||
local gvariant = require "formats.gvariant";
|
||||
local fmt = require "fmt";
|
||||
local zlib = require "zlib";
|
||||
|
||||
local header_fmt = fmt.new ">! I4 I4";
|
||||
|
||||
--- OSTREE_COMMIT_GVARIANT_FORMAT:
|
||||
--- - a{sv}: metadata
|
||||
--- - r: previous commit checksum
|
||||
--- - a(sr): related objects
|
||||
--- - s: subject
|
||||
--- - s: body
|
||||
--- - t: timestamp
|
||||
--- - r: root dir ref
|
||||
--- - r: root dirmeta ref
|
||||
local read_commit = gvariant "a{sv}ra(sr)sstrr";
|
||||
|
||||
--- OSTREE_TREE_GVARIANT_FORMAT:
|
||||
--- - a(sr): a map of filename to its file ref
|
||||
--- - a(srr): a map of dirname to its dir ref and dirmeta ref
|
||||
local read_dir = gvariant "a(sr)a(srr)";
|
||||
|
||||
--- OSTREE_DIRMETA_GVARIANT_FORMAT:
|
||||
--- - u: uid (big-endian)
|
||||
--- - u: gid (big-endian)
|
||||
--- - u: mode (big-endian)
|
||||
--- - a(ayay): xattrs
|
||||
local read_dirmeta = gvariant "uuua(ayay)";
|
||||
|
||||
--- - t: size
|
||||
--- - u: uid
|
||||
--- - u: gid
|
||||
--- - u: mode
|
||||
--- - u: rdev
|
||||
--- - s: symlink_target
|
||||
--- - a(ayay): xattrs
|
||||
local read_header = gvariant "tuuuusa(ayay)";
|
||||
|
||||
--- OSTREE_SUMMARY_GVARIANT_FORMAT:
|
||||
--- - a{s(tra{sv})}: map of ref name to data about it
|
||||
--- - t: commit size
|
||||
--- - r: checksum
|
||||
--- - a{sv}: metadata
|
||||
--- - a{sv}: metadata
|
||||
local read_summary = gvariant "a{s(tra{sv})}a{sv}";
|
||||
|
||||
--- Flatpak-specific format (I think):
|
||||
--- - a{s(rara{sv})}: map of architecture name to data about it
|
||||
--- - r: checksum
|
||||
--- - ar: history of previous checksums
|
||||
--- - a{sv}: metadata
|
||||
--- - a{sv}: metadata
|
||||
local read_summary_index = gvariant "a{s(rara{sv})}a{sv}";
|
||||
|
||||
--- @class ostree
|
||||
--- @field reader fun(path: string): string
|
||||
local ostree = {};
|
||||
ostree.__index = ostree;
|
||||
|
||||
--- @param reader fun(path: string): string
|
||||
--- @return ostree
|
||||
function ostree.new(reader)
|
||||
return setmetatable({ reader = reader }, ostree);
|
||||
end
|
||||
--- @param url string
|
||||
--- @return ostree
|
||||
function ostree.from_http(url)
|
||||
local http = require "http";
|
||||
|
||||
return ostree.new(function (path)
|
||||
return http.get(url .. "/" .. path);
|
||||
end);
|
||||
end
|
||||
--- @param root string
|
||||
--- @return ostree
|
||||
function ostree.from_fs(root)
|
||||
return ostree.new(function (path)
|
||||
local f = assert(io.open(root .. "/" .. path));
|
||||
local content = assert(f:read "*a");
|
||||
assert(f:close());
|
||||
|
||||
return content;
|
||||
end);
|
||||
end
|
||||
|
||||
--- @param data string
|
||||
function ostree.parse_commit(data)
|
||||
local metadata, prev, related, subject, body, timestamp, root_dir, rood_dirmeta = read_commit(data);
|
||||
|
||||
return {
|
||||
metadata = metadata,
|
||||
prev = prev,
|
||||
related = related,
|
||||
subject = subject,
|
||||
body = body,
|
||||
timestamp = timestamp,
|
||||
root = { root_dir, rood_dirmeta },
|
||||
};
|
||||
end
|
||||
--- @param dir string | { [1]: string, [2]?: string }
|
||||
--- @param meta? string
|
||||
function ostree.parse_dir(dir, meta)
|
||||
if type(dir) ~= "string" then
|
||||
meta = dir[2];
|
||||
dir = dir[1];
|
||||
end
|
||||
|
||||
local files_arr, dirs_arr = read_dir(dir);
|
||||
|
||||
local files = {};
|
||||
local dirs = {};
|
||||
|
||||
for i = 1, #files_arr do
|
||||
files[files_arr[i][1]] = files_arr[i][2];
|
||||
end
|
||||
for i = 1, #dirs_arr do
|
||||
dirs[dirs_arr[i][1]] = { dirs_arr[i][2], dirs_arr[i][3] };
|
||||
end
|
||||
|
||||
if meta then
|
||||
local uid, gid, mode, xattrs = read_dirmeta(meta);
|
||||
return { type = "dir", files = files, dirs = dirs, uid = uid, gid = gid, mode = mode, xattrs = xattrs };
|
||||
else
|
||||
return { type = "dir", files = files, dirs = dirs };
|
||||
end
|
||||
end
|
||||
--- @param data string
|
||||
function ostree.parse_file(data)
|
||||
local header_size, idk = header_fmt:unpack(data);
|
||||
local header = data:sub(9, header_size + 8);
|
||||
local size, uid, gid, mode, rdev, link, xattrs = read_header(header);
|
||||
|
||||
if link ~= "" then
|
||||
if size > 0 then
|
||||
error "link may not have contents";
|
||||
else
|
||||
return {
|
||||
type = "link",
|
||||
target = link,
|
||||
uid = uid,
|
||||
gid = gid,
|
||||
mode = mode,
|
||||
xattrs = xattrs,
|
||||
};
|
||||
end
|
||||
else
|
||||
local get_content;
|
||||
if size == 0 then
|
||||
function get_content()
|
||||
return function ()
|
||||
return nil;
|
||||
end
|
||||
end
|
||||
else
|
||||
function get_content()
|
||||
local compressed = data:sub(header_size + 9);
|
||||
return zlib.inflate_raw(compressed);
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
type = "file",
|
||||
get_content = get_content,
|
||||
uid = uid,
|
||||
gid = gid,
|
||||
mode = mode,
|
||||
size = size,
|
||||
xattrs = xattrs,
|
||||
};
|
||||
end
|
||||
end
|
||||
--- @param data string
|
||||
function ostree.parse_summary(data)
|
||||
local refs, metadata = read_summary(data);
|
||||
|
||||
for key, val in pairs(refs) do
|
||||
refs[key] = {
|
||||
size = val[1],
|
||||
checksum = val[2],
|
||||
metadata = val[3],
|
||||
};
|
||||
end
|
||||
|
||||
return {
|
||||
refs = refs,
|
||||
metadata = metadata,
|
||||
};
|
||||
end
|
||||
--- @param data string
|
||||
function ostree.parse_subsummary(data)
|
||||
local parts = {};
|
||||
for part in zlib.gunzip(data) do parts[#parts + 1] = part end
|
||||
return ostree.parse_summary(table.concat(parts));
|
||||
end
|
||||
--- @param data string
|
||||
function ostree.parse_summary_index(data)
|
||||
local refs, metadata = read_summary_index(data);
|
||||
|
||||
for key, val in pairs(refs) do
|
||||
refs[key] = {
|
||||
checksum = val[1],
|
||||
-- history = val[2],
|
||||
metadata = val[3],
|
||||
};
|
||||
end
|
||||
|
||||
return {
|
||||
refs = refs,
|
||||
metadata = metadata,
|
||||
};
|
||||
end
|
||||
|
||||
--- @param type string
|
||||
--- @param ref string
|
||||
function ostree:read_object(type, ref)
|
||||
return self.reader("objects/" .. ref:sub(1, 2) .. "/" .. ref:sub(3) .. "." .. type);
|
||||
end
|
||||
|
||||
--- @param ref string
|
||||
function ostree:read_commit(ref)
|
||||
return ostree.parse_commit(self:read_object("commit", ref));
|
||||
end
|
||||
--- @param dir string | { [1]: string, [2]?: string }
|
||||
--- @param meta? string
|
||||
function ostree:read_dir(dir, meta)
|
||||
if type(dir) ~= "strign" then
|
||||
dir = dir[1];
|
||||
meta = dir[2];
|
||||
end
|
||||
|
||||
return ostree.parse_dir(self:read_object("dirtree", dir), meta and self:read_object("dirmeta", meta));
|
||||
end
|
||||
--- @param ref string
|
||||
function ostree:read_file(ref)
|
||||
return ostree.parse_file(self:read_object("filez", ref));
|
||||
end
|
||||
function ostree:read_summary()
|
||||
return ostree.parse_summary(self.reader("summary"));
|
||||
end
|
||||
function ostree:read_subsummary(ref)
|
||||
return ostree.parse_subsummary(self.reader("summaries/" .. ref .. ".gz"));
|
||||
end
|
||||
function ostree:read_summary_index()
|
||||
return ostree.parse_summary_index(self.reader("summary.idx"));
|
||||
end
|
||||
--- @param pkg string
|
||||
function ostree:read_ref(pkg)
|
||||
return self.reader("refs/heads/" .. pkg):gsub("%s", "");
|
||||
end
|
||||
|
||||
return ostree;
|
106
src/util/args.lua
Normal file
106
src/util/args.lua
Normal file
@ -0,0 +1,106 @@
|
||||
---@param consumers table<string, (fun(arg: string): boolean?, boolean?) | string>
|
||||
--- @param ... string
|
||||
--- @returns nil
|
||||
return function (consumers, ...)
|
||||
local consumer_stack = {};
|
||||
local pass_args = false;
|
||||
|
||||
local function digest_arg(v)
|
||||
local consumed = false;
|
||||
|
||||
while true do
|
||||
local el = table.remove(consumer_stack);
|
||||
if el == nil then break end
|
||||
|
||||
local res, fin = el(v);
|
||||
if res then
|
||||
consumed = true;
|
||||
end
|
||||
if fin then
|
||||
pass_args = true;
|
||||
break;
|
||||
end
|
||||
|
||||
if fin or res then
|
||||
break;
|
||||
end
|
||||
end
|
||||
|
||||
if not consumed then
|
||||
if consumers[1](v) then
|
||||
pass_args = true;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function get_consumer(name)
|
||||
local consumer = name;
|
||||
local path = {};
|
||||
local n = 0;
|
||||
|
||||
while true do
|
||||
local curr = consumer;
|
||||
if path[curr] then
|
||||
local path_arr = {};
|
||||
|
||||
for k, v in next, path do
|
||||
path_arr[v] = k;
|
||||
end
|
||||
|
||||
error("Alias to '" .. curr .. "' is recursive: " .. table.concat(path_arr, " -> "));
|
||||
end
|
||||
|
||||
consumer = consumers[curr];
|
||||
if consumer == nil then
|
||||
local prefix;
|
||||
if n == 0 then
|
||||
prefix = "Unknown flag";
|
||||
else
|
||||
prefix = "Unknown alias";
|
||||
end
|
||||
|
||||
if #curr == 1 then
|
||||
error(prefix .. " '-" .. curr .. "'");
|
||||
else
|
||||
error(prefix .. " '--" .. curr .. "'");
|
||||
end
|
||||
elseif type(consumer) == "function" then
|
||||
return consumer;
|
||||
end
|
||||
|
||||
path[curr] = n;
|
||||
n = n + 1;
|
||||
end
|
||||
end
|
||||
|
||||
local function next(...)
|
||||
if ... == nil then
|
||||
while #consumer_stack > 0 do
|
||||
local el = table.remove(consumer_stack);
|
||||
|
||||
if el(nil) then
|
||||
error("Unexpected end of arguments", 0);
|
||||
break;
|
||||
end
|
||||
end
|
||||
else
|
||||
if pass_args then
|
||||
consumers[1]((...));
|
||||
elseif ... == "--" then
|
||||
pass_args = true;
|
||||
elseif (...):match "^%-%-" then
|
||||
consumer_stack[#consumer_stack + 1] = get_consumer((...):sub(3));
|
||||
elseif (...):match "^%-" then
|
||||
for c in (...):sub(2):gmatch "." do
|
||||
table.insert(consumer_stack, 1, get_consumer(c));
|
||||
end
|
||||
else
|
||||
digest_arg(...);
|
||||
end
|
||||
|
||||
return next(select(2, ...));
|
||||
end
|
||||
end
|
||||
|
||||
return next(...);
|
||||
end
|
128
src/util/printing.lua
Normal file
128
src/util/printing.lua
Normal file
@ -0,0 +1,128 @@
|
||||
local function escape_str(s)
|
||||
local in_char = { "\\", "\"", "/", "\b", "\f", "\n", "\r", "\t" };
|
||||
local out_char = { "\\", "\"", "/", "b", "f", "n", "r", "t" };
|
||||
for i, c in ipairs(in_char) do
|
||||
s = s:gsub(c, "\\" .. out_char[i]);
|
||||
end
|
||||
return s
|
||||
end
|
||||
|
||||
local function stringify_impl(obj, indent_str, n, passed)
|
||||
local parts = {};
|
||||
local kind = type(obj);
|
||||
|
||||
if kind == "table" then
|
||||
if passed[obj] then return "<circular>" end
|
||||
passed[obj] = true;
|
||||
|
||||
local len = #obj;
|
||||
|
||||
for i = 1, len do
|
||||
parts[i] = stringify_impl(obj[i], indent_str, n + 1, passed) .. ",";
|
||||
end
|
||||
|
||||
local keys = {};
|
||||
|
||||
for k, v in pairs(obj) do
|
||||
if type(k) ~= "number" or k > len then
|
||||
keys[#keys + 1] = { k, v };
|
||||
end
|
||||
end
|
||||
|
||||
table.sort(keys, function (a, b)
|
||||
if type(a[1]) == "number" and type(b[1]) == "number" then
|
||||
return a[1] < b[1];
|
||||
elseif type(a[1]) == "string" and type(b[1]) == "string" then
|
||||
return a[1] < b[1];
|
||||
else
|
||||
return type(a[1]) < type(b[1]) or tostring(a[1]) < tostring(b[1]);
|
||||
end
|
||||
end);
|
||||
|
||||
for i = 1, #keys do
|
||||
local k = keys[i][1];
|
||||
local v = keys[i][2];
|
||||
|
||||
local val = stringify_impl(v, indent_str, n + 1, passed);
|
||||
if val ~= nil then
|
||||
if type(k) == "string" then
|
||||
parts[#parts + 1] = table.concat { k, " = ", val, "," };
|
||||
else
|
||||
parts[#parts + 1] = table.concat { "[", stringify_impl(k, indent_str, n + 1, passed), "] = ", val, "," };
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local meta = getmetatable(obj);
|
||||
|
||||
passed[obj] = false;
|
||||
|
||||
if #parts == 0 then
|
||||
return "{}";
|
||||
end
|
||||
|
||||
local contents = table.concat(parts, " "):sub(1, -2);
|
||||
if #contents > 80 then
|
||||
local indent = "\n" .. string.rep(indent_str, n + 1);
|
||||
|
||||
contents = table.concat {
|
||||
indent,
|
||||
table.concat(parts, indent),
|
||||
"\n", string.rep(indent_str, n)
|
||||
};
|
||||
else
|
||||
contents = " " .. contents .. " ";
|
||||
end
|
||||
|
||||
return table.concat { "{", contents, "}" };
|
||||
elseif kind == "string" then
|
||||
return "\"" .. escape_str(obj) .. "\"";
|
||||
elseif kind == "function" then
|
||||
local data = debug.getinfo(obj, "S");
|
||||
return table.concat { tostring(obj), " @ ", data.short_src, ":", data.linedefined };
|
||||
elseif kind == "nil" then
|
||||
return "nil";
|
||||
else
|
||||
return tostring(obj);
|
||||
end
|
||||
end
|
||||
|
||||
--- Turns the given value to a human-readable string.
|
||||
--- Should be used only for debugging and display purposes
|
||||
local function to_readable(obj, indent_str)
|
||||
return stringify_impl(obj, indent_str or " ", 0, {});
|
||||
end
|
||||
|
||||
local function print(...)
|
||||
for i = 1, select("#", ...) do
|
||||
if i > 1 then
|
||||
io.stderr:write("\t");
|
||||
end
|
||||
|
||||
io.stderr:write(tostring((select(i, ...))));
|
||||
end
|
||||
|
||||
io.stderr:write("\n");
|
||||
end
|
||||
|
||||
--- Prints the given values in a human-readable manner to stderr
|
||||
--- Should be used only for debugging
|
||||
local function pprint(...)
|
||||
if select("#", ...) == 0 then
|
||||
io.stderr:write "<nothing>\n";
|
||||
else
|
||||
for i = 1, select("#", ...) do
|
||||
if i > 1 then
|
||||
io.stderr:write "\t";
|
||||
end
|
||||
|
||||
io.stderr:write(to_readable((select(i, ...))));
|
||||
end
|
||||
|
||||
io.stderr:write "\n";
|
||||
end
|
||||
end
|
||||
|
||||
_G.to_readable = to_readable;
|
||||
_G.print = print;
|
||||
_G.pprint = pprint;
|
Loading…
Reference in New Issue
Block a user