A => .build.yml +28 -0
@@ 1,28 @@
+image: debian/bullseye
+packages:
+- python3
+- python3-pip
+- mingw-w64
+sources:
+- https://git.sr.ht/~mrms/tarum
+- https://git.sr.ht/~mrms/pak
+environment:
+ project: tar
+secrets:
+- 1f9f8058-774e-4765-83d4-548a138a1c05
+shell: false
+tasks:
+- setup: |
+ cd pak
+ python3 -m pip install -r requirements.txt
+ ./setup.py install --user
+- build: |
+ cd tarum
+ python3 -m pak update
+ python3 -m pak build
+- deploy: |
+ cd tarum
+ set +x
+ python3 -m pak upload -t `cat ../.secret` pak.tar
+ python3 -m pak upload -t `cat ../.secret` pak.json
+ set -x
A => .gitignore +4 -0
@@ 1,4 @@
+*.umi
+pak/
+extracted
+test.tar
A => .gitmodules +3 -0
@@ 1,3 @@
+[submodule "microtar"]
+ path = microtar
+ url = https://github.com/rxi/microtar
A => LICENSE +37 -0
@@ 1,37 @@
+This software is available under 2 licenses -- choose whichever you prefer.
+------------------------------------------------------------------------------
+ALTERNATIVE A - MIT License
+Copyright (c) 2023 Marek Maškarinec
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+------------------------------------------------------------------------------
+ALTERNATIVE B - Public Domain (www.unlicense.org)
+This is free and unencumbered software released into the public domain.
+Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
+software, either in source code form or as a compiled binary, for any purpose,
+commercial or non-commercial, and by any means.
+In jurisdictions that recognize copyright laws, the author or authors of this
+software dedicate any and all copyright interest in the software to the public
+domain. We make this dedication for the benefit of the public at large and to
+the detriment of our heirs and successors. We intend this dedication to be an
+overt act of relinquishment in perpetuity of all present and future rights to
+this software under copyright law.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.<
\ No newline at end of file
A => Makefile +34 -0
@@ 1,34 @@
+
+CROSS_CC=x86_64-w64-mingw32-gcc
+NAME=tar
+UMKA=./pak/umka/linux/umka
+CFLAGS= \
+ -Wall \
+ -Wno-unused-label \
+ -Wno-pointer-to-int-cast \
+ -Wno-unused-result \
+ -Ipak/umka/ \
+ -I. \
+ -Imicrotar/src/ \
+ -fPIC \
+ -g
+LDFLAGS=pak/umka/linux/libumka_static.a
+
+.PHONY: all
+all: build
+
+%_linux.umi: %.c Makefile
+ @echo CC $@
+ @$(CC) $(CFLAGS) -shared -o $@ $< microtar/src/microtar.c $(LDFLAGS)
+
+%_windows.umi: %.c Makefile
+ @echo CC $@
+ @$(CROSS_CC) -shared $(CFLAGS) -o $@ $< microtar/src/microtar.c -Lpak/umka/windows -lumka
+
+.PHONY: build
+build: $(NAME)_windows.umi $(NAME)_linux.umi
+
+.PHONY: run
+run: build
+ @$(UMKA) $(NAME).um
+
A => README.md +61 -0
@@ 1,61 @@
+# tar.um
+
+A tar archive library for Umka. Install using
+[PAK](https://pak.tophat2d.dev/package/tar).
+
+## Example
+
+```
+import (
+ "pak/tar/tar.um"
+)
+
+fn main() {
+ var err: Errno
+ var t: tar.Tar
+
+ t, err = tar.open("test.tar", "w")
+ if err != 0 {
+ printf("Error opening tar file: %s\n", tar.strerror(err))
+ return
+ }
+
+ t.addFile("pak.json")
+ t.addFile("microtar/README.md")
+ t.addFile("tar_linux.umi")
+
+ err = t.finalize()
+ if err != 0 {
+ printf("Error finalizing tar file: %s\n", tar.strerror(err))
+ return
+ }
+
+ err = t.close()
+ if err != 0 {
+ printf("Error closing tar file: %s\n", tar.strerror(err))
+ return
+ }
+
+ t, err = tar.open("test.tar", "r")
+ if err != 0 {
+ printf("Error opening tar file: %s\n", tar.strerror(err))
+ return
+ }
+
+ err = t.extract("extracted")
+ if err != 0 {
+ printf("Error extracting tar file: %s\n", tar.strerror(err))
+ return
+ }
+
+ err = t.close()
+ if err != 0 {
+ printf("Error closing tar file: %s\n", tar.strerror(err))
+ return
+ }
+}
+```
+
+## License
+
+Unlicense/MIT
A => microtar +1 -0
@@ 1,1 @@
+Subproject commit 27076e1b9290e9c7842bb7890a54fcf172406c84
A => pak.json +22 -0
@@ 1,22 @@
+{
+ "name": "tar",
+ "version": "v0.1.0",
+ "author": "Marek Maškarinec <marek@mrms.cz>",
+ "license": "Unlicense/MIT",
+ "description": "A tar library",
+ "readme": "README.md",
+ "link": "https://git.sr.ht/~mrms/tarum",
+ "dependencies": [
+ "umka",
+ "os",
+ "filepath"
+ ],
+ "include": [
+ "tar.um",
+ "tar_windows.umi",
+ "tar_linux.umi"
+ ],
+ "pre_build": "make",
+ "run_posix": "make run",
+ "run_windows": "./pak/umka/windows/umka.exe tar.um"
+}<
\ No newline at end of file
A => tar.c +217 -0
@@ 1,217 @@
+
+#include <microtar.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <umka_api.h>
+
+#ifdef _WIN32
+#else
+#include <unistd.h>
+#endif
+
+typedef struct
+{
+ uint32_t mode;
+ uint32_t owner;
+ uint32_t size;
+ uint32_t mtime;
+ uint32_t type;
+ char *name;
+ char *linkname;
+} File;
+
+// fn umc__tar_strerror(err: int): str
+void
+umc__tar_strerror(UmkaStackSlot *p, UmkaStackSlot *r)
+{
+ int err = p[0].intVal;
+ void *umka = r->ptrVal;
+
+ r->ptrVal = umkaMakeStr(umka, mtar_strerror(err));
+}
+
+// fn umc__tar_open(path: str, mode: str, out: ^^struct{}): int
+void
+umc__tar_open(UmkaStackSlot *p, UmkaStackSlot *r)
+{
+ char *path = (char *)p[2].ptrVal;
+ char *mode = (char *)p[1].ptrVal;
+ mtar_t **out = (mtar_t **)p[0].ptrVal;
+ void *umka = r->ptrVal;
+
+ *out = umkaAllocData(umka, sizeof(mtar_t), NULL);
+ r->intVal = mtar_open(*out, path, mode);
+
+ if (r->intVal != MTAR_ESUCCESS) {
+ *out = NULL;
+ }
+}
+
+void *fileArrType = NULL;
+
+// fn umc__tar_get_files(tar: ^struct{}, out: ^[]File): int
+void
+umc__tar_get_files(UmkaStackSlot *p, UmkaStackSlot *r)
+{
+ mtar_t *tar = (mtar_t *)p[1].ptrVal;
+ UmkaDynArray(File) *out = p[0].ptrVal;
+ void *umka = r->ptrVal;
+ r->intVal = 0;
+
+ int ret = mtar_seek(tar, 0);
+ if (ret) {
+ r->intVal = ret;
+ return;
+ }
+
+ int count = 0;
+ mtar_header_t h;
+ while ((mtar_read_header(tar, &h)) != MTAR_ENULLRECORD) {
+ ++count;
+ mtar_next(tar);
+ }
+
+ if (fileArrType == NULL) {
+ fileArrType = umkaGetType(umka, "tar.um", "FileArr");
+ }
+
+ umkaMakeDynArray(umka, out, fileArrType, count);
+
+ mtar_seek(tar, 0);
+ for (int i = 0; i < count; ++i) {
+ mtar_read_header(tar, &h);
+ out->data[i] = (File){
+ .mode = h.mode,
+ .owner = h.owner,
+ .size = h.size,
+ .mtime = h.mtime,
+ .type = h.type,
+ .name = umkaMakeStr(umka, h.name),
+ .linkname = umkaMakeStr(umka, h.linkname),
+ };
+ mtar_next(tar);
+ }
+}
+
+// fn umc__tar_read_str(tar: ^struct{}, file: str, out: ^str): int
+void
+umc__tar_read_str(UmkaStackSlot *p, UmkaStackSlot *r)
+{
+ mtar_t *tar = (mtar_t *)p[2].ptrVal;
+ char *file = p[1].ptrVal;
+ char **out = p[0].ptrVal;
+ void *umka = r->ptrVal;
+ r->intVal = 0;
+
+ mtar_header_t h;
+ int ret = mtar_find(tar, file, &h);
+ if (ret) {
+ r->intVal = ret;
+ return;
+ }
+
+ char *buf = malloc(h.size + 1);
+
+ ret = mtar_read_data(tar, buf, h.size);
+ if (ret) {
+ r->intVal = ret;
+ return;
+ }
+
+ buf[h.size] = '\0';
+ *out = umkaMakeStr(umka, buf);
+}
+
+// fn umc__tar_read_bin(tar: ^struct{}, file: str, out: ^[]byte): int
+void
+umc__tar_read_bin(UmkaStackSlot *p, UmkaStackSlot *r)
+{
+ static void *byteArrType = NULL;
+ if (byteArrType == NULL) {
+ byteArrType = umkaGetType(r->ptrVal, "tar.um", "ByteArr");
+ }
+
+ mtar_t *tar = (mtar_t *)p[2].ptrVal;
+ char *file = p[1].ptrVal;
+ UmkaDynArray(uint8_t) *out = p[0].ptrVal;
+ void *umka = r->ptrVal;
+ r->intVal = 0;
+
+ mtar_header_t h;
+ int ret = mtar_find(tar, file, &h);
+ if (ret) {
+ r->intVal = ret;
+ return;
+ }
+
+ umkaMakeDynArray(umka, out, byteArrType, h.size);
+
+ ret = mtar_read_data(tar, out->data, h.size);
+ if (ret) {
+ r->intVal = ret;
+ return;
+ }
+}
+
+// fn umc__tar_add_file(tar: ^struct{}, file: str): int
+void
+umc__tar_add_file(UmkaStackSlot *p, UmkaStackSlot *r)
+{
+ mtar_t *tar = p[1].ptrVal;
+ char *file = p[0].ptrVal;
+ r->intVal = 0;
+
+#ifdef _WIN32
+#define stat _stat
+#endif
+
+ struct stat st;
+ stat(file, &st);
+
+ mtar_header_t h = {
+ .mtime = st.st_mtime,
+ .size = st.st_size,
+ .type = S_ISREG(st.st_mode) ? MTAR_TREG : MTAR_TDIR,
+ .mode = st.st_mode,
+ };
+ strncpy(h.name, file, sizeof(h.name) - 1);
+
+ mtar_write_header(tar, &h);
+
+ if (S_ISDIR(st.st_mode)) {
+ return;
+ }
+
+ FILE *f = fopen(file, "rb");
+ if (!f) {
+ r->intVal = MTAR_EFAILURE;
+ return;
+ }
+
+ fseek(f, 0, SEEK_END);
+ size_t size = ftell(f);
+ fseek(f, 0, SEEK_SET);
+
+ char *buf = malloc(size);
+ fread(buf, 1, size, f);
+
+ r->intVal = mtar_write_data(tar, buf, size);
+}
+
+// fn umc__tar_close(tar: ^struct{}): int
+void
+umc__tar_close(UmkaStackSlot *p, UmkaStackSlot *r)
+{
+ mtar_t *tar = p[0].ptrVal;
+ r->intVal = mtar_close(tar);
+}
+
+// fn umc__tar_finalize(tar: ^struct{}): int
+void
+umc__tar_finalize(UmkaStackSlot *p, UmkaStackSlot *r)
+{
+ mtar_t *tar = p[0].ptrVal;
+ r->intVal = mtar_finalize(tar);
+}<
\ No newline at end of file
A => tar.um +209 -0
@@ 1,209 @@
+
+import (
+ "pak/os/os.um"
+ "pak/filepath/filepath.um"
+ "std.um"
+)
+
+//~~struct File
+// Struct representing a file in a tar archive.
+type File* = struct {
+//~~
+ mode: uint32
+ owner: uint32
+ size: uint32
+ mtime: uint32
+ filetype: uint32
+ name: str
+ linkname: str
+}
+
+//~~opaque Tar
+// A tar archive handle
+type Tar* = struct{ _: ^struct{} }
+//~~
+
+//~~enum Errno
+// Error codes returned by tar functions
+type Errno* = int32
+//
+
+fn umc__tar_strerror(err: Errno): str
+
+//~~fn strerror
+// Returns a string describing the given error code.
+fn strerror*(err: Errno): str {
+//~~
+ return umc__tar_strerror(err)
+}
+
+fn umc__tar_open(path: str, mode: str, out: ^Tar): Errno
+
+//~~fn open
+// Opens a tar archive at the given path with the given mode.
+fn open*(path: str, mode: str): (Tar, Errno) {
+//~~
+ var t: Tar
+ err := umc__tar_open(path, mode, &t)
+ return t, err
+}
+
+fn umc__tar_close(tar: ^struct{}): Errno
+
+//~~fn close
+// Closes the given tar archive.
+fn (t: ^Tar) close*(): Errno {
+//~~
+ return umc__tar_close(t._)
+}
+
+type FileArr* = []File
+
+fn umc__tar_get_files(tar: ^struct{}, out: ^FileArr): Errno
+
+//~~fn getFiles
+// Returns a list of files in the given tar archive.
+fn (t: ^Tar) getFiles*(): ([]File, Errno) {
+//~~
+ files := []File{}
+ err := umc__tar_get_files(t._, &files)
+ return files, err
+}
+
+fn umc__tar_read_str(tar: ^struct{}, path: str, out: ^str): Errno
+
+//~~fn readStr
+// Reads the given file from the tar archive as a string.
+fn (t: ^Tar) readStr*(path: str): (str, Errno) {
+//~~
+ var s: str
+ err := umc__tar_read_str(t._, path, &s)
+ return s, err
+}
+
+type ByteArr* = []uint8
+fn umc__tar_read_bin(tar: ^struct{}, path: str, out: ^[]uint8): Errno
+
+//~~fn readBin
+// Reads the given file from the tar archive as a byte array.
+fn (t: ^Tar) readBin*(path: str): ([]uint8, Errno) {
+//~~
+ var s: []uint8
+ err := umc__tar_read_bin(t._, path, &s)
+ return s, err
+}
+
+//~~fn extract
+// Extracts the given tar archive to the given directory.
+fn (t: ^Tar) extract*(dir: str): Errno {
+//~~
+ files, err := t.getFiles()
+ if (err != 0) {
+ return err
+ }
+
+ os.mkdirp(dir)
+
+ for i:=0; i < len(files); i++ {
+ path := filepath.join(dir, files[i].name)
+ os.mkdirp(filepath.dir(path))
+
+ dat, err := t.readBin(files[i].name)
+ if (err != 0) {
+ return err
+ }
+
+ f := std.fopen(path, "wb")
+ std.fwrite(f, dat)
+ std.fclose(f)
+
+ os.chmod(path, files[i].mode)
+ }
+
+ return 0
+}
+
+fn umc__tar_add_file(t: ^struct{}, path: str): Errno
+
+//~~fn addFile
+// Adds the given file to the tar archive. Directories are not added
+// recursively.
+fn (t: ^Tar) addFile*(path: str): Errno {
+//~~
+ return umc__tar_add_file(t._, path)
+}
+
+fn umc__tar_finalize(t: ^struct{}): Errno
+
+//~~fn finalize
+// Finalizes the tar archive.
+fn (t: ^Tar) finalize*(): Errno {
+//~~
+ return umc__tar_finalize(t._)
+}
+
+fn main() {
+ var err: Errno
+ var tar: Tar
+
+ tar, err = open("test.tar", "w")
+ if err != 0 {
+ printf("Error opening tar file: %s\n", strerror(err))
+ return
+ }
+
+ tar.addFile("pak.json")
+ tar.addFile("microtar/README.md")
+ tar.addFile("tar_linux.umi")
+
+ err = tar.finalize()
+ if err != 0 {
+ printf("Error finalizing tar file: %s\n", strerror(err))
+ return
+ }
+
+ err = tar.close()
+ if err != 0 {
+ printf("Error closing tar file: %s\n", strerror(err))
+ return
+ }
+
+ tar, err = open("test.tar", "r")
+ if err != 0 {
+ printf("Error opening tar file: %s\n", strerror(err))
+ return
+ }
+
+ var files: []File
+ files, err = tar.getFiles()
+ if err != 0 {
+ printf("Error reading tar file: %s\n", strerror(err))
+ return
+ }
+
+ printf("Files in tar file:\n")
+ for i in files {
+ printf(" %s\n", files[i].name)
+ }
+
+ printf("Reading pak.json:\n")
+ var pakJson: str
+ pakJson, err = tar.readStr("pak.json")
+ if err != 0 {
+ printf("Error reading pak.json: %s\n", strerror(err))
+ return
+ }
+ printf("%s\n", pakJson)
+
+ err = tar.extract("extracted")
+ if err != 0 {
+ printf("Error extracting tar file: %s\n", strerror(err))
+ return
+ }
+
+ err = tar.close()
+ if err != 0 {
+ printf("Error closing tar file: %s\n", strerror(err))
+ return
+ }
+}<
\ No newline at end of file