├── tests ├── _repose.h ├── conftest.py ├── test_utils.py ├── _repose.c ├── test_pkginfo.py ├── test_desc.py └── wrappers.py ├── .travis.yml ├── src ├── filecache.h ├── signing.h ├── base64.h ├── database.h ├── filters.h ├── coverity_model.c ├── buffer.h ├── desc.h ├── pkginfo.h ├── repose.h ├── filters.c ├── pkgcache.h ├── package.h ├── util.h ├── buffer.c ├── filecache.c ├── pkginfo.rl ├── base64.c ├── desc.rl ├── util.c ├── signing.c ├── package.c ├── pkgcache.c ├── database.c └── repose.c ├── .gitignore ├── Dockerfile ├── _repose ├── Makefile ├── man └── repose.1 ├── README.md ├── compile_commands.json └── COPYING /tests/_repose.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: generic 2 | sudo: false 3 | services: 4 | - docker 5 | 6 | install: 7 | - docker build -t repose . 8 | 9 | script: 10 | - docker run repose 11 | -------------------------------------------------------------------------------- /src/filecache.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "pkgcache.h" 5 | 6 | struct pkgcache *get_filecache(int dirfd, alpm_list_t *targets, const char *arch); 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /repose 2 | /src/desc.c 3 | /src/pkginfo.c 4 | *.o 5 | *.dot 6 | *.png 7 | *.db 8 | *.files 9 | *.pyc 10 | __pycache__/ 11 | .cache 12 | /tests/repose.c 13 | /tests/*.so 14 | -------------------------------------------------------------------------------- /src/signing.h: -------------------------------------------------------------------------------- 1 | #ifndef SIGNING_H 2 | #define SIGNING_H 3 | 4 | void gpgme_sign(int rootfd, const char *file, const char *key); 5 | int gpgme_verify(int rootfd, const char *file); 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /src/base64.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | char *base64_encode(const unsigned char *data, size_t data_length, 6 | size_t *output_length); 7 | 8 | char *base64_decode(const unsigned char *data, size_t data_length, 9 | size_t *output_length); 10 | -------------------------------------------------------------------------------- /src/database.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "pkgcache.h" 4 | 5 | struct repo; 6 | 7 | enum contents { 8 | DB_DESC = 1, 9 | DB_DEPENDS = 1 << 2, 10 | DB_FILES = 1 << 3, 11 | DB_DELTAS = 1 << 4 12 | }; 13 | 14 | int load_database(int fd, struct pkgcache **pkgcache); 15 | int write_database(struct repo *repo, const char *repo_name, enum contents what); 16 | -------------------------------------------------------------------------------- /src/filters.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "package.h" 7 | #include "util.h" 8 | 9 | bool match_targets(struct pkg *pkg, alpm_list_t *targets); 10 | 11 | static inline bool match_arch(struct pkg *pkg, const char *arch) 12 | { 13 | if (!pkg->arch) 14 | return arch != NULL; 15 | return streq(pkg->arch, arch) || streq(pkg->arch, "any"); 16 | } 17 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | MAINTAINER "Simon Gomizelj " 3 | 4 | RUN apk add --no-cache \ 5 | python3 \ 6 | python3-dev \ 7 | build-base \ 8 | ragel \ 9 | pacman-dev \ 10 | libffi-dev \ 11 | && rm -rf /var/cache/apk/* 12 | 13 | RUN python3 -m ensurepip && pip3 install \ 14 | cffi \ 15 | pytest \ 16 | pytest-xdist 17 | 18 | ADD . /usr/src 19 | WORKDIR /usr/src 20 | CMD ["make", "tests"] 21 | -------------------------------------------------------------------------------- /src/coverity_model.c: -------------------------------------------------------------------------------- 1 | typedef struct __dirstream DIR; 2 | typedef struct __alpm_list_t alpm_list_t; 3 | typedef int (*alpm_list_fn_cmp)(const void *, const void *); 4 | 5 | alpm_list_t *alpm_list_add(alpm_list_t *list, void *data) { 6 | __coverity_escape__(data); 7 | } 8 | 9 | alpm_list_t *alpm_list_add_sorted(alpm_list_t *list, void *data, alpm_list_fn_cmp fn) { 10 | __coverity_escape__(data); 11 | } 12 | 13 | DIR *fdopendir(int fd) { 14 | __coverity_escape__(fd); 15 | } 16 | -------------------------------------------------------------------------------- /src/buffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | struct buffer { 7 | char *data; 8 | size_t len; 9 | size_t buflen; 10 | }; 11 | 12 | int buffer_reserve(struct buffer *buf, size_t reserve); 13 | void buffer_release(struct buffer *buf); 14 | void buffer_clear(struct buffer *buf); 15 | 16 | int buffer_putc(struct buffer *buf, const char c); 17 | ssize_t buffer_printf(struct buffer *buf, const char *fmt, ...) __attribute__((format (printf, 2, 3))); 18 | -------------------------------------------------------------------------------- /src/desc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "package.h" 7 | 8 | struct archive; 9 | 10 | struct desc_parser { 11 | int cs; 12 | enum pkg_entry entry; 13 | size_t pos; 14 | char store[LINE_MAX]; 15 | }; 16 | 17 | void desc_parser_init(struct desc_parser *parser); 18 | ssize_t desc_parser_feed(struct desc_parser *parser, struct pkg *pkg, 19 | char *buf, size_t buf_len); 20 | ssize_t read_desc(struct archive *archive, struct pkg *pkg); 21 | -------------------------------------------------------------------------------- /src/pkginfo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "package.h" 7 | 8 | struct archive; 9 | 10 | struct pkginfo_parser { 11 | int cs; 12 | enum pkg_entry entry; 13 | size_t pos; 14 | char store[LINE_MAX]; 15 | }; 16 | 17 | void pkginfo_parser_init(struct pkginfo_parser *parser); 18 | ssize_t pkginfo_parser_feed(struct pkginfo_parser *parser, struct pkg *pkg, 19 | char *buf, size_t buf_len); 20 | ssize_t read_pkginfo(struct archive *archive, struct pkg *pkg); 21 | -------------------------------------------------------------------------------- /src/repose.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "pkgcache.h" 5 | #include "util.h" 6 | 7 | struct repo { 8 | const char *root; 9 | const char *pool; 10 | int rootfd; 11 | int poolfd; 12 | 13 | char *dbname; 14 | char *filesname; 15 | 16 | bool dirty; 17 | struct pkgcache *cache; 18 | }; 19 | 20 | struct config { 21 | int verbose; 22 | int compression; 23 | bool reflink; 24 | bool sign; 25 | char *arch; 26 | }; 27 | 28 | extern struct config config; 29 | void trace(const char *fmt, ...) _printf_(1, 2); 30 | -------------------------------------------------------------------------------- /src/filters.c: -------------------------------------------------------------------------------- 1 | #include "filters.h" 2 | 3 | #include 4 | #include "package.h" 5 | #include "util.h" 6 | 7 | static bool match_target(struct pkg *pkg, const char *target, const char *fullname) 8 | { 9 | if (streq(target, pkg->filename) || streq(target, pkg->name)) 10 | return true; 11 | return fnmatch(target, fullname, 0) == 0; 12 | } 13 | 14 | bool match_targets(struct pkg *pkg, alpm_list_t *targets) 15 | { 16 | _cleanup_free_ char *fullname = joinstring(pkg->name, "-", pkg->version, NULL); 17 | const alpm_list_t *node; 18 | 19 | for (node = targets; node; node = node->next) { 20 | if (match_target(pkg, node->data, fullname)) 21 | return true; 22 | } 23 | 24 | return false; 25 | } 26 | -------------------------------------------------------------------------------- /src/pkgcache.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include "package.h" 8 | 9 | struct pkgcache { 10 | alpm_list_t **hash_table; 11 | alpm_list_t *list; 12 | size_t buckets; 13 | size_t entries; 14 | size_t limit; 15 | }; 16 | 17 | hash_t sdbm(const char *str); 18 | 19 | struct pkgcache *pkgcache_create(size_t size); 20 | void pkgcache_free(struct pkgcache *cache); 21 | 22 | struct pkgcache *pkgcache_add(struct pkgcache *cache, struct pkg *pkg); 23 | struct pkgcache *pkgcache_replace(struct pkgcache *cache, struct pkg *new, struct pkg *old); 24 | struct pkgcache *pkgcache_add_sorted(struct pkgcache *cache, struct pkg *pkg); 25 | struct pkgcache *pkgcache_remove(struct pkgcache *cache, struct pkg *pkg, struct pkg **data); 26 | 27 | struct pkg *pkgcache_find(struct pkgcache *cache, const char *name); 28 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import cffi 3 | 4 | 5 | CFLAGS = ['-std=c11', '-O0', '-g', '-D_GNU_SOURCE'] 6 | SOURCES = ['../src/desc.c', '../src/pkginfo.c', 7 | '../src/package.c', '../src/pkgcache.c', 8 | '../src/util.c', '../src/base64.c'] 9 | 10 | 11 | def pytest_configure(config): 12 | ffi = cffi.FFI() 13 | with open('tests/_repose.h') as header: 14 | header = ffi.set_source('repose', 15 | header.read(), 16 | include_dirs=['../src'], 17 | libraries=['archive', 'alpm'], 18 | sources=SOURCES, 19 | extra_compile_args=CFLAGS) 20 | 21 | with open('tests/_repose.c') as cdef: 22 | ffi.cdef(cdef.read()) 23 | 24 | ffi.compile(tmpdir='tests') 25 | 26 | 27 | @pytest.fixture 28 | def size_t_max(): 29 | from repose import lib 30 | return lib.SIZE_MAX 31 | -------------------------------------------------------------------------------- /_repose: -------------------------------------------------------------------------------- 1 | #compdef repose 2 | 3 | _arguments -s \ 4 | {-h,--help}'[display this help and exit]' \ 5 | {-V,--version}'[display version]' \ 6 | {-v,--verbose}'[verbose output]' \ 7 | {-f,--files}'[generate complementing files database]' \ 8 | {-l,--list}'[list packages in the repository]' \ 9 | {-d,--drop}'[drop package from database]:packages:_files -g "*.pkg.tar*~*.sig(.,@)"' \ 10 | {-s,--sign}'[create a database signature]' \ 11 | {-r,--root=-}'[repository root directory]:root:_directories' \ 12 | {-p,--pool=-}'[set the pool to find packages in it]:pool:_directories' \ 13 | {-m,--arch=-}'[the primary architecture of the database]:arch:(i686 x86_64)' \ 14 | {-j,--bzip2}'[compress the database with bzip2]' \ 15 | {-J,--xz}'[compress the database with xz]' \ 16 | {-z,--gzip}'[compress the database with gzip]' \ 17 | {-Z,--compress}'[compress the database with LZ]' \ 18 | '--reflink[use reflinks instead of symlinks]' \ 19 | '--rebuild[force rebuild the repo]' \ 20 | '1:database:_files -g "*.db*~*.sig(.,@)(\:r)"' \ 21 | '*::packages:_files -g "*.pkg.tar*~*.sig(.,@)"' 22 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import errno 3 | from repose import ffi, lib 4 | 5 | 6 | @pytest.mark.parametrize('input', [ 7 | (b'foo ', b'bar ', b'baz'), 8 | (b'Hello ', b'', b'World', b'!') 9 | ]) 10 | def test_joinstring(input): 11 | args = [ffi.new('char[]', x) for x in input] 12 | args.append(ffi.NULL) 13 | 14 | result = lib.joinstring(*args) 15 | assert ffi.string(result) == b''.join(input) 16 | 17 | 18 | @pytest.mark.parametrize('input', [ 19 | b'Hello World', 20 | b'Hello World ', 21 | b' Hello World', 22 | b'\tHello World ' 23 | ]) 24 | def test_strstrip(input): 25 | arg = ffi.new('char[]', input) 26 | 27 | result = lib.strstrip(arg) 28 | assert ffi.string(result) == input.strip() 29 | 30 | 31 | def test_parse_size(): 32 | arg = ffi.new('char[]', b'832421') 33 | out = ffi.new('size_t *') 34 | 35 | assert lib.parse_size(arg, out) == 0 36 | assert out[0] == 832421 37 | 38 | 39 | def test_parse_size_ERANGE(size_t_max): 40 | input = str(size_t_max + 1).encode() 41 | arg = ffi.new('char[]', input) 42 | out = ffi.new('size_t *') 43 | 44 | assert lib.parse_size(arg, out) == -1 45 | assert ffi.errno == errno.ERANGE 46 | assert out[0] == 0 47 | 48 | 49 | def test_parse_time(): 50 | arg = ffi.new('char[]', b'1448690669') 51 | out = ffi.new('time_t *') 52 | 53 | assert lib.parse_time(arg, out) == 0 54 | assert out[0] == 1448690669 55 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DOT = dot 2 | RAGEL = ragel 3 | RAGEL_FLAGS := -G2 4 | 5 | COMPILE.rl = $(RAGEL) $(RAGEL_FLAGS) 6 | COMPILE.dot = $(DOT) $(DOT_FLAGS) 7 | 8 | %.c: %.rl 9 | $(COMPILE.rl) -C $(OUTPUT_OPTION) $< 10 | 11 | %.dot: %.rl 12 | $(COMPILE.rl) -Vp $(OUTPUT_OPTION) $< 13 | 14 | %.png: %.dot 15 | $(COMPILE.dot) -Tpng $(OUTPUT_OPTION) $< 16 | 17 | VERSION=7.1 18 | GIT_DESC=$(shell test -d .git && git describe 2>/dev/null) 19 | 20 | ifneq "$(GIT_DESC)" "" 21 | VERSION=$(GIT_DESC) 22 | endif 23 | 24 | CFLAGS := -std=c18 -g \ 25 | -Wall -Wextra -pedantic \ 26 | -Wshadow -Wpointer-arith -Wcast-qual -Wstrict-prototypes -Wmissing-prototypes \ 27 | -Wno-missing-field-initializers \ 28 | -D_GNU_SOURCE \ 29 | -D_FILE_OFFSET_BITS=64 \ 30 | -DREPOSE_VERSION=\"$(VERSION)\" \ 31 | $(CFLAGS) 32 | 33 | PYTEST_FLAGS := --forked $(PYTEST_FLAGS) 34 | 35 | VPATH = src 36 | LDLIBS = -larchive -lalpm -lgpgme -lcrypto 37 | PREFIX = /usr 38 | 39 | all: repose 40 | desc.o: $(VPATH)/desc.c 41 | desc.dot: $(VPATH)/desc.rl 42 | pkginfo.o: $(VPATH)/pkginfo.c 43 | pkginfo.dot: $(VPATH)/pkginfo.rl 44 | 45 | repose: repose.o database.o package.o util.o filecache.o \ 46 | pkgcache.o buffer.o base64.o filters.o signing.o \ 47 | pkginfo.o desc.o 48 | 49 | tests: desc.c pkginfo.c 50 | pytest tests $(PYTEST_FLAGS) 51 | 52 | graphs: desc.png pkginfo.png 53 | 54 | install: repose 55 | install -Dm755 repose $(DESTDIR)$(PREFIX)/bin/repose 56 | install -Dm644 _repose $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_repose 57 | install -Dm644 man/repose.1 $(DESTDIR)$(PREFIX)/share/man/man1/repose.1 58 | 59 | clean: 60 | $(RM) repose $(VPATH)/desc.c $(VPATH)/pkginfo.c *.o *.dot *.png 61 | 62 | .PHONY: tests clean graph install uninstall 63 | -------------------------------------------------------------------------------- /src/package.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | typedef uint64_t hash_t; 9 | 10 | enum pkg_entry { 11 | PKG_FILENAME, 12 | PKG_PKGNAME, 13 | PKG_PKGBASE, 14 | PKG_VERSION, 15 | PKG_DESCRIPTION, 16 | PKG_GROUPS, 17 | PKG_CSIZE, 18 | PKG_ISIZE, 19 | PKG_SHA256SUM, 20 | PKG_PGPSIG, 21 | PKG_URL, 22 | PKG_LICENSE, 23 | PKG_ARCH, 24 | PKG_BUILDDATE, 25 | PKG_PACKAGER, 26 | PKG_REPLACES, 27 | PKG_DEPENDS, 28 | PKG_CONFLICTS, 29 | PKG_PROVIDES, 30 | PKG_OPTDEPENDS, 31 | PKG_MAKEDEPENDS, 32 | PKG_CHECKDEPENDS, 33 | PKG_FILES, 34 | PKG_BACKUP, 35 | PKG_DELTAS, 36 | PKG_MAKEPKGOPT 37 | }; 38 | 39 | typedef struct pkg { 40 | hash_t hash; 41 | char *filename; 42 | char *name; 43 | char *base; 44 | char *version; 45 | char *desc; 46 | char *url; 47 | char *packager; 48 | char *sha256sum; 49 | char *base64sig; 50 | char *arch; 51 | size_t size; 52 | size_t isize; 53 | time_t builddate; 54 | time_t mtime; 55 | 56 | alpm_list_t *groups; 57 | alpm_list_t *licenses; 58 | alpm_list_t *replaces; 59 | alpm_list_t *depends; 60 | alpm_list_t *conflicts; 61 | alpm_list_t *provides; 62 | alpm_list_t *optdepends; 63 | alpm_list_t *makedepends; 64 | alpm_list_t *checkdepends; 65 | alpm_list_t *files; 66 | alpm_list_t *deltas; 67 | } pkg_t; 68 | 69 | int load_package(pkg_t *pkg, int fd); 70 | int load_package_signature(struct pkg *pkg, int fd); 71 | int load_package_files(pkg_t *pkg, int fd); 72 | void package_free(pkg_t *pkg); 73 | void package_set(pkg_t *pkg, enum pkg_entry type, const char *entry, size_t len); 74 | -------------------------------------------------------------------------------- /tests/_repose.c: -------------------------------------------------------------------------------- 1 | #define SIZE_MAX ... 2 | 3 | typedef int... time_t; 4 | 5 | typedef struct __alpm_list_t { 6 | void *data; 7 | struct __alpm_list_t *next; 8 | ...; 9 | } alpm_list_t; 10 | 11 | struct pkg { 12 | char *filename; 13 | char *name; 14 | char *base; 15 | char *version; 16 | char *desc; 17 | char *url; 18 | char *packager; 19 | char *sha256sum; 20 | char *base64sig; 21 | char *arch; 22 | size_t size; 23 | size_t isize; 24 | time_t builddate; 25 | time_t mtime; 26 | 27 | alpm_list_t *groups; 28 | alpm_list_t *licenses; 29 | alpm_list_t *replaces; 30 | alpm_list_t *depends; 31 | alpm_list_t *conflicts; 32 | alpm_list_t *provides; 33 | alpm_list_t *optdepends; 34 | alpm_list_t *makedepends; 35 | alpm_list_t *checkdepends; 36 | alpm_list_t *files; 37 | ...; 38 | }; 39 | 40 | enum pkg_entry { 41 | PKG_FILENAME, 42 | PKG_PKGNAME, 43 | PKG_PKGBASE, 44 | PKG_VERSION, 45 | PKG_DESCRIPTION, 46 | PKG_GROUPS, 47 | PKG_CSIZE, 48 | PKG_ISIZE, 49 | PKG_SHA256SUM, 50 | PKG_PGPSIG, 51 | PKG_URL, 52 | PKG_LICENSE, 53 | PKG_ARCH, 54 | PKG_BUILDDATE, 55 | PKG_PACKAGER, 56 | PKG_REPLACES, 57 | PKG_DEPENDS, 58 | PKG_CONFLICTS, 59 | PKG_PROVIDES, 60 | PKG_OPTDEPENDS, 61 | PKG_MAKEDEPENDS, 62 | PKG_CHECKDEPENDS, 63 | PKG_FILES, 64 | PKG_BACKUP, 65 | PKG_DELTAS, 66 | PKG_MAKEPKGOPT 67 | }; 68 | 69 | // desc 70 | struct desc_parser { 71 | enum pkg_entry entry; 72 | ...; 73 | }; 74 | 75 | void desc_parser_init(struct desc_parser *parser); 76 | ssize_t desc_parser_feed(struct desc_parser *parser, struct pkg *pkg, 77 | char *buf, size_t buf_len); 78 | 79 | // pkginfo 80 | struct pkginfo_parser { 81 | enum pkg_entry entry; 82 | ...; 83 | }; 84 | 85 | void pkginfo_parser_init(struct pkginfo_parser *parser); 86 | ssize_t pkginfo_parser_feed(struct pkginfo_parser *parser, struct pkg *pkg, 87 | char *buf, size_t buf_len); 88 | 89 | // utils 90 | char *joinstring(const char *root, ...); 91 | int parse_size(const char *str, size_t *out); 92 | int parse_time(const char *size, time_t *out); 93 | char *strstrip(char *s); 94 | -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define _unlikely_(x) __builtin_expect(!!(x), 1) 13 | #define _unused_ __attribute__((unused)) 14 | #define _noreturn_ __attribute__((noreturn)) 15 | #define _cleanup_(x) __attribute__((cleanup(x))) 16 | #define _printf_(a,b) __attribute__((format (printf, a, b))) 17 | #define _sentinel_ __attribute__((sentinel)) 18 | #define _cleanup_free_ _cleanup_(freep) 19 | #define _cleanup_fclose_ _cleanup_(fclosep) 20 | #define _cleanup_closedir_ _cleanup_(closedirp) 21 | #define _cleanup_close_ _cleanup_(closep) 22 | 23 | /* XXX: clang does not have generic builtin */ 24 | #if __clang__ 25 | #define __builtin_add_overflow(a, b, r) _Generic((a), \ 26 | int: __builtin_sadd_overflow, \ 27 | long int: __builtin_saddl_overflow, \ 28 | long long: __builtin_saddll_overflow, \ 29 | unsigned int: __builtin_uadd_overflow, \ 30 | unsigned long int: __builtin_uaddl_overflow, \ 31 | unsigned long long: __builtin_uaddll_overflow)(a, b, r) 32 | #endif 33 | 34 | struct archive; 35 | 36 | static inline void freep(void *p) { free(*(void **)p); } 37 | static inline void fclosep(FILE **fp) { if (*fp) fclose(*fp); } 38 | static inline void closedirp(DIR **dp) { if (*dp) closedir(*dp); } 39 | static inline void closep(int *fd) { if (*fd >= 0) close(*fd); } 40 | 41 | static inline bool streq(const char *s1, const char *s2) { return strcmp(s1, s2) == 0; } 42 | static inline bool strneq(const char *s1, const char *s2, size_t len) { return strncmp(s1, s2, len) == 0; } 43 | 44 | void check_posix(intmax_t rc, const char *fmt, ...) _printf_(2, 3); 45 | void check_null(const void *ptr, const char *fmt, ...) _printf_(2, 3); 46 | 47 | FILE *fopenat(int dirfd, const char *path, const char *mode); 48 | 49 | char *joinstring(const char *root, ...) _sentinel_; 50 | 51 | int parse_size(const char *str, size_t *out); 52 | int parse_time(const char *str, time_t *out); 53 | 54 | char *strstrip(char *s); 55 | char *hex_representation(unsigned char *bytes, size_t size); 56 | 57 | int archive_read(struct archive *archive, char **buf, size_t *buf_len); 58 | -------------------------------------------------------------------------------- /src/buffer.c: -------------------------------------------------------------------------------- 1 | #include "buffer.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "util.h" 9 | 10 | static inline size_t next_power(size_t x) 11 | { 12 | const size_t zeros = __builtin_clzl(x - 1); 13 | if (!zeros) 14 | return SIZE_MAX; 15 | return 1UL << (sizeof(x) * 8 - zeros); 16 | } 17 | 18 | static inline int addsz(size_t a, size_t b, size_t *r) 19 | { 20 | errno = 0; 21 | if (_unlikely_(__builtin_add_overflow(a, b, r))) 22 | errno = ERANGE; 23 | return -errno; 24 | } 25 | 26 | static int buffer_extendby(struct buffer *buf, size_t extby) 27 | { 28 | size_t newlen = 64; 29 | if (buf->buflen || extby > newlen) { 30 | if (addsz(buf->len, extby, &newlen) < 0) 31 | return -errno; 32 | } 33 | 34 | if (_unlikely_(!buf->data) || newlen > buf->buflen) { 35 | newlen = next_power(newlen); 36 | 37 | char *data = realloc(buf->data, newlen); 38 | if (!data) 39 | return -errno; 40 | 41 | buf->buflen = newlen; 42 | buf->data = data; 43 | } 44 | 45 | buf->data[buf->len] = 0; 46 | return 0; 47 | } 48 | 49 | int buffer_reserve(struct buffer *buf, size_t reserve) 50 | { 51 | if (buffer_extendby(buf, reserve) < 0) 52 | return -errno; 53 | return 0; 54 | } 55 | 56 | void buffer_release(struct buffer *buf) 57 | { 58 | free(buf->data); 59 | *buf = (struct buffer){0}; 60 | } 61 | 62 | void buffer_clear(struct buffer *buf) 63 | { 64 | buf->len = 0; 65 | if (buf->data) 66 | buf->data[buf->len] = '\0'; 67 | } 68 | 69 | int buffer_putc(struct buffer *buf, const char c) 70 | { 71 | if (buffer_extendby(buf, 2) < 0) 72 | return -errno; 73 | 74 | buf->data[buf->len++] = c; 75 | buf->data[buf->len] = '\0'; 76 | return 0; 77 | } 78 | 79 | ssize_t buffer_printf(struct buffer *buf, const char *fmt, ...) 80 | { 81 | size_t len = buf->buflen - buf->len; 82 | 83 | if (!buf->data && buffer_extendby(buf, 0) < 0) 84 | return -errno; 85 | 86 | va_list ap; 87 | va_start(ap, fmt); 88 | size_t nbytes_w = vsnprintf(&buf->data[buf->len], len, fmt, ap); 89 | va_end(ap); 90 | 91 | if (nbytes_w >= len) { 92 | if (buffer_extendby(buf, nbytes_w + 1) < 0) 93 | return -errno; 94 | 95 | va_start(ap, fmt); 96 | nbytes_w = vsnprintf(&buf->data[buf->len], nbytes_w + 1, fmt, ap); 97 | va_end(ap); 98 | } 99 | 100 | buf->len += nbytes_w; 101 | return nbytes_w; 102 | } 103 | -------------------------------------------------------------------------------- /man/repose.1: -------------------------------------------------------------------------------- 1 | .TH repose "1" "July 23" "repose" "User Commands" 2 | .SH NAME 3 | repose \- an Archlinux repository compiler 4 | .SH SYNOPSIS 5 | \fBrepose\fP [options] [pkgs|deltas ...] 6 | .SH DESCRIPTION 7 | \fBrepose\fP create and manipulates Archlinux repositories, automating 8 | their generation from a directory of packages. It scans the filesystem 9 | packages and for changes in those packages and compiles them into 10 | databases \fBpacman\fR understands. 11 | .SH OPTIONS 12 | .PP 13 | .IP "\fB\-h\fR, \fB\-\-help\fR" 14 | Display help message. 15 | .IP "\fB\-V\fR, \fB\-\-version\fR" 16 | Display version information. 17 | .IP "\fB\-v\fR, \fB\-\-verbose\fR" 18 | Produce verbose output. When scanning for changes, \fBrepose\fP will 19 | list every package that'll be added, dropped, or updated and will also 20 | list each database that's serialized to disk. 21 | .IP "\fB\-f\fR, \fB\-\-files\fR" 22 | In addition to building the repository database, build a files database 23 | usable by \fBpkgfile\fR and keep it in sync with the main database. If 24 | this flag is not provided by a files database is found, this flag is 25 | implied. 26 | .IP "\fB\-l, \fB\-\-list\fR" 27 | List all packages and their current versions. 28 | .IP "\fB\-d, \fB\-\-drop\fR" 29 | Instead of adding the specified set of packages, instead drop them from the 30 | database. 31 | .IP "\fB\-s\fR, \fB\-\-sign\fR" 32 | Create a detached PGP signature for the database. 33 | .IP "\fB\-r\fR \fIPATH\fR, \fB\-\-root\fR=\fIPATH\fR" 34 | Set the root of the repository where the database files will live. If 35 | the pool directory different from the root directory, maintain symlinks 36 | between the two so that the root directory also contains a link to all 37 | packages referenced by the repository. The default value if it isn't 38 | overridden is the current working directory. 39 | .IP "\fB\-p\fR \fIPATH\fR, \fB\-\-pool\fR=\fIPATH\fR" 40 | Set the pool for the repository. The pool is where \fBrepose\fR will 41 | scan for new, changed, or missing packages to update the repository 42 | database. The default value if it isn't overridden is the current 43 | working directory. 44 | .IP "\fB\-m\fR \fIARCH\fR, \fB\-\-arch\fR=\fIARCH\fR" 45 | Set the primary architecture of the database. The database will only 46 | contain packages found for the architecture set in \fIARCH\fR or marked 47 | as 'any'. If this argument is not provided, the machines architecture is 48 | assumed. 49 | .IP "\fB\-j\fR, \fB\-\-bzip2\fR" 50 | Compress the resulting database with bzip2(1). 51 | .IP "\fB\-J\fR, \fB\-\-xz\fR" 52 | Compress the resulting database with xz(1). 53 | .IP "\fB\-z\fR, \fB\-\-gzip\fR" 54 | Compress the resulting database with gzip(1). 55 | .IP "\fB\-Z\fR, \fB\-\-compress\fR" 56 | Compress the resulting database with compress(1). 57 | .IP "\fB\-\-reflink\fR" 58 | Make repose create reflinks instead of symlinks when compiling 59 | a repository. 60 | .IP "\fB\-\-rebuild\fR" 61 | Rather than attempting to update the existing database, rebuild it. 62 | .SH AUTHORS 63 | .nf 64 | Simon Gomizelj 65 | .fi 66 | -------------------------------------------------------------------------------- /tests/test_pkginfo.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from datetime import datetime 3 | from repose import lib, ffi 4 | from wrappers import Parser, ParserError, Package 5 | 6 | 7 | REPOSE_PKGINFO = '''# Generated by makepkg 5.0.1 8 | # using fakeroot version 1.21 9 | # Sun Oct 30 16:09:47 UTC 2016 10 | pkgname = repose-git 11 | pkgver = 6.2.10.gbab93f3-1 12 | pkgdesc = A archlinux repo building tool 13 | url = http://github.com/vodik/repose 14 | builddate = 1477843787 15 | packager = Simon Gomizelj 16 | size = 63488 17 | arch = x86_64 18 | license = GPL 19 | conflict = repose 20 | provides = repose 21 | depend = pacman 22 | depend = libarchive 23 | depend = gnupg 24 | makedepend = git 25 | makedepend = ragel 26 | ''' 27 | 28 | 29 | class PKGINFOParser(Parser): 30 | def init_parser(self): 31 | parser = ffi.new('struct pkginfo_parser*') 32 | lib.pkginfo_parser_init(parser) 33 | return parser 34 | 35 | def feed_parser(self, parser, pkg, data): 36 | return lib.pkginfo_parser_feed(parser, pkg, data, len(data)) 37 | 38 | 39 | @pytest.fixture 40 | def parser(): 41 | return PKGINFOParser() 42 | 43 | 44 | @pytest.fixture 45 | def pkg(): 46 | return Package() 47 | 48 | 49 | def test_parse_pkginfo(pkg, parser): 50 | parser.feed(pkg, REPOSE_PKGINFO) 51 | assert parser.entry == lib.PKG_MAKEDEPENDS 52 | 53 | assert pkg.base is None 54 | assert pkg.base64sig is None 55 | assert pkg.desc == 'A archlinux repo building tool' 56 | assert pkg.isize == 63488 57 | assert pkg.url == 'http://github.com/vodik/repose' 58 | assert pkg.arch == 'x86_64' 59 | assert pkg.builddate == "Oct 30, 2016, 16:09:47" 60 | assert pkg.packager == 'Simon Gomizelj ' 61 | assert pkg.licenses == ['GPL'] 62 | 63 | 64 | @pytest.mark.parametrize('chunksize', [1, 10, 100]) 65 | def test_parse_chunked(pkg, parser, chunksize): 66 | def chunk(data, size): 67 | return (data[i:i+size] for i in range(0, len(data), size)) 68 | 69 | for chunk in chunk(REPOSE_PKGINFO, chunksize): 70 | parser.feed(pkg, chunk) 71 | 72 | assert pkg.base is None 73 | assert pkg.base64sig is None 74 | assert pkg.desc == 'A archlinux repo building tool' 75 | assert pkg.isize == 63488 76 | assert pkg.url == 'http://github.com/vodik/repose' 77 | assert pkg.arch == 'x86_64' 78 | assert pkg.builddate == "Oct 30, 2016, 16:09:47" 79 | assert pkg.packager == 'Simon Gomizelj ' 80 | assert pkg.licenses == ['GPL'] 81 | 82 | 83 | def test_pkginfo_with_backup(pkg, parser): 84 | parser.feed(pkg, '''pkgname = example 85 | backup = etc/example/conf 86 | ''') 87 | 88 | 89 | def test_invalid_pkginfo_entry(pkg, parser): 90 | with pytest.raises(ParserError): 91 | parser.feed(pkg, '''pkgname = invalid_pkginfo_entry 92 | badentry = etc/example/conf 93 | ''') 94 | 95 | 96 | def test_empty_pkginfo_entry(pkg, parser): 97 | parser.feed(pkg, '''pkgname = empty_pkginfo_entry 98 | url = 99 | ''') 100 | 101 | 102 | def test_makepkgopt(pkg, parser): 103 | parser.feed(pkg, '''pkgname = ttf-ms-win10-sea 104 | makepkgopt = strip 105 | makepkgopt = !debug 106 | ''') 107 | -------------------------------------------------------------------------------- /src/filecache.c: -------------------------------------------------------------------------------- 1 | #include "filecache.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "package.h" 14 | #include "pkgcache.h" 15 | #include "filters.h" 16 | #include "util.h" 17 | 18 | static inline bool is_file(int d_type) 19 | { 20 | return d_type == DT_REG || d_type == DT_UNKNOWN; 21 | } 22 | 23 | static inline struct pkgcache *filecache_add(struct pkgcache *cache, struct pkg *pkg) 24 | { 25 | struct pkg *old = pkgcache_find(cache, pkg->name); 26 | if (!old) { 27 | return pkgcache_add(cache, pkg); 28 | } 29 | 30 | int vercmp = alpm_pkg_vercmp(pkg->version, old->version); 31 | if (vercmp == 0 || vercmp == 1) { 32 | return pkgcache_replace(cache, pkg, old); 33 | } 34 | 35 | return cache; 36 | } 37 | 38 | static size_t get_filecache_size(DIR *dirp) 39 | { 40 | struct dirent *dp; 41 | size_t size = 0; 42 | 43 | for (dp = readdir(dirp); dp; dp = readdir(dirp)) { 44 | if (is_file(dp->d_type)) 45 | ++size; 46 | } 47 | 48 | rewinddir(dirp); 49 | return size; 50 | } 51 | 52 | static struct pkg *load_from_file(int dirfd, const char *filename) 53 | { 54 | _cleanup_close_ int pkgfd = openat(dirfd, filename, O_RDONLY); 55 | check_posix(pkgfd, "failed to open %s", filename); 56 | 57 | struct pkg *pkg = malloc(sizeof(pkg_t)); 58 | *pkg = (struct pkg){ .filename = strdup(filename) }; 59 | 60 | if (load_package(pkg, pkgfd) < 0) { 61 | package_free(pkg); 62 | return NULL; 63 | } 64 | 65 | if (load_package_signature(pkg, dirfd) < 0 && errno != ENOENT) { 66 | package_free(pkg); 67 | return NULL; 68 | } 69 | 70 | return pkg; 71 | } 72 | 73 | static struct pkgcache *scan_for_targets(struct pkgcache *cache, int dirfd, DIR *dirp, 74 | alpm_list_t *targets, const char *arch) 75 | { 76 | const struct dirent *dp; 77 | 78 | for (dp = readdir(dirp); dp; dp = readdir(dirp)) { 79 | if (!is_file(dp->d_type)) 80 | continue; 81 | 82 | struct pkg *pkg = load_from_file(dirfd, dp->d_name); 83 | if (!pkg) 84 | continue; 85 | 86 | if (targets && !match_targets(pkg, targets)) { 87 | package_free(pkg); 88 | continue; 89 | } 90 | 91 | if (arch && !match_arch(pkg, arch)) { 92 | package_free(pkg); 93 | continue; 94 | } 95 | 96 | cache = filecache_add(cache, pkg); 97 | } 98 | 99 | return cache; 100 | } 101 | 102 | struct pkgcache *get_filecache(int dirfd, alpm_list_t *targets, const char *arch) 103 | { 104 | int dupfd = dup(dirfd); 105 | check_posix(dupfd, "failed to duplicate fd"); 106 | check_posix(lseek(dupfd, 0, SEEK_SET), "failed to lseek"); 107 | 108 | _cleanup_closedir_ DIR *dirp = fdopendir(dupfd); 109 | check_null(dirp, "fdopendir failed"); 110 | 111 | size_t size = get_filecache_size(dirp); 112 | struct pkgcache *cache = pkgcache_create(size); 113 | 114 | return scan_for_targets(cache, dirfd, dirp, targets, arch); 115 | } 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## repose 2 | 3 | 4 | Travis CI Status 6 | 7 | 8 | Coverity Scan Build Status 10 | 11 | 12 | Owning more than one Archlinux machine, I operate my own repository to 13 | distribute customized and/or extra packages between the various 14 | machines. `repo-add`, the provided tool for repository management, is 15 | frustratingly limited. Updating the repository after building a series 16 | of packages quickly turned into a slow monstrous bash script: either I 17 | had to have rather complex logic to figure out which packages are new, 18 | or I had to do the expensive operation of rebuilding the repository each 19 | time. Surly, though, this was something that could be automated. 20 | 21 | `repose` is an Archlinux repository compiler. 22 | 23 | Generally, it operates by building up two package caches: one that 24 | represents the contents of the database and another that represents the 25 | various packages sitting in the root directory of the database. 26 | Updating, then, is simply a sync operation operation between the two. 27 | 28 | To sync, it takes advantage of several rules of Archlinux repositories 29 | to automate as much logic as possible: 30 | 31 | 1. Repositories typically only hold one version of a package (and we're 32 | interesting in the newest version). 33 | 2. Repositories typically only hold only one architecture. 34 | 3. Repositories and packages are expected to be in the same directory. 35 | 36 | ### Updating/Removing 37 | 38 | Most simplistically: 39 | 40 | repose -z foo 41 | 42 | 1. Parse the contents of `foo.db` if it exists. 43 | 2. If we find a new package in the database's folder, add it to the 44 | database. 45 | 3. If we can't find a corresponding package to a database entry, remove 46 | it from the database. 47 | 4. Write out an updated database. 48 | 49 | To explicitly remove a package: 50 | 51 | repose -zd foo [pkgs] 52 | 53 | Removes the specified packages from the database. 54 | 55 | To generate a complementary `foo.files` file, add the `-f` flags 56 | 57 | repose -zf foo 58 | 59 | ### Globbing 60 | 61 | Its possible to use globbing. `repose` uses the following logic for 62 | finding/filtering packages: 63 | 64 | 1. Does it match the package's filename 65 | 2. Does it match the package's name 66 | 3. Does it glob pkgname-pkgver 67 | 68 | This allows for operations like: 69 | 70 | Add latest detected version `systemd` to a repo: 71 | 72 | repose foo systemd 73 | 74 | Add a specific version of `systemd` to a repo: 75 | 76 | repose foo systemd-209-1 77 | 78 | Drop all git packages from the repo: 79 | 80 | repose -zd foo '*-git-*' 81 | 82 | ``` 83 | __ 84 | '. \ 85 | '- \ 86 | / /_ .---. 87 | / | \\,.\/--.// ) 88 | | \// )/ / 89 | \ ' ^ ^ / )____.----.. 6 90 | '.____. .___/ \._) 91 | .\/. ) 92 | '\ / 93 | _/ \/ ). ) ( 94 | /# .! | /\ / 95 | \ C// # /'-----''/ # / 96 | . 'C/ | | | | |mrf , 97 | \), .. .'OOO-'. ..'OOO'OOO-'. ..\(, 98 | ``` 99 | -------------------------------------------------------------------------------- /src/pkginfo.rl: -------------------------------------------------------------------------------- 1 | #include "pkginfo.h" 2 | 3 | #include 4 | #include "package.h" 5 | #include "util.h" 6 | 7 | %%{ 8 | machine pkginfo; 9 | 10 | action store { 11 | parser->store[parser->pos++] = fc; 12 | if (parser->pos == LINE_MAX) { 13 | errx(1, "desc line too long"); 14 | } 15 | } 16 | 17 | action emit { 18 | if (parser->pos) { 19 | const char *entry = parser->store; 20 | const size_t entry_len = parser->pos; 21 | parser->store[parser->pos] = 0; 22 | parser->pos = 0; 23 | 24 | package_set(pkg, parser->entry, entry, entry_len); 25 | } 26 | } 27 | 28 | header = 'pkgname' %{ parser->entry = PKG_PKGNAME; } 29 | | 'pkgbase' %{ parser->entry = PKG_PKGBASE; } 30 | | 'pkgver' %{ parser->entry = PKG_VERSION; } 31 | | 'pkgdesc' %{ parser->entry = PKG_DESCRIPTION; } 32 | | 'url' %{ parser->entry = PKG_URL; } 33 | | 'builddate' %{ parser->entry = PKG_BUILDDATE; } 34 | | 'packager' %{ parser->entry = PKG_PACKAGER; } 35 | | 'size' %{ parser->entry = PKG_ISIZE; } 36 | | 'arch' %{ parser->entry = PKG_ARCH; } 37 | | 'group' %{ parser->entry = PKG_GROUPS; } 38 | | 'license' %{ parser->entry = PKG_LICENSE; } 39 | | 'replaces' %{ parser->entry = PKG_REPLACES; } 40 | | 'depend' %{ parser->entry = PKG_DEPENDS; } 41 | | 'conflict' %{ parser->entry = PKG_CONFLICTS; } 42 | | 'provides' %{ parser->entry = PKG_PROVIDES; } 43 | | 'optdepend' %{ parser->entry = PKG_OPTDEPENDS; } 44 | | 'makedepend' %{ parser->entry = PKG_MAKEDEPENDS; } 45 | | 'checkdepend' %{ parser->entry = PKG_CHECKDEPENDS; } 46 | | 'backup' %{ parser->entry = PKG_BACKUP; } 47 | | 'makepkgopt' %{ parser->entry = PKG_MAKEPKGOPT; }; 48 | 49 | entry = header ' = ' [^\n]* @store %emit '\n'; 50 | comment = '#' [^\n]* '\n'; 51 | 52 | main := ( entry | comment )*; 53 | }%% 54 | 55 | %%write data nofinal; 56 | 57 | void pkginfo_parser_init(struct pkginfo_parser *parser) 58 | { 59 | *parser = (struct pkginfo_parser){0}; 60 | %%access parser->; 61 | %%write init; 62 | } 63 | 64 | ssize_t pkginfo_parser_feed(struct pkginfo_parser *parser, struct pkg *pkg, 65 | char *buf, size_t buf_len) 66 | { 67 | char *p = buf; 68 | char *pe = p + buf_len; 69 | 70 | %%access parser->; 71 | %%write exec; 72 | 73 | (void)pkginfo_en_main; 74 | if (parser->cs == pkginfo_error) 75 | return -1; 76 | 77 | return buf_len; 78 | } 79 | 80 | ssize_t read_pkginfo(struct archive *archive, struct pkg *pkg) 81 | { 82 | char *buf; 83 | ssize_t nbytes_r = 0; 84 | struct pkginfo_parser parser; 85 | pkginfo_parser_init(&parser); 86 | 87 | for (;;) { 88 | size_t bufsize; 89 | archive_read(archive, &buf, &bufsize); 90 | 91 | ssize_t result = pkginfo_parser_feed(&parser, pkg, buf, bufsize); 92 | if (result < 0) { 93 | return result; 94 | } else { 95 | nbytes_r += result; 96 | } 97 | 98 | break; 99 | } 100 | 101 | return nbytes_r; 102 | } 103 | -------------------------------------------------------------------------------- /src/base64.c: -------------------------------------------------------------------------------- 1 | #include "base64.h" 2 | #include 3 | #include 4 | 5 | static const char encoding_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef" 6 | "ghijklmnopqrstuvwxyz0123456789+/"; 7 | 8 | static uint8_t decoding_table[256] = { 9 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 10 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 11 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 12 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 13 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 14 | 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x3F, 15 | 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 16 | 0x3C, 0x3D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 17 | 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 18 | 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x1E, 19 | 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 20 | 0x17, 0x18, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 21 | 0x00, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 22 | 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 23 | 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 24 | 0x31, 0x32, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 25 | }; 26 | 27 | char *base64_encode(const unsigned char *data, size_t data_length, 28 | size_t *output_length) 29 | { 30 | const size_t length = 4 * ((data_length + 2) / 3); 31 | char *encoded_data = malloc(length + 1), *p = encoded_data; 32 | if (encoded_data == NULL) 33 | return NULL; 34 | 35 | for (size_t i = 0; i < data_length; i += 3) { 36 | const uint8_t lookahead[2] = { 37 | i + 1 < data_length, 38 | i + 2 < data_length 39 | }; 40 | 41 | const uint8_t octets[3] = { 42 | data[i], 43 | lookahead[0] ? data[i + 1] : 0, 44 | lookahead[1] ? data[i + 2] : 0 45 | }; 46 | 47 | const uint32_t bitpattern = (octets[0] << 16) + (octets[1] << 8) + octets[2]; 48 | *p++ = encoding_table[(bitpattern >> 18) & 0x3F]; 49 | *p++ = encoding_table[(bitpattern >> 12) & 0x3F]; 50 | *p++ = lookahead[0] ? encoding_table[(bitpattern >> 6) & 0x3F] : '='; 51 | *p++ = lookahead[1] ? encoding_table[bitpattern & 0x3F] : '='; 52 | } 53 | 54 | *p = '\0'; 55 | if (output_length) 56 | *output_length = length; 57 | return encoded_data; 58 | } 59 | 60 | char *base64_decode(const unsigned char *data, size_t data_length, 61 | size_t *output_length) 62 | { 63 | const size_t length = 3 * ((data_length + 2) / 4); 64 | char *decoded_data = malloc(length + 1), *p = decoded_data; 65 | if (decoded_data == NULL) 66 | return NULL; 67 | 68 | for (size_t i = 0; i < data_length; i += 4) { 69 | const uint8_t lookahead[3] = { 70 | i + 1 < data_length, 71 | i + 2 < data_length, 72 | i + 3 < data_length 73 | }; 74 | 75 | const uint8_t octets[4] = { 76 | decoding_table[data[i]], 77 | lookahead[0] ? decoding_table[data[i + 1]] : 0, 78 | lookahead[1] ? decoding_table[data[i + 2]] : 0, 79 | lookahead[1] ? decoding_table[data[i + 3]] : 0 80 | }; 81 | 82 | *p++ = (octets[0] << 2) + ((octets[1] & 0x30) >> 4); 83 | *p++ = ((octets[1] & 0xf) << 4) + ((octets[2] & 0x3c) >> 2); 84 | *p++ = ((octets[2] & 0x3) << 6) + octets[3]; 85 | } 86 | 87 | *p = '\0'; 88 | if (output_length) 89 | *output_length = length; 90 | return decoded_data; 91 | } 92 | -------------------------------------------------------------------------------- /tests/test_desc.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from datetime import datetime 3 | from repose import lib, ffi 4 | from wrappers import Parser, Package 5 | 6 | 7 | REPOSE_DESC = '''%FILENAME% 8 | repose-git-5.19.g82c3d4a-1-x86_64.pkg.tar.xz 9 | 10 | %NAME% 11 | repose-git 12 | 13 | %VERSION% 14 | 5.19.g82c3d4a-1 15 | 16 | %DESC% 17 | A archlinux repo building tool 18 | 19 | %CSIZE% 20 | 18804 21 | 22 | %ISIZE% 23 | 51200 24 | 25 | %SHA256SUM% 26 | 4045b3b24bae8a2d811323e5dd3727345e9e6f81788c65d5935d07b2ee06b505 27 | 28 | %URL% 29 | http://github.com/vodik/repose 30 | 31 | %LICENSE% 32 | GPL 33 | 34 | %ARCH% 35 | x86_64 36 | 37 | %BUILDDATE% 38 | 1448690669 39 | 40 | %PACKAGER% 41 | Simon Gomizelj 42 | ''' 43 | 44 | 45 | REPOSE_DEPENDS = '''%DEPENDS% 46 | pacman 47 | libarchive 48 | gnupg 49 | 50 | %CONFLICTS% 51 | repose 52 | 53 | %PROVIDES% 54 | repose 55 | 56 | %MAKEDEPENDS% 57 | git 58 | ''' 59 | 60 | 61 | class DescParser(Parser): 62 | def init_parser(self): 63 | parser = ffi.new('struct desc_parser*') 64 | lib.desc_parser_init(parser) 65 | return parser 66 | 67 | def feed_parser(self, parser, pkg, data): 68 | return lib.desc_parser_feed(parser, pkg, data, len(data)) 69 | 70 | 71 | @pytest.fixture 72 | def parser(): 73 | return DescParser() 74 | 75 | 76 | @pytest.fixture 77 | def pkg(): 78 | return Package(name='repose-git', version='5.19.g82c3d4a-1') 79 | 80 | 81 | def test_parse_desc(pkg, parser): 82 | parser.feed(pkg, REPOSE_DESC) 83 | assert parser.entry == lib.PKG_PACKAGER 84 | 85 | assert pkg.base is None 86 | assert pkg.base64sig is None 87 | assert pkg.filename == 'repose-git-5.19.g82c3d4a-1-x86_64.pkg.tar.xz' 88 | assert pkg.desc == 'A archlinux repo building tool' 89 | assert pkg.size == 18804 90 | assert pkg.isize == 51200 91 | assert pkg.sha256sum == '4045b3b24bae8a2d811323e5dd3727345e9e6f81788c65d5935d07b2ee06b505' 92 | assert pkg.url == 'http://github.com/vodik/repose' 93 | assert pkg.arch == 'x86_64' 94 | assert pkg.builddate == "Nov 28, 2015, 06:04:29" 95 | assert pkg.packager == 'Simon Gomizelj ' 96 | assert pkg.licenses == ['GPL'] 97 | 98 | 99 | def test_parse_depends(pkg, parser): 100 | parser.feed(pkg, REPOSE_DEPENDS) 101 | assert parser.entry == lib.PKG_MAKEDEPENDS 102 | 103 | assert pkg.depends == ['pacman', 'libarchive', 'gnupg'] 104 | assert pkg.conflicts == ['repose'] 105 | assert pkg.provides == ['repose'] 106 | assert pkg.makedepends == ['git'] 107 | 108 | 109 | @pytest.mark.parametrize('chunksize', [1, 10, 100]) 110 | def test_parse_chunked(pkg, parser, chunksize): 111 | def chunk(data, size): 112 | return (data[i:i+size] for i in range(0, len(data), size)) 113 | 114 | for chunk in chunk(REPOSE_DESC, chunksize): 115 | parser.feed(pkg, chunk) 116 | 117 | assert pkg.base is None 118 | assert pkg.base64sig is None 119 | assert pkg.filename == 'repose-git-5.19.g82c3d4a-1-x86_64.pkg.tar.xz' 120 | assert pkg.desc == 'A archlinux repo building tool' 121 | assert pkg.size == 18804 122 | assert pkg.isize == 51200 123 | assert pkg.sha256sum == '4045b3b24bae8a2d811323e5dd3727345e9e6f81788c65d5935d07b2ee06b505' 124 | assert pkg.url == 'http://github.com/vodik/repose' 125 | assert pkg.arch == 'x86_64' 126 | assert pkg.builddate == "Nov 28, 2015, 06:04:29" 127 | assert pkg.packager == 'Simon Gomizelj ' 128 | assert pkg.licenses == ['GPL'] 129 | -------------------------------------------------------------------------------- /src/desc.rl: -------------------------------------------------------------------------------- 1 | #include "desc.h" 2 | 3 | #include 4 | #include "package.h" 5 | #include "util.h" 6 | 7 | %%{ 8 | machine desc; 9 | 10 | action store { 11 | parser->store[parser->pos++] = fc; 12 | if (parser->pos == LINE_MAX) { 13 | errx(1, "desc line too long"); 14 | } 15 | } 16 | 17 | action emit { 18 | const char *entry = parser->store; 19 | const size_t entry_len = parser->pos; 20 | parser->store[parser->pos] = 0; 21 | parser->pos = 0; 22 | 23 | package_set(pkg, parser->entry, entry, entry_len); 24 | } 25 | 26 | header = '%FILENAME%' %{ parser->entry = PKG_FILENAME; } 27 | | '%NAME%' %{ parser->entry = PKG_PKGNAME; } 28 | | '%BASE%' %{ parser->entry = PKG_PKGBASE; } 29 | | '%VERSION%' %{ parser->entry = PKG_VERSION; } 30 | | '%DESC%' %{ parser->entry = PKG_DESCRIPTION; } 31 | | '%GROUPS%' %{ parser->entry = PKG_GROUPS; } 32 | | '%CSIZE%' %{ parser->entry = PKG_CSIZE; } 33 | | '%ISIZE%' %{ parser->entry = PKG_ISIZE; } 34 | | '%SHA256SUM%' %{ parser->entry = PKG_SHA256SUM; } 35 | | '%PGPSIG%' %{ parser->entry = PKG_PGPSIG; } 36 | | '%URL%' %{ parser->entry = PKG_URL; } 37 | | '%LICENSE%' %{ parser->entry = PKG_LICENSE; } 38 | | '%ARCH%' %{ parser->entry = PKG_ARCH; } 39 | | '%BUILDDATE%' %{ parser->entry = PKG_BUILDDATE; } 40 | | '%PACKAGER%' %{ parser->entry = PKG_PACKAGER; } 41 | | '%REPLACES%' %{ parser->entry = PKG_REPLACES; } 42 | | '%DEPENDS%' %{ parser->entry = PKG_DEPENDS; } 43 | | '%CONFLICTS%' %{ parser->entry = PKG_CONFLICTS; } 44 | | '%PROVIDES%' %{ parser->entry = PKG_PROVIDES; } 45 | | '%OPTDEPENDS%' %{ parser->entry = PKG_OPTDEPENDS; } 46 | | '%MAKEDEPENDS%' %{ parser->entry = PKG_MAKEDEPENDS; } 47 | | '%CHECKDEPENDS%' %{ parser->entry = PKG_CHECKDEPENDS; } 48 | | '%FILES%' %{ parser->entry = PKG_FILES; } 49 | | '%DELTAS%' %{ parser->entry = PKG_FILES; }; 50 | 51 | section = header '\n'; 52 | contents = [^%\n]+ @store %emit '\n'; 53 | 54 | main := ( section contents* '\n' | '\n' )*; 55 | }%% 56 | 57 | %%write data nofinal; 58 | 59 | void desc_parser_init(struct desc_parser *parser) 60 | { 61 | *parser = (struct desc_parser){0}; 62 | %%access parser->; 63 | %%write init; 64 | } 65 | 66 | ssize_t desc_parser_feed(struct desc_parser *parser, struct pkg *pkg, 67 | char *buf, size_t buf_len) 68 | { 69 | char *p = buf; 70 | char *pe = p + buf_len; 71 | 72 | %%access parser->; 73 | %%write exec; 74 | 75 | (void)desc_en_main; 76 | if (parser->cs == desc_error) 77 | return -1; 78 | 79 | return buf_len; 80 | } 81 | 82 | ssize_t read_desc(struct archive *archive, struct pkg *pkg) 83 | { 84 | char *buf; 85 | ssize_t nbytes_r = 0; 86 | struct desc_parser parser; 87 | desc_parser_init(&parser); 88 | 89 | for (;;) { 90 | size_t bufsize; 91 | archive_read(archive, &buf, &bufsize); 92 | 93 | ssize_t result = desc_parser_feed(&parser, pkg, buf, bufsize); 94 | if (result < 0) { 95 | return result; 96 | } else { 97 | nbytes_r += result; 98 | } 99 | 100 | break; 101 | } 102 | 103 | return nbytes_r; 104 | } 105 | -------------------------------------------------------------------------------- /tests/wrappers.py: -------------------------------------------------------------------------------- 1 | import abc 2 | import weakref 3 | from datetime import datetime 4 | from repose import ffi 5 | 6 | 7 | class marshal_int(object): 8 | def __init__(self, field): 9 | self.field = field 10 | 11 | def __get__(self, obj, cls): 12 | return getattr(obj._struct, self.field) 13 | 14 | 15 | class marshal_date(object): 16 | def __init__(self, field): 17 | self.field = field 18 | 19 | def __get__(self, obj, cls): 20 | timestamp = datetime.utcfromtimestamp(getattr(obj._struct, self.field)) 21 | return timestamp.strftime("%b %d, %Y, %H:%M:%S") 22 | 23 | 24 | class marshal_string(object): 25 | def __init__(self, field): 26 | self.field = field 27 | 28 | def __get__(self, obj, cls): 29 | attr = getattr(obj._struct, self.field) 30 | if attr == ffi.NULL: 31 | return None 32 | return ffi.string(attr).decode() 33 | 34 | 35 | class marshal_string_list(object): 36 | def __init__(self, field): 37 | self.field = field 38 | 39 | def __get__(self, obj, cls): 40 | def marshal_list(node): 41 | while node != ffi.NULL: 42 | yield ffi.string(ffi.cast('char*', node.data)).decode() 43 | node = node.next 44 | 45 | attr = getattr(obj._struct, self.field) 46 | return list(marshal_list(attr)) 47 | 48 | 49 | class Package(object): 50 | def __init__(self, name=None, version=None): 51 | self.weakkeydict = weakref.WeakKeyDictionary() 52 | 53 | init_data = {} 54 | if name: 55 | init_data['name'] = ffi.new('char[]', name.encode()) 56 | if version: 57 | init_data['version'] = ffi.new('char[]', version.encode()) 58 | 59 | self._struct = ffi.new('struct pkg*', init_data) 60 | self.weakkeydict[self._struct] = tuple(init_data.values()) 61 | 62 | arch = marshal_string('arch') 63 | base = marshal_string('base') 64 | base64sig = marshal_string('base64sig') 65 | builddate = marshal_date('builddate') 66 | checkdepends = marshal_string_list('checkdepends') 67 | conflicts = marshal_string_list('conflicts') 68 | depends = marshal_string_list('depends') 69 | desc = marshal_string('desc') 70 | filename = marshal_string('filename') 71 | isize = marshal_int('isize') 72 | licenses = marshal_string_list('licenses') 73 | makedepends = marshal_string_list('makedepends') 74 | optdepends = marshal_string_list('optdepends') 75 | packager = marshal_string('packager') 76 | provides = marshal_string_list('provides') 77 | sha256sum = marshal_string('sha256sum') 78 | size = marshal_int('size') 79 | url = marshal_string('url') 80 | 81 | 82 | class ParserError(Exception): 83 | pass 84 | 85 | 86 | class Parser(object): 87 | __metaclass__ = abc.ABCMeta 88 | 89 | def __init__(self): 90 | self._saved = b'' 91 | self.parser = self.init_parser() 92 | if not self.parser: 93 | raise RuntimeError('Parser failed to initialize properly') 94 | 95 | def feed(self, pkg, data): 96 | data = self._saved + data.encode() 97 | result = self.feed_parser(self.parser, pkg._struct, data) 98 | if result == -1: 99 | raise ParserError("Failed to parse input") 100 | self._saved = data[result:] 101 | 102 | @property 103 | def entry(self): 104 | return self.parser.entry 105 | 106 | @abc.abstractmethod 107 | def init_parser(self): 108 | return 109 | 110 | @abc.abstractmethod 111 | def feed_parser(self, parser, pkg, data): 112 | return 113 | -------------------------------------------------------------------------------- /src/util.c: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #define WHITESPACE " \t\n\r" 14 | 15 | static int oflags(const char *mode) 16 | { 17 | int m, o; 18 | 19 | switch (mode[0]) { 20 | case 'r': 21 | m = O_RDONLY; 22 | o = 0; 23 | break; 24 | case 'w': 25 | m = O_WRONLY; 26 | o = O_CREAT | O_TRUNC; 27 | break; 28 | case 'a': 29 | m = O_WRONLY; 30 | o = O_CREAT | O_APPEND; 31 | break; 32 | default: 33 | errno = EINVAL; 34 | return -1; 35 | } 36 | 37 | while (*++mode) { 38 | switch (*mode) { 39 | case '+': 40 | m = (m & ~O_ACCMODE) | O_RDWR; 41 | break; 42 | } 43 | } 44 | 45 | return m | o; 46 | } 47 | 48 | FILE *fopenat(int dirfd, const char *path, const char *mode) 49 | { 50 | int flags = oflags(mode); 51 | if (flags < 0) 52 | return NULL; 53 | 54 | int fd = openat(dirfd, path, flags); 55 | if (_unlikely_(fd < 0)) 56 | return NULL; 57 | return fdopen(fd, mode); 58 | } 59 | 60 | void check_posix(intmax_t rc, const char *fmt, ...) 61 | { 62 | if (_unlikely_(rc == -1)) { 63 | va_list args; 64 | va_start(args, fmt); 65 | verr(EXIT_FAILURE, fmt, args); 66 | va_end(args); 67 | } 68 | } 69 | 70 | void check_null(const void *ptr, const char *fmt, ...) 71 | { 72 | if (_unlikely_(!ptr)) { 73 | va_list args; 74 | va_start(args, fmt); 75 | verr(EXIT_FAILURE, fmt, args); 76 | va_end(args); 77 | } 78 | } 79 | 80 | char *joinstring(const char *root, ...) 81 | { 82 | size_t len; 83 | char *ret = NULL, *p; 84 | const char *temp; 85 | va_list ap; 86 | 87 | if (!root) 88 | return NULL; 89 | 90 | len = strlen(root); 91 | 92 | va_start(ap, root); 93 | while ((temp = va_arg(ap, const char *))) { 94 | size_t temp_len = strlen(temp); 95 | 96 | if (temp_len > ((size_t) -1) - len) { 97 | va_end(ap); 98 | return NULL; 99 | } 100 | 101 | len += temp_len; 102 | } 103 | va_end(ap); 104 | 105 | ret = malloc(len + 1); 106 | if (ret) { 107 | p = stpcpy(ret, root); 108 | 109 | va_start(ap, root); 110 | while ((temp = va_arg(ap, const char *))) 111 | p = stpcpy(p, temp); 112 | va_end(ap); 113 | } 114 | 115 | return ret; 116 | } 117 | 118 | static int xstrtoul(const char *str, unsigned long *out) 119 | { 120 | char *end = NULL; 121 | errno = 0; 122 | 123 | if (!str || !str[0]) { 124 | errno = EINVAL; 125 | return -1; 126 | } 127 | 128 | *out = strtoul(str, &end, 10); 129 | if (errno) { 130 | return -1; 131 | } else if (str == end || (end && *end)) { 132 | errno = EINVAL; 133 | return -1; 134 | } 135 | return 0; 136 | } 137 | 138 | int parse_size(const char *str, size_t *out) 139 | { 140 | unsigned long value = 0; 141 | if (xstrtoul(str, &value) < 0) 142 | return -1; 143 | 144 | if (value > SIZE_MAX) { 145 | errno = ERANGE; 146 | return -1; 147 | } 148 | 149 | *out = (size_t)value; 150 | return 0; 151 | } 152 | 153 | int parse_time(const char *str, time_t *out) 154 | { 155 | unsigned long value = 0; 156 | if (xstrtoul(str, &value) < 0) 157 | return -1; 158 | 159 | if (value > INT_MAX) { 160 | errno = ERANGE; 161 | return -1; 162 | } 163 | 164 | *out = (time_t)value; 165 | return 0; 166 | } 167 | 168 | char *hex_representation(unsigned char *bytes, size_t size) 169 | { 170 | static const char *hex_digits = "0123456789abcdef"; 171 | char *str = malloc(2 * size + 1); 172 | size_t i; 173 | 174 | for(i = 0; i < size; i++) { 175 | str[2 * i] = hex_digits[bytes[i] >> 4]; 176 | str[2 * i + 1] = hex_digits[bytes[i] & 0x0f]; 177 | } 178 | 179 | str[2 * size] = '\0'; 180 | return str; 181 | } 182 | 183 | char *strstrip(char *s) 184 | { 185 | char *e; 186 | s += strspn(s, WHITESPACE); 187 | 188 | for (e = strchr(s, 0); e > s; --e) { 189 | if (!strchr(WHITESPACE, e[-1])) 190 | break; 191 | } 192 | 193 | *e = 0; 194 | return s; 195 | } 196 | 197 | int archive_read(struct archive *archive, char **buf, size_t *buf_len) 198 | { 199 | for (;;) { 200 | int status = archive_read_data_block(archive, (void *)buf, 201 | buf_len, &(int64_t){0}); 202 | if (status == ARCHIVE_RETRY) 203 | continue; 204 | if (status <= ARCHIVE_WARN) 205 | warnx("%s", archive_error_string(archive)); 206 | return status; 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/signing.c: -------------------------------------------------------------------------------- 1 | #include "signing.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "util.h" 16 | 17 | static void _noreturn_ _printf_(3,4) gpgme_err(int eval, gpgme_error_t err, const char *fmt, ...) 18 | { 19 | fprintf(stderr, "%s: ", program_invocation_short_name); 20 | 21 | if (fmt) { 22 | va_list ap; 23 | 24 | va_start(ap, fmt); 25 | vfprintf(stderr, fmt, ap); 26 | va_end(ap); 27 | fprintf(stderr, ": "); 28 | } 29 | 30 | fprintf(stderr, "%s\n", gpgme_strerror(err)); 31 | exit(eval); 32 | } 33 | 34 | static inline char *sig_for(const char *file) 35 | { 36 | return joinstring(file, ".sig", NULL); 37 | } 38 | 39 | static int init_gpgme(void) 40 | { 41 | static int inited = false; 42 | gpgme_error_t err; 43 | gpgme_engine_info_t enginfo; 44 | 45 | /* we already successfully initialized the library */ 46 | if (inited) 47 | return 0; 48 | 49 | /* calling gpgme_check_version() returns the current version and runs 50 | * some internal library setup code */ 51 | gpgme_check_version(NULL); 52 | gpgme_set_locale(NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL)); 53 | #ifdef LC_MESSAGES 54 | gpgme_set_locale(NULL, LC_MESSAGES, setlocale(LC_MESSAGES, NULL)); 55 | #endif 56 | 57 | /* check for OpenPGP support (should be a no-brainer, but be safe) */ 58 | err = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP); 59 | if (gpg_err_code(err) != GPG_ERR_NO_ERROR) 60 | return -1; 61 | 62 | err = gpgme_get_engine_info(&enginfo); 63 | if (gpg_err_code(err) != GPG_ERR_NO_ERROR) 64 | return -1; 65 | 66 | inited = true; 67 | return 0; 68 | } 69 | 70 | int gpgme_verify(int rootfd, const char *file) 71 | { 72 | gpgme_error_t err; 73 | gpgme_ctx_t ctx; 74 | gpgme_data_t in, sig; 75 | gpgme_verify_result_t result; 76 | gpgme_signature_t sigs; 77 | int rc = 0; 78 | 79 | if (init_gpgme() < 0) 80 | return -1; 81 | 82 | _cleanup_free_ char *sigfile = sig_for(file); 83 | _cleanup_close_ int sigfd = openat(rootfd, sigfile, O_RDONLY); 84 | _cleanup_close_ int fd = openat(rootfd, file, O_RDONLY); 85 | 86 | err = gpgme_new(&ctx); 87 | if (gpg_err_code(err) != GPG_ERR_NO_ERROR) 88 | gpgme_err(EXIT_FAILURE, err, "failed to call gpgme_new()"); 89 | 90 | err = gpgme_data_new_from_fd(&in, fd); 91 | if (err) 92 | gpgme_err(EXIT_FAILURE, err, "error reading %s", file); 93 | 94 | err = gpgme_data_new_from_fd(&sig, sigfd); 95 | if (gpg_err_code(err) != GPG_ERR_NO_ERROR) 96 | gpgme_err(EXIT_FAILURE, err, "error reading %s", sigfile); 97 | 98 | err = gpgme_op_verify(ctx, sig, in, NULL); 99 | if (gpg_err_code(err) != GPG_ERR_NO_ERROR) 100 | gpgme_err(EXIT_FAILURE, err, "failed to verify"); 101 | 102 | result = gpgme_op_verify_result(ctx); 103 | sigs = result->signatures; 104 | if (gpgme_err_code(sigs->status) != GPG_ERR_NO_ERROR) { 105 | warnx("unexpected signature status: %s", gpgme_strerror(sigs->status)); 106 | rc = -1; 107 | } else if (sigs->next) { 108 | warnx("unexpected number of signatures"); 109 | rc = -1; 110 | } else if (sigs->summary == GPGME_SIGSUM_RED) { 111 | warnx("unexpected signature summary 0x%x", sigs->summary); 112 | rc = -1; 113 | } else if (sigs->wrong_key_usage) { 114 | warnx("unexpected wrong key usage"); 115 | rc = -1; 116 | } else if (sigs->validity != GPGME_VALIDITY_FULL) { 117 | warnx("unexpected validity 0x%x", sigs->validity); 118 | rc = -1; 119 | } else if (gpgme_err_code(sigs->validity_reason) != GPG_ERR_NO_ERROR) { 120 | warnx("unexpected validity reason: %s", gpgme_strerror(sigs->validity_reason)); 121 | rc = -1; 122 | } 123 | 124 | gpgme_data_release(in); 125 | gpgme_data_release(sig); 126 | gpgme_release(ctx); 127 | return rc; 128 | } 129 | 130 | void gpgme_sign(int rootfd, const char *file, const char *key) 131 | { 132 | gpgme_error_t err; 133 | gpgme_ctx_t ctx; 134 | gpgme_data_t in, out; 135 | gpgme_sign_result_t result; 136 | 137 | if (init_gpgme() < 0) 138 | return; 139 | 140 | err = gpgme_new(&ctx); 141 | if (gpg_err_code(err) != GPG_ERR_NO_ERROR) 142 | gpgme_err(EXIT_FAILURE, err, "failed to call gpgme_new()"); 143 | 144 | if (key) { 145 | gpgme_key_t akey; 146 | 147 | err = gpgme_get_key(ctx, key, &akey, 1); 148 | if (err) 149 | gpgme_err(EXIT_FAILURE, err, "failed to set key %s", key); 150 | 151 | err = gpgme_signers_add(ctx, akey); 152 | if (gpg_err_code(err) != GPG_ERR_NO_ERROR) 153 | gpgme_err(EXIT_FAILURE, err, "failed to call gpgme_signers_add()"); 154 | 155 | gpgme_key_unref(akey); 156 | } 157 | 158 | _cleanup_free_ char *sigfile = sig_for(file); 159 | _cleanup_close_ int sigfd = openat(rootfd, sigfile, O_CREAT | O_WRONLY, 00644); 160 | _cleanup_close_ int fd = openat(rootfd, file, O_RDONLY); 161 | 162 | err = gpgme_data_new_from_fd(&in, fd); 163 | if (err) 164 | gpgme_err(EXIT_FAILURE, err, "error reading %s", file); 165 | 166 | err = gpgme_data_new(&out); 167 | if (gpg_err_code(err) != GPG_ERR_NO_ERROR) 168 | gpgme_err(EXIT_FAILURE, err, "failed to call gpgme_data_new()"); 169 | 170 | err = gpgme_op_sign(ctx, in, out, GPGME_SIG_MODE_DETACH); 171 | if (err) 172 | gpgme_err(EXIT_FAILURE, err, "signing failed"); 173 | 174 | result = gpgme_op_sign_result(ctx); 175 | if (!result) 176 | gpgme_err(EXIT_FAILURE, err, "signaure failed?"); 177 | 178 | char buf[BUFSIZ]; 179 | int ret; 180 | 181 | ret = gpgme_data_seek(out, 0, SEEK_SET); 182 | if (ret) 183 | return; 184 | 185 | while ((ret = gpgme_data_read(out, buf, BUFSIZ)) > 0) 186 | write(sigfd, buf, ret); 187 | 188 | gpgme_data_release(out); 189 | gpgme_data_release(in); 190 | gpgme_release(ctx); 191 | } 192 | -------------------------------------------------------------------------------- /src/package.c: -------------------------------------------------------------------------------- 1 | #include "package.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "util.h" 14 | #include "pkginfo.h" 15 | #include "pkgcache.h" 16 | #include "base64.h" 17 | 18 | int load_package(pkg_t *pkg, int fd) 19 | { 20 | struct archive *archive; 21 | struct stat st; 22 | 23 | check_posix(fstat(fd, &st), "failed to stat file"); 24 | 25 | archive = archive_read_new(); 26 | archive_read_support_filter_all(archive); 27 | archive_read_support_format_all(archive); 28 | 29 | if (archive_read_open_fd(archive, fd, 8192) != ARCHIVE_OK) { 30 | archive_read_free(archive); 31 | return -1; 32 | } 33 | 34 | bool found_pkginfo = false; 35 | struct archive_entry *entry; 36 | while (archive_read_next_header(archive, &entry) == ARCHIVE_OK && !found_pkginfo) { 37 | const char *entry_name = archive_entry_pathname(entry); 38 | const mode_t mode = archive_entry_mode(entry); 39 | 40 | if (S_ISREG(mode) && streq(entry_name, ".PKGINFO")) { 41 | if (read_pkginfo(archive, pkg) < 0) { 42 | errx(EXIT_FAILURE, "failed to parse PKGINFO on %s", pkg->filename); 43 | } 44 | found_pkginfo = true; 45 | } 46 | } 47 | 48 | archive_read_close(archive); 49 | archive_read_free(archive); 50 | 51 | if (found_pkginfo) { 52 | pkg->hash = sdbm(pkg->name); 53 | pkg->size = st.st_size; 54 | pkg->mtime = st.st_mtime; 55 | return 0; 56 | } 57 | 58 | return -1; 59 | } 60 | 61 | int load_package_signature(struct pkg *pkg, int dirfd) 62 | { 63 | _cleanup_free_ char *signame = joinstring(pkg->filename, ".sig", NULL); 64 | _cleanup_close_ int fd = openat(dirfd, signame, O_RDONLY); 65 | if (fd < 0) 66 | return -1; 67 | 68 | struct stat st; 69 | check_posix(fstat(fd, &st), "failed to stat signature"); 70 | 71 | _cleanup_free_ char *signature = malloc(st.st_size); 72 | check_posix(read(fd, signature, st.st_size), "failed to read signature"); 73 | 74 | pkg->base64sig = base64_encode((const unsigned char *)signature, 75 | st.st_size, NULL); 76 | check_null(pkg->base64sig, "failed to find base64 signature"); 77 | 78 | // If the signature's timestamp is new than the packages, update 79 | // it to the newer value. 80 | if (st.st_mtime > pkg->mtime) 81 | pkg->mtime = st.st_mtime; 82 | 83 | return 0; 84 | } 85 | 86 | int load_package_files(struct pkg *pkg, int fd) 87 | { 88 | struct archive *archive; 89 | struct stat st; 90 | 91 | check_posix(fstat(fd, &st), "failed to stat file"); 92 | 93 | archive = archive_read_new(); 94 | archive_read_support_filter_all(archive); 95 | archive_read_support_format_all(archive); 96 | 97 | if (archive_read_open_fd(archive, fd, 8192) != ARCHIVE_OK) { 98 | archive_read_free(archive); 99 | return -1; 100 | } 101 | 102 | struct archive_entry *entry; 103 | while (archive_read_next_header(archive, &entry) == ARCHIVE_OK) { 104 | const char *entry_name = archive_entry_pathname(entry); 105 | 106 | if (entry_name[0] != '.') 107 | pkg->files = alpm_list_add(pkg->files, strdup(entry_name)); 108 | } 109 | 110 | archive_read_close(archive); 111 | archive_read_free(archive); 112 | return 0; 113 | } 114 | 115 | void package_free(pkg_t *pkg) 116 | { 117 | free(pkg->filename); 118 | free(pkg->name); 119 | free(pkg->version); 120 | free(pkg->desc); 121 | free(pkg->url); 122 | free(pkg->packager); 123 | free(pkg->sha256sum); 124 | free(pkg->base64sig); 125 | free(pkg->arch); 126 | 127 | alpm_list_free_inner(pkg->groups, free); 128 | alpm_list_free(pkg->groups); 129 | alpm_list_free_inner(pkg->licenses, free); 130 | alpm_list_free(pkg->licenses); 131 | alpm_list_free_inner(pkg->depends, free); 132 | alpm_list_free(pkg->depends); 133 | alpm_list_free_inner(pkg->conflicts, free); 134 | alpm_list_free(pkg->conflicts); 135 | alpm_list_free_inner(pkg->provides, free); 136 | alpm_list_free(pkg->provides); 137 | alpm_list_free_inner(pkg->optdepends, free); 138 | alpm_list_free(pkg->optdepends); 139 | alpm_list_free_inner(pkg->makedepends, free); 140 | alpm_list_free(pkg->makedepends); 141 | alpm_list_free_inner(pkg->files, free); 142 | alpm_list_free(pkg->files); 143 | 144 | free(pkg); 145 | } 146 | 147 | static void pkg_append_list(const char *entry, size_t len, alpm_list_t **list) 148 | { 149 | *list = alpm_list_add(*list, strndup(entry, len)); 150 | } 151 | 152 | static void pkg_set_string(const char *entry, size_t len, char **data) 153 | { 154 | free(*data); 155 | *data = strndup(entry, len); 156 | } 157 | 158 | static void pkg_set_size(const char *entry, size_t _unused_ len, size_t *data) 159 | { 160 | parse_size(entry, data); 161 | } 162 | 163 | static void pkg_set_time(const char *entry, size_t _unused_ len, time_t *data) 164 | { 165 | parse_time(entry, data); 166 | } 167 | 168 | #define pkg_set(entry, len, field) _Generic((field), \ 169 | alpm_list_t **: pkg_append_list, \ 170 | char **: pkg_set_string, \ 171 | size_t *: pkg_set_size, \ 172 | time_t *: pkg_set_time)(entry, len, field) 173 | 174 | void package_set(pkg_t *pkg, enum pkg_entry type, const char *entry, size_t len) 175 | { 176 | switch (type) { 177 | case PKG_FILENAME: 178 | pkg_set(entry, len, &pkg->filename); 179 | break; 180 | case PKG_PKGNAME: 181 | if (!pkg->name) { 182 | pkg_set(entry, len, &pkg->name); 183 | } else if (!strneq(entry, pkg->name, len)) { 184 | errx(EXIT_FAILURE, "database entry %%NAME%% and desc record are mismatched!"); 185 | } 186 | break; 187 | case PKG_PKGBASE: 188 | pkg_set(entry, len, &pkg->base); 189 | break; 190 | case PKG_VERSION: 191 | if (!pkg->version) { 192 | pkg_set(entry, len, &pkg->version); 193 | } else if (!strneq(entry, pkg->version, len)) { 194 | errx(EXIT_FAILURE, "database entry %%VERSION%% and desc record are mismatched!"); 195 | } 196 | break; 197 | case PKG_DESCRIPTION: 198 | pkg_set(entry, len, &pkg->desc); 199 | break; 200 | case PKG_GROUPS: 201 | pkg_set(entry, len, &pkg->groups); 202 | break; 203 | case PKG_CSIZE: 204 | pkg_set(entry, len, &pkg->size); 205 | break; 206 | case PKG_ISIZE: 207 | pkg_set(entry, len, &pkg->isize); 208 | break; 209 | case PKG_SHA256SUM: 210 | pkg_set(entry, len, &pkg->sha256sum); 211 | break; 212 | case PKG_PGPSIG: 213 | pkg_set(entry, len, &pkg->base64sig); 214 | break; 215 | case PKG_URL: 216 | pkg_set(entry, len, &pkg->url); 217 | break; 218 | case PKG_LICENSE: 219 | pkg_set(entry, len, &pkg->licenses); 220 | break; 221 | case PKG_ARCH: 222 | pkg_set(entry, len, &pkg->arch); 223 | break; 224 | case PKG_BUILDDATE: 225 | pkg_set(entry, len, &pkg->builddate); 226 | break; 227 | case PKG_PACKAGER: 228 | pkg_set(entry, len, &pkg->packager); 229 | break; 230 | case PKG_REPLACES: 231 | pkg_set(entry, len, &pkg->replaces); 232 | break; 233 | case PKG_DEPENDS: 234 | pkg_set(entry, len, &pkg->depends); 235 | break; 236 | case PKG_CONFLICTS: 237 | pkg_set(entry, len, &pkg->conflicts); 238 | break; 239 | case PKG_PROVIDES: 240 | pkg_set(entry, len, &pkg->provides); 241 | break; 242 | case PKG_OPTDEPENDS: 243 | pkg_set(entry, len, &pkg->optdepends); 244 | break; 245 | case PKG_MAKEDEPENDS: 246 | pkg_set(entry, len, &pkg->makedepends); 247 | break; 248 | case PKG_CHECKDEPENDS: 249 | pkg_set(entry, len, &pkg->checkdepends); 250 | break; 251 | case PKG_FILES: 252 | pkg_set(entry, len, &pkg->files); 253 | break; 254 | case PKG_DELTAS: 255 | pkg_set(entry, len, &pkg->deltas); 256 | break; 257 | default: 258 | break; 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /compile_commands.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "arguments": [ 4 | "cc", 5 | "-c", 6 | "-std=c18", 7 | "-g", 8 | "-Wall", 9 | "-Wextra", 10 | "-pedantic", 11 | "-Wshadow", 12 | "-Wpointer-arith", 13 | "-Wcast-qual", 14 | "-Wstrict-prototypes", 15 | "-Wmissing-prototypes", 16 | "-Wno-missing-field-initializers", 17 | "-D_GNU_SOURCE", 18 | "-D_FILE_OFFSET_BITS=64", 19 | "-DREPOSE_VERSION=\"7.1-8-g159aced\"", 20 | "-o", 21 | "util.o", 22 | "src/util.c" 23 | ], 24 | "directory": "/home/simon/src/repose", 25 | "file": "src/util.c" 26 | }, 27 | { 28 | "arguments": [ 29 | "cc", 30 | "-c", 31 | "-std=c18", 32 | "-g", 33 | "-Wall", 34 | "-Wextra", 35 | "-pedantic", 36 | "-Wshadow", 37 | "-Wpointer-arith", 38 | "-Wcast-qual", 39 | "-Wstrict-prototypes", 40 | "-Wmissing-prototypes", 41 | "-Wno-missing-field-initializers", 42 | "-D_GNU_SOURCE", 43 | "-D_FILE_OFFSET_BITS=64", 44 | "-DREPOSE_VERSION=\"7.1-8-g159aced\"", 45 | "-o", 46 | "desc.o", 47 | "src/desc.c" 48 | ], 49 | "directory": "/home/simon/src/repose", 50 | "file": "src/desc.c" 51 | }, 52 | { 53 | "arguments": [ 54 | "cc", 55 | "-c", 56 | "-std=c18", 57 | "-g", 58 | "-Wall", 59 | "-Wextra", 60 | "-pedantic", 61 | "-Wshadow", 62 | "-Wpointer-arith", 63 | "-Wcast-qual", 64 | "-Wstrict-prototypes", 65 | "-Wmissing-prototypes", 66 | "-Wno-missing-field-initializers", 67 | "-D_GNU_SOURCE", 68 | "-D_FILE_OFFSET_BITS=64", 69 | "-DREPOSE_VERSION=\"7.1-8-g159aced\"", 70 | "-o", 71 | "pkgcache.o", 72 | "src/pkgcache.c" 73 | ], 74 | "directory": "/home/simon/src/repose", 75 | "file": "src/pkgcache.c" 76 | }, 77 | { 78 | "arguments": [ 79 | "cc", 80 | "-c", 81 | "-std=c18", 82 | "-g", 83 | "-Wall", 84 | "-Wextra", 85 | "-pedantic", 86 | "-Wshadow", 87 | "-Wpointer-arith", 88 | "-Wcast-qual", 89 | "-Wstrict-prototypes", 90 | "-Wmissing-prototypes", 91 | "-Wno-missing-field-initializers", 92 | "-D_GNU_SOURCE", 93 | "-D_FILE_OFFSET_BITS=64", 94 | "-DREPOSE_VERSION=\"7.1-8-g159aced\"", 95 | "-o", 96 | "base64.o", 97 | "src/base64.c" 98 | ], 99 | "directory": "/home/simon/src/repose", 100 | "file": "src/base64.c" 101 | }, 102 | { 103 | "arguments": [ 104 | "cc", 105 | "-c", 106 | "-std=c18", 107 | "-g", 108 | "-Wall", 109 | "-Wextra", 110 | "-pedantic", 111 | "-Wshadow", 112 | "-Wpointer-arith", 113 | "-Wcast-qual", 114 | "-Wstrict-prototypes", 115 | "-Wmissing-prototypes", 116 | "-Wno-missing-field-initializers", 117 | "-D_GNU_SOURCE", 118 | "-D_FILE_OFFSET_BITS=64", 119 | "-DREPOSE_VERSION=\"7.1-8-g159aced\"", 120 | "-o", 121 | "package.o", 122 | "src/package.c" 123 | ], 124 | "directory": "/home/simon/src/repose", 125 | "file": "src/package.c" 126 | }, 127 | { 128 | "arguments": [ 129 | "cc", 130 | "-c", 131 | "-std=c18", 132 | "-g", 133 | "-Wall", 134 | "-Wextra", 135 | "-pedantic", 136 | "-Wshadow", 137 | "-Wpointer-arith", 138 | "-Wcast-qual", 139 | "-Wstrict-prototypes", 140 | "-Wmissing-prototypes", 141 | "-Wno-missing-field-initializers", 142 | "-D_GNU_SOURCE", 143 | "-D_FILE_OFFSET_BITS=64", 144 | "-DREPOSE_VERSION=\"7.1-8-g159aced\"", 145 | "-o", 146 | "filters.o", 147 | "src/filters.c" 148 | ], 149 | "directory": "/home/simon/src/repose", 150 | "file": "src/filters.c" 151 | }, 152 | { 153 | "arguments": [ 154 | "cc", 155 | "-c", 156 | "-std=c18", 157 | "-g", 158 | "-Wall", 159 | "-Wextra", 160 | "-pedantic", 161 | "-Wshadow", 162 | "-Wpointer-arith", 163 | "-Wcast-qual", 164 | "-Wstrict-prototypes", 165 | "-Wmissing-prototypes", 166 | "-Wno-missing-field-initializers", 167 | "-D_GNU_SOURCE", 168 | "-D_FILE_OFFSET_BITS=64", 169 | "-DREPOSE_VERSION=\"7.1-8-g159aced\"", 170 | "-o", 171 | "repose.o", 172 | "src/repose.c" 173 | ], 174 | "directory": "/home/simon/src/repose", 175 | "file": "src/repose.c" 176 | }, 177 | { 178 | "arguments": [ 179 | "cc", 180 | "-c", 181 | "-std=c18", 182 | "-g", 183 | "-Wall", 184 | "-Wextra", 185 | "-pedantic", 186 | "-Wshadow", 187 | "-Wpointer-arith", 188 | "-Wcast-qual", 189 | "-Wstrict-prototypes", 190 | "-Wmissing-prototypes", 191 | "-Wno-missing-field-initializers", 192 | "-D_GNU_SOURCE", 193 | "-D_FILE_OFFSET_BITS=64", 194 | "-DREPOSE_VERSION=\"7.1-8-g159aced\"", 195 | "-o", 196 | "pkginfo.o", 197 | "src/pkginfo.c" 198 | ], 199 | "directory": "/home/simon/src/repose", 200 | "file": "src/pkginfo.c" 201 | }, 202 | { 203 | "arguments": [ 204 | "cc", 205 | "-c", 206 | "-std=c18", 207 | "-g", 208 | "-Wall", 209 | "-Wextra", 210 | "-pedantic", 211 | "-Wshadow", 212 | "-Wpointer-arith", 213 | "-Wcast-qual", 214 | "-Wstrict-prototypes", 215 | "-Wmissing-prototypes", 216 | "-Wno-missing-field-initializers", 217 | "-D_GNU_SOURCE", 218 | "-D_FILE_OFFSET_BITS=64", 219 | "-DREPOSE_VERSION=\"7.1-8-g159aced\"", 220 | "-o", 221 | "database.o", 222 | "src/database.c" 223 | ], 224 | "directory": "/home/simon/src/repose", 225 | "file": "src/database.c" 226 | }, 227 | { 228 | "arguments": [ 229 | "cc", 230 | "-c", 231 | "-std=c18", 232 | "-g", 233 | "-Wall", 234 | "-Wextra", 235 | "-pedantic", 236 | "-Wshadow", 237 | "-Wpointer-arith", 238 | "-Wcast-qual", 239 | "-Wstrict-prototypes", 240 | "-Wmissing-prototypes", 241 | "-Wno-missing-field-initializers", 242 | "-D_GNU_SOURCE", 243 | "-D_FILE_OFFSET_BITS=64", 244 | "-DREPOSE_VERSION=\"7.1-8-g159aced\"", 245 | "-o", 246 | "signing.o", 247 | "src/signing.c" 248 | ], 249 | "directory": "/home/simon/src/repose", 250 | "file": "src/signing.c" 251 | }, 252 | { 253 | "arguments": [ 254 | "cc", 255 | "-c", 256 | "-std=c18", 257 | "-g", 258 | "-Wall", 259 | "-Wextra", 260 | "-pedantic", 261 | "-Wshadow", 262 | "-Wpointer-arith", 263 | "-Wcast-qual", 264 | "-Wstrict-prototypes", 265 | "-Wmissing-prototypes", 266 | "-Wno-missing-field-initializers", 267 | "-D_GNU_SOURCE", 268 | "-D_FILE_OFFSET_BITS=64", 269 | "-DREPOSE_VERSION=\"7.1-8-g159aced\"", 270 | "-o", 271 | "filecache.o", 272 | "src/filecache.c" 273 | ], 274 | "directory": "/home/simon/src/repose", 275 | "file": "src/filecache.c" 276 | }, 277 | { 278 | "arguments": [ 279 | "cc", 280 | "-c", 281 | "-std=c18", 282 | "-g", 283 | "-Wall", 284 | "-Wextra", 285 | "-pedantic", 286 | "-Wshadow", 287 | "-Wpointer-arith", 288 | "-Wcast-qual", 289 | "-Wstrict-prototypes", 290 | "-Wmissing-prototypes", 291 | "-Wno-missing-field-initializers", 292 | "-D_GNU_SOURCE", 293 | "-D_FILE_OFFSET_BITS=64", 294 | "-DREPOSE_VERSION=\"7.1-8-g159aced\"", 295 | "-o", 296 | "buffer.o", 297 | "src/buffer.c" 298 | ], 299 | "directory": "/home/simon/src/repose", 300 | "file": "src/buffer.c" 301 | } 302 | ] -------------------------------------------------------------------------------- /src/pkgcache.c: -------------------------------------------------------------------------------- 1 | #include "pkgcache.h" 2 | 3 | #include 4 | #include 5 | 6 | hash_t sdbm(const char *str) 7 | { 8 | hash_t c; 9 | hash_t hash = 0; 10 | 11 | if (!str) { 12 | return hash; 13 | } 14 | while ((c = *str++)) { 15 | hash = c + hash * 65599; 16 | } 17 | 18 | return hash; 19 | } 20 | 21 | static int pkg_cmp(const void *p1, const void *p2) 22 | { 23 | const struct pkg *pkg1 = p1; 24 | const struct pkg *pkg2 = p2; 25 | return strcmp(pkg1->name, pkg2->name); 26 | } 27 | 28 | /* List of primes for possible sizes of hash tables. 29 | * 30 | * The maximum table size is the last prime under 1,000,000. That is 31 | * more than an order of magnitude greater than the number of packages 32 | * in any Linux distribution, and well under UINT_MAX. 33 | */ 34 | static const size_t prime_list[] = 35 | { 36 | 11u, 13u, 17u, 19u, 23u, 29u, 31u, 37u, 41u, 43u, 47u, 37 | 53u, 59u, 61u, 67u, 71u, 73u, 79u, 83u, 89u, 97u, 103u, 38 | 109u, 113u, 127u, 137u, 139u, 149u, 157u, 167u, 179u, 193u, 39 | 199u, 211u, 227u, 241u, 257u, 277u, 293u, 313u, 337u, 359u, 40 | 383u, 409u, 439u, 467u, 503u, 541u, 577u, 619u, 661u, 709u, 41 | 761u, 823u, 887u, 953u, 1031u, 1109u, 1193u, 1289u, 1381u, 42 | 1493u, 1613u, 1741u, 1879u, 2029u, 2179u, 2357u, 2549u, 43 | 2753u, 2971u, 3209u, 3469u, 3739u, 4027u, 4349u, 4703u, 44 | 5087u, 5503u, 5953u, 6427u, 6949u, 7517u, 8123u, 8783u, 45 | 9497u, 10273u, 11113u, 12011u, 12983u, 14033u, 15173u, 46 | 16411u, 17749u, 19183u, 20753u, 22447u, 24281u, 26267u, 47 | 28411u, 30727u, 33223u, 35933u, 38873u, 42043u, 45481u, 48 | 49201u, 53201u, 57557u, 62233u, 67307u, 72817u, 78779u, 49 | 85229u, 92203u, 99733u, 107897u, 116731u, 126271u, 136607u, 50 | 147793u, 159871u, 172933u, 187091u, 202409u, 218971u, 236897u, 51 | 256279u, 277261u, 299951u, 324503u, 351061u, 379787u, 410857u, 52 | 444487u, 480881u, 520241u, 562841u, 608903u, 658753u, 712697u, 53 | 771049u, 834181u, 902483u, 976369u 54 | }; 55 | 56 | /* How far forward do we look when linear probing for a spot? */ 57 | static const size_t stride = 1; 58 | /* What is the maximum load percentage of our hash table? */ 59 | static const double max_hash_load = 0.68; 60 | /* Initial load percentage given a certain size */ 61 | static const double initial_hash_load = 0.58; 62 | 63 | /* Allocate a hash table with space for at least "size" elements */ 64 | struct pkgcache *pkgcache_create(size_t size) 65 | { 66 | struct pkgcache *cache = NULL; 67 | size_t i, loopsize; 68 | 69 | cache = calloc(1, sizeof(struct pkgcache)); 70 | if (!cache) 71 | return NULL; 72 | 73 | size = size / initial_hash_load + 1; 74 | 75 | loopsize = sizeof(prime_list) / sizeof(*prime_list); 76 | for (i = 0; i < loopsize; i++) { 77 | if (prime_list[i] > size) { 78 | cache->buckets = prime_list[i]; 79 | cache->limit = cache->buckets * max_hash_load; 80 | break; 81 | } 82 | } 83 | 84 | if (cache->buckets < size) { 85 | errno = ERANGE; 86 | free(cache); 87 | return NULL; 88 | } 89 | 90 | cache->hash_table = calloc(cache->buckets, sizeof(alpm_list_t *)); 91 | if (!cache->hash_table) { 92 | free(cache); 93 | return NULL; 94 | } 95 | 96 | return cache; 97 | } 98 | 99 | static size_t get_hash_position(hash_t hash, struct pkgcache *cache) 100 | { 101 | size_t position; 102 | 103 | position = hash % cache->buckets; 104 | 105 | /* collision resolution using open addressing with linear probing */ 106 | while (cache->hash_table[position] != NULL) { 107 | position += stride; 108 | while (position >= cache->buckets) { 109 | position -= cache->buckets; 110 | } 111 | } 112 | 113 | return position; 114 | } 115 | 116 | /* Expand the hash table size to the next increment and rebin the entries */ 117 | static struct pkgcache *rehash(struct pkgcache *oldcache) 118 | { 119 | struct pkgcache *newcache; 120 | size_t newsize, i; 121 | 122 | /* Hash tables will need resized in two cases: 123 | * - adding packages to the local database 124 | * - poor estimation of the number of packages in sync database 125 | * 126 | * For small hash tables sizes (<500) the increase in size is by a 127 | * minimum of a factor of 2 for optimal rehash efficiency. For 128 | * larger database sizes, this increase is reduced to avoid excess 129 | * memory allocation as both scenarios requiring a rehash should not 130 | * require a table size increase that large. */ 131 | if (oldcache->buckets < 500) { 132 | newsize = oldcache->buckets * 2; 133 | } else if (oldcache->buckets < 2000) { 134 | newsize = oldcache->buckets * 3 / 2; 135 | } else if (oldcache->buckets < 5000) { 136 | newsize = oldcache->buckets * 4 / 3; 137 | } else { 138 | newsize = oldcache->buckets + 1; 139 | } 140 | 141 | newcache = pkgcache_create(newsize); 142 | if (newcache == NULL) { 143 | /* creation of newcache failed, stick with old one... */ 144 | return oldcache; 145 | } 146 | 147 | newcache->list = oldcache->list; 148 | oldcache->list = NULL; 149 | 150 | for (i = 0; i < oldcache->buckets; i++) { 151 | if (oldcache->hash_table[i] != NULL) { 152 | struct pkg *package = oldcache->hash_table[i]->data; 153 | size_t position = get_hash_position(package->hash, newcache); 154 | 155 | newcache->hash_table[position] = oldcache->hash_table[i]; 156 | oldcache->hash_table[i] = NULL; 157 | } 158 | } 159 | 160 | newcache->entries = oldcache->entries; 161 | 162 | pkgcache_free(oldcache); 163 | 164 | return newcache; 165 | } 166 | 167 | static struct pkgcache *pkgcache_add_pkg(struct pkgcache *cache, struct pkg *pkg, 168 | int sorted) 169 | { 170 | alpm_list_t *ptr; 171 | size_t position; 172 | 173 | if (pkg == NULL || cache == NULL) { 174 | return cache; 175 | } 176 | 177 | if (cache->entries >= cache->limit) { 178 | cache = rehash(cache); 179 | } 180 | 181 | position = get_hash_position(pkg->hash, cache); 182 | 183 | ptr = malloc(sizeof(alpm_list_t)); 184 | if (ptr == NULL) { 185 | return cache; 186 | } 187 | 188 | ptr->data = pkg; 189 | ptr->prev = ptr; 190 | ptr->next = NULL; 191 | 192 | cache->hash_table[position] = ptr; 193 | if (!sorted) { 194 | cache->list = alpm_list_join(cache->list, ptr); 195 | } else { 196 | cache->list = alpm_list_mmerge(cache->list, ptr, pkg_cmp); 197 | } 198 | 199 | cache->entries += 1; 200 | return cache; 201 | } 202 | 203 | 204 | struct pkgcache *pkgcache_add(struct pkgcache *cache, struct pkg *pkg) 205 | { 206 | return pkgcache_add_pkg(cache, pkg, 0); 207 | } 208 | 209 | struct pkgcache *pkgcache_replace(struct pkgcache *cache, struct pkg *new, struct pkg *old) 210 | { 211 | cache = pkgcache_remove(cache, old, NULL); 212 | return pkgcache_add(cache, new); 213 | 214 | } 215 | 216 | struct pkgcache *pkgcache_add_sorted(struct pkgcache *cache, struct pkg *pkg) 217 | { 218 | return pkgcache_add_pkg(cache, pkg, 1); 219 | } 220 | 221 | static size_t move_one_entry(struct pkgcache *cache, 222 | size_t start, size_t end) 223 | { 224 | /* Iterate backwards from 'end' to 'start', seeing if any of the items 225 | * would hash to 'start'. If we find one, we move it there and break. If 226 | * we get all the way back to position and find none that hash to it, we 227 | * also end iteration. Iterating backwards helps prevent needless shuffles; 228 | * we will never need to move more than one item per function call. The 229 | * return value is our current iteration location; if this is equal to 230 | * 'start' we can stop this madness. */ 231 | while (end != start) { 232 | alpm_list_t *i = cache->hash_table[end]; 233 | struct pkg *info = i->data; 234 | size_t new_position = get_hash_position(info->hash, cache); 235 | 236 | if (new_position == start) { 237 | cache->hash_table[start] = i; 238 | cache->hash_table[end] = NULL; 239 | break; 240 | } 241 | 242 | /* the odd math ensures we are always positive, e.g. 243 | * e.g. (0 - 1) % 47 == -1 244 | * e.g. (47 + 0 - 1) % 47 == 46 */ 245 | end = (cache->buckets + end - stride) % cache->buckets; 246 | } 247 | return end; 248 | } 249 | 250 | struct pkgcache *pkgcache_remove(struct pkgcache *cache, struct pkg *pkg, 251 | struct pkg **data) 252 | { 253 | alpm_list_t *i; 254 | size_t position; 255 | 256 | if (data) { 257 | *data = NULL; 258 | } 259 | 260 | if (pkg == NULL || cache == NULL) { 261 | return cache; 262 | } 263 | 264 | position = pkg->hash % cache->buckets; 265 | while ((i = cache->hash_table[position]) != NULL) { 266 | struct pkg *info = i->data; 267 | 268 | if (info->hash == pkg->hash && 269 | strcmp(info->name, pkg->name) == 0) { 270 | size_t stop, prev; 271 | 272 | /* remove from list and hash */ 273 | cache->list = alpm_list_remove_item(cache->list, i); 274 | if (data) { 275 | *data = info; 276 | } 277 | cache->hash_table[position] = NULL; 278 | free(i); 279 | cache->entries -= 1; 280 | 281 | /* Potentially move entries following removed entry to keep open 282 | * addressing collision resolution working. We start by finding the 283 | * next null bucket to know how far we have to look. */ 284 | stop = position + stride; 285 | while (stop >= cache->buckets) { 286 | stop -= cache->buckets; 287 | } 288 | while (cache->hash_table[stop] != NULL && stop != position) { 289 | stop += stride; 290 | while (stop >= cache->buckets) { 291 | stop -= cache->buckets; 292 | } 293 | } 294 | stop = (cache->buckets + stop - stride) % cache->buckets; 295 | 296 | /* We now search backwards from stop to position. If we find an 297 | * item that now hashes to position, we will move it, and then try 298 | * to plug the new hole we just opened up, until we finally don't 299 | * move anything. */ 300 | while ((prev = move_one_entry(cache, position, stop)) != position) { 301 | position = prev; 302 | } 303 | 304 | return cache; 305 | } 306 | 307 | position += stride; 308 | while (position >= cache->buckets) { 309 | position -= cache->buckets; 310 | } 311 | } 312 | 313 | return cache; 314 | } 315 | 316 | void pkgcache_free(struct pkgcache *cache) 317 | { 318 | if (cache != NULL) { 319 | size_t i; 320 | for (i = 0; i < cache->buckets; i++) { 321 | free(cache->hash_table[i]); 322 | } 323 | free(cache->hash_table); 324 | } 325 | free(cache); 326 | } 327 | 328 | struct pkg *pkgcache_find(struct pkgcache *cache, const char *name) 329 | { 330 | alpm_list_t *lp; 331 | size_t position; 332 | 333 | if (name == NULL || cache == NULL) { 334 | return NULL; 335 | } 336 | 337 | hash_t hash = sdbm(name); 338 | position = hash % cache->buckets; 339 | 340 | while ((lp = cache->hash_table[position]) != NULL) { 341 | struct pkg *info = lp->data; 342 | 343 | if (info->hash == hash && strcmp(info->name, name) == 0) { 344 | return info; 345 | } 346 | 347 | position += stride; 348 | while (position >= cache->buckets) { 349 | position -= cache->buckets; 350 | } 351 | } 352 | 353 | return NULL; 354 | } 355 | -------------------------------------------------------------------------------- /src/database.c: -------------------------------------------------------------------------------- 1 | #include "database.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "repose.h" 19 | #include "package.h" 20 | #include "pkgcache.h" 21 | #include "util.h" 22 | #include "desc.h" 23 | #include "buffer.h" 24 | #include "signing.h" 25 | 26 | struct database_reader { 27 | struct archive *archive; 28 | struct pkg *likely_pkg; 29 | time_t mtime; 30 | }; 31 | 32 | struct database_writer { 33 | struct archive *archive; 34 | struct archive_entry *entry; 35 | struct buffer buf; 36 | enum contents contents; 37 | int poolfd; 38 | }; 39 | 40 | /* The name, version and type fields all share the same memory */ 41 | struct entry_info { 42 | char *name; 43 | const char *type; 44 | const char *version; 45 | }; 46 | 47 | static char *sha256_fd(int fd) 48 | { 49 | SHA256_CTX ctx; 50 | unsigned char output[32]; 51 | 52 | SHA256_Init(&ctx); 53 | for (;;) { 54 | char buf[BUFSIZ]; 55 | ssize_t nbytes_r = read(fd, buf, sizeof(buf)); 56 | check_posix(nbytes_r, "failed to read file"); 57 | if (nbytes_r == 0) 58 | break; 59 | SHA256_Update(&ctx, buf, nbytes_r); 60 | } 61 | SHA256_Final(output, &ctx); 62 | 63 | return hex_representation(output, sizeof(output)); 64 | } 65 | 66 | static char *sha256_file(int dirfd, const char *filename) 67 | { 68 | _cleanup_close_ int fd = openat(dirfd, filename, O_RDONLY); 69 | check_posix(fd, "failed to open %s for sha256 checksum", filename); 70 | return sha256_fd(fd); 71 | } 72 | 73 | static int parse_database_pathname(const char *entryname, struct entry_info *entry) 74 | { 75 | entry->name = strdup(entryname); 76 | const char *slash = strchrnul(entry->name, '/'), *dash = slash; 77 | 78 | dash = memrchr(entry->name, '-', dash - entry->name); 79 | dash = memrchr(entry->name, '-', dash - entry->name); 80 | 81 | if (*dash != '-') 82 | return -EINVAL; 83 | 84 | entry->name[dash - entry->name] = entry->name[slash - entry->name] = '\0'; 85 | entry->type = slash ? slash + 1 : NULL; 86 | entry->version = &entry->name[dash - entry->name + 1]; 87 | 88 | return 0; 89 | } 90 | 91 | static inline void entry_info_free(struct entry_info *entry_info) 92 | { 93 | free(entry_info->name); 94 | } 95 | 96 | static struct pkg *get_package(struct database_reader *db, struct entry_info *entry_info, 97 | struct pkgcache **pkgcache, bool allocate) 98 | { 99 | struct pkg *pkg; 100 | 101 | if (db->likely_pkg) { 102 | hash_t pkgname_hash = sdbm(entry_info->name); 103 | if (pkgname_hash == db->likely_pkg->hash && streq(db->likely_pkg->name, entry_info->name)) 104 | return db->likely_pkg; 105 | } 106 | 107 | pkg = pkgcache_find(*pkgcache, entry_info->name); 108 | if (allocate && !pkg) { 109 | pkg = malloc(sizeof(struct pkg)); 110 | if (!pkg) 111 | return NULL; 112 | 113 | *pkg = (struct pkg){ 114 | .hash = sdbm(entry_info->name), 115 | .name = strdup(entry_info->name), 116 | .version = strdup(entry_info->version), 117 | .mtime = db->mtime 118 | }; 119 | 120 | *pkgcache = pkgcache_add_sorted(*pkgcache, pkg); 121 | } 122 | 123 | if (pkg) 124 | db->likely_pkg = pkg; 125 | 126 | return pkg; 127 | } 128 | 129 | static bool is_database_metadata(const char *filename) 130 | { 131 | static const char *metadata_names[] = { 132 | "desc", 133 | "depends", 134 | "files", 135 | NULL 136 | }; 137 | 138 | for (const char **n = metadata_names; *n; ++n) { 139 | if (streq(filename, *n)) 140 | return true; 141 | } 142 | 143 | return false; 144 | } 145 | 146 | static int parse_database_entry(struct database_reader *db, struct archive_entry *entry, 147 | struct pkgcache **pkgcache) 148 | { 149 | const char *pathname = archive_entry_pathname(entry); 150 | struct entry_info entry_info; 151 | int ret = 0; 152 | 153 | if (parse_database_pathname(pathname, &entry_info) < 0) { 154 | ret = -1; 155 | goto cleanup; 156 | } 157 | 158 | if (entry_info.type && is_database_metadata(entry_info.type)) { 159 | bool allocate = !streq(entry_info.type, "files"); 160 | 161 | struct pkg *pkg = get_package(db, &entry_info, pkgcache, allocate); 162 | if (allocate && !pkg) { 163 | ret = -1; 164 | goto cleanup; 165 | } 166 | 167 | if (pkg && read_desc(db->archive, pkg) < 0) { 168 | errx(EXIT_FAILURE, "failed to parse %s for %s", entry_info.type, pathname); 169 | } 170 | } 171 | 172 | cleanup: 173 | entry_info_free(&entry_info); 174 | return ret; 175 | } 176 | 177 | int load_database(int fd, struct pkgcache **pkgcache) 178 | { 179 | int ret = 0; 180 | 181 | struct stat st; 182 | check_posix(fstat(fd, &st), "failed to stat database"); 183 | 184 | struct archive_entry *entry; 185 | struct database_reader db = { 186 | .archive = archive_read_new(), 187 | .mtime = st.st_mtime 188 | }; 189 | 190 | archive_read_support_filter_all(db.archive); 191 | archive_read_support_format_all(db.archive); 192 | 193 | if (archive_read_open_fd(db.archive, fd, 8192) != ARCHIVE_OK) { 194 | ret = -1; 195 | goto cleanup; 196 | } 197 | 198 | while (archive_read_next_header(db.archive, &entry) == ARCHIVE_OK) { 199 | const mode_t mode = archive_entry_mode(entry); 200 | 201 | if (S_ISREG(mode) && parse_database_entry(&db, entry, pkgcache) < 0) { 202 | ret = -1; 203 | goto cleanup; 204 | } 205 | } 206 | 207 | cleanup: 208 | archive_read_close(db.archive); 209 | archive_read_free(db.archive); 210 | return ret; 211 | } 212 | 213 | static void archive_entry_populate(struct archive_entry *e, unsigned int type, 214 | const char *path, mode_t mode) 215 | { 216 | time_t now = time(NULL); 217 | 218 | archive_entry_set_pathname(e, path); 219 | archive_entry_set_filetype(e, type); 220 | archive_entry_set_perm(e, mode); 221 | archive_entry_set_uname(e, "repose"); 222 | archive_entry_set_gname(e, "repose"); 223 | archive_entry_set_ctime(e, now, 0); 224 | archive_entry_set_mtime(e, now, 0); 225 | archive_entry_set_atime(e, now, 0); 226 | } 227 | 228 | static void commit_entry(struct database_writer *db, const char *name, 229 | const char *folder) 230 | { 231 | if (!db->buf.len) 232 | return; 233 | 234 | _cleanup_free_ char *entrypath = joinstring(folder, "/", name, NULL); 235 | 236 | archive_entry_populate(db->entry, AE_IFREG, entrypath, 0644); 237 | archive_entry_set_size(db->entry, db->buf.len); 238 | archive_write_header(db->archive, db->entry); 239 | archive_write_data(db->archive, db->buf.data, db->buf.len); 240 | archive_entry_clear(db->entry); 241 | buffer_clear(&db->buf); 242 | } 243 | 244 | static void write_list(struct buffer *buf, const char *header, const alpm_list_t *lst) 245 | { 246 | if (lst == NULL) 247 | return; 248 | 249 | buffer_printf(buf, "%%%s%%\n", header); 250 | for (; lst; lst = lst->next) 251 | buffer_printf(buf, "%s\n", (const char *)lst->data); 252 | buffer_putc(buf, '\n'); 253 | } 254 | 255 | static void write_string(struct buffer *buf, const char *header, const char *str) 256 | { 257 | if (str == NULL) 258 | return; 259 | 260 | buffer_printf(buf, "%%%s%%\n%s\n\n", header, str); 261 | } 262 | 263 | static void write_size(struct buffer *buf, const char *header, size_t val) 264 | { 265 | buffer_printf(buf, "%%%s%%\n%zd\n\n", header, val); 266 | } 267 | 268 | static void write_time(struct buffer *buf, const char *header, time_t val) 269 | { 270 | buffer_printf(buf, "%%%s%%\n%ld\n\n", header, val); 271 | } 272 | 273 | #define write_entry(buf, header, val) _Generic((val), \ 274 | alpm_list_t *: write_list, \ 275 | char *: write_string, \ 276 | size_t: write_size, \ 277 | time_t: write_time)(buf, header, val) 278 | 279 | static void compile_desc_entry(struct database_writer *db, struct pkg *pkg) 280 | { 281 | write_entry(&db->buf, "FILENAME", pkg->filename); 282 | write_entry(&db->buf, "NAME", pkg->name); 283 | write_entry(&db->buf, "BASE", pkg->base); 284 | write_entry(&db->buf, "VERSION", pkg->version); 285 | write_entry(&db->buf, "DESC", pkg->desc); 286 | write_entry(&db->buf, "GROUPS", pkg->groups); 287 | write_entry(&db->buf, "CSIZE", pkg->size); 288 | write_entry(&db->buf, "ISIZE", pkg->isize); 289 | 290 | if (pkg->base64sig) { 291 | write_entry(&db->buf, "PGPSIG", pkg->base64sig); 292 | } else { 293 | if (!pkg->sha256sum) 294 | pkg->sha256sum = sha256_file(db->poolfd, pkg->filename); 295 | write_entry(&db->buf, "SHA256SUM", pkg->sha256sum); 296 | } 297 | 298 | write_entry(&db->buf, "URL", pkg->url); 299 | write_entry(&db->buf, "LICENSE", pkg->licenses); 300 | write_entry(&db->buf, "ARCH", pkg->arch); 301 | write_entry(&db->buf, "BUILDDATE", pkg->builddate); 302 | write_entry(&db->buf, "PACKAGER", pkg->packager); 303 | write_entry(&db->buf, "REPLACES", pkg->replaces); 304 | } 305 | 306 | static void compile_depends_entry(struct database_writer *db, struct pkg *pkg) 307 | { 308 | write_entry(&db->buf, "DEPENDS", pkg->depends); 309 | write_entry(&db->buf, "CONFLICTS", pkg->conflicts); 310 | write_entry(&db->buf, "PROVIDES", pkg->provides); 311 | write_entry(&db->buf, "OPTDEPENDS", pkg->optdepends); 312 | write_entry(&db->buf, "MAKEDEPENDS", pkg->makedepends); 313 | write_entry(&db->buf, "CHECKDEPENDS", pkg->checkdepends); 314 | } 315 | 316 | static void compile_files_entry(struct database_writer *db, struct pkg *pkg) 317 | { 318 | if (!pkg->files) { 319 | _cleanup_close_ int pkgfd = openat(db->poolfd, pkg->filename, O_RDONLY); 320 | if (pkgfd < 0 && errno != ENOENT) 321 | err(EXIT_FAILURE, "failed to open %s", pkg->filename); 322 | 323 | load_package_files(pkg, pkgfd); 324 | } 325 | 326 | write_entry(&db->buf, "FILES", pkg->files); 327 | } 328 | 329 | static void compile_database_entry(struct database_writer *db, struct pkg *pkg) 330 | { 331 | _cleanup_free_ char *folder = joinstring(pkg->name, "-", pkg->version, NULL); 332 | 333 | archive_entry_populate(db->entry, AE_IFDIR, folder, 0755); 334 | archive_write_header(db->archive, db->entry); 335 | archive_entry_clear(db->entry); 336 | 337 | if (db->contents & DB_DESC) { 338 | compile_desc_entry(db, pkg); 339 | commit_entry(db, "desc", folder); 340 | } 341 | if (db->contents & DB_DEPENDS) { 342 | compile_depends_entry(db, pkg); 343 | commit_entry(db, "depends", folder); 344 | } 345 | if (db->contents & DB_FILES) { 346 | compile_files_entry(db, pkg); 347 | commit_entry(db, "files", folder); 348 | } 349 | if (db->contents & DB_DELTAS) { 350 | write_entry(&db->buf, "DELTAS", pkg->deltas); 351 | commit_entry(db, "deltas", folder); 352 | } 353 | } 354 | 355 | static int compile_database(struct repo *repo, const char *repo_name, 356 | enum contents what) 357 | { 358 | int ret = 0; 359 | _cleanup_close_ int dbfd = openat(repo->rootfd, repo_name, 360 | O_CREAT | O_WRONLY | O_TRUNC, 0644); 361 | if (dbfd < 0) 362 | return -1; 363 | 364 | struct database_writer db = { 365 | .archive = archive_write_new(), 366 | .entry = archive_entry_new(), 367 | .buf = {0}, 368 | .contents = what, 369 | .poolfd = repo->poolfd, 370 | }; 371 | 372 | archive_write_add_filter(db.archive, config.compression); 373 | archive_write_set_format_pax_restricted(db.archive); 374 | 375 | if (archive_write_open_fd(db.archive, dbfd) < 0) { 376 | ret = -1; 377 | goto cleanup; 378 | } 379 | 380 | archive_entry_populate(db.entry, AE_IFDIR, "", 0755); 381 | archive_write_header(db.archive, db.entry); 382 | archive_entry_clear(db.entry); 383 | 384 | /* The files database can get very, very large. Lets allocate a 385 | * 2MiB buffer so we have plenty of room and avoid reallocation. */ 386 | buffer_reserve(&db.buf, 0x200000); 387 | 388 | const alpm_list_t *node; 389 | for (node = repo->cache->list; node; node = node->next) { 390 | struct pkg *pkg = node->data; 391 | compile_database_entry(&db, pkg); 392 | } 393 | 394 | archive_write_close(db.archive); 395 | buffer_release(&db.buf); 396 | 397 | cleanup: 398 | archive_entry_free(db.entry); 399 | archive_write_free(db.archive); 400 | return ret; 401 | } 402 | 403 | int write_database(struct repo *repo, const char *repo_name, enum contents what) 404 | { 405 | trace("writing %s...\n", repo_name); 406 | check_posix(compile_database(repo, repo_name, what), 407 | "failed to write %s database", repo_name); 408 | 409 | if (config.sign) 410 | gpgme_sign(repo->rootfd, repo_name, NULL); 411 | 412 | return 0; 413 | } 414 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /src/repose.c: -------------------------------------------------------------------------------- 1 | #include "repose.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "database.h" 24 | #include "filecache.h" 25 | #include "package.h" 26 | #include "pkgcache.h" 27 | #include "filters.h" 28 | #include "signing.h" 29 | #include "base64.h" 30 | #include "util.h" 31 | 32 | struct config config = {0}; 33 | 34 | void trace(const char *fmt, ...) 35 | { 36 | if (config.verbose) { 37 | va_list ap; 38 | 39 | va_start(ap, fmt); 40 | vprintf(fmt, ap); 41 | va_end(ap); 42 | } 43 | } 44 | 45 | static _noreturn_ void usage(FILE *out) 46 | { 47 | fprintf(out, "usage: %s [options] [pkgs|deltas ...]\n", program_invocation_short_name); 48 | fputs("Options\n" 49 | " -h, --help display this help and exit\n" 50 | " -V, --version display version\n" 51 | " -v, --verbose verbose output\n" 52 | " -f, --files also build the .files database\n" 53 | " -l, --list list packages in the repository\n" 54 | " -d, --drop drop the specified package from the db\n" 55 | " -r, --root=PATH set the root for the repository\n" 56 | " -p, --pool=PATH set the pool to find packages in\n" 57 | " -m, --arch=ARCH the architecture of the database\n" 58 | " -s, --sign create a database signature\n" 59 | " -j, --bzip2 filter the archive through bzip2\n" 60 | " -J, --xz filter the archive through xz\n" 61 | " -z, --gzip filter the archive through gzip\n" 62 | " -Z, --compress filter the archive through compress\n" 63 | " --reflink make repose make reflinks instead of symlinks\n" 64 | " --rebuild force rebuild the repo\n", out); 65 | 66 | exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); 67 | } 68 | 69 | static _noreturn_ void elephant(void) 70 | { 71 | static const char *big_elephant = 72 | "ICAgICBfXwogICAgJy4gXAogICAgICctIFwKICAgICAgLyAvXyAgICAgICAgIC4tLS0uCiAgICAg" 73 | "LyB8IFxcLC5cLy0tLi8vICAgICkKICAgICB8ICBcLy8gICAgICAgICkvICAvCiAgICAgIFwgICcg" 74 | "XiBeICAgIC8gICAgKV9fX18uLS0tLS4uICA2CiAgICAgICAnLl9fX18uICAgIC5fX18vICAgICAg" 75 | "ICAgICAgXC5fKQogICAgICAgICAgLlwvLiAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAg" 76 | "ICAgJ1wgICAgICAgICAgICAgICAgICAgICAgIC8KICAgICAgICAgICBfLyBcLyAgICApLiAgICAg" 77 | "ICAgKSAgICAoCiAgICAgICAgICAvIyAgLiEgICAgfCAgICAgICAgL1wgICAgLwogICAgICAgICAg" 78 | "XCAgQy8vICMgIC8nLS0tLS0nJy8gIyAgLwogICAgICAgLiAgICdDLyB8ICAgIHwgICAgfCAgIHwg" 79 | "ICAgfG1yZiAgLAogICAgICAgXCksIC4uIC4nT09PLScuIC4uJ09PTydPT08tJy4gLi5cKCw="; 80 | 81 | static const char *small_elephant = 82 | "ICAgIF8gICAgXwogICAvIFxfXy8gXF9fX19fCiAgLyAgLyAgXCAgXCAgICBgXAogICkgIFwnJy8g" 83 | "ICggICAgIHxcCiAgYFxfXykvX18vJ19cICAvIGAKICAgICAvL198X3x+fF98X3wKICAgICBeIiIn" 84 | "IicgIiInIic="; 85 | 86 | char *data = NULL; 87 | 88 | switch (srand(time(NULL)), rand() % 2) { 89 | case 0: 90 | data = base64_decode((const unsigned char *)big_elephant, strlen(big_elephant), NULL); 91 | break; 92 | case 1: 93 | data = base64_decode((const unsigned char *)small_elephant, strlen(small_elephant), NULL); 94 | break; 95 | default: 96 | errx(EXIT_FAILURE, "failed to find elephant"); 97 | break; 98 | } 99 | 100 | puts(data); 101 | exit(EXIT_SUCCESS); 102 | } 103 | 104 | static int clone_file(const struct repo *repo, const char *filename) 105 | { 106 | _cleanup_close_ int src = openat(repo->poolfd, filename, O_RDONLY); 107 | if (src < 0) 108 | return src; 109 | 110 | _cleanup_close_ int dest = openat(repo->rootfd, filename, O_WRONLY | O_TRUNC, 0664); 111 | if (dest < 0 && errno == ENOENT) { 112 | dest = openat(repo->rootfd, filename, O_WRONLY | O_CREAT, 0664); 113 | } 114 | if (dest < 0) 115 | return dest; 116 | 117 | return ioctl(dest, BTRFS_IOC_CLONE, src); 118 | } 119 | 120 | static int symlink_file(const struct repo *repo, const char *path1, const char *path2) 121 | { 122 | _cleanup_free_ char* canonical_path1 = canonicalize_file_name(path1); 123 | int ret = symlinkat(canonical_path1, repo->rootfd, path2); 124 | if (ret < 0 && errno == EEXIST) 125 | return 0; 126 | return ret; 127 | } 128 | 129 | static inline int unlink_file(const struct repo *repo, const char *filename) 130 | { 131 | struct stat st; 132 | if (fstatat(repo->rootfd, filename, &st, AT_SYMLINK_NOFOLLOW) < 0) 133 | return errno != ENOENT ? -1 : 0; 134 | if (S_ISLNK(st.st_mode)) 135 | return unlinkat(repo->rootfd, filename, 0); 136 | return 0; 137 | } 138 | 139 | static int clone_pkg(const struct repo *repo, const struct pkg *pkg) 140 | { 141 | _cleanup_free_ char *signame = joinstring(pkg->filename, ".sig", NULL); 142 | if (clone_file(repo, signame) < 0 && errno != ENOENT) 143 | err(1, "failed to clone signature %s", signame); 144 | 145 | return clone_file(repo, pkg->filename); 146 | } 147 | 148 | static int symlink_pkg(const struct repo *repo, const struct pkg *pkg) 149 | { 150 | _cleanup_free_ char *link = joinstring(repo->pool, "/", pkg->filename, NULL); 151 | _cleanup_free_ char *siglink = joinstring(link, ".sig", NULL); 152 | 153 | if (access(siglink, F_OK) != -1) { 154 | _cleanup_free_ char *signame = joinstring(pkg->filename, ".sig", NULL); 155 | if (symlink_file(repo, siglink, signame) < 0 && errno != EEXIST) 156 | err(1, "failed to symlink signature %s", signame); 157 | } 158 | 159 | return symlink_file(repo, link, pkg->filename); 160 | } 161 | 162 | static inline void link_pkg(const struct repo *repo, const struct pkg *pkg) 163 | { 164 | if (config.reflink) { 165 | check_posix(clone_pkg(repo, pkg), 166 | "failed to make reflink for %s", pkg->filename); 167 | } else { 168 | check_posix(symlink_pkg(repo, pkg), 169 | "failed to make symlink for %s", pkg->filename); 170 | } 171 | } 172 | 173 | static inline int unlink_pkg(const struct repo *repo, const struct pkg *pkg) 174 | { 175 | int ret = unlink_file(repo, pkg->filename); 176 | if (ret < 0) 177 | return ret; 178 | 179 | _cleanup_free_ char *signame = joinstring(pkg->filename, ".sig", NULL); 180 | return unlink_file(repo, signame); 181 | } 182 | 183 | static void link_db(struct repo *repo) 184 | { 185 | if (!repo->pool) 186 | return; 187 | 188 | alpm_list_t *node; 189 | for (node = repo->cache->list; node; node = node->next) 190 | link_pkg(repo, node->data); 191 | } 192 | 193 | static void drop_from_repo(struct repo *repo, alpm_list_t *targets) 194 | { 195 | if (!targets || !repo->cache) 196 | return; 197 | 198 | alpm_list_t *node; 199 | for (node = repo->cache->list; node; node = node->next) { 200 | struct pkg *pkg = node->data; 201 | 202 | if (match_targets(pkg, targets)) { 203 | trace("dropping %s\n", pkg->name); 204 | 205 | repo->cache = pkgcache_remove(repo->cache, pkg, NULL); 206 | unlink_pkg(repo, pkg); 207 | package_free(pkg); 208 | repo->dirty = true; 209 | } 210 | } 211 | } 212 | 213 | static void list_repo(struct repo *repo) 214 | { 215 | alpm_list_t *node; 216 | for (node = repo->cache->list; node; node = node->next) { 217 | struct pkg *pkg = node->data; 218 | 219 | printf("%s %s\n", pkg->name, pkg->version); 220 | } 221 | } 222 | 223 | static void reduce_repo(struct repo *repo) 224 | { 225 | if (!repo->cache) 226 | return; 227 | 228 | alpm_list_t *node; 229 | for (node = repo->cache->list; node; node = node->next) { 230 | struct pkg *pkg = node->data; 231 | 232 | if (faccessat(repo->poolfd, pkg->filename, F_OK, 0) < 0) { 233 | if (errno != ENOENT) 234 | err(EXIT_FAILURE, "couldn't access package %s", pkg->filename); 235 | 236 | trace("dropping %s\n", pkg->name); 237 | repo->cache = pkgcache_remove(repo->cache, pkg, NULL); 238 | unlink_pkg(repo, pkg); 239 | package_free(pkg); 240 | repo->dirty = true; 241 | } 242 | } 243 | } 244 | 245 | static void update_repo(struct repo *repo, struct pkgcache *src) 246 | { 247 | if (!repo->cache) 248 | repo->cache = pkgcache_create(src->entries); 249 | 250 | alpm_list_t *node; 251 | for (node = src->list; node; node = node->next) { 252 | struct pkg *pkg = node->data; 253 | struct pkg *old = pkgcache_find(repo->cache, pkg->name); 254 | 255 | if (!old) { 256 | /* The package isn't already in the database. Just add it */ 257 | trace("adding %s %s\n", pkg->name, pkg->version); 258 | repo->cache = pkgcache_add(repo->cache, pkg); 259 | repo->dirty = true; 260 | continue; 261 | } 262 | 263 | switch (alpm_pkg_vercmp(pkg->version, old->version)) { 264 | case 1: 265 | /* The filecache package has a newer version than the 266 | package in the database. */ 267 | trace("updating %s %s => %s\n", pkg->name, old->version, pkg->version); 268 | break; 269 | case 0: 270 | /* The filecache package has the same version as the 271 | package in the database. Only update the package if the 272 | file is newer than the database */ 273 | if (pkg->mtime > old->mtime) { 274 | trace("updating %s %s [newer timestamp]\n", pkg->name, pkg->version); 275 | } else if (pkg->builddate > old->builddate) { 276 | trace("updating %s %s [newer build]\n", pkg->name, pkg->version); 277 | } else if (old->base64sig == NULL && pkg->base64sig) { 278 | trace("adding signature for %s\n", pkg->name); 279 | } else { 280 | continue; 281 | } 282 | break; 283 | default: 284 | continue; 285 | } 286 | 287 | repo->cache = pkgcache_replace(repo->cache, pkg, old); 288 | unlink_pkg(repo, pkg); 289 | package_free(old); 290 | repo->dirty = true; 291 | } 292 | } 293 | 294 | static alpm_list_t *parse_targets(char *targets[], int count) 295 | { 296 | int i; 297 | alpm_list_t *list = NULL; 298 | for (i = 0; i < count; ++i) 299 | list = alpm_list_add(list, targets[i]); 300 | return list; 301 | } 302 | 303 | static int load_db(struct repo *repo, const char *filename) 304 | { 305 | _cleanup_close_ int dbfd = openat(repo->rootfd, filename, O_RDONLY); 306 | if (dbfd < 0) { 307 | if (errno != ENOENT) 308 | err(EXIT_FAILURE, "failed to open database %s", filename); 309 | return -1; 310 | } 311 | 312 | if (load_database(dbfd, &repo->cache) < 0) { 313 | warn("failed to open %s database", filename); 314 | return -1; 315 | } 316 | 317 | return 0; 318 | } 319 | 320 | static void check_signature(struct repo *repo, const char *name) 321 | { 322 | _cleanup_free_ char *sig = joinstring(name, ".sig", NULL); 323 | 324 | if (faccessat(repo->rootfd, sig, F_OK, 0) == 0) { 325 | if (gpgme_verify(repo->rootfd, name) < 0) { 326 | errx(EXIT_FAILURE, "repo signature is invalid or corrupt!"); 327 | } else { 328 | trace("found a valid signature, will resign...\n"); 329 | config.sign = true; 330 | } 331 | } else if (errno != ENOENT) { 332 | err(EXIT_FAILURE, "countn't access %s", name); 333 | } 334 | } 335 | 336 | static int init_repo(struct repo *repo, const char *reponame, bool files, 337 | bool load_cache) 338 | { 339 | repo->rootfd = open(repo->root, O_RDONLY | O_DIRECTORY); 340 | check_posix(repo->rootfd, "failed to open root directory %s", repo->root); 341 | 342 | if (repo->pool) { 343 | repo->poolfd = open(repo->pool, O_RDONLY | O_DIRECTORY); 344 | check_posix(repo->poolfd, "failed to open pool directory %s", repo->pool); 345 | } else { 346 | repo->poolfd = repo->rootfd; 347 | } 348 | 349 | repo->dbname = joinstring(reponame, ".db", NULL); 350 | repo->filesname = joinstring(reponame, ".files", NULL); 351 | 352 | if (!files && faccessat(repo->rootfd, repo->filesname, F_OK, 0) < 0) { 353 | if (errno == ENOENT) { 354 | free(repo->filesname); 355 | repo->filesname = NULL; 356 | } else { 357 | err(EXIT_FAILURE, "couldn't access %s", repo->filesname); 358 | } 359 | } 360 | 361 | if (config.sign) { 362 | check_signature(repo, repo->dbname); 363 | if (repo->filesname) 364 | check_signature(repo, repo->filesname); 365 | } 366 | 367 | if (load_cache) { 368 | repo->cache = pkgcache_create(100); 369 | 370 | if (load_db(repo, repo->dbname) < 0) { 371 | /* Database doesn't exist. Mark it dirty so we force its 372 | generation */ 373 | repo->dirty = true; 374 | return -1; 375 | } 376 | 377 | if (repo->filesname) 378 | return load_db(repo, repo->filesname); 379 | } 380 | 381 | return 0; 382 | } 383 | 384 | static alpm_list_t *load_manifest(struct repo *repo, const char *reponame) 385 | { 386 | _cleanup_free_ char *manifest = joinstring(reponame, ".manifest", NULL); 387 | _cleanup_fclose_ FILE *fp = fopenat(repo->rootfd, manifest, "r"); 388 | if (fp == NULL) 389 | return NULL; 390 | 391 | alpm_list_t *list = NULL; 392 | for (;;) { 393 | errno = 0; 394 | char *line = NULL; 395 | ssize_t nbytes_r = getline(&line, &(size_t){ 0 }, fp); 396 | if (nbytes_r < 0) { 397 | if (errno != 0) 398 | err(EXIT_FAILURE, "Failed to read manifest file"); 399 | break; 400 | } 401 | 402 | line[nbytes_r - 1] = 0; 403 | if (line[0]) 404 | list = alpm_list_add(list, line); 405 | } 406 | return list; 407 | } 408 | 409 | static char *get_rootname(char *name) 410 | { 411 | char *sep = strrchr(name, '.'); 412 | if (sep && streq(sep, ".db")) 413 | *sep = 0; 414 | return name; 415 | } 416 | 417 | int main(int argc, char *argv[]) 418 | { 419 | const char *rootname; 420 | bool files = false, rebuild = false, drop = false, list = false; 421 | 422 | setlocale(LC_ALL, ""); 423 | 424 | static const struct option opts[] = { 425 | { "help", no_argument, 0, 'h' }, 426 | { "version", no_argument, 0, 'V' }, 427 | { "drop", no_argument, 0, 'd' }, 428 | { "list", no_argument, 0, 'l' }, 429 | { "verbose", no_argument, 0, 'v' }, 430 | { "files", no_argument, 0, 'f' }, 431 | { "sign", no_argument, 0, 's' }, 432 | { "root", required_argument, 0, 'r' }, 433 | { "pool", required_argument, 0, 'p' }, 434 | { "arch", required_argument, 0, 'm' }, 435 | { "bzip2", no_argument, 0, 'j' }, 436 | { "xz", no_argument, 0, 'J' }, 437 | { "gzip", no_argument, 0, 'z' }, 438 | { "compress", no_argument, 0, 'Z' }, 439 | { "reflink", no_argument, 0, 0x100 }, 440 | { "rebuild", no_argument, 0, 0x101 }, 441 | { "elephant", no_argument, 0, 0x102 }, 442 | { 0, 0, 0, 0 } 443 | }; 444 | 445 | struct repo repo = { .root = "." }; 446 | 447 | for (;;) { 448 | int opt = getopt_long(argc, argv, "hVvdlfsr:p:m:jJzZ", opts, NULL); 449 | if (opt < 0) 450 | break; 451 | 452 | switch (opt) { 453 | case 'h': 454 | usage(stdout); 455 | break; 456 | case 'V': 457 | printf("%s %s\n", program_invocation_short_name, REPOSE_VERSION); 458 | exit(EXIT_SUCCESS); 459 | case 'v': 460 | config.verbose += 1; 461 | break; 462 | case 'd': 463 | drop = true; 464 | break; 465 | case 'l': 466 | list = true; 467 | break; 468 | case 'f': 469 | files = true; 470 | break; 471 | case 's': 472 | config.sign = true; 473 | break; 474 | case 'r': 475 | repo.root = optarg; 476 | break; 477 | case 'p': 478 | repo.pool = optarg; 479 | break; 480 | case 'm': 481 | config.arch = optarg; 482 | break; 483 | case 'j': 484 | config.compression = ARCHIVE_FILTER_BZIP2; 485 | break; 486 | case 'J': 487 | config.compression = ARCHIVE_FILTER_XZ; 488 | break; 489 | case 'z': 490 | config.compression = ARCHIVE_FILTER_GZIP; 491 | break; 492 | case 'Z': 493 | config.compression = ARCHIVE_FILTER_COMPRESS; 494 | break; 495 | case 0x100: 496 | config.reflink = true; 497 | break; 498 | case 0x101: 499 | rebuild = true; 500 | repo.dirty = true; 501 | break; 502 | case 0x102: 503 | elephant(); 504 | break; 505 | } 506 | } 507 | 508 | argv += optind; 509 | argc -= optind; 510 | 511 | if (argc == 0) 512 | errx(1, "incorrect number of arguments provided"); 513 | 514 | if (!config.arch) { 515 | struct utsname uts; 516 | uname(&uts); 517 | config.arch = strdup(uts.machine); 518 | } 519 | 520 | if (list && drop) 521 | errx(EXIT_FAILURE, "List and drop operations are mutually exclusive"); 522 | 523 | if (rebuild && (list || drop)) { 524 | fprintf(stderr, "Can't rebuild while performing a list or drop operation.\n" 525 | "Ignoring the --rebuild flag.\n"); 526 | rebuild = false; 527 | } 528 | 529 | rootname = get_rootname(*argv++), --argc; 530 | int ret = init_repo(&repo, rootname, files, !rebuild); 531 | if (list) { 532 | check_posix(ret, "failed to open database %s.db", rootname); 533 | list_repo(&repo); 534 | return 0; 535 | } 536 | 537 | alpm_list_t *targets = parse_targets(argv, argc); 538 | 539 | if (drop) { 540 | drop_from_repo(&repo, targets); 541 | } else { 542 | if (argc == 0) { 543 | targets = load_manifest(&repo, rootname); 544 | } 545 | 546 | struct pkgcache *filecache = get_filecache(repo.poolfd, targets, config.arch); 547 | check_null(filecache, "failed to get filecache"); 548 | 549 | reduce_repo(&repo); 550 | update_repo(&repo, filecache); 551 | } 552 | 553 | if (!repo.dirty) { 554 | trace("repo does not need updating\n"); 555 | } else { 556 | write_database(&repo, repo.dbname, DB_DESC | DB_DEPENDS); 557 | 558 | if (repo.filesname) { 559 | write_database(&repo, repo.filesname, DB_FILES); 560 | } 561 | 562 | link_db(&repo); 563 | } 564 | } 565 | --------------------------------------------------------------------------------