Compare commits
6 Commits
fec171c2b7
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
cc4f8a7239
|
|||
|
0101ed8a24
|
|||
|
7c7832ba40
|
|||
|
7bd20fc6bd
|
|||
|
db54d5deec
|
|||
|
de6c7876c3
|
@@ -1,5 +1,5 @@
|
||||
[*.?ts]
|
||||
[*]
|
||||
end_of_line = lf
|
||||
indent_style = tab
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
53
Makefile
53
Makefile
@@ -1,38 +1,55 @@
|
||||
# requires lua, curl and zlib (in the future, lua will be statically linked)
|
||||
libraries := -llua -lcurl -lz
|
||||
flags := -Wall
|
||||
|
||||
# lua is required in the build process, in order to compile the bytecode
|
||||
lua := lua
|
||||
cc := gcc
|
||||
bundle = $(src_entry_dir)/bundle.lua
|
||||
dump_loader = $(src_entry_dir)/dump_loader.lua
|
||||
|
||||
all_flags += -W -Wall -fPIC -std=gnu11
|
||||
|
||||
# requires lua, curl and zlib (in the future, lua will be statically linked)
|
||||
main_libraries += -llua -lcurl -lz
|
||||
main_cflags += $(all_flags)
|
||||
|
||||
loader_libraries += -lfuse -lpthread -lsquashfuse
|
||||
loader_cflags += $(all_flags)
|
||||
loader_cflags += -D_FILE_OFFSET_BITS=64 -DFUSE_USE_VERSION=29
|
||||
loader_cflags += -I/usr/include/fuse
|
||||
|
||||
dst_dir = dst
|
||||
src_lua_dir = src
|
||||
src_c_dir = lib
|
||||
src_main_dir = src/main
|
||||
src_entry_dir = src/entry
|
||||
src_loader_dir = src/loader
|
||||
|
||||
# Uncomment to get debugging symbols
|
||||
# flags := -g
|
||||
|
||||
lua_sources += $(wildcard $(src_lua_dir)/*.lua)
|
||||
lua_sources += $(wildcard $(src_lua_dir)/cli/*.lua)
|
||||
lua_sources += $(wildcard $(src_lua_dir)/formats/*.lua)
|
||||
lua_sources += $(wildcard $(src_lua_dir)/util/*.lua)
|
||||
main_sources += $(wildcard $(src_main_dir)/*.lua)
|
||||
main_sources += $(wildcard $(src_main_dir)/cli/*.lua)
|
||||
main_sources += $(wildcard $(src_main_dir)/formats/*.lua)
|
||||
main_sources += $(wildcard $(src_main_dir)/util/*.lua)
|
||||
|
||||
c_sources = $(wildcard $(src_c_dir)/*.c)
|
||||
entry_sources = $(wildcard $(src_entry_dir)/*.c)
|
||||
|
||||
build_entry = $(src_c_dir)/build.lua
|
||||
loader_sources = $(wildcard $(src_loader_dir)/*.c)
|
||||
|
||||
target = $(dst_dir)/slimpack
|
||||
bytecode_target = $(dst_dir)/bytecode.h
|
||||
main_target = $(dst_dir)/slimpack
|
||||
loader_target = $(dst_dir)/loader
|
||||
bytecode_h_target = $(dst_dir)/bytecode.h
|
||||
loader_h_target = $(dst_dir)/loader.h
|
||||
|
||||
.PHONY: build
|
||||
|
||||
build: $(target)
|
||||
build: $(main_target)
|
||||
|
||||
$(dst_dir):
|
||||
mkdir -p $@
|
||||
|
||||
$(bytecode_target): $(lua_sources) $(dst_dir)
|
||||
lua lib/build.lua src $@ $(lua_sources)
|
||||
$(target): $(c_sources) $(bytecode_target) $(dst_dir)
|
||||
gcc $(flags) $(libraries) -include $(bytecode_target) $(c_sources) -o $@
|
||||
$(bytecode_h_target): $(dst_dir) $(bundle) $(main_sources)
|
||||
$(lua) $(bundle) $(src_main_dir) $@ $(main_sources)
|
||||
$(loader_h_target): $(dst_dir) $(dump_loader) $(loader_target)
|
||||
$(lua) $(dump_loader) $(loader_target) $@
|
||||
$(loader_target): $(dst_dir) $(loader_sources)
|
||||
$(cc) $(loader_cflags) $(loader_libraries) $(loader_sources) -o $@
|
||||
$(main_target): $(dst_dir) $(bytecode_h_target) $(loader_h_target) $(entry_sources) $(main_sources)
|
||||
$(cc) $(main_cflags) $(main_libraries) -include $(bytecode_h_target) -include $(loader_h_target) $(entry_sources) -o $@
|
||||
|
||||
45
README.md
45
README.md
@@ -1,36 +1,51 @@
|
||||
# Slimpack
|
||||
|
||||
My shot at creating a lighter flatpak client with (almost) no containerization of the applications.
|
||||
A pragmatic and minimalistic repacker of flatpak applications without (much) isolation.
|
||||
|
||||
## Why?
|
||||
|
||||
Some might be big proponents of the whole isolation thing, but I believe that only works in theory with simple apps, as anyone who has tried to get anything running under flatpak can tell you. In my humble opinion, isolation offers too little security benefits for the heaps of problems it creates - yet again, we are solving a problem that really wasn't a problem. To be more precise, the following are some major issues with flatpak, which isolation creates:
|
||||
Some might be big proponents of the whole isolation thing, but I believe that linux is in the unique position where isolation doesn't really *work* on the desktop. Why? Because to have isolation, you need to have a way to talk to the outside world. On phones, this happens trough well-established APIs that the vendors provide the programmers, because of which isolation on mobile works. However, on linux, there isn't really a standardized set of APIs to do desktop apps. You have KDE and Gnome's takes on them, but no one unified standard. This of course means that if we ever wish to containerize, we need to create APIs for KDE, Gnome and whatever comes next.
|
||||
|
||||
- You need to do a lot of fiddling before you get the themes right
|
||||
- Basic UI features (like drag-n-drop) just don't work
|
||||
- You get mysterious "file doesn't exist" errors
|
||||
- Flatpak just straight up installs 3 other distros to run your apps, which takes up A LOT of space
|
||||
- Apps usually come with enough permissions to be unsafe, but not enough permissions to work properly, partially due to laziness and the shitty permission system flatpaks have
|
||||
- The CLI is TREMENDOUSLY bad
|
||||
Flatpak's take on this conundrum, of course, is as dreadful as possible - not only are they not solving the issues, they refuse to recognize that there's an issue at all! Furthermore, the tooling around flatpak is simply, and without any doubt, a whole new level of terrible.
|
||||
|
||||
However, flatpak did one thing - it created a network. If you want to publish an application, probably the easiest way to do so is to do it using flatpak. This has resulted in a lot of apps being published exclusively for flatpak. However, we don't have to deal with this whole debacle, as we can just create and use an alternative client that just runs the apps, but outside of their designated containers. At the end of the day, we are still distributing files, just with some permissions metadata sprinkled in.
|
||||
However, flatpak did one thing - it convinced a lot of developers they were the only place you should be publishing your apps. This means that flathub is one of, if not the most valuable assets to the linux desktop ecosystem. If only we could run those apps like any sane person would - natively...
|
||||
|
||||
This project aims to do just that - be the minimalistic client that allows you to just run the darn flatpak app, without having to fiddle with flatseal for 2 hours.
|
||||
This is where this project comes in. Slimpack aims to be a small tool that allows you to repackage a flatpak package into a self-contained executable, that runs pretty much natively. No longer do you have to fiddle with flatseal for 2 hours just to run a darn electron app.
|
||||
|
||||
## Why not?
|
||||
|
||||
This project is VERY early stage. You are expected to know what you're doing, and you will brick your system if you don't. At the very least, you are expected to take a quick look trough the code. The whole process of running an app using this client is very manual, but will improve in the future, to a point where you can just integrate it into discover or another client of your choosing.
|
||||
This project is VERY experimental, employing a custom filesystem, which has bricked my whole OS I/O, until I restart. You are expected to know what you're doing, and even if you do, expect stuff to go south.
|
||||
|
||||
## How?
|
||||
|
||||
Since the flagship flatpak client needs to download the apps from a repository, we can do that too. In short, this project implements a simple OSTree client (without all the BS the flagship client comes with) that can successfully list the remote refs (apps) and download any given app (and its metadata).
|
||||
|
||||
The other big (and not yet implemented) part of the project is the actual repacking of the app. In short, each app has dependencies and a specific runtime it demands to be present. The original flatpak client will 1. download the runtime (for all intents and purposes a distro) and 2. download all the dependencies it has. This client in stark contrast will only download the dependencies and PRAY to the lords that the correct runtime libraries are already present on the host.
|
||||
The other big part of the project is the actual repacking of the app. In short, each app has dependencies and a specific runtime it demands to be present. The original flatpak client will 1. download the runtime (for all intents and purposes a distro) and 2. download all the dependencies it has. This client in stark contrast will only download the dependencies and will assume that the libraries, packaged by flatpak's runtimes are present on the host.
|
||||
|
||||
The last thing this project does is create a *light* container around the application, so that the application-specific dependencies and the app itself can be layered on top of the existing system. Now, don't be mistaken, I'm not doing this because it's the most minimalistic way to do it, but because flatpak basically REQUIRES this architecture. However, the container won't isolate the container any further. In the future, a more configurable isolation might be created, but for the moment, I'm trying to achieve the bare minimum.
|
||||
When this program repacks a flatpak, it will get put in a self-contained executable as a squashfs image. When the executable gets run, the application will get run in a *VERY* light container, which only layers the squashfs on top of the container (unfortunately, using the currently employed methods, there are *some* limitations, but everything that ran under flatpak *should* run here, too).
|
||||
|
||||
Now, don't be mistaken, I'm not containerizing because it's the best solution (the best solution is called AppImage), but because flatpak basically REQUIRES its own view of the filesystem (namely the /app folder). However, we don't isolate more than absolutely necessary, because the whole point is to run the programs as natively as possible.
|
||||
|
||||
## Usage
|
||||
|
||||
First of all, you need to build the project. This is done via the `make` command. You will need lua 5.4, curl, zlib and some sort of cc installed. The executable will be `dst/slimpack`.
|
||||
First of all, you need to build the project. This is done via the `make` command. You will need lua 5.4, curl, zlib and some sort of cc installed. The executable will be `dst/slimpack`. Feel free to copy it to `./local/bin/` or `/usr/local/bin/`
|
||||
|
||||
TODO: add actual usage when the tool is done lol
|
||||
The CLI of slimpack works in a very simple way - first, you specify the generic options (for now, only --repo is available), followed by the action. Each action has its own options and then its own subaction and so on.
|
||||
|
||||
In syntax form, it looks like this:
|
||||
|
||||
`[--global-opt-1 --global-opt-2 ...] <action> [--action-opt ...] <subaction> [--subaction-opt ...]`
|
||||
|
||||
Sometimes, if an action is final, it might just take a plain argument (like how `pack` will just take one argument which is the ref to pack)
|
||||
|
||||
### Querying
|
||||
|
||||
The first action is `query`. It groups all operations that are related to asking questions about a flatpak remote.
|
||||
|
||||
To find a ref, you can use `slimpack query search <name>`. This is case-insensitive, but you need to be careful with dashes and underscores. Usually, you will care about one result - `app/.../x86_64/stable`. The rest, for all intents and purposes, may be ignored.
|
||||
|
||||
To find out more info about a ref, you can do `slimpack query info <ref>`. This will print some information about the package, like its version, license, dependencies, etc.
|
||||
|
||||
### Packaging
|
||||
|
||||
The star of the show is the packaging - this will take a flatpak package and repack it into a self-contained executable (not too unlike AppImage). To repack a package, run `slimpack pack <ref> -o <output-filename>`. This will eventually, after a lot of stdout spam, write an executable file to "output-filename". When run, it will just run the flatpak application without (a lot of) containerization, without any fuss.
|
||||
|
||||
126
lib/main.c
126
lib/main.c
@@ -1,126 +0,0 @@
|
||||
#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;
|
||||
}
|
||||
6
mod/elf_loader.d.lua
Normal file
6
mod/elf_loader.d.lua
Normal file
@@ -0,0 +1,6 @@
|
||||
--- @meta elf_loader
|
||||
|
||||
--- @type string
|
||||
local res;
|
||||
|
||||
return res;
|
||||
23
mod/fs.d.lua
Normal file
23
mod/fs.d.lua
Normal file
@@ -0,0 +1,23 @@
|
||||
--- @meta fs
|
||||
|
||||
local fs = {};
|
||||
|
||||
--- @param path string
|
||||
--- @param recursive? boolean
|
||||
--- @return boolean? ok
|
||||
--- @return string? err
|
||||
function fs.mkdir(path, recursive) end
|
||||
|
||||
--- @param target string
|
||||
--- @param name string
|
||||
--- @return boolean? ok
|
||||
--- @return string? err
|
||||
function fs.symlink(target, name) end
|
||||
|
||||
--- @param path string
|
||||
--- @param mode string | integer
|
||||
--- @return boolean? ok
|
||||
--- @return string? err
|
||||
function fs.chmod(path, mode) end
|
||||
|
||||
return fs;
|
||||
@@ -1,41 +0,0 @@
|
||||
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
|
||||
@@ -1,28 +0,0 @@
|
||||
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
|
||||
@@ -11,17 +11,35 @@ end
|
||||
|
||||
local function all_loader(paths, ...)
|
||||
local files = { ... };
|
||||
local function coro_entry()
|
||||
|
||||
return coroutine.wrap(function ()
|
||||
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);
|
||||
coroutine.yield("require 'init' (...);");
|
||||
end);
|
||||
end
|
||||
|
||||
local function escape(str)
|
||||
return "\"" .. str:gsub(".", function (c)
|
||||
local b = string.byte(c);
|
||||
|
||||
if c == "\n" then
|
||||
return "\\n";
|
||||
elseif c == "\\" then
|
||||
return "\\\\";
|
||||
elseif c == "\"" then
|
||||
return "\\\"";
|
||||
elseif b >= 32 and b <= 126 then
|
||||
return c;
|
||||
else
|
||||
return ("\\%.3o"):format(b);
|
||||
end
|
||||
end) .. "\"";
|
||||
end
|
||||
|
||||
local function main(root, out, ...)
|
||||
@@ -36,13 +54,15 @@ local function main(root, out, ...)
|
||||
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[] = { ";
|
||||
f:write "#define BYTECODE_SIZE (sizeof entry_bytecode - 1)\n";
|
||||
f:write "static const uint8_t entry_bytecode[] = ";
|
||||
|
||||
for i = 1, #res do
|
||||
f:write(string.byte(res, i), ", ");
|
||||
for i = 1, math.ceil(#res / 64) do
|
||||
f:write(escape(res:sub((i - 1) * 64 + 1, i * 64)));
|
||||
f:write"\n";
|
||||
end
|
||||
|
||||
f:write "};";
|
||||
f:write ";";
|
||||
|
||||
assert(f:close());
|
||||
end
|
||||
36
src/entry/dump_loader.lua
Normal file
36
src/entry/dump_loader.lua
Normal file
@@ -0,0 +1,36 @@
|
||||
local function escape(str)
|
||||
return "\"" .. str:gsub(".", function (c)
|
||||
local b = string.byte(c);
|
||||
|
||||
if c == "\n" then
|
||||
return "\\n";
|
||||
elseif c == "\\" then
|
||||
return "\\\\";
|
||||
elseif c == "\"" then
|
||||
return "\\\"";
|
||||
elseif b >= 32 and b <= 126 then
|
||||
return c;
|
||||
else
|
||||
return ("\\%.3o"):format(b);
|
||||
end
|
||||
end) .. "\"";
|
||||
end
|
||||
|
||||
local function main(in_f, out_f)
|
||||
local f = assert(io.open(out_f, "w"));
|
||||
f:write "#include <stdint.h>\n";
|
||||
f:write "#define LOADER loader_bytecode\n";
|
||||
f:write "#define LOADER_SIZE (sizeof loader_bytecode - 1)\n";
|
||||
f:write "static const uint8_t loader_bytecode[] = ";
|
||||
|
||||
for part in io.lines(in_f, 64) do
|
||||
f:write(escape(part));
|
||||
f:write"\n";
|
||||
end
|
||||
|
||||
f:write ";";
|
||||
|
||||
assert(f:close());
|
||||
end
|
||||
|
||||
main(...);
|
||||
@@ -77,7 +77,7 @@ static int fmt_parse_width(size_t *i, const char *raw, size_t raw_size) {
|
||||
|
||||
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);
|
||||
if (raw_size == SIZE_MAX) raw_size = strlen(raw);
|
||||
|
||||
fmt_segment_t segments[raw_size];
|
||||
size_t segments_n = 0;
|
||||
@@ -278,7 +278,7 @@ static void write_buff_fit(write_buff_t *buff) {
|
||||
|
||||
|
||||
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;
|
||||
if ((*i) + 1 > size) return false;
|
||||
|
||||
*res = raw[(*i)++];
|
||||
|
||||
@@ -289,7 +289,7 @@ static bool fmt_read_int8(const uint8_t *raw, size_t *i, size_t size, int8_t *re
|
||||
}
|
||||
|
||||
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;
|
||||
if ((*i) + 2 > size) return false;
|
||||
|
||||
uint16_t a = raw[(*i)++];
|
||||
uint16_t b = raw[(*i)++];
|
||||
@@ -304,7 +304,7 @@ static bool fmt_read_int16(const uint8_t *raw, size_t *i, size_t size, int16_t *
|
||||
}
|
||||
|
||||
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;
|
||||
if ((*i) + 4 > size) return false;
|
||||
|
||||
uint8_t a = raw[(*i)++];
|
||||
uint8_t b = raw[(*i)++];
|
||||
@@ -321,7 +321,7 @@ static bool fmt_read_int32(const uint8_t *raw, size_t *i, size_t size, int32_t *
|
||||
}
|
||||
|
||||
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;
|
||||
if ((*i) + 8 > size) return false;
|
||||
|
||||
uint64_t a = raw[(*i)++];
|
||||
uint64_t b = raw[(*i)++];
|
||||
@@ -342,7 +342,7 @@ static bool fmt_read_int64(const uint8_t *raw, size_t *i, size_t size, int64_t *
|
||||
}
|
||||
|
||||
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;
|
||||
if ((*i) + 4 > size) return false;
|
||||
|
||||
uint8_t a = raw[(*i)++];
|
||||
uint8_t b = raw[(*i)++];
|
||||
@@ -356,7 +356,7 @@ static bool fmt_read_float32(const uint8_t *raw, size_t *i, size_t size, float *
|
||||
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;
|
||||
if ((*i) + 8 > size) return false;
|
||||
|
||||
uint8_t a = raw[(*i)++];
|
||||
uint8_t b = raw[(*i)++];
|
||||
@@ -411,7 +411,6 @@ static bool fmt_read_string(lua_State *ctx, fmt_segment_t segment, const uint8_t
|
||||
|
||||
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);
|
||||
125
src/entry/fs.c
Normal file
125
src/entry/fs.c
Normal file
@@ -0,0 +1,125 @@
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include <lua.h>
|
||||
#include <lualib.h>
|
||||
#include <lauxlib.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <err.h>
|
||||
|
||||
#include "lib.h"
|
||||
|
||||
static const char *fs_check_path(lua_State *ctx, int i, size_t *psize) {
|
||||
size_t n;
|
||||
const char *path = luaL_checklstring(ctx, i, &n);
|
||||
|
||||
size_t real_len = strlen(path);
|
||||
if (n != real_len) {
|
||||
luaL_error(ctx, "path may not contain \\0");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (psize) *psize = n;
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
static int lib_fs_mkdir(lua_State *ctx) {
|
||||
size_t path_size;
|
||||
const char *ro_path = fs_check_path(ctx, 1, &path_size);
|
||||
bool recursive = lua_toboolean(ctx, 2);
|
||||
|
||||
if (recursive) {
|
||||
char path[path_size + 1];
|
||||
memcpy(path, ro_path, path_size + 1);
|
||||
|
||||
for (size_t i = 0; i <= path_size ; i++) {
|
||||
if (path[i] == '/' || path[i] == '\0') {
|
||||
path[i] = '\0';
|
||||
|
||||
if (mkdir(path, 0755) < 0 && errno != EEXIST) {
|
||||
lua_pushnil(ctx);
|
||||
lua_pushfstring(ctx, "can't make directory: %s", strerror(errno));
|
||||
return 12;
|
||||
}
|
||||
|
||||
if (i < path_size) path[i] = '/';
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (mkdir(ro_path, 0755) < 0) {
|
||||
lua_pushnil(ctx);
|
||||
lua_pushfstring(ctx, "can't make directory: %s", strerror(errno));
|
||||
return 12;
|
||||
}
|
||||
}
|
||||
|
||||
lua_pushboolean(ctx, true);
|
||||
return 1;
|
||||
}
|
||||
static int lib_fs_symlink(lua_State *ctx) {
|
||||
const char *src = fs_check_path(ctx, 1, NULL);
|
||||
const char *dst = fs_check_path(ctx, 2, NULL);
|
||||
|
||||
if (symlink(src, dst) < 0) {
|
||||
lua_pushnil(ctx);
|
||||
lua_pushfstring(ctx, "failed to symlink: %s", strerror(errno));
|
||||
return 2;
|
||||
}
|
||||
|
||||
lua_pushboolean(ctx, true);
|
||||
return 1;
|
||||
}
|
||||
static int lib_fs_chmod(lua_State *ctx) {
|
||||
const char *path = fs_check_path(ctx, 1, NULL);
|
||||
int mode;
|
||||
|
||||
if (lua_isinteger(ctx, 2)) {
|
||||
mode = lua_tointeger(ctx, 2);
|
||||
mode = mode & 0xFFF;
|
||||
}
|
||||
else {
|
||||
const char *strmode = luaL_checkstring(ctx, 2);
|
||||
|
||||
mode = 0;
|
||||
|
||||
for (size_t i = 0; strmode[i]; i++) {
|
||||
char c = strmode[i];
|
||||
if (c >= '0' && c <= '7') {
|
||||
mode <<= 3;
|
||||
mode |= c - '0';
|
||||
}
|
||||
else {
|
||||
return luaL_error(ctx, "invalid mode '%s' - must be an octal integer", strmode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (chmod(path, mode) < 0) {
|
||||
lua_pushnil(ctx);
|
||||
lua_pushfstring(ctx, "failed to chmod: %s", strerror(errno));
|
||||
return 2;
|
||||
}
|
||||
|
||||
lua_pushboolean(ctx, true);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int fs_open_lib(lua_State *ctx) {
|
||||
luaL_newlib(ctx, ((luaL_Reg[]) {
|
||||
{ "mkdir", lib_fs_mkdir },
|
||||
{ "chmod", lib_fs_chmod },
|
||||
{ "symlink", lib_fs_symlink },
|
||||
{ NULL, NULL },
|
||||
}));
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -14,6 +14,8 @@ typedef struct {
|
||||
} http_body_buff_t;
|
||||
|
||||
static size_t body_writer(char *ptr, size_t _, size_t size, void *pbuff) {
|
||||
(void)_;
|
||||
|
||||
http_body_buff_t *buff = pbuff;
|
||||
|
||||
size_t new_cap = buff->cap;
|
||||
@@ -87,6 +89,7 @@ static int lib_http_get(lua_State *ctx) {
|
||||
char *body = body_compress(buff, &size);
|
||||
lua_pushlstring(ctx, body, size);
|
||||
free(body);
|
||||
// fprintf(stderr, "HTTP %s\n", url);
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -3,3 +3,4 @@
|
||||
int http_open_lib(lua_State *ctx);
|
||||
int fmt_open_lib(lua_State *ctx);
|
||||
int zlib_open_lib(lua_State *ctx);
|
||||
int fs_open_lib(lua_State *ctx);
|
||||
65
src/entry/main.c
Normal file
65
src/entry/main.c
Normal file
@@ -0,0 +1,65 @@
|
||||
#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 uint8_t entry_bytecode[] = "error \"bad build\"";
|
||||
#define BYTECODE entry_bytecode
|
||||
#define BYTECODE_SIZE (sizeof entry_bytecode - 1)
|
||||
#endif
|
||||
|
||||
#ifndef LOADER
|
||||
static uint8_t loader_bytecode[] = "";
|
||||
#define LOADER loader_bytecode
|
||||
#define LOADER_SIZE (sizeof loader_bytecode - 1)
|
||||
#endif
|
||||
|
||||
static char err_bytecode[] =
|
||||
"local err = ...;"
|
||||
"local trace = debug.traceback(nil, 2):match \"^[^\\n]+\\n(.+)$\""
|
||||
"print(table.concat { \"unhandled error: \", err, \"\\n\", trace })";
|
||||
|
||||
static int elf_loader_open_lib(lua_State *ctx) {
|
||||
lua_pushlstring(ctx, (const char*)LOADER, LOADER_SIZE);
|
||||
return 1;
|
||||
}
|
||||
|
||||
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);
|
||||
luaL_requiref(ctx, "fs", fs_open_lib, false);
|
||||
luaL_requiref(ctx, "elf_loader", elf_loader_open_lib, false);
|
||||
}
|
||||
|
||||
int main(int argc, const char **argv) {
|
||||
lua_State *ctx = luaL_newstate();
|
||||
load_modules(ctx);
|
||||
|
||||
if (luaL_loadbuffer(ctx, err_bytecode, sizeof err_bytecode - 1, "<main>")) {
|
||||
fprintf(stderr, "error while loading lua bytecode for errfunc: %s", lua_tostring(ctx, -1));
|
||||
return 1;
|
||||
}
|
||||
int errfunc_i = lua_gettop(ctx);
|
||||
|
||||
if (luaL_loadbuffer(ctx, (char*)BYTECODE, BYTECODE_SIZE, "<main>")) {
|
||||
fprintf(stderr, "error while loading lua bytecode for main: %s", lua_tostring(ctx, -1));
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (int 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;
|
||||
}
|
||||
@@ -39,6 +39,7 @@ static int lib_decompress_iter(lua_State *ctx) {
|
||||
case Z_STREAM_END:
|
||||
inflateEnd(&stream->stream);
|
||||
stream->finalized = true;
|
||||
// fall through
|
||||
case Z_OK: {
|
||||
lua_pushlstring(ctx, (const char*)stream->buff, sizeof stream->buff - stream->stream.avail_out);
|
||||
stream->processed = size - stream->stream.avail_in;
|
||||
16
src/init.lua
16
src/init.lua
@@ -1,16 +0,0 @@
|
||||
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
|
||||
|
||||
204
src/loader/entry.c
Normal file
204
src/loader/entry.c
Normal file
@@ -0,0 +1,204 @@
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include <linux/limits.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/signal.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "loader.h"
|
||||
#include "slimfs.h"
|
||||
|
||||
typedef struct {
|
||||
char root_dir[PATH_MAX];
|
||||
struct fuse *fuse;
|
||||
bool freed;
|
||||
} *fuse_pthread_data_t;
|
||||
|
||||
static slimfs_t fs;
|
||||
static char *mount_dir;
|
||||
|
||||
static void *fuse_pthread_entry(void *pdata) {
|
||||
slimfs_t fs = pdata;
|
||||
|
||||
if (fuse_loop(fs->fuse) < 0) err(1, "Couldn't start fuse loop");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static slimfs_t slimfs_mount(const char *target_dir, const char *root_dir, const char *img_path, size_t img_offset) {
|
||||
slimfs_t fs = slimfs_new(1, (char*[]){ "", NULL }, target_dir, root_dir, img_path, img_offset);
|
||||
if (fs == NULL) return NULL;
|
||||
|
||||
pthread_t thread;
|
||||
pthread_create(&thread, NULL, fuse_pthread_entry, fs);
|
||||
|
||||
return fs;
|
||||
}
|
||||
|
||||
static void handle_close() {
|
||||
fprintf(stderr, "\nCTRL+C detected, closing SlimFS...\n");
|
||||
slimfs_free(fs);
|
||||
rmdir(mount_dir);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
static int write_flag(const char *file, const char *format, ...) {
|
||||
int fd = open(file, O_WRONLY);
|
||||
if (fd < 0) {
|
||||
warn("Failed to write to %s", file);
|
||||
return -1;
|
||||
}
|
||||
|
||||
va_list list;
|
||||
|
||||
va_start(list, format);
|
||||
char buff[vsnprintf(NULL, 0, format, list) + 1];
|
||||
va_end(list);
|
||||
|
||||
va_start(list, format);
|
||||
vsnprintf(buff, sizeof buff, format, list);
|
||||
va_end(list);
|
||||
|
||||
int n = write(fd, buff, sizeof buff - 1);
|
||||
if (n < 0 || (size_t)n != sizeof buff - 1) {
|
||||
warn("Failed to write to %s", file);
|
||||
return -1;
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int err_mount(const char *name) {
|
||||
warn("Failed to setup virtual mount for %s", name);
|
||||
return 127;
|
||||
}
|
||||
|
||||
int slimpak_isolate(const char *root_dir, char **argv) {
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) err(EXIT_EXECERROR, "fork error");
|
||||
else if (pid == 0) {
|
||||
int uid = geteuid();
|
||||
int gid = getegid();
|
||||
|
||||
errno = 0;
|
||||
|
||||
// Child is responsible for running the actual command
|
||||
if (unshare(CLONE_NEWUSER | CLONE_NEWNS) < 0) {
|
||||
warn("Failed to containerize");
|
||||
exit(127);
|
||||
}
|
||||
|
||||
if (write_flag("/proc/self/uid_map", "%d %d 1\n", uid, uid) < 0) exit(127);
|
||||
if (write_flag("/proc/self/setgroups", "deny\n") < 0) exit(127);
|
||||
if (write_flag("/proc/self/gid_map", "%d %d 1\n", gid, gid) < 0) exit(127);
|
||||
|
||||
char *cwd = getcwd(NULL, 0);
|
||||
chdir(root_dir);
|
||||
|
||||
// if (mount(NULL, "/", NULL, MS_REC | MS_SILENT | MS_SLAVE, NULL) < 0) return err_mount("root");
|
||||
if (mount("/dev", "dev", NULL, MS_REC | MS_SILENT | MS_BIND, NULL) < 0) return err_mount("/dev");
|
||||
if (mount("/proc", "proc", NULL, MS_REC | MS_SILENT | MS_BIND, NULL) < 0) return err_mount("/proc");
|
||||
if (mount("/run", "run", NULL, MS_REC | MS_SILENT | MS_BIND, NULL) < 0) return err_mount("/run");
|
||||
|
||||
if (chroot(root_dir) < 0) {
|
||||
warn("Failed to chroot into virtual root");
|
||||
exit(127);
|
||||
}
|
||||
|
||||
chdir(cwd);
|
||||
free(cwd);
|
||||
|
||||
execv(argv[0], argv);
|
||||
|
||||
err(127, "Failed to execute command");
|
||||
}
|
||||
else {
|
||||
int status;
|
||||
if (waitpid(pid, &status, 0) < 0) {
|
||||
warn("Failed to get child status");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (WIFEXITED(status)) return (uint16_t)WEXITSTATUS(status);
|
||||
else return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const char *self_path = "/proc/self/exe";
|
||||
const char *temp_base = "/tmp";
|
||||
const char *slimpack_arg = NULL;
|
||||
|
||||
size_t offset = slimpack_get_offset(self_path);
|
||||
|
||||
if (argc == 2 && argv[1][0] == '-' && argv[1][1] == '-') {
|
||||
slimpack_arg = (const char*)argv[1] + 2;
|
||||
}
|
||||
|
||||
if (slimpack_arg && !strcmp(slimpack_arg, "slimpack-offset")) {
|
||||
printf("%zu\n", offset);
|
||||
return 0;
|
||||
}
|
||||
|
||||
mount_dir = malloc(strlen(temp_base) + 19 + 1);
|
||||
strcpy(mount_dir, temp_base);
|
||||
strcat(mount_dir, "/.slimpack.XXXXXXXX");
|
||||
|
||||
if (!mkdtemp(mount_dir)) err(EXIT_EXECERROR, "create mount dir error");
|
||||
|
||||
fs = slimfs_mount(mount_dir, "/", self_path, offset);
|
||||
if (fs == NULL) errx(1, "Couldn't mount SlimFS");
|
||||
|
||||
signal(SIGINT, handle_close);
|
||||
|
||||
int status;
|
||||
|
||||
if (slimpack_arg && !strcmp(slimpack_arg, "slimpack-mount")) {
|
||||
fprintf(stderr, "The mountpoint is at %s. Press Ctrl+D when you're ready\n", mount_dir);
|
||||
|
||||
char buff[4096];
|
||||
|
||||
while (read(STDIN_FILENO, buff, sizeof buff) != 0);
|
||||
|
||||
slimfs_free(fs);
|
||||
rmdir(mount_dir);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (slimpack_arg && !strcmp(slimpack_arg, "slimpack-shell")) {
|
||||
status = slimpak_isolate((const char*)mount_dir, (char*[]) { "/bin/bash", NULL });
|
||||
}
|
||||
else {
|
||||
argv[0] = "/entry";
|
||||
status = slimpak_isolate((const char*)mount_dir, argv);
|
||||
}
|
||||
|
||||
slimfs_free(fs);
|
||||
rmdir(mount_dir);
|
||||
|
||||
if (status < 0) {
|
||||
err(127, "Failed to start isolated process (status %d)", status);
|
||||
}
|
||||
else {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
16
src/loader/loader.h
Normal file
16
src/loader/loader.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
// Exit status to use when launching an AppImage fails.
|
||||
// For applications that assign meanings to exit status codes (e.g. rsync),
|
||||
// we avoid "cluttering" pre-defined exit status codes by using 127 which
|
||||
// is known to alias an application exit status and also known as launcher
|
||||
// error, see SYSTEM(3POSIX).
|
||||
#define EXIT_EXECERROR 127
|
||||
|
||||
ssize_t slimpack_get_offset(const char *executable);
|
||||
int slimpak_isolate(const char *root_dir, char **argv);
|
||||
int slimpak_mount(const char *executable, const char *work_dir, const char *root_dir);
|
||||
int slimpak_unmount(const char *work_dir, const char *root_dir);
|
||||
228
src/loader/offset.c
Normal file
228
src/loader/offset.c
Normal file
@@ -0,0 +1,228 @@
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <err.h>
|
||||
|
||||
#include "loader.h"
|
||||
|
||||
#define EI_NIDENT 16
|
||||
|
||||
#define ELFCLASS32 1
|
||||
#define ELFDATA2LSB 1
|
||||
#define ELFDATA2MSB 2
|
||||
#define ELFCLASS64 2
|
||||
#define EI_CLASS 4
|
||||
#define EI_DATA 5
|
||||
|
||||
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||
#define ELFDATANATIVE ELFDATA2LSB
|
||||
#elif __BYTE_ORDER == __BIG_ENDIAN
|
||||
#define ELFDATANATIVE ELFDATA2MSB
|
||||
#else
|
||||
#error "Unknown machine endian"
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
unsigned char e_ident[EI_NIDENT];
|
||||
uint16_t e_type;
|
||||
uint16_t e_machine;
|
||||
uint32_t e_version;
|
||||
uint32_t e_entry; /* Entry point */
|
||||
uint32_t e_phoff;
|
||||
uint32_t e_shoff;
|
||||
uint32_t e_flags;
|
||||
uint16_t e_ehsize;
|
||||
uint16_t e_phentsize;
|
||||
uint16_t e_phnum;
|
||||
uint16_t e_shentsize;
|
||||
uint16_t e_shnum;
|
||||
uint16_t e_shstrndx;
|
||||
} elf32_hdr_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t sh_name;
|
||||
uint32_t sh_type;
|
||||
uint32_t sh_flags;
|
||||
uint32_t sh_addr;
|
||||
uint32_t sh_offset;
|
||||
uint32_t sh_size;
|
||||
uint32_t sh_link;
|
||||
uint32_t sh_info;
|
||||
uint32_t sh_addralign;
|
||||
uint32_t sh_entsize;
|
||||
} elf32_shdr_t;
|
||||
|
||||
typedef struct {
|
||||
unsigned char e_ident[EI_NIDENT]; /* ELF "magic number" */
|
||||
uint16_t e_type;
|
||||
uint16_t e_machine;
|
||||
uint32_t e_version;
|
||||
uint64_t e_entry; /* Entry point virtual address */
|
||||
uint64_t e_phoff; /* Program header table file offset */
|
||||
uint64_t e_shoff; /* Section header table file offset */
|
||||
uint32_t e_flags;
|
||||
uint16_t e_ehsize;
|
||||
uint16_t e_phentsize;
|
||||
uint16_t e_phnum;
|
||||
uint16_t e_shentsize;
|
||||
uint16_t e_shnum;
|
||||
uint16_t e_shstrndx;
|
||||
} elf64_hdr_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t sh_name; /* Section name, index in string tbl */
|
||||
uint32_t sh_type; /* Type of section */
|
||||
uint64_t sh_flags; /* Miscellaneous section attributes */
|
||||
uint64_t sh_addr; /* Section virtual addr at execution */
|
||||
uint64_t sh_offset; /* Section file offset */
|
||||
uint64_t sh_size; /* Size of section in bytes */
|
||||
uint32_t sh_link; /* Index of another section */
|
||||
uint32_t sh_info; /* Additional section information */
|
||||
uint64_t sh_addralign; /* Section alignment */
|
||||
uint64_t sh_entsize; /* Entry size if section holds table */
|
||||
} elf64_shdr_t;
|
||||
|
||||
/* Note header in a PT_NOTE section */
|
||||
typedef struct elf32_note {
|
||||
uint32_t n_namesz; /* Name size */
|
||||
uint32_t n_descsz; /* Content size */
|
||||
uint32_t n_type; /* Content type */
|
||||
} Elf32_Nhdr;
|
||||
|
||||
typedef Elf32_Nhdr Elf_Nhdr;
|
||||
|
||||
static uint16_t file16_to_cpu(uint8_t ident[EI_NIDENT], uint16_t val) {
|
||||
if (ident[EI_DATA] != ELFDATANATIVE) {
|
||||
uint16_t a = val & 0xFF;
|
||||
uint16_t b = (val >> 8) & 0xFF;
|
||||
val = a << 8 | b;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
static uint32_t file32_to_cpu(uint8_t ident[EI_NIDENT], uint32_t val) {
|
||||
if (ident[EI_DATA] != ELFDATANATIVE) {
|
||||
uint32_t a = val & 0xFF;
|
||||
uint32_t b = (val >> 8) & 0xFF;
|
||||
uint32_t c = (val >> 16) & 0xFF;
|
||||
uint32_t d = (val >> 24) & 0xFF;
|
||||
val = a << 24 | b << 16 | c << 8 | d;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
static uint64_t file64_to_cpu(uint8_t ident[EI_NIDENT], uint64_t val) {
|
||||
if (ident[EI_DATA] != ELFDATANATIVE) {
|
||||
uint64_t a = val & 0xFF;
|
||||
uint64_t b = (val >> 8) & 0xFF;
|
||||
uint64_t c = (val >> 16) & 0xFF;
|
||||
uint64_t d = (val >> 24) & 0xFF;
|
||||
uint64_t e = (val >> 32) & 0xFF;
|
||||
uint64_t f = (val >> 40) & 0xFF;
|
||||
uint64_t g = (val >> 48) & 0xFF;
|
||||
uint64_t h = (val >> 56) & 0xFF;
|
||||
val = a << 56 | b << 48 | c << 40 | d << 32 | e << 24 | f << 16 | g << 8 | h;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
static ssize_t read_elf32_size(uint8_t ident[EI_NIDENT], FILE *fd) {
|
||||
ssize_t ret;
|
||||
|
||||
fseek(fd, 0, SEEK_SET);
|
||||
|
||||
elf32_hdr_t header;
|
||||
ret = fread(&header, 1, sizeof(header), fd);
|
||||
if (ret < 0 || ret != sizeof header) {
|
||||
warn("Read of ELF header failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t sh_offset = file32_to_cpu(ident, header.e_shoff);
|
||||
size_t sh_entry_size = file16_to_cpu(ident, header.e_shentsize);
|
||||
size_t sh_entry_n = file16_to_cpu(ident, header.e_shnum);
|
||||
|
||||
off_t last_shdr_offset = sh_offset + (sh_entry_size * (sh_entry_n - 1));
|
||||
fseek(fd, last_shdr_offset, SEEK_SET);
|
||||
|
||||
elf32_shdr_t sect_header;
|
||||
ret = fread(§_header, 1, sizeof(sect_header), fd);
|
||||
if (ret < 0 || (size_t) ret != sizeof(sect_header)) {
|
||||
warn("Read of ELF section header failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* ELF ends either with the table of section headers (SHT) or with a section. */
|
||||
off_t sh_end = sh_offset + (sh_entry_size * sh_entry_n);
|
||||
off_t s_end = file64_to_cpu(ident, sect_header.sh_offset) + file64_to_cpu(ident, sect_header.sh_size);
|
||||
|
||||
return sh_end > s_end ? sh_end : s_end;
|
||||
}
|
||||
static ssize_t read_elf64_size(uint8_t ident[EI_NIDENT], FILE *fd) {
|
||||
off_t ret;
|
||||
|
||||
fseek(fd, 0, SEEK_SET);
|
||||
|
||||
elf64_hdr_t header;
|
||||
ret = fread(&header, 1, sizeof(header), fd);
|
||||
if (ret < 0 || (size_t) ret != sizeof(header)) {
|
||||
warn("Read of ELF header failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t sh_offset = file64_to_cpu(ident, header.e_shoff);
|
||||
size_t sh_entry_size = file16_to_cpu(ident, header.e_shentsize);
|
||||
size_t sh_entry_n = file16_to_cpu(ident, header.e_shnum);
|
||||
|
||||
off_t last_shdr_offset = sh_offset + (sh_entry_size * (sh_entry_n - 1));
|
||||
fseek(fd, last_shdr_offset, SEEK_SET);
|
||||
|
||||
elf64_shdr_t sect_header;
|
||||
ret = fread(§_header, 1, sizeof(sect_header), fd);
|
||||
if (ret < 0 || ret != sizeof(sect_header)) {
|
||||
warn("Read of ELF section header failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* ELF ends either with the table of section headers (SHT) or with a section. */
|
||||
off_t sht_end = sh_offset + (sh_entry_size * sh_entry_n);
|
||||
off_t s_end = file64_to_cpu(ident, sect_header.sh_offset) + file64_to_cpu(ident, sect_header.sh_size);
|
||||
return sht_end > s_end ? sht_end : s_end;
|
||||
}
|
||||
|
||||
ssize_t slimpack_get_offset(const char *executable) {
|
||||
FILE *fd = fopen(executable, "rb");
|
||||
if (fd == NULL) {
|
||||
warn("Cannot open executable");
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint8_t ident[EI_NIDENT];
|
||||
|
||||
off_t ret = fread(ident, 1, EI_NIDENT, fd);
|
||||
if (ret != EI_NIDENT) {
|
||||
warn("Read of e_ident failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ident[EI_DATA] != ELFDATA2LSB && ident[EI_DATA] != ELFDATA2MSB) {
|
||||
warnx("Unknown ELF data order %u", ident[EI_DATA]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t res;
|
||||
|
||||
if (ident[EI_CLASS] == ELFCLASS32) res = read_elf32_size(ident, fd);
|
||||
else if (ident[EI_CLASS] == ELFCLASS64) res = read_elf64_size(ident, fd);
|
||||
else {
|
||||
fprintf(stderr, "Unknown ELF class %u\n", ident[EI_CLASS]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
fclose(fd);
|
||||
return res;
|
||||
}
|
||||
1019
src/loader/slimfs.c
Normal file
1019
src/loader/slimfs.c
Normal file
File diff suppressed because it is too large
Load Diff
25
src/loader/slimfs.h
Normal file
25
src/loader/slimfs.h
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* License: BSD-style license
|
||||
* Copyright: Radek Podgorny <radek@podgorny.cz>,
|
||||
* Bernd Schubert <bernd-schubert@gmx.de>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <fuse.h>
|
||||
#include <squashfuse/squashfuse.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct {
|
||||
struct fuse *fuse;
|
||||
struct fuse_chan *channel;
|
||||
struct sqfs image;
|
||||
char root_dir[PATH_MAX];
|
||||
char target_dir[PATH_MAX];
|
||||
} *slimfs_t;
|
||||
|
||||
extern struct fuse_operations unionfs_oper;
|
||||
|
||||
slimfs_t slimfs_new(int argc, char **argv, const char *target_dir, const char *root_dir, const char *img_path, size_t img_offset);
|
||||
void slimfs_free(slimfs_t fs);
|
||||
13
src/main/cli/dump.lua
Normal file
13
src/main/cli/dump.lua
Normal file
@@ -0,0 +1,13 @@
|
||||
local ostree = require "ostree";
|
||||
|
||||
return function (options, name, out)
|
||||
local repo = ostree.from_http(options.repo);
|
||||
local ref = repo:read_ref(name);
|
||||
local commit = repo:read_commit(ref);
|
||||
|
||||
local root = repo:read_dir(commit.root);
|
||||
|
||||
root:recurse(repo, function (path, node)
|
||||
return ostree.dump_node(out .. path, node);
|
||||
end);
|
||||
end
|
||||
110
src/main/cli/pack.lua
Normal file
110
src/main/cli/pack.lua
Normal file
@@ -0,0 +1,110 @@
|
||||
local ostree = require "ostree";
|
||||
local flatpak = require "flatpak";
|
||||
local fs = require "fs";
|
||||
local elf_loader = require "elf_loader";
|
||||
|
||||
local entry_preifx = [[#!/bin/sh
|
||||
|
||||
export PATH=$PATH:/app/bin
|
||||
]];
|
||||
|
||||
local function help()
|
||||
print "Slimpack pack help:";
|
||||
print "Subcommand of slimpack, used to repack a flatpak application into";
|
||||
print "an AppImage-like self-contained executable.";
|
||||
print " --help: Shows this message";
|
||||
print " --target (-o): Specify the output file. Defaults to 'app'";
|
||||
print " <arg>: Specifies a package to be converted. At the moment, one may be specified.";
|
||||
print " in the future however, extensions will be specified as more package refs";
|
||||
|
||||
os.exit();
|
||||
end
|
||||
|
||||
local function dump_at(repo, name, target)
|
||||
local ref = repo:read_ref(name);
|
||||
local commit = repo:read_commit(ref);
|
||||
|
||||
assert(repo:read_dir(commit.root))
|
||||
:dir(repo, "files")
|
||||
:recurse(repo, function (path, node)
|
||||
print(path);
|
||||
return ostree.dump_node(target .. path, node);
|
||||
end);
|
||||
end
|
||||
|
||||
local function write_entry(repo, name, target)
|
||||
local meta = flatpak.parse_package(repo, name);
|
||||
|
||||
local f = assert(io.open(target, "wb"));
|
||||
f:write(entry_preifx);
|
||||
f:write(meta.command);
|
||||
f:close();
|
||||
assert(fs.chmod(target, "755"));
|
||||
end
|
||||
|
||||
local function write_res(img_path, target)
|
||||
local f = assert(io.open(target, "wb"));
|
||||
f:write(elf_loader);
|
||||
|
||||
local img_f = assert(io.open(img_path, "rb"));
|
||||
|
||||
for part in img_f:lines(4096) do
|
||||
f:write(part);
|
||||
end
|
||||
|
||||
img_f:close();
|
||||
f:close();
|
||||
assert(fs.chmod(target, "755"));
|
||||
end
|
||||
|
||||
return function (options, ...)
|
||||
local target;
|
||||
local name;
|
||||
|
||||
require "util.args" ({
|
||||
function (val)
|
||||
if name then
|
||||
error "only one package may be specified (at the moment)";
|
||||
else
|
||||
name = val;
|
||||
return true;
|
||||
end
|
||||
end,
|
||||
target = function (val)
|
||||
if target then
|
||||
error "target may be specified only once";
|
||||
else
|
||||
target = val;
|
||||
return true;
|
||||
end
|
||||
end,
|
||||
help = help,
|
||||
|
||||
o = "target",
|
||||
}, ...);
|
||||
|
||||
target = target or "./app";
|
||||
if not name then
|
||||
error "package must be specified";
|
||||
end
|
||||
|
||||
local repo = ostree.from_http(options.repo);
|
||||
|
||||
local tmp_dir = assert(io.popen "mktemp -d .pack_XXXXXXXX":read "*a"):sub(1, -2);
|
||||
|
||||
local ok, err = pcall(function ()
|
||||
dump_at(repo, name, tmp_dir .. "/dump/app");
|
||||
|
||||
write_entry(repo, name, tmp_dir .. "/dump/entry");
|
||||
|
||||
-- TODO: replace with C code
|
||||
assert(os.execute(("mksquashfs %q %q"):format(tmp_dir .. "/dump", tmp_dir .. "/img.squashfs")));
|
||||
|
||||
write_res(tmp_dir .. "/img.squashfs", target);
|
||||
end);
|
||||
|
||||
-- TODO: replace with C code
|
||||
os.execute(("rm -rf %q"):format(tmp_dir));
|
||||
|
||||
if not ok then error(err, 0) end
|
||||
end
|
||||
53
src/main/cli/query.lua
Normal file
53
src/main/cli/query.lua
Normal file
@@ -0,0 +1,53 @@
|
||||
local ostree = require "ostree";
|
||||
local flatpak = require "flatpak";
|
||||
|
||||
local actions = {};
|
||||
function actions.list(options)
|
||||
local repo = ostree.from_http(options.repo);
|
||||
local index = repo:read_summary_index();
|
||||
local summary = repo:read_subsummary(index.refs.x86_64.checksum);
|
||||
|
||||
for ref in summary.refs do
|
||||
print(ref);
|
||||
end
|
||||
end
|
||||
function actions.search(options, term)
|
||||
local repo = ostree.from_http(options.repo);
|
||||
local index = repo:read_summary_index();
|
||||
local summary = repo:read_subsummary(index.refs.x86_64.checksum);
|
||||
|
||||
for ref in summary.refs do
|
||||
if ref:lower():match(term:lower(), 1, true) then
|
||||
print(ref);
|
||||
end
|
||||
end
|
||||
end
|
||||
function actions.info(options, name)
|
||||
local repo = ostree.from_http(options.repo);
|
||||
print(name);
|
||||
local meta = flatpak.parse_package(repo, name);
|
||||
pprint(meta);
|
||||
|
||||
print("Name: " .. meta.name);
|
||||
print("Branch: " .. meta.branch);
|
||||
print("Architecture: " .. meta.arch);
|
||||
print("Version: " .. (meta.version or "(not specified)"));
|
||||
print("License: " .. (meta.license or "(not specified)"));
|
||||
print("Summary: " .. (meta.summary or "(not specified)"));
|
||||
|
||||
print("Dependencies:");
|
||||
if meta.deps.debug then
|
||||
print("\tDebug package: " .. meta.deps.debug.name);
|
||||
end
|
||||
if meta.deps.locale then
|
||||
print("\tLocale package: " .. meta.deps.locale.name);
|
||||
end
|
||||
for i = 1, #meta.deps do
|
||||
local dep = meta.deps[i];
|
||||
print("\t" .. dep.name .. (dep.required and "" or " (optional)"));
|
||||
end
|
||||
end
|
||||
|
||||
return function (options, action, ...)
|
||||
actions[action](options, ...);
|
||||
end
|
||||
105
src/main/flatpak.lua
Normal file
105
src/main/flatpak.lua
Normal file
@@ -0,0 +1,105 @@
|
||||
local ini = require "formats.ini";
|
||||
local xml = require "formats.xml";
|
||||
|
||||
local flatpak = {};
|
||||
|
||||
--- @param repo ostree
|
||||
local function get_metainfo(repo, commit, metadata)
|
||||
local root_dir = repo:read_dir(commit.root);
|
||||
|
||||
local export_dir = root_dir:dir(repo, "export");
|
||||
if export_dir == nil then return nil end
|
||||
|
||||
local share_dir = export_dir:dir(repo, "share");
|
||||
if share_dir == nil then return nil end
|
||||
|
||||
local metainfo_dir = share_dir:dir(repo, "metainfo");
|
||||
if metainfo_dir == nil then return nil end
|
||||
|
||||
local metainfo_ref =
|
||||
metainfo_dir.files[metadata.Application.name .. ".metainfo.xml"] or
|
||||
metainfo_dir.files[metadata.Application.name .. ".appdata.xml"];
|
||||
if metainfo_ref == nil then return nil end
|
||||
|
||||
local metainfo_f = repo:read_file(metainfo_ref);
|
||||
|
||||
local parts = {};
|
||||
for el in metainfo_f:get_content() do parts[#parts + 1] = el end
|
||||
print(table.concat(parts, ""));
|
||||
return xml.parse(table.concat(parts, ""));
|
||||
end
|
||||
|
||||
--- @param repo ostree
|
||||
function flatpak.parse_package(repo, full_name)
|
||||
local ref = repo:read_ref(full_name);
|
||||
local commit = repo:read_commit(ref);
|
||||
local name, arch, branch = full_name:match "^.-/(.-)/(.-)/(.-)$";
|
||||
print(arch, branch);
|
||||
|
||||
local metadata = ini.parse(commit.metadata["xa.metadata"]);
|
||||
local metainfo = get_metainfo(repo, commit, metadata);
|
||||
|
||||
local res = {};
|
||||
|
||||
pprint(metadata);
|
||||
|
||||
res.ref = full_name;
|
||||
res.name = name;
|
||||
res.arch = arch;
|
||||
res.branch = branch;
|
||||
|
||||
res.size = commit.metadata["xa.download-size"];
|
||||
res.command = metadata.Application.command;
|
||||
res.runtime = metadata.Application.runtime;
|
||||
res.deps = {};
|
||||
|
||||
for key, map in pairs(metadata) do
|
||||
local dep = key:match "^Extension%s+(.-)%s*$";
|
||||
if dep then
|
||||
dep = (dep .. "@"):match "^(.-)@";
|
||||
|
||||
local dep_ref = "runtime/" .. dep .. "/" .. arch .. "/" .. (map.version or branch);
|
||||
local required = map["no-autodownload"] ~= "true";
|
||||
local mounts = {};
|
||||
|
||||
if map.directory then
|
||||
mounts[#mounts + 1] = map.directory;
|
||||
elseif map.directories then
|
||||
mounts = ini.parse_list(map.directories);
|
||||
end
|
||||
|
||||
if dep:find "%.Debug$" then
|
||||
res.deps.debug = {
|
||||
name = dep_ref,
|
||||
required = required,
|
||||
mounts = mounts,
|
||||
};
|
||||
elseif dep:find "%.Locale$" then
|
||||
res.deps.locale = {
|
||||
name = dep_ref,
|
||||
required = required,
|
||||
mounts = mounts,
|
||||
};
|
||||
else
|
||||
res.deps[#res.deps + 1] = {
|
||||
name = dep_ref,
|
||||
required = required,
|
||||
mounts = mounts,
|
||||
};
|
||||
end
|
||||
|
||||
pprint(dep, map);
|
||||
end
|
||||
end
|
||||
|
||||
if metainfo then
|
||||
local app_meta = metainfo:get "component";
|
||||
res.version = app_meta:get "releases":get_all "release"[1].attribs.version;
|
||||
res.license = app_meta:get "project_license":text();
|
||||
res.summary = app_meta:get_all "summary"[1]:text();
|
||||
end
|
||||
|
||||
return res, commit;
|
||||
end
|
||||
|
||||
return flatpak;
|
||||
@@ -1,5 +1,3 @@
|
||||
-- TODO: remove string.unpack
|
||||
|
||||
local fmt = require "fmt";
|
||||
|
||||
local uint8_le_fmt = fmt.new "<! I1";
|
||||
@@ -66,8 +64,10 @@ local function construct_reader_no_cache(str, i)
|
||||
local reader;
|
||||
reader, i = construct_reader_no_cache(str, i + 1);
|
||||
return parsers.maybe(reader), i;
|
||||
elseif c == "a" then
|
||||
elseif c == "a" or c == "l" then
|
||||
i = i + 1;
|
||||
local is_gen = c == "l";
|
||||
|
||||
if str:sub(i, i) == "{" then
|
||||
i = i + 1;
|
||||
|
||||
@@ -87,7 +87,7 @@ local function construct_reader_no_cache(str, i)
|
||||
else
|
||||
local reader;
|
||||
reader, i = construct_reader_no_cache(str, i);
|
||||
return parsers.array(reader), i;
|
||||
return parsers.array(reader, is_gen), i;
|
||||
end
|
||||
elseif c == "(" then
|
||||
i = i + 1;
|
||||
@@ -235,10 +235,11 @@ function parsers.array(el_parser, as_iterable)
|
||||
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));
|
||||
-- print(indent, "ARR\t", s, e, n);
|
||||
-- indent = indent .. "\t";
|
||||
|
||||
assert(not (n % 1 > 0), "offset calculation error");
|
||||
assert(n > 0, "negative array length");
|
||||
n = math.floor(n);
|
||||
|
||||
if as_iterable then
|
||||
@@ -1,4 +1,6 @@
|
||||
local function parse_ini(raw, glob_group)
|
||||
local ini = {};
|
||||
|
||||
function ini.parse(raw, glob_group)
|
||||
local lines = {};
|
||||
|
||||
for line in raw:gmatch "[^\n]+" do
|
||||
@@ -19,8 +21,9 @@ local function parse_ini(raw, glob_group)
|
||||
line_i = line_i + 1;
|
||||
line = line:match "^%s*(.-)%s*$";
|
||||
|
||||
|
||||
if line ~= "" then
|
||||
local group = line.match "^%[%s*(.-)%s*%]$";
|
||||
local group = line:match "^%[%s*(.-)%s*%]$";
|
||||
|
||||
if group ~= nil then
|
||||
curr_group = {};
|
||||
@@ -28,7 +31,7 @@ local function parse_ini(raw, glob_group)
|
||||
elseif curr_group == nil then
|
||||
error("line " .. line_i .. ": Unexpected global key");
|
||||
else
|
||||
local key, value = line.match "^%s*(.-)%s*=%s*(.-)%s*$";
|
||||
local key, value = line:match "^%s*(.-)%s*=%s*(.-)%s*$";
|
||||
if key == nil then
|
||||
error("line " .. line_i .. ": Unexpected ini syntax");
|
||||
end
|
||||
@@ -41,7 +44,7 @@ local function parse_ini(raw, glob_group)
|
||||
return groups;
|
||||
end
|
||||
|
||||
local function parse_ini_list(raw)
|
||||
function ini.parse_list(raw)
|
||||
local res = {};
|
||||
|
||||
for el in raw:gmatch "%s*([^;]-)%s*" do
|
||||
@@ -53,7 +56,4 @@ local function parse_ini_list(raw)
|
||||
return res;
|
||||
end
|
||||
|
||||
return {
|
||||
parse = parse_ini,
|
||||
list = parse_ini_list,
|
||||
};
|
||||
return ini;
|
||||
@@ -72,7 +72,7 @@ end
|
||||
local function parse_tag(raw, i)
|
||||
i = skip_spaces(raw, i);
|
||||
|
||||
local tag = raw:match("^[%w0-9%-]+", 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;
|
||||
@@ -82,7 +82,7 @@ local function parse_tag(raw, i)
|
||||
while true do
|
||||
i = skip_spaces(raw, i);
|
||||
|
||||
local all, key, _, val = raw:match("^(([%w0-9%-]-)%s*=%s*(['\"])(.-)%3%s*)", i);
|
||||
local all, key, _, val = raw:match("^(([%w0-9%-_:]-)%s*=%s*(['\"])(.-)%3%s*)", i);
|
||||
if all then
|
||||
attribs[key] = val;
|
||||
i = i + #all;
|
||||
@@ -101,10 +101,15 @@ local function parse_part(raw, i, allow_version)
|
||||
local comment_end;
|
||||
|
||||
if raw:sub(i, i + 3) == "<!--" then
|
||||
i = i + 43;
|
||||
i = i + 4;
|
||||
|
||||
comment_end = raw:find("-->", i);
|
||||
if comment_end then
|
||||
i = comment_end + 3;
|
||||
else
|
||||
i = #raw + 1;
|
||||
end
|
||||
|
||||
_, comment_end = raw:find("-->", i);
|
||||
i = comment_end or #raw;
|
||||
i = skip_spaces(raw, i);
|
||||
end
|
||||
until not comment_end;
|
||||
@@ -127,7 +132,7 @@ local function parse_part(raw, i, allow_version)
|
||||
i = i + 2;
|
||||
i = skip_spaces(raw, i);
|
||||
|
||||
local tag = raw:match("[%w0-9%-]+", 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;
|
||||
@@ -145,7 +150,6 @@ local function parse_part(raw, i, allow_version)
|
||||
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;
|
||||
@@ -161,7 +165,10 @@ local function parse_part(raw, i, allow_version)
|
||||
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*$";
|
||||
local text_part = raw:sub(i, text_end and text_end - 1 or #raw)
|
||||
:match "^%s*(.-)%s*$"
|
||||
:gsub("%s+", " ");
|
||||
|
||||
if text_part ~= "" then
|
||||
text_parts[#text_parts + 1] = text_part;
|
||||
end
|
||||
@@ -175,8 +182,13 @@ local function parse_part(raw, i, allow_version)
|
||||
if raw:sub(i, i + 3) == "<!--" then
|
||||
i = i + 4;
|
||||
|
||||
_, comment_end = raw:find("-->", i);
|
||||
i = comment_end and comment_end + 1 or #raw;
|
||||
comment_end = raw:find("-->", i);
|
||||
if comment_end then
|
||||
i = comment_end + 3;
|
||||
else
|
||||
i = #raw + 1;
|
||||
end
|
||||
|
||||
i = skip_spaces(raw, i);
|
||||
else
|
||||
break
|
||||
@@ -210,9 +222,11 @@ local function parse(raw)
|
||||
|
||||
first = false;
|
||||
|
||||
pprint(part);
|
||||
|
||||
if part.type == "text" then
|
||||
if #stack == 1 then
|
||||
error "text may not appear outside a tag";
|
||||
error("text may not appear outside a tag (near '" .. raw:sub(i, i + 25) .. "')");
|
||||
else
|
||||
curr_node[#curr_node + 1] = part.text;
|
||||
end
|
||||
56
src/main/init.lua
Normal file
56
src/main/init.lua
Normal file
@@ -0,0 +1,56 @@
|
||||
require "util.printing";
|
||||
|
||||
local actions = {
|
||||
help = require "cli.help",
|
||||
query = require "cli.query",
|
||||
dump = require "cli.dump",
|
||||
pack = require "cli.pack",
|
||||
};
|
||||
|
||||
local function parse_args(...)
|
||||
local options = {};
|
||||
local args = {};
|
||||
|
||||
require "util.args" ({
|
||||
function (arg)
|
||||
if options.action then
|
||||
args[#args + 1] = arg;
|
||||
else
|
||||
options.action = arg;
|
||||
end
|
||||
|
||||
return true, true;
|
||||
end,
|
||||
repo = function (arg)
|
||||
if options.repo ~= nil then
|
||||
error "a repo may be specified only once";
|
||||
else
|
||||
options.repo = arg;
|
||||
return true;
|
||||
end
|
||||
end,
|
||||
help = actions.help,
|
||||
}, ...);
|
||||
|
||||
if not options.action then
|
||||
error "no action specified";
|
||||
end
|
||||
|
||||
return options, args;
|
||||
end
|
||||
|
||||
return function (...)
|
||||
xpcall(
|
||||
function (...)
|
||||
local options, args = parse_args(...);
|
||||
options.repo = options.repo or "https://dl.flathub.org/repo";
|
||||
|
||||
return actions[options.action](options, table.unpack(args));
|
||||
end,
|
||||
function (err)
|
||||
print(table.concat { "unhandled error: ", err, "\n", debug.traceback(nil, 2):match "^[^\n]+\n(.+)$" });
|
||||
end,
|
||||
...
|
||||
);
|
||||
end
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
local gvariant = require "formats.gvariant";
|
||||
local fmt = require "fmt";
|
||||
local zlib = require "zlib";
|
||||
local fs = require "fs";
|
||||
|
||||
local header_fmt = fmt.new ">! I4 I4";
|
||||
|
||||
@@ -42,7 +43,7 @@ local read_header = gvariant "tuuuusa(ayay)";
|
||||
--- - r: checksum
|
||||
--- - a{sv}: metadata
|
||||
--- - a{sv}: metadata
|
||||
local read_summary = gvariant "a{s(tra{sv})}a{sv}";
|
||||
local read_summary = gvariant "l(s(tra{sv}))a{sv}";
|
||||
|
||||
--- Flatpak-specific format (I think):
|
||||
--- - a{s(rara{sv})}: map of architecture name to data about it
|
||||
@@ -52,6 +53,61 @@ local read_summary = gvariant "a{s(tra{sv})}a{sv}";
|
||||
--- - a{sv}: metadata
|
||||
local read_summary_index = gvariant "a{s(rara{sv})}a{sv}";
|
||||
|
||||
--- @class ostree_file
|
||||
--- @field type "file"
|
||||
--- @field uid integer
|
||||
--- @field gid integer
|
||||
--- @field mode integer
|
||||
--- @field size integer
|
||||
--- @field get_content fun(): fun(): string?
|
||||
|
||||
--- @class ostree_link
|
||||
--- @field type "link"
|
||||
--- @field uid integer
|
||||
--- @field gid integer
|
||||
--- @field mode integer
|
||||
--- @field target string
|
||||
|
||||
--- @class ostree_dir
|
||||
--- @field type "dir"
|
||||
--- @field files { [string]: string }
|
||||
--- @field dirs { [string]: { [1]: string, [2]: string } }
|
||||
--- @field uid integer
|
||||
--- @field gid integer
|
||||
--- @field mode integer
|
||||
local ostree_dir = {};
|
||||
ostree_dir.__index = ostree_dir;
|
||||
|
||||
--- @param repo ostree
|
||||
--- @param name string
|
||||
function ostree_dir:file(repo, name)
|
||||
if self.files[name] == nil then return nil end
|
||||
return repo:read_file(self.files[name]);
|
||||
end
|
||||
--- @param repo ostree
|
||||
--- @param name string
|
||||
function ostree_dir:dir(repo, name)
|
||||
if self.dirs[name] == nil then return nil end
|
||||
return repo:read_dir(self.dirs[name]);
|
||||
end
|
||||
--- @param repo ostree
|
||||
--- @param callback fun(path: string, val: ostree_dir | ostree_file | ostree_link)
|
||||
function ostree_dir:recurse(repo, callback)
|
||||
local function recurser(self, repo, callback, path)
|
||||
callback(path, self);
|
||||
|
||||
for file, ref in pairs(self.files) do
|
||||
callback(path .. file, repo:read_file(ref));
|
||||
end
|
||||
|
||||
for dir, ref in pairs(self.dirs) do
|
||||
recurser(repo:read_dir(ref), repo, callback, path .. dir .. "/");
|
||||
end
|
||||
end
|
||||
|
||||
return recurser(self, repo, callback, "/");
|
||||
end
|
||||
|
||||
--- @class ostree
|
||||
--- @field reader fun(path: string): string
|
||||
local ostree = {};
|
||||
@@ -119,14 +175,15 @@ function ostree.parse_dir(dir, meta)
|
||||
|
||||
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 };
|
||||
return setmetatable({ type = "dir", files = files, dirs = dirs, uid = uid, gid = gid, mode = mode & 0xFFF, xattrs = xattrs }, ostree_dir);
|
||||
else
|
||||
return { type = "dir", files = files, dirs = dirs };
|
||||
return setmetatable({ type = "dir", files = files, dirs = dirs }, ostree_dir);
|
||||
end
|
||||
end
|
||||
--- @param data string
|
||||
--- @return ostree_file
|
||||
function ostree.parse_file(data)
|
||||
local header_size, idk = header_fmt:unpack(data);
|
||||
local header_size = header_fmt:unpack(data);
|
||||
local header = data:sub(9, header_size + 8);
|
||||
local size, uid, gid, mode, rdev, link, xattrs = read_header(header);
|
||||
|
||||
@@ -139,7 +196,7 @@ function ostree.parse_file(data)
|
||||
target = link,
|
||||
uid = uid,
|
||||
gid = gid,
|
||||
mode = mode,
|
||||
mode = mode & 0xFFF,
|
||||
xattrs = xattrs,
|
||||
};
|
||||
end
|
||||
@@ -163,7 +220,7 @@ function ostree.parse_file(data)
|
||||
get_content = get_content,
|
||||
uid = uid,
|
||||
gid = gid,
|
||||
mode = mode,
|
||||
mode = mode & 0xFFF,
|
||||
size = size,
|
||||
xattrs = xattrs,
|
||||
};
|
||||
@@ -173,16 +230,22 @@ end
|
||||
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],
|
||||
};
|
||||
local function refs_iter()
|
||||
local el = refs();
|
||||
|
||||
if el == nil then
|
||||
return nil;
|
||||
else
|
||||
return el[1], {
|
||||
size = el[2][1],
|
||||
checksum = el[2][2],
|
||||
metadata = el[2][3],
|
||||
};
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
refs = refs,
|
||||
refs = refs_iter,
|
||||
metadata = metadata,
|
||||
};
|
||||
end
|
||||
@@ -209,6 +272,31 @@ function ostree.parse_summary_index(data)
|
||||
metadata = metadata,
|
||||
};
|
||||
end
|
||||
--- @param path string
|
||||
--- @param node ostree_dir | ostree_link | ostree_file
|
||||
function ostree.dump_node(path, node)
|
||||
if node.type == "dir" then
|
||||
assert(fs.mkdir(path, true));
|
||||
assert(fs.chmod(path, node.mode & 0xFFF));
|
||||
elseif node.type == "file" then
|
||||
assert(fs.mkdir(path:match "^(.+)/[^/]+$", true));
|
||||
|
||||
local f = assert(io.open(path, "w"));
|
||||
|
||||
for seg in node.get_content() do
|
||||
assert(f:write(seg));
|
||||
end
|
||||
|
||||
assert(f:close());
|
||||
|
||||
assert(fs.chmod(path, node.mode & 0xFFF));
|
||||
elseif node.type == "link" then
|
||||
assert(fs.mkdir(path:match "^(.+)/[^/]+$", true));
|
||||
assert(fs.symlink(node.target, path));
|
||||
else
|
||||
error("Unknown node type '" .. node.type .. "'");
|
||||
end
|
||||
end
|
||||
|
||||
--- @param type string
|
||||
--- @param ref string
|
||||
@@ -223,9 +311,9 @@ 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];
|
||||
if type(dir) ~= "string" then
|
||||
meta = dir[2];
|
||||
dir = dir[1];
|
||||
end
|
||||
|
||||
return ostree.parse_dir(self:read_object("dirtree", dir), meta and self:read_object("dirmeta", meta));
|
||||
@@ -235,6 +323,7 @@ function ostree:read_file(ref)
|
||||
return ostree.parse_file(self:read_object("filez", ref));
|
||||
end
|
||||
function ostree:read_summary()
|
||||
print(self.reader("summary"));
|
||||
return ostree.parse_summary(self.reader("summary"));
|
||||
end
|
||||
function ostree:read_subsummary(ref)
|
||||
@@ -1,4 +1,4 @@
|
||||
---@param consumers table<string, (fun(arg: string): boolean?, boolean?) | string>
|
||||
--- @param consumers table<string, (fun(arg: string): boolean?, boolean?) | string>
|
||||
--- @param ... string
|
||||
--- @returns nil
|
||||
return function (consumers, ...)
|
||||
@@ -6,30 +6,30 @@ return function (consumers, ...)
|
||||
local pass_args = false;
|
||||
|
||||
local function digest_arg(v)
|
||||
local consumed = false;
|
||||
|
||||
while true do
|
||||
--- @type (fun(arg: string): boolean?, boolean?)?
|
||||
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;
|
||||
return;
|
||||
end
|
||||
end
|
||||
|
||||
if not consumed then
|
||||
if consumers[1](v) then
|
||||
while true do
|
||||
local res, fin = consumers[1](v);
|
||||
if fin then
|
||||
pass_args = true;
|
||||
end
|
||||
|
||||
if res or fin then
|
||||
return;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
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);
|
||||
@@ -17,10 +8,6 @@ local function stringify_impl(obj, indent_str, n, passed)
|
||||
|
||||
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
|
||||
@@ -53,7 +40,9 @@ local function stringify_impl(obj, indent_str, n, passed)
|
||||
end
|
||||
end
|
||||
|
||||
local meta = getmetatable(obj);
|
||||
for i = 1, len do
|
||||
parts[#parts + 1] = stringify_impl(obj[i], indent_str, n + 1, passed) .. ",";
|
||||
end
|
||||
|
||||
passed[obj] = false;
|
||||
|
||||
@@ -76,7 +65,7 @@ local function stringify_impl(obj, indent_str, n, passed)
|
||||
|
||||
return table.concat { "{", contents, "}" };
|
||||
elseif kind == "string" then
|
||||
return "\"" .. escape_str(obj) .. "\"";
|
||||
return ("%q"):format(obj);
|
||||
elseif kind == "function" then
|
||||
local data = debug.getinfo(obj, "S");
|
||||
return table.concat { tostring(obj), " @ ", data.short_src, ":", data.linedefined };
|
||||
Reference in New Issue
Block a user