From b0d07b91422d2e69bd4393fdb0d033d445052cdd Mon Sep 17 00:00:00 2001 From: Jonas 'Sortie' Termansen Date: Tue, 26 Mar 2013 12:40:43 +0100 Subject: [PATCH] Add the Tix package management system. --- Makefile | 4 +- README | 7 +- tix/.gitignore | 11 + tix/Makefile | 44 ++ tix/porttix-create.cpp | 436 ++++++++++++++ tix/srctix-create.cpp | 321 +++++++++++ tix/tix-build.cpp | 713 +++++++++++++++++++++++ tix/tix-collection.cpp | 175 ++++++ tix/tix-eradicate-libtool-la | 28 + tix/tix-execdiff.cpp | 224 ++++++++ tix/tix-execpatch.cpp | 246 ++++++++ tix/tix-install.cpp | 342 +++++++++++ tix/tix-object-insert.cpp | 427 ++++++++++++++ tix/tix-rmpatch.cpp | 240 ++++++++ tix/tix.cpp | 285 ++++++++++ tix/util.h | 1033 ++++++++++++++++++++++++++++++++++ 16 files changed, 4532 insertions(+), 4 deletions(-) create mode 100644 tix/.gitignore create mode 100644 tix/Makefile create mode 100644 tix/porttix-create.cpp create mode 100644 tix/srctix-create.cpp create mode 100644 tix/tix-build.cpp create mode 100644 tix/tix-collection.cpp create mode 100755 tix/tix-eradicate-libtool-la create mode 100644 tix/tix-execdiff.cpp create mode 100644 tix/tix-execpatch.cpp create mode 100644 tix/tix-install.cpp create mode 100644 tix/tix-object-insert.cpp create mode 100644 tix/tix-rmpatch.cpp create mode 100644 tix/tix.cpp create mode 100644 tix/util.h diff --git a/Makefile b/Makefile index 92d27b60..fe550814 100644 --- a/Makefile +++ b/Makefile @@ -34,11 +34,13 @@ all: sysroot build-tools: $(MAKE) -C mkinitrd $(MAKE) -C mxmpp + $(MAKE) -C tix .PHONY: install-build-tools install-build-tools: $(MAKE) -C mkinitrd install $(MAKE) -C mxmpp install + $(MAKE) -C tix install .PHONY: sysroot-fsh sysroot-fsh: @@ -99,7 +101,7 @@ sysroot: sysroot-system sysroot-source sysroot-overlay sysroot-home-directory .PHONY: clean-core clean-core: - (for D in $(MODULES); do $(MAKE) clean $(SUBMAKE_OPTIONS) --directory $$D || exit $$?; done) + (for D in $(MODULES) tix; do $(MAKE) clean $(SUBMAKE_OPTIONS) --directory $$D || exit $$?; done) .PHONY: clean-builds clean-builds: diff --git a/README b/README index 0126100d..3a23f0fb 100644 --- a/README +++ b/README @@ -91,9 +91,10 @@ The build scripts might not contain a copyright license in which case they are covered by the standard license for the software component they relate to. Unless the license header in the source code states otherwise, the Sortix -kernel, the filesystem servers, the initrd tools, the utilities, the games, and -the benchmark programs are licensed under the GNU General Public License, either -version 3 or (at your option) any later version. +kernel, the filesystem servers, the initrd tools, the utilities, the games, the +benchmark programs, and the tix package management programs are licensed under +the GNU General Public License, either version 3 or (at your option) any later +version. Unless the license header in the source code states otherwise, the libc library and the libdispd library are licensed under the GNU Lesser General Public diff --git a/tix/.gitignore b/tix/.gitignore new file mode 100644 index 00000000..5117883c --- /dev/null +++ b/tix/.gitignore @@ -0,0 +1,11 @@ +*.tix* +porttix-create +srctix-create +tix +tix-build +tix-collection +tix-execdiff +tix-execpatch +tix-install +tix-object-insert +tix-rmpatch diff --git a/tix/Makefile b/tix/Makefile new file mode 100644 index 00000000..1d3e03d1 --- /dev/null +++ b/tix/Makefile @@ -0,0 +1,44 @@ +include ../compiler.mak +include ../version.mak +include ../dirs.mak + +OPTLEVEL?=-g -O2 +CPPFLAGS?= +CXXFLAGS?=$(OPTLEVEL) + +CPPFLAGS:=$(CPPFLAGS) +CXXFLAGS:=$(CXXFLAGS) -Wall -Wextra -fno-exceptions -fno-rtti + +BINARIES:=\ +porttix-create \ +srctix-create \ +tix \ +tix-build \ +tix-collection \ +tix-execdiff \ +tix-execpatch \ +tix-install \ +tix-object-insert \ +tix-rmpatch \ + +PROGRAMS:=\ +$(BINARIES) \ +tix-eradicate-libtool-la \ + +LIBS:=-lnettle + +all: $(PROGRAMS) + +.PHONY: all install clean + +%: %.cpp util.h + $(CXX) -std=gnu++11 $(CPPFLAGS) $(CXXFLAGS) $< -o $@ $(LIBS) + +$(DESTDIR)$(BINDIR): + mkdir -p $@ + +install: all $(DESTDIR)$(BINDIR) + install $(PROGRAMS) $(DESTDIR)$(BINDIR) + +clean: + rm -f $(BINARIES) diff --git a/tix/porttix-create.cpp b/tix/porttix-create.cpp new file mode 100644 index 00000000..e3eccbbf --- /dev/null +++ b/tix/porttix-create.cpp @@ -0,0 +1,436 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + This file is part of Tix. + + Tix is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or (at your option) any later + version. + + Tix is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + details. + + You should have received a copy of the GNU General Public License along with + Tix. If not, see . + + porttix-create.cpp + Creates a port tix by generating patches using source code and tarballs. + +*******************************************************************************/ + +#define __STDC_CONSTANT_MACROS +#define __STDC_LIMIT_MACROS + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" + +int redirect(const char* path, int flags, mode_t mode = 0) +{ + int fd = open(path, flags, mode); + if ( fd < 0 ) + return -1; + dup2(fd, 1); + close(fd); + return 0; +} + +void help(FILE* fp, const char* argv0) +{ + fprintf(fp, "Usage: %s [OPTION]... --tarball=TARBALL --normalized=NORMALIZED SOURCE-TIX\n", argv0); + fprintf(fp, "Creates a port tix by generating patches using source code and tarballs.\n"); +} + +void version(FILE* fp, const char* argv0) +{ + help(fp, argv0); +} + +int main(int argc, char* argv[]) +{ + char* cp = strdup(getenv_def("CP", "cp")); + char* diff = strdup(getenv_def("DIFF", "diff")); + char* input_normalized_path = NULL; + char* input_tarball_path = NULL; + char* output_directory = strdup("."); + char* output = NULL; + char* tar = strdup(getenv_def("TAR", "tar")); + char* tix_execdiff = strdup(getenv_def("TIX_EXECDIFF", "tix-execdiff")); + char* tmp = strdup(getenv_def("TMP", "/tmp")); + + const char* argv0 = argv[0]; + for ( int i = 0; i < argc; i++ ) + { + const char* arg = argv[i]; + if ( arg[0] != '-' ) + continue; + argv[i] = NULL; + if ( !strcmp(arg, "--") ) + break; + if ( arg[1] != '-' ) + { + while ( char c = *++arg ) switch ( c ) + { + default: + fprintf(stderr, "%s: unknown option -- `%c'\n", argv0, c); + help(stderr, argv0); + exit(1); + } + } + else if ( !strcmp(arg, "--help") ) { help(stdout, argv0); exit(0); } + else if ( !strcmp(arg, "--version") ) { version(stdout, argv0); exit(0); } + else if ( GET_OPTION_VARIABLE("--cp", &cp) ) { } + else if ( GET_OPTION_VARIABLE("--diff", &diff) ) { } + else if ( GET_OPTION_VARIABLE("--normalized", &input_normalized_path) ) { } + else if ( GET_OPTION_VARIABLE("--output-directory", &output_directory) ) { } + else if ( GET_OPTION_VARIABLE("--output", &output) ) { } + else if ( GET_OPTION_VARIABLE("--tarball", &input_tarball_path) ) { } + else if ( GET_OPTION_VARIABLE("--tar", &tar) ) { } + else if ( GET_OPTION_VARIABLE("--tix-execdiff", &tix_execdiff) ) { } + else if ( GET_OPTION_VARIABLE("--tmp", &tmp) ) { } + else + { + fprintf(stderr, "%s: unknown option: `%s'\n", argv0, arg); + help(stderr, argv0); + exit(1); + } + } + + if ( argc == 1 ) + { + help(stdout, argv0); + exit(0); + } + + CompactArguments(&argc, &argv); + + if ( argc <= 1 ) + { + fprintf(stderr, "%s: no source tix specified\n", argv0); + help(stderr, argv0); + exit(1); + } + + if ( 3 <= argc ) + { + fprintf(stderr, "%s: unexpected extra operand `%s'\n", argv0, argv[2]); + help(stderr, argv0); + exit(1); + } + + const char* input_srctix_path = argv[1]; + + if ( !IsDirectory(input_srctix_path) ) + error(1, errno, "`%s'", input_srctix_path); + + char* tixbuildinfo_path = print_string("%s/tixbuildinfo", input_srctix_path); + + string_array_t package_info = string_array_make(); + if ( !dictionary_append_file_path(&package_info, tixbuildinfo_path) ) + { + if ( errno == ENOENT ) + fprintf(stderr, "%s: `%s' doesn't appear to be a source tix:\n", + argv0, input_srctix_path); + error(1, errno, "`%s'", tixbuildinfo_path); + } + + const char* package_name = strdup(dictionary_get(&package_info, "pkg.name")); + + if ( !output ) + output = print_string("%s/%s.porttix.tar.xz", output_directory, package_name); + + char* tmp_root = print_string("%s/tmppid.%ju", tmp, (uintmax_t) getpid()); + if ( mkdir_p(tmp_root, 0777) != 0 ) + error(1, errno, "mkdir: `%s'", tmp_root); + + on_exit(cleanup_file_or_directory, tmp_root); + + const char* tarball_basename = non_modify_basename(input_tarball_path); + + char* rel_srctix_path = print_string("%s.srctix", package_name); + char* rel_normalized_path = print_string("%s.normalized", package_name); + + char* porttix_path = print_string("%s/%s", tmp_root, package_name); + if ( mkdir_p(porttix_path, 0777) != 0 ) + error(1, errno, "mkdir: `%s'", porttix_path); + + char* srctix_path = print_string("%s/%s", tmp_root, rel_srctix_path); + if ( mkdir_p(srctix_path, 0777) != 0 ) + error(1, errno, "mkdir: `%s'", srctix_path); + + char* normalized_path = print_string("%s/%s", tmp_root, rel_normalized_path); + if ( mkdir_p(normalized_path, 0777) != 0 ) + error(1, errno, "mkdir: `%s'", normalized_path); + + // Create the porttixinfo file. + char* porttixinfo_path = join_paths(porttix_path, "porttixinfo"); + FILE* porttixinfo_fp = fopen(porttixinfo_path, "w"); + if ( !porttixinfo_fp ) + error(1, errno, "`%s'", porttixinfo_path); + fprintf(porttixinfo_fp, "package_name %s\n", package_name); + + // Copy the input source tix to the temporary root. + if ( fork_and_wait_or_death() ) + { + const char* cmd_argv[] = + { + cp, + "-HRT", + "--preserve=timestamps,links", + "--", + input_srctix_path, + srctix_path, + NULL, + }; + execvp(cmd_argv[0], (char* const*) cmd_argv); + error(127, errno, "%s", cmd_argv[0]); + } + + // If no tarball exists, then package up the source directory! + if ( !input_tarball_path ) + { + input_tarball_path = print_string("%s/%s.tar.xz", tmp_root, package_name); + if ( fork_and_wait_or_death() ) + { + char* work_dir = dirname(strdup(srctix_path)); + char* subdir_name = dirname(strdup(srctix_path)); + if ( chdir(work_dir) != 0 ) + error(1, errno, "chdir: `%s'", work_dir); + const char* cmd_argv[] = + { + tar, + "--create", + "--xz", + "--directory", input_normalized_path, + "--file", input_tarball_path, + "--", + subdir_name, + NULL, + }; + execvp(cmd_argv[0], (char* const*) cmd_argv); + error(127, errno, "%s", cmd_argv[0]); + } + } + + // Copy the normalized directory (if one exists) to the temporary root. + if ( input_normalized_path ) + { + if ( fork_and_wait_or_death() ) + { + const char* cmd_argv[] = + { + cp, + "-HRT", + "--preserve=timestamps,links", + "--", + input_normalized_path, + normalized_path, + NULL, + }; + execvp(cmd_argv[0], (char* const*) cmd_argv); + error(127, errno, "%s", cmd_argv[0]); + } + } + + // There is no input normalized directory, so just extract the tarball here. + else + { + if ( fork_and_wait_or_death() ) + { + const char* cmd_argv[] = + { + tar, + "--extract", + "--directory", normalized_path, + "--file", input_tarball_path, + "--strip-components=1", + NULL, + }; + execvp(cmd_argv[0], (char* const*) cmd_argv); + error(127, errno, "%s", cmd_argv[0]); + } + } + + // Copy the tarball into the port tix. + char* porttix_tarball_path = print_string("%s/%s", porttix_path, tarball_basename); + if ( fork_and_wait_or_death() ) + { + const char* cmd_argv[] = + { + cp, + "--", + input_tarball_path, + porttix_tarball_path, + NULL, + }; + execvp(cmd_argv[0], (char* const*) cmd_argv); + error(127, errno, "%s", cmd_argv[0]); + } + fprintf(porttixinfo_fp, "tar_extract %s\n", tarball_basename); + + // Create the normalization patch. + int normalized_fd = open(normalized_path, O_RDONLY | O_DIRECTORY); + if ( normalized_fd < 0 ) + error(1, errno, "`%s'", normalized_path); + + char* patch_normalize_path = join_paths(porttix_path, "patch.normalize"); + FILE* patch_normalize_fp = fopen(patch_normalize_path, "w"); + if ( !patch_normalize_fp ) + error(1, errno, "`%s'", patch_normalize_path); + + int pipes[2]; + if ( pipe(pipes) ) + error(1, errno, "pipe"); + pid_t tar_pid = fork_or_death(); + if ( !tar_pid ) + { + dup2(pipes[1], 1); + close(pipes[1]); + close(pipes[0]); + const char* cmd_argv[] = + { + tar, + "--list", + "--file", porttix_tarball_path, + "--strip-components=1", + NULL + }; + execvp(cmd_argv[0], (char* const*) cmd_argv); + error(127, errno, "%s", cmd_argv[0]); + } + close(pipes[1]); + FILE* tar_fp = fdopen(pipes[0], "r"); + + char* line = NULL; + size_t line_size = 0; + ssize_t line_len; + while ( 0 < (line_len = getline(&line, &line_size, tar_fp)) ) + { + if ( line_len && line[line_len-1] == '\n' ) + line[--line_len] = '\0'; + const char* path = line; + while ( *path != '/' ) + path++; + if ( *path == '/' ) + path++; + if ( !*path ) + continue; + struct stat st; + if ( fstatat(normalized_fd, path, &st, 0) != 0 && errno == ENOENT ) + { + fprintf(patch_normalize_fp, "rm -rf -- '"); + for ( size_t i = 0; path[i]; i++ ) + if ( path[i] == '\'' ) + fprintf(patch_normalize_fp, "'\\''"); + else + fputc(path[i], patch_normalize_fp); + fprintf(patch_normalize_fp, "'\n"); + } + } + free(line); + + fclose(tar_fp); + int tar_exit_status; + waitpid(tar_pid, &tar_exit_status, 0); + if ( !WIFEXITED(tar_exit_status) || WEXITSTATUS(tar_exit_status) != 0 ) + { + error(1, 0, "Unable to list contents of `%s'.", porttix_tarball_path); + exit(WEXITSTATUS(tar_exit_status)); + } + + fclose(patch_normalize_fp); + free(patch_normalize_path); + fprintf(porttixinfo_fp, "apply_normalize patch.normalize\n"); + + close(normalized_fd); + + // Create the patch between the source tix and the normalized tree. + char* patch_path = join_paths(porttix_path, "patch.patch"); + if ( fork_and_wait_or_death(false) ) + { + close(1); + if ( open(patch_path, O_WRONLY | O_CREAT | O_TRUNC, 0666) != 1 ) + error(1, errno, "`%s'", patch_path); + if ( chdir(tmp_root) != 0 ) + error(1, errno, "chdir(`%s')", tmp_root); + const char* cmd_argv[] = + { + diff, + "--no-dereference", + "-Naur", + "--", + rel_normalized_path, + rel_srctix_path, + NULL, + }; + execvp(cmd_argv[0], (char* const*) cmd_argv); + error(127, errno, "%s", cmd_argv[0]); + } + free(patch_path); + fprintf(porttixinfo_fp, "apply_patch patch.patch\n"); + + // Created the execpatch between the source tix and the normalized tree. + char* patch_exec_path = join_paths(porttix_path, "patch.execpatch"); + if ( fork_and_wait_or_death(false) ) + { + if ( redirect(patch_exec_path, O_WRONLY | O_CREAT | O_TRUNC, 0666) != 0 ) + error(1, errno, "`%s'", patch_exec_path); + if ( chdir(tmp_root) != 0 ) + error(1, errno, "chdir(`%s')", tmp_root); + const char* cmd_argv[] = + { + tix_execdiff, + "--", + rel_normalized_path, + rel_srctix_path, + NULL, + }; + execvp(cmd_argv[0], (char* const*) cmd_argv); + error(127, errno, "%s", cmd_argv[0]); + } + free(patch_exec_path); + fprintf(porttixinfo_fp, "apply_execpatch patch.execpatch\n"); + + // Close the porttixinfo file. + fclose(porttixinfo_fp); + free(porttixinfo_path); + + // Package up the output archived port tix. + if ( fork_and_wait_or_death() ) + { + const char* cmd_argv[] = + { + tar, + "--create", + "--xz", + "--directory", tmp_root, + "--file", output, + "--", + package_name, + NULL, + }; + execvp(cmd_argv[0], (char* const*) cmd_argv); + error(127, errno, "%s", cmd_argv[0]); + } + + return 0; +} diff --git a/tix/srctix-create.cpp b/tix/srctix-create.cpp new file mode 100644 index 00000000..6c0d08c7 --- /dev/null +++ b/tix/srctix-create.cpp @@ -0,0 +1,321 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + This file is part of Tix. + + Tix is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or (at your option) any later + version. + + Tix is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + details. + + You should have received a copy of the GNU General Public License along with + Tix. If not, see . + + srctix-create.cpp + Converts an archived port tix into an archived source tix. + +*******************************************************************************/ + +#define __STDC_CONSTANT_MACROS +#define __STDC_LIMIT_MACROS + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" + +void help(FILE* fp, const char* argv0) +{ + fprintf(fp, "Usage: %s [OPTION]... PORT-TIX\n", argv0); + fprintf(fp, "Converts an archived port tix into an archived source tix.\n"); +} + +void version(FILE* fp, const char* argv0) +{ + help(fp, argv0); +} + +bool is_file_name(const char* path) +{ + return !(strchr(path, '/') || !strcmp(path, ".") || !strcmp(path, "..")); +} + +int main(int argc, char* argv[]) +{ + char* output_directory = strdup("."); + char* output = NULL; + char* patch = strdup(getenv_def("PATCH", "patch")); + char* tar = strdup(getenv_def("TAR", "tar")); + char* tix_execpatch = strdup(getenv_def("TIX_EXECPATCH", "tix-execpatch")); + char* tix_rmpatch = strdup(getenv_def("TIX_RMPATCH", "tix-rmpatch")); + char* tmp = strdup(getenv_def("TMP", "/tmp")); + + const char* argv0 = argv[0]; + for ( int i = 0; i < argc; i++ ) + { + const char* arg = argv[i]; + if ( arg[0] != '-' ) + continue; + argv[i] = NULL; + if ( !strcmp(arg, "--") ) + break; + if ( arg[1] != '-' ) + { + while ( char c = *++arg ) switch ( c ) + { + default: + fprintf(stderr, "%s: unknown option -- `%c'\n", argv0, c); + help(stderr, argv0); + exit(1); + } + } + else if ( !strcmp(arg, "--help") ) { help(stdout, argv0); exit(0); } + else if ( !strcmp(arg, "--version") ) { version(stdout, argv0); exit(0); } + else if ( GET_OPTION_VARIABLE("--output-directory", &output_directory) ) { } + else if ( GET_OPTION_VARIABLE("--output", &output) ) { } + else if ( GET_OPTION_VARIABLE("--patch", &patch) ) { } + else if ( GET_OPTION_VARIABLE("--tar", &tar) ) { } + else if ( GET_OPTION_VARIABLE("--tix-execpatch", &tix_execpatch) ) { } + else if ( GET_OPTION_VARIABLE("--tix-rmpatch", &tix_rmpatch) ) { } + else if ( GET_OPTION_VARIABLE("--tmp", &tmp) ) { } + else + { + fprintf(stderr, "%s: unknown option: `%s'\n", argv0, arg); + help(stderr, argv0); + exit(1); + } + } + + if ( argc == 1 ) + { + help(stdout, argv0); + exit(0); + } + + CompactArguments(&argc, &argv); + + if ( argc <= 1 ) + { + fprintf(stderr, "%s: no archived port tix specified\n", argv0); + help(stderr, argv0); + exit(1); + } + + if ( 3 <= argc ) + { + fprintf(stderr, "%s: unexpected extra operand `%s'\n", argv0, argv[2]); + help(stderr, argv0); + exit(1); + } + + const char* porttix_path = argv[1]; + + char* tmp_in_root = print_string("%s/tmppid.%ju.in", tmp, (uintmax_t) getpid()); + if ( mkdir_p(tmp_in_root, 0777) != 0 ) + error(1, errno, "mkdir: `%s'", tmp_in_root); + on_exit(cleanup_file_or_directory, tmp_in_root); + + if ( fork_and_wait_or_death() ) + { + const char* cmd_argv[] = + { + tar, + "--extract", + "--directory", tmp_in_root, + "--file", porttix_path, + "--strip-components=1", + NULL, + }; + execvp(cmd_argv[0], (char* const*) cmd_argv); + error(127, errno, "%s", cmd_argv[0]); + } + + char* porttixinfo_path = join_paths(tmp_in_root, "porttixinfo"); + FILE* porttixinfo_fp = fopen(porttixinfo_path, "r"); + if ( !porttixinfo_fp ) + { + if ( errno == ENOENT ) + error(0, 0, "`%s' doesn't appear to be an archived port tix", + porttix_path); + error(1, errno, "`%s'", porttixinfo_path); + } + + char* tmp_out_root = print_string("%s/tmppid.%ju.out", tmp, (uintmax_t) getpid()); + if ( mkdir_p(tmp_out_root, 0777) != 0 ) + error(1, errno, "mkdir: `%s'", tmp_out_root); + on_exit(cleanup_file_or_directory, tmp_out_root); + + char* package_name = NULL; + char* srctix_path = NULL; + + char* line = NULL; + size_t line_size = 0; + ssize_t line_len; + while ( 0 < (line_len = getline(&line, &line_size, porttixinfo_fp)) ) + { + if ( line_len && line[line_len-1] == '\n' ) + line[--line_len] = '\0'; + char* first_space = strchr(line, ' '); + if ( !first_space ) + error(1, errno, "`%s`: malformed line `%s'", + porttixinfo_path, line); + *first_space = '\0'; + const char* function = line; + const char* parameter = first_space + 1; + + if ( !strcmp(function, "package_name") ) + { + if ( package_name ) + error(1, errno, "`%s`: unexpected additional package name `%s'", + porttixinfo_path, parameter); + if ( !is_file_name(parameter) ) + error(1, errno, "`%s`: malformed package name `%s'", + porttixinfo_path, parameter); + package_name = strdup(parameter); + srctix_path = join_paths(tmp_out_root, package_name); + if ( mkdir_p(srctix_path, 0777) != 0 ) + error(1, errno, "mkdir: `%s'", srctix_path); + } + else if ( !package_name ) + error(1, errno, "`%s`: expected package name before `%s'", + porttixinfo_path, function); + else if ( !strcmp(function, "tar_extract") ) + { + if ( !is_file_name(parameter) ) + error(1, errno, "`%s`: malformed tarball filename `%s'", + porttixinfo_path, parameter); + char* tarball_path = join_paths(tmp_in_root, parameter); + if ( fork_and_wait_or_death() ) + { + const char* cmd_argv[] = + { + tar, + "--extract", + "--directory", srctix_path, + "--file", tarball_path, + "--strip-components=1", + NULL, + }; + execvp(cmd_argv[0], (char* const*) cmd_argv); + error(127, errno, "%s", cmd_argv[0]); + } + free(tarball_path); + } + else if ( !strcmp(function, "apply_normalize") ) + { + if ( !is_file_name(parameter) ) + error(1, errno, "`%s`: malformed normalize filename `%s'", + porttixinfo_path, parameter); + char* rmpatch_path = join_paths(tmp_in_root, parameter); + if ( fork_and_wait_or_death() ) + { + const char* cmd_argv[] = + { + tix_rmpatch, + "--directory", srctix_path, + "--", + rmpatch_path, + NULL, + }; + execvp(cmd_argv[0], (char* const*) cmd_argv); + error(127, errno, "%s", cmd_argv[0]); + } + free(rmpatch_path); + } + else if ( !strcmp(function, "apply_patch") ) + { + if ( !is_file_name(parameter) ) + error(1, errno, "`%s`: malformed patch filename `%s'", + porttixinfo_path, parameter); + char* patch_path = join_paths(tmp_in_root, parameter); + if ( fork_and_wait_or_death() ) + { + const char* cmd_argv[] = + { + patch, + "--strip=1", + "--silent", + "--directory", srctix_path, + "--input", patch_path, + NULL, + }; + execvp(cmd_argv[0], (char* const*) cmd_argv); + error(127, errno, "%s", cmd_argv[0]); + } + free(patch_path); + } + else if ( !strcmp(function, "apply_execpatch") ) + { + if ( !is_file_name(parameter) ) + error(1, errno, "`%s`: malformed execpatch filename `%s'", + porttixinfo_path, parameter); + char* execpatch_path = join_paths(tmp_in_root, parameter); + if ( fork_and_wait_or_death() ) + { + const char* cmd_argv[] = + { + tix_execpatch, + "--directory", srctix_path, + "--", + execpatch_path, + NULL, + }; + execvp(cmd_argv[0], (char* const*) cmd_argv); + error(127, errno, "%s", cmd_argv[0]); + } + free(execpatch_path); + } + else + error(1, errno, "`%s`: unsupported function `%s'", + porttixinfo_path, function); + } + free(line); + + fclose(porttixinfo_fp); + free(porttixinfo_path); + + if ( !output ) + output = print_string("%s/%s.srctix.tar.xz", output_directory, package_name); + + if ( fork_and_wait_or_death() ) + { + const char* cmd_argv[] = + { + tar, + "--create", + "--xz", + "--directory", tmp_out_root, + "--file", output, + "--", + package_name, + NULL, + }; + execvp(cmd_argv[0], (char* const*) cmd_argv); + error(127, errno, "%s", cmd_argv[0]); + } + + free(srctix_path); + free(package_name); + + return 0; +} diff --git a/tix/tix-build.cpp b/tix/tix-build.cpp new file mode 100644 index 00000000..33b39f27 --- /dev/null +++ b/tix/tix-build.cpp @@ -0,0 +1,713 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + This file is part of Tix. + + Tix is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or (at your option) any later + version. + + Tix is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + details. + + You should have received a copy of the GNU General Public License along with + Tix. If not, see . + + tix-build.cpp + Compile a source tix into a tix suitable for installation. + +*******************************************************************************/ + +#define __STDC_CONSTANT_MACROS +#define __STDC_LIMIT_MACROS + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" + +enum build_step +{ + BUILD_STEP_NO_SUCH_STEP, + BUILD_STEP_START, + BUILD_STEP_PRE_CLEAN, + BUILD_STEP_CONFIGURE, + BUILD_STEP_BUILD, + BUILD_STEP_INSTALL, + BUILD_STEP_POST_INSTALL, + BUILD_STEP_PACKAGE, + BUILD_STEP_POST_CLEAN, + BUILD_STEP_END, +}; + +bool should_do_build_step(enum build_step step, + enum build_step start, + enum build_step end) +{ + return start <= step && step <= end; +} + +#define SHOULD_DO_BUILD_STEP(step, minfo) \ + should_do_build_step((step), (minfo)->start_step, (minfo)->end_step) + +enum build_step step_of_step_name(const char* step_name) +{ + if ( !strcmp(step_name, "start") ) + return BUILD_STEP_START; + if ( !strcmp(step_name, "pre-clean") ) + return BUILD_STEP_PRE_CLEAN; + if ( !strcmp(step_name, "configure") ) + return BUILD_STEP_CONFIGURE; + if ( !strcmp(step_name, "build") ) + return BUILD_STEP_BUILD; + if ( !strcmp(step_name, "install") ) + return BUILD_STEP_INSTALL; + if ( !strcmp(step_name, "post-install") ) + return BUILD_STEP_POST_INSTALL; + if ( !strcmp(step_name, "post-clean") ) + return BUILD_STEP_POST_CLEAN; + if ( !strcmp(step_name, "package") ) + return BUILD_STEP_PACKAGE; + if ( !strcmp(step_name, "clean") ) + return BUILD_STEP_POST_CLEAN; + if ( !strcmp(step_name, "end") ) + return BUILD_STEP_END; + return BUILD_STEP_NO_SUCH_STEP; +} + +typedef struct +{ + char* build; + char* build_dir; + char* destination; + char* host; + char* make; + char* makeflags; + char* package_dir; + char* package_info_path; + char* package_name; + char* prefix; + char* sysroot; + char* tar; + char* target; + char* tmp; + string_array_t package_info; + enum build_step start_step; + enum build_step end_step; +} metainfo_t; + +void emit_pkg_config_wrapper(metainfo_t* minfo) +{ + char* bindir = print_string("%s/tmppid.%ju.bin", minfo->tmp, (uintmax_t) getpid()); + if ( mkdir_p(bindir, 0777) != 0 ) + error(1, errno, "mkdir: `%s'", bindir); + + on_exit(cleanup_file_or_directory, strdup(bindir)); + + // Create a pkg-config script for the build system. + char* pkg_config_for_build_path = print_string("%s/build-pkg-config", bindir); + FILE* pkg_config_for_build = fopen(pkg_config_for_build_path, "w"); + if ( !pkg_config_for_build ) + error(1, errno, "`%s'", pkg_config_for_build_path); + fprintf(pkg_config_for_build, "#!/bin/sh\n"); + fprint_shell_variable_assignment(pkg_config_for_build, "PATH", getenv("PATH")); + fprint_shell_variable_assignment(pkg_config_for_build, "PKG_CONFIG", getenv("PKG_CONFIG")); + fprint_shell_variable_assignment(pkg_config_for_build, "PKG_CONFIG_PATH", getenv("PKG_CONFIG_PATH")); + fprint_shell_variable_assignment(pkg_config_for_build, "PKG_CONFIG_SYSROOT_DIR", getenv("PKG_CONFIG_SYSROOT_DIR")); + fprint_shell_variable_assignment(pkg_config_for_build, "PKG_CONFIG_FOR_BUILD", getenv("PKG_CONFIG_FOR_BUILD")); + fprintf(pkg_config_for_build, "exec ${PKG_CONFIG:-pkg-config} \"$@\"\n"); + fflush(pkg_config_for_build); + fchmod_plus_x(fileno(pkg_config_for_build)); + fclose(pkg_config_for_build); + free(pkg_config_for_build_path); + + // Create a pkg-config script for the host system. + char* pkg_config_path = print_string("%s/pkg-config", bindir); + FILE* pkg_config = fopen(pkg_config_path, "w"); + if ( !pkg_config ) + error(1, errno, "`%s'", pkg_config_path); + fprintf(pkg_config, "#!/bin/sh\n"); + fprint_shell_variable_assignment(pkg_config, "PATH", getenv("PATH")); + fprint_shell_variable_assignment(pkg_config, "PKG_CONFIG", getenv("PKG_CONFIG")); + fprintf(pkg_config, "exec ${PKG_CONFIG:-pkg-config} --static \"$@\"\n"); + fflush(pkg_config); + fchmod_plus_x(fileno(pkg_config)); + fclose(pkg_config); + free(pkg_config_path); + + // Point to the correct pkg-config configuration through the environment. + char* var_pkg_config = print_string("%s/pkg-config", bindir); + char* var_pkg_config_for_build = print_string("%s/build-pkg-config", bindir); + char* var_pkg_config_libdir = + print_string("%s%s/%s/lib/pkgconfig", + minfo->sysroot, minfo->prefix, minfo->host); + char* var_pkg_config_path = print_string("%s", var_pkg_config_libdir); + char* var_pkg_config_sysroot_dir = print_string("%s", minfo->sysroot); + setenv("PKG_CONFIG", var_pkg_config, 1); + setenv("PKG_CONFIG_FOR_BUILD", var_pkg_config_for_build, 1); + setenv("PKG_CONFIG_LIBDIR", var_pkg_config_libdir, 1); + setenv("PKG_CONFIG_PATH", var_pkg_config_path, 1); + setenv("PKG_CONFIG_SYSROOT_DIR", var_pkg_config_sysroot_dir, 1); + free(var_pkg_config); + free(var_pkg_config_for_build); + free(var_pkg_config_libdir); + free(var_pkg_config_path); + free(var_pkg_config_sysroot_dir); + + char* new_path = print_string("%s:%s", bindir, getenv("PATH") ? getenv("PATH") : ""); + setenv("PATH", new_path, 1); + free(new_path); + + free(bindir); +} + +void Configure(metainfo_t* minfo) +{ + if ( fork_and_wait_or_recovery() ) + { + string_array_t* pkg_info = &minfo->package_info; + const char* configure_raw = + dictionary_get(pkg_info, "pkg.configure.cmd", "./configure"); + char* configure; + if ( strcmp(minfo->build_dir, minfo->package_dir) == 0 ) + configure = strdup(configure_raw); + else + configure = print_string("%s/%s", minfo->package_dir, configure_raw); + const char* conf_extra_args = + dictionary_get(pkg_info, "pkg.configure.args", ""); + const char* conf_extra_vars = + dictionary_get(pkg_info, "pkg.configure.vars", ""); + bool with_sysroot = + parse_boolean(dictionary_get(pkg_info, "pkg.configure.with-sysroot", + "false")); + bool with_build_sysroot = + parse_boolean(dictionary_get(pkg_info, "pkg.configure.with-build-sysroot", + "false")); + if ( chdir(minfo->build_dir) != 0 ) + error(1, errno, "chdir: `%s'", minfo->build_dir); + string_array_t env_vars = string_array_make(); + string_array_append_token_string(&env_vars, conf_extra_vars); + for ( size_t i = 0; i < env_vars.length; i++ ) + { + char* key = env_vars.strings[i]; + assert(key); + char* assignment = strchr((char*) key, '='); + if ( !assignment ) + continue; + *assignment = '\0'; + char* value = assignment+1; + setenv(key, value, 1); + } + const char* fixed_cmd_argv[] = + { + configure, + print_string("--build=%s", minfo->build), + print_string("--host=%s", minfo->host), + print_string("--target=%s", minfo->target), + print_string("--prefix=%s", minfo->prefix), + print_string("--exec-prefix=%s/%s", minfo->prefix, minfo->host), + NULL + }; + string_array_t args = string_array_make(); + for ( size_t i = 0; fixed_cmd_argv[i]; i++ ) + string_array_append(&args, fixed_cmd_argv[i]); + if ( minfo->sysroot && with_build_sysroot ) + { + string_array_append(&args, print_string("--with-build-sysroot=%s", + minfo->sysroot)); + if ( minfo->sysroot && with_sysroot ) + string_array_append(&args, "--with-sysroot=/"); + unsetenv("HOST_SYSTEM_ROOT"); + } + else if ( minfo->sysroot && with_sysroot ) + { + string_array_append(&args, print_string("--with-sysroot=%s", + minfo->sysroot)); + unsetenv("HOST_SYSTEM_ROOT"); + } + else if ( minfo->sysroot ) + { + setenv("HOST_SYSTEM_ROOT", minfo->sysroot, 1); + } + string_array_append_token_string(&args, conf_extra_args); + string_array_append(&args, NULL); + recovery_execvp(args.strings[0], (char* const*) args.strings); + error(127, errno, "`%s'", args.strings[0]); + } +} + +void Make(metainfo_t* minfo, const char* make_target, + const char* destdir = NULL, bool die_on_error = true, + const char* subdir = NULL) +{ + if ( (!die_on_error && fork_and_wait_or_death(die_on_error)) || + (die_on_error && fork_and_wait_or_recovery()) ) + { + string_array_t* pkg_info = &minfo->package_info; + char* make = strdup(minfo->make); + const char* override_make = dictionary_get(pkg_info, "pkg.make.cmd"); + const char* make_extra_args = dictionary_get(pkg_info, "pkg.make.args", ""); + const char* make_extra_vars = dictionary_get(pkg_info, "pkg.make.vars", ""); + if ( override_make ) + { + free(make); + make = join_paths(minfo->package_dir, override_make); + } + if ( dictionary_get(pkg_info, "pkg.make.needed-vars.CC", NULL) ) + setenv("CC", strcmp(minfo->build, minfo->host) ? + print_string("%s-gcc", minfo->host) : "gcc", 1); + if ( dictionary_get(pkg_info, "pkg.make.needed-vars.CXX", NULL) ) + setenv("CXX", strcmp(minfo->build, minfo->host) ? + print_string("%s-g++", minfo->host) : "g++", 1); + bool with_sysroot = + parse_boolean(dictionary_get(pkg_info, "pkg.configure.with-sysroot", + "false")); + bool with_build_sysroot = + parse_boolean(dictionary_get(pkg_info, "pkg.configure.with-build-sysroot", + "false")); + if ( chdir(minfo->build_dir) != 0 ) + error(1, errno, "chdir: `%s'", minfo->build_dir); + if ( subdir && chdir(subdir) != 0 ) + error(1, errno, "chdir: `%s/%s'", minfo->build_dir, subdir); + if ( destdir ) + setenv("DESTDIR", destdir, 1); + setenv("BUILD", minfo->build, 1); + setenv("HOST", minfo->host, 1); + setenv("TARGET", minfo->target, 1); + if ( minfo->prefix ) + setenv("PREFIX", minfo->prefix, 1), + setenv("EXEC_PREFIX", join_paths(minfo->prefix, minfo->host), 1); + else + unsetenv("PREFIX"), + unsetenv("EXEC_PREFIX"); + if ( !(with_sysroot || with_build_sysroot) && minfo->sysroot ) + setenv("HOST_SYSTEM_ROOT", minfo->sysroot, 1); + if ( minfo->makeflags ) + setenv("MAKEFLAGS", minfo->makeflags, 1); + setenv("MAKE", minfo->make, 1); + string_array_t env_vars = string_array_make(); + string_array_append_token_string(&env_vars, make_extra_vars); + for ( size_t i = 0; i < env_vars.length; i++ ) + { + char* key = env_vars.strings[i]; + assert(key); + char* assignment = strchr((char*) key, '='); + if ( !assignment ) + continue; + *assignment = '\0'; + char* value = assignment+1; + setenv(key, value, 1); + } + const char* fixed_cmd_argv[] = + { + make, + NULL + }; + string_array_t args = string_array_make(); + for ( size_t i = 0; fixed_cmd_argv[i]; i++ ) + string_array_append(&args, fixed_cmd_argv[i]); + string_array_append_token_string(&args, make_target); + string_array_append_token_string(&args, make_extra_args); + string_array_append(&args, NULL); + if ( die_on_error ) + recovery_execvp(args.strings[0], (char* const*) args.strings); + else + execvp(args.strings[0], (char* const*) args.strings); + error(127, errno, "`%s'", args.strings[0]); + } +} + +void BuildPackage(metainfo_t* minfo) +{ + // Detect which build system we are interfacing with. + string_array_t* pinfo = &minfo->package_info; + const char* build_system = dictionary_get(pinfo, "pkg.build-system"); + assert(build_system); + + // Determine whether need to do an out-of-directory build. + const char* use_build_dir_var = + dictionary_get(pinfo, "pkg.configure.use-build-directory", "false"); + bool use_build_dir = parse_boolean(use_build_dir_var); + if ( use_build_dir ) + { + minfo->build_dir = print_string("%s/tmppid.%ju", minfo->tmp, + (uintmax_t) getpid()); + if ( mkdir_p(minfo->build_dir, 0777) != 0 ) + error(1, errno, "mkdir: `%s'", minfo->build_dir); + } + else + minfo->build_dir = strdup(minfo->package_dir); + + // Reset the build directory if needed. + const char* default_clean_target = + !strcmp(build_system, "configure") ? "distclean" : "clean"; + const char* clean_target = dictionary_get(pinfo, "pkg.make.clean-target", + default_clean_target); + const char* ignore_clean_failure_var = + dictionary_get(pinfo, "pkg.make.ignore-clean-failure", + !strcmp(build_system, "configure") ? "true" : "false"); + bool ignore_clean_failure = parse_boolean(ignore_clean_failure_var); + + if ( SHOULD_DO_BUILD_STEP(BUILD_STEP_PRE_CLEAN, minfo) && !use_build_dir ) + Make(minfo, clean_target, NULL, !ignore_clean_failure); + + // Configure the build directory if needed. + if ( strcmp(build_system, "configure") == 0 && + SHOULD_DO_BUILD_STEP(BUILD_STEP_CONFIGURE, minfo) ) + Configure(minfo); + + bool location_independent = + parse_boolean(dictionary_get(pinfo, "pkg.location-independent", "false")); + + const char* subdir = dictionary_get(pinfo, "pkg.subdir", NULL); + + const char* build_target = dictionary_get(pinfo, "pkg.make.build-target", "all"); + const char* install_target = dictionary_get(pinfo, "pkg.make.install-target", "install"); + + if ( !location_independent && !minfo->prefix ) + error(1, 0, "error: %s is not location independent and you need to " + "specify the intended destination prefix using --prefix or " + "PREFIX", minfo->package_name); + + if ( SHOULD_DO_BUILD_STEP(BUILD_STEP_BUILD, minfo) ) + Make(minfo, build_target, NULL, true, subdir); + + char* tardir_rel = print_string("%s/%s", minfo->tmp, "tmp-tixbuild"); + char* destdir_rel = print_string("%s/%s", minfo->tmp, "tmp-tixbuild/data"); + char* tixdir_rel = print_string("%s/%s", minfo->tmp, "tmp-tixbuild/tix"); + char* tixinfo_rel = print_string("%s/%s", minfo->tmp, "tmp-tixbuild/tix/tixinfo"); + + while ( mkdir(tardir_rel, 0777) != 0 ) + { + if ( errno != EEXIST ) + error(1, errno, "mkdir: `%s'", tardir_rel); + if ( rmdir(tardir_rel) != 0 ) + error(1, errno, "rmdir: `%s'", tardir_rel); + } + + if ( mkdir(destdir_rel, 0777) != 0 ) + error(1, errno, "mkdir: `%s'", destdir_rel); + if ( mkdir(tixdir_rel, 0777) != 0 ) + error(1, errno, "mkdir: `%s'", tixdir_rel); + + char* destdir = canonicalize_file_name(destdir_rel); + if ( !destdir ) + error(1, errno, "canonicalize_file_name: `%s'", destdir_rel); + + if ( SHOULD_DO_BUILD_STEP(BUILD_STEP_INSTALL, minfo) ) + Make(minfo, install_target, destdir, true, subdir); + + const char* post_install_cmd = dictionary_get(pinfo, "pkg.post-install.cmd"); + + if ( post_install_cmd && + SHOULD_DO_BUILD_STEP(BUILD_STEP_POST_INSTALL, minfo) && + fork_and_wait_or_recovery() ) + { + if ( chdir(minfo->package_dir) != 0 ) + error(1, errno, "chdir: `%s'", minfo->package_dir); + setenv("TIX_BUILD_DIR", minfo->build_dir, 1); + setenv("TIX_SOURCE_DIR", minfo->package_dir, 1); + setenv("TIX_INSTALL_DIR", destdir, 1); + setenv("BUILD", minfo->build, 1); + setenv("HOST", minfo->host, 1); + setenv("TARGET", minfo->target, 1); + if ( minfo->prefix ) + setenv("PREFIX", minfo->prefix, 1), + setenv("EXEC_PREFIX", join_paths(minfo->prefix, minfo->host), 1); + else + unsetenv("PREFIX"), + unsetenv("EXEC_PREFIX"); + const char* cmd_argv[] = + { + post_install_cmd, + NULL + }; + recovery_execvp(cmd_argv[0], (char* const*) cmd_argv); + error(127, errno, "%s", cmd_argv[0]); + } + + const char* tix_ext = ".tix.tar.xz"; + char* package_tix = print_string("%s/%s%s", minfo->destination, + minfo->package_name, tix_ext); + + FILE* tixinfo_fp = fopen(tixinfo_rel, "w"); + if ( !tixinfo_fp ) + error(1, errno, "`%s'", tixinfo_rel); + + const char* runtime_deps = dictionary_get(pinfo, "pkg.runtime-deps"); + + fprintf(tixinfo_fp, "tix.version=1\n"); + fprintf(tixinfo_fp, "tix.class=tix\n"); + fprintf(tixinfo_fp, "tix.platform=%s\n", minfo->host); + fprintf(tixinfo_fp, "pkg.name=%s\n", minfo->package_name); + if ( runtime_deps ) + fprintf(tixinfo_fp, "pkg.runtime-deps=%s\n", runtime_deps); + if ( location_independent ) + fprintf(tixinfo_fp, "pkg.location-independent=true\n"); + else + fprintf(tixinfo_fp, "pkg.prefix=%s\n", minfo->prefix); + + if ( ferror(tixinfo_fp) ) + error(1, errno, "write: `%s'", tixinfo_rel); + + fclose(tixinfo_fp); + + if ( SHOULD_DO_BUILD_STEP(BUILD_STEP_PACKAGE, minfo) ) + { + printf("Creating `%s'...\n", package_tix); + if ( fork_and_wait_or_recovery() ) + { + const char* cmd_argv[] = + { + minfo->tar, + "-C", tardir_rel, + "--remove-files", + "--create", + "--xz", + "--file", package_tix, + "tix", + "data", + NULL + }; + recovery_execvp(cmd_argv[0], (char* const*) cmd_argv); + error(127, errno, "%s", cmd_argv[0]); + } + } + + unlink(tixinfo_rel); + rmdir(destdir_rel); + rmdir(tixdir_rel); + rmdir(tardir_rel); + + free(tardir_rel); + free(destdir_rel); + free(tixdir_rel); + free(tixinfo_rel); + + free(package_tix); + + // Clean the build directory after the successful build. + if ( SHOULD_DO_BUILD_STEP(BUILD_STEP_POST_CLEAN, minfo) ) + Make(minfo, clean_target, NULL, !ignore_clean_failure); +} + +void VerifySourceTixInformation(metainfo_t* minfo) +{ + const char* pipath = minfo->package_info_path; + string_array_t* pinfo = &minfo->package_info; + const char* tix_version = VerifyInfoVariable(pinfo, "tix.version", pipath); + if ( atoi(tix_version) != 1 ) + error(1, 0, "error: `%s': tix version `%s' not supported", pipath, + tix_version); + const char* tix_class = VerifyInfoVariable(pinfo, "tix.class", pipath); + if ( !strcmp(tix_class, "tix") ) + error(1, 0, "error: `%s': this object is a binary tix and is already " + "compiled.\n", pipath); + if ( strcmp(tix_class, "srctix") ) + error(1, 0, "error: `%s': tix class `%s' is not `srctix': this object " + "is not suitable for compilation.", pipath, tix_class); + VerifyInfoVariable(pinfo, "pkg.name", pipath); + VerifyInfoVariable(pinfo, "pkg.build-system", pipath); +} + +// TODO: The MAKEFLAGS variable is actually not in the same format as the token +// string language. It appears that GNU make doesn't escape " characters, +// but instead consider them normal characters. This should work as +// expected, though, as long as the MAKEFLAGS variable doesn't contain any +// quote characters. +void PurifyMakeflags() +{ + const char* makeflags_environment = getenv("MAKEFLAGS"); + if ( !makeflags_environment ) + return; + string_array_t makeflags = string_array_make(); + string_array_append_token_string(&makeflags, makeflags_environment); + for ( size_t i = 0; i < makeflags.length; i++ ) + { + char* flag = makeflags.strings[i]; + assert(flag); + if ( flag[0] == '-' ) + continue; + if ( !strchr(flag, '=') ) + continue; + free(flag); + for ( size_t n = i + 1; n < makeflags.length; n++ ) + makeflags.strings[n-1] = makeflags.strings[n]; + makeflags.length--; + } + char* new_makeflags_environment = token_string_of_string_array(&makeflags); + assert(new_makeflags_environment); + setenv("MAKEFLAGS", new_makeflags_environment, 1); + free(new_makeflags_environment); + string_array_reset(&makeflags); +} + +void Usage(FILE* fp, const char* argv0) +{ + fprintf(fp, "Usage: %s [OPTION]... PACKAGE\n", argv0); + fprintf(fp, "Compile a source tix into a tix suitable for installation.\n"); +} + +void Help(FILE* fp, const char* argv0) +{ + Usage(fp, argv0); +} + +void Version(FILE* fp, const char* argv0) +{ + Usage(fp, argv0); +} + +int main(int argc, char* argv[]) +{ + metainfo_t minfo; + minfo.build = NULL; + minfo.destination = strdup(getenv_def("TIX_BUILD_DESTINATION", ".")); + minfo.host = NULL; + minfo.makeflags = strdup_null(getenv_def("MAKEFLAGS", NULL)); + minfo.make = strdup(getenv_def("MAKE", "make")); + minfo.prefix = strdup_null(getenv_def("PREFIX", NULL)); + minfo.sysroot = strdup_null(getenv_def("HOST_SYSTEM_ROOT", NULL)); + minfo.target = NULL; + minfo.tar = strdup(getenv_def("TAR", "tar")); + minfo.tmp = strdup(getenv_def("BUILDTMP", ".")); + char* start_step_string = strdup("start"); + char* end_step_string = strdup("end"); + + const char* argv0 = argv[0]; + for ( int i = 0; i < argc; i++ ) + { + const char* arg = argv[i]; + if ( arg[0] != '-' ) + continue; + argv[i] = NULL; + if ( !strcmp(arg, "--") ) + break; + if ( arg[1] != '-' ) + { + while ( char c = *++arg ) switch ( c ) + { + default: + fprintf(stderr, "%s: unknown option -- `%c'\n", argv0, c); + Usage(stderr, argv0); + exit(1); + } + } + else if ( !strcmp(arg, "--help") ) { Help(stdout, argv0); exit(0); } + else if ( !strcmp(arg, "--usage") ) { Usage(stdout, argv0); exit(0); } + else if ( !strcmp(arg, "--version") ) { Version(stdout, argv0); exit(0); } + else if ( GET_OPTION_VARIABLE("--build", &minfo.build) ) { } + else if ( GET_OPTION_VARIABLE("--destination", &minfo.destination) ) { } + else if ( GET_OPTION_VARIABLE("--end", &end_step_string) ) { } + else if ( GET_OPTION_VARIABLE("--host", &minfo.host) ) { } + else if ( GET_OPTION_VARIABLE("--makeflags", &minfo.makeflags) ) { } + else if ( GET_OPTION_VARIABLE("--make", &minfo.make) ) { } + else if ( GET_OPTION_VARIABLE("--prefix", &minfo.prefix) ) { } + else if ( GET_OPTION_VARIABLE("--start", &start_step_string) ) { } + else if ( GET_OPTION_VARIABLE("--sysroot", &minfo.sysroot) ) { } + else if ( GET_OPTION_VARIABLE("--target", &minfo.target) ) { } + else if ( GET_OPTION_VARIABLE("--tar", &minfo.tar) ) { } + else if ( GET_OPTION_VARIABLE("--tmp", &minfo.tmp) ) { } + else + { + fprintf(stderr, "%s: unknown option: `%s'\n", argv0, arg); + Usage(stderr, argv0); + exit(1); + } + } + + if ( !(minfo.start_step = step_of_step_name(start_step_string)) ) + { + fprintf(stderr, "%s: no such step `%s'\n", argv0, start_step_string); + Usage(stderr, argv0); + exit(1); + } + + if ( !(minfo.end_step = step_of_step_name(end_step_string)) ) + { + fprintf(stderr, "%s: no such step `%s'\n", argv0, end_step_string); + Usage(stderr, argv0); + exit(1); + } + + if ( argc == 1 ) + { + Usage(stdout, argv0); + exit(0); + } + + CompactArguments(&argc, &argv); + + PurifyMakeflags(); + + if ( minfo.prefix && !strcmp(minfo.prefix, "/") ) + minfo.prefix[0] = '\0'; + + if ( argc < 2 ) + { + fprintf(stderr, "%s: no package specified\n", argv0); + Usage(stderr, argv0); + exit(1); + } + + if ( 2 < argc ) + { + fprintf(stderr, "%s: unexpected extra operand `%s'\n", argv0, argv[2]); + Usage(stderr, argv0); + exit(1); + } + + minfo.package_dir = canonicalize_file_name(argv[1]); + if ( !minfo.package_dir ) + error(1, errno, "canonicalize_file_name: `%s'", argv[1]); + + if ( !minfo.build && !(minfo.build = GetBuildTriplet()) ) + error(1, errno, "unable to determine host, use --host or BUILD"); + if ( !minfo.host && !(minfo.host = strdup_null(getenv("HOST"))) ) + minfo.host = strdup(minfo.build); + if ( !minfo.target && !(minfo.target = strdup_null(getenv("TARGET"))) ) + minfo.target = strdup(minfo.host); + + if ( !IsDirectory(minfo.package_dir) ) + error(1, errno, "`%s'", minfo.package_dir); + + minfo.package_info_path = print_string("%s/tixbuildinfo", + minfo.package_dir); + + string_array_t* package_info = &(minfo.package_info = string_array_make()); + if ( !dictionary_append_file_path(package_info, minfo.package_info_path) ) + { + if ( errno == ENOENT ) + fprintf(stderr, "%s: `%s' doesn't appear to be a source .tix:\n", + argv0, minfo.package_dir); + error(1, errno, "`%s'", minfo.package_info_path); + } + + VerifySourceTixInformation(&minfo); + minfo.package_name = strdup(dictionary_get(package_info, "pkg.name")); + + emit_pkg_config_wrapper(&minfo); + + BuildPackage(&minfo); + + return 0; +} diff --git a/tix/tix-collection.cpp b/tix/tix-collection.cpp new file mode 100644 index 00000000..876f8627 --- /dev/null +++ b/tix/tix-collection.cpp @@ -0,0 +1,175 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + This file is part of Tix. + + Tix is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or (at your option) any later + version. + + Tix is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + details. + + You should have received a copy of the GNU General Public License along with + Tix. If not, see . + + tix-collection.cpp + Administer and configure a tix collection. + +*******************************************************************************/ + +#define __STDC_CONSTANT_MACROS +#define __STDC_LIMIT_MACROS + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" + +void Usage(FILE* fp, const char* argv0) +{ + fprintf(fp, "Usage: %s PREFIX [OPTION]... COMMAND\n", argv0); + fprintf(fp, "Administer and configure a tix collection.\n"); +} + +void Help(FILE* fp, const char* argv0) +{ + Usage(fp, argv0); +} + +void Version(FILE* fp, const char* argv0) +{ + Usage(fp, argv0); +} + +int main(int argc, char* argv[]) +{ + char* collection = strdup_null(getenv_def("TIX_COLLECTION", NULL)); + char* platform = NULL; + char* prefix = NULL; + + const char* argv0 = argv[0]; + for ( int i = 0; i < argc; i++ ) + { + const char* arg = argv[i]; + if ( arg[0] != '-' ) + continue; + argv[i] = NULL; + if ( !strcmp(arg, "--") ) + break; + if ( arg[1] != '-' ) + { + while ( char c = *++arg ) switch ( c ) + { + default: + fprintf(stderr, "%s: unknown option -- `%c'\n", argv0, c); + Usage(stderr, argv0); + exit(1); + } + } + else if ( !strcmp(arg, "--help") ) { Help(stdout, argv0); exit(0); } + else if ( !strcmp(arg, "--usage") ) { Usage(stdout, argv0); exit(0); } + else if ( !strcmp(arg, "--version") ) { Version(stdout, argv0); exit(0); } + else if ( GET_OPTION_VARIABLE("--collection", &collection) ) { } + else if ( GET_OPTION_VARIABLE("--platform", &platform) ) { } + else if ( GET_OPTION_VARIABLE("--prefix", &prefix) ) { } + else + { + fprintf(stderr, "%s: unknown option: `%s'\n", argv0, arg); + Usage(stderr, argv0); + exit(1); + } + } + + if ( argc == 1 ) + { + Usage(stdout, argv0); + exit(0); + } + + CompactArguments(&argc, &argv); + + ParseOptionalCommandLineCollectionPrefix(&collection, &argc, &argv); + VerifyCommandLineCollection(&collection); + + if ( !prefix ) + prefix = strdup(collection); + + if ( argc == 1 ) + { + error(0, 0, "error: no command specified."); + Usage(stderr, argv0); + exit(1); + } + + const char* cmd = argv[1]; + if ( !strcmp(cmd, "create") ) + { + if ( !platform && !(platform = GetBuildTriplet()) ) + error(1, errno, "unable to determine platform, use --platform"); + + char* tix_path = join_paths(collection, "tix"); + if ( mkdir_p(tix_path, 0777) != 0 ) + error(1, errno, "mkdir: `%s'", tix_path); + + char* tixdb_path = join_paths(tix_path, platform); + if ( mkdir_p(tixdb_path, 0777) != 0 ) + error(1, errno, "mkdir: `%s'", tixdb_path); + + char* collection_conf_path = join_paths(tixdb_path, "collection.conf"); + FILE* conf_fp = fopen(collection_conf_path, "wx"); + if ( !conf_fp && errno == EEXIST ) + error(1, 0, "error: `%s' already exists, a tix collection is " + "already installed at `%s'.", collection_conf_path, + collection); + fprintf(conf_fp, "tix.version=1\n"); + fprintf(conf_fp, "tix.class=collection\n"); + fprintf(conf_fp, "collection.prefix=%s\n", !strcmp(prefix, "/") ? "" : + prefix); + fprintf(conf_fp, "collection.platform=%s\n", platform); + fclose(conf_fp); + free(collection_conf_path); + + const char* repo_list_path = join_paths(tixdb_path, "repository.list"); + FILE* repo_list_fp = fopen(repo_list_path, "w"); + if ( !repo_list_fp ) + error(1, errno, "`%s'", repo_list_path); + fclose(repo_list_fp); + + const char* inst_list_path = join_paths(tixdb_path, "installed.list"); + FILE* inst_list_fp = fopen(inst_list_path, "w"); + if ( !inst_list_fp ) + error(1, errno, "`%s'", inst_list_path); + fclose(inst_list_fp); + + printf("Created empty tix collection at `%s' with no repositories.\n", + collection); + + return 0; + } + else + { + fprintf(stderr, "%s: unknown command: `%s'\n", argv0, cmd); + Usage(stderr, argv0); + exit(1); + } + + return 0; +} diff --git a/tix/tix-eradicate-libtool-la b/tix/tix-eradicate-libtool-la new file mode 100755 index 00000000..7b34fbac --- /dev/null +++ b/tix/tix-eradicate-libtool-la @@ -0,0 +1,28 @@ +#!/bin/sh +################################################################################ +# +# Copyright(C) Jonas 'Sortie' Termansen 2013. +# +# This file is part of Tix. +# +# Tix is free software: you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# Tix is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# Tix. If not, see . +# +# tix-eradicate-libtool-la +# Deletes useless libtool .la files from the staging directory. +# +################################################################################ + +set -e +cd "$TIX_INSTALL_DIR" +find | grep '\.la$' | while read la; do rm -f "$la"; done diff --git a/tix/tix-execdiff.cpp b/tix/tix-execdiff.cpp new file mode 100644 index 00000000..f113dc2a --- /dev/null +++ b/tix/tix-execdiff.cpp @@ -0,0 +1,224 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + This file is part of Tix. + + Tix is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or (at your option) any later + version. + + Tix is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + details. + + You should have received a copy of the GNU General Public License along with + Tix. If not, see . + + tix-execdiff.cpp + Reports which files have had the executable bit changed between two trees. + +*******************************************************************************/ + +#define __STDC_CONSTANT_MACROS +#define __STDC_LIMIT_MACROS + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" + +DIR* fdopendupdir(int fd) +{ + int newfd = dup(fd); + if ( newfd < 0 ) + return NULL; + DIR* result = fdopendir(newfd); + if ( !result ) + return close(newfd), (DIR*) NULL; + return result; +} + +int ftestexecutableat(int dirfd, const char* path) +{ + struct stat st; + if ( fstatat(dirfd, path, &st, AT_SYMLINK_NOFOLLOW) != 0 ) + return -1; + if ( !S_ISREG(st.st_mode) ) + return errno = EISDIR, -1; + if ( faccessat(dirfd, path, X_OK, AT_EACCESS | AT_SYMLINK_NOFOLLOW) == 0 ) + return 1; + else if ( errno == EACCES ) + return 0; + else + return -1; +} + +void execdiff(int tree_a, const char* tree_a_path, + int tree_b, const char* tree_b_path, + const char* relpath) +{ + DIR* dir_b = fdopendupdir(tree_b); + if ( !dir_b ) + error(1, errno, "fdopendupdir(`%s`)", tree_b_path); + while ( struct dirent* entry = readdir(dir_b) ) + { + if ( !strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..") ) + continue; + + char* subrelpath = join_paths(relpath, entry->d_name); + + int diropenflags = O_RDONLY | O_DIRECTORY | O_NOFOLLOW; + int subtree_b = openat(tree_b, entry->d_name, diropenflags); + if ( 0 <= subtree_b ) + { + char* subtree_b_path = join_paths(tree_b_path, entry->d_name); + int subtree_a = openat(tree_a, entry->d_name, diropenflags); + if ( subtree_a < 0 ) + { + if ( !(errno == ENOTDIR || errno == ELOOP || errno == ENOENT) ) + error(1, errno, "`%s/%s`", tree_b_path, entry->d_name); + execdiff(-1, NULL, subtree_b, subtree_b_path, subrelpath); + free(subtree_b_path); + close(subtree_b); + free(subrelpath); + continue; + } + char* subtree_a_path = join_paths(tree_a_path, entry->d_name); + execdiff(subtree_a, subtree_a_path, subtree_b, subtree_b_path, + subrelpath); + free(subtree_a_path); + close(subtree_a); + free(subtree_b_path); + close(subtree_b); + free(subrelpath); + continue; + } + else if ( !(errno == ENOTDIR || errno == ELOOP) ) + error(1, errno, "`%s/%s`", tree_b_path, entry->d_name); + + int a_executableness = ftestexecutableat(tree_a, entry->d_name); + int b_executableness = ftestexecutableat(tree_b, entry->d_name); + + if ( (a_executableness == 1) && (b_executableness == 0) ) + { + printf("chmod -x -- '"); + for ( size_t i = 0; subrelpath[i]; i++ ) + if ( subrelpath[i] == '\'' ) + printf("'\\''"); + else + putchar(subrelpath[i]); + printf("'\n"); + } + + if ( (a_executableness != 1) && (b_executableness == 1) ) + { + printf("chmod +x -- '"); + for ( size_t i = 0; subrelpath[i]; i++ ) + if ( subrelpath[i] == '\'' ) + printf("'\\''"); + else + putchar(subrelpath[i]); + printf("'\n"); + } + + free(subrelpath); + continue; + } + closedir(dir_b); +} + +void help(FILE* fp, const char* argv0) +{ + fprintf(fp, "Usage: %s [OPTION]... TREE-A TREE-B\n", argv0); + fprintf(fp, "Reports which files have had the executable bit changed between two trees.\n"); +} + +void version(FILE* fp, const char* argv0) +{ + help(fp, argv0); +} + +int main(int argc, char* argv[]) +{ + const char* argv0 = argv[0]; + for ( int i = 0; i < argc; i++ ) + { + const char* arg = argv[i]; + if ( arg[0] != '-' ) + continue; + argv[i] = NULL; + if ( !strcmp(arg, "--") ) + break; + if ( arg[1] != '-' ) + { + while ( char c = *++arg ) switch ( c ) + { + default: + fprintf(stderr, "%s: unknown option -- `%c'\n", argv0, c); + help(stderr, argv0); + exit(1); + } + } + else if ( !strcmp(arg, "--help") ) { help(stdout, argv0); exit(0); } + else if ( !strcmp(arg, "--version") ) { version(stdout, argv0); exit(0); } + else + { + fprintf(stderr, "%s: unknown option: `%s'\n", argv0, arg); + help(stderr, argv0); + exit(1); + } + } + + if ( argc == 1 ) + { + help(stdout, argv0); + exit(0); + } + + CompactArguments(&argc, &argv); + + if ( argc < 3 ) + { + fprintf(stderr, "%s: you need to specify two directories\n", argv0); + help(stderr, argv0); + exit(1); + } + + if ( 3 < argc ) + { + fprintf(stderr, "%s: unexpected extra operand `%s'\n", argv0, argv[2]); + help(stderr, argv0); + exit(1); + } + + const char* tree_a_path = argv[1]; + int tree_a = open(tree_a_path, O_RDONLY | O_DIRECTORY); + if ( tree_a < 0 ) + error(1, errno, "`%s'", tree_a_path); + + const char* tree_b_path = argv[2]; + int tree_b = open(tree_b_path, O_RDONLY | O_DIRECTORY); + if ( tree_b < 0 ) + error(1, errno, "`%s'", tree_b_path); + + execdiff(tree_a, tree_a_path, tree_b, tree_b_path, "."); + + return 0; +} diff --git a/tix/tix-execpatch.cpp b/tix/tix-execpatch.cpp new file mode 100644 index 00000000..4b229702 --- /dev/null +++ b/tix/tix-execpatch.cpp @@ -0,0 +1,246 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + This file is part of Tix. + + Tix is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or (at your option) any later + version. + + Tix is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + details. + + You should have received a copy of the GNU General Public License along with + Tix. If not, see . + + tix-execpatch.cpp + Patches the executable bits of files in the current source directory. + +*******************************************************************************/ + +#define __STDC_CONSTANT_MACROS +#define __STDC_LIMIT_MACROS + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" + +int fgetc_or_die(FILE* input, const char* input_path, size_t* line, + size_t* column) +{ + int result = fgetc(input); + if ( result == '\n' ) + (*line)++, *column = 0; + else + (*column)++; + if ( result == EOF && ferror(input) ) + error(1, errno, "read: `%s'", input_path); + return result; +} + +int fgetc_or_die_eof(FILE* input, const char* input_path, size_t* line, + size_t* column) +{ + int result = fgetc_or_die(input, input_path, line, column); + if ( result == EOF ) + error(1, errno, "%s:%zu:%zu: unexpected end of file", + input_path, *line, *column); + return result; +} + +bool check_eof(FILE* input, const char* input_path) +{ + size_t line = 0; + size_t column = 0; + int c = fgetc_or_die(input, input_path, &line, &column); + if ( c != EOF ) + { + ungetc(c, input); + return false; + } + return true; +} + +void parse_fixed(const char* text, FILE* input, const char* input_path, + size_t* line, size_t* column) +{ + for ( size_t i = 0; text[i]; i++ ) + { + int ic = fgetc_or_die(input, input_path, line, column); + if ( ic == EOF ) + error(1, errno, "%s:%zu:%zu: unexpected end of file, expected `%s'", + input_path, *line, *column, text + i); + if ( ic != (unsigned char) text[i] ) + error(1, errno, "%s:%zu:%zu: parse error, expected `%s'", input_path, + *line, *column, text + i); + } +} + +bool execpatch(FILE* input, const char* input_path, bool check) +{ + char* buffer = NULL; + size_t buffer_used = 0; + size_t buffer_length = 0; + + bool result = true; + size_t line = 1; + while ( !check_eof(input, input_path) ) + { + buffer_used = 0; + size_t column = 0; + parse_fixed("chmod ", input, input_path, &line, &column); + bool plus; + switch ( fgetc_or_die(input, input_path, &line, &column) ) + { + case '-': plus = false; break; + case '+': plus = true; break; + default: + error(1, errno, "%s:%zu:%zu: parse error, expected '-' or '+'", + input_path, line, column); + } + parse_fixed("x -- '", input, input_path, &line, &column); + while ( true ) + { + int ic = fgetc_or_die_eof(input, input_path, &line, &column); + if ( ic == '\'' ) + { + ic = fgetc_or_die(input, input_path, &line, &column); + if ( ic == EOF || ic == '\n' ) + break; + ungetc(ic, input); + parse_fixed("\\''", input, input_path, &line, &column); + ic = '\''; + } + if ( buffer_used == buffer_length ) + { + size_t new_length = buffer_length ? 2 * buffer_length : 16; + buffer = (char*) realloc(buffer, sizeof(char) * (new_length + 1)); + buffer_length = new_length; + } + buffer[buffer_used++] = ic; + } + if ( !buffer_used ) + error(1, errno, "%s:%zu: unexpected empty path", input_path, line); + assert(buffer_length); + buffer[buffer_used] = '\0'; + if ( buffer[0] == '/' ) + error(1, errno, "%s:%zu: unexpected absolute path", input_path, line); + if ( does_path_contain_dotdot(buffer) ) + error(1, errno, "%s:%zu: unexpected path with ..", input_path, line); + if ( check ) + continue; + struct stat st; + if ( fstatat(AT_FDCWD, buffer, &st, AT_SYMLINK_NOFOLLOW) != 0 ) + { + error(0, errno, "chmod %cx: `%s'", plus ? '+' : '-', buffer); + result = false; + continue; + } + mode_t new_mode = st.st_mode; + if ( plus ) + new_mode |= 0111 & ~get_umask_value(); + else + new_mode &= ~0111; + if ( fchmodat(AT_FDCWD, buffer, new_mode, 0) != 0 ) + { + error(0, errno, "chmod %cx: `%s'", plus ? '+' : '-', buffer); + result = false; + continue; + } + } + return result; +} + +void help(FILE* fp, const char* argv0) +{ + fprintf(fp, "Usage: %s [OPTION]... [PATCH]\n", argv0); + fprintf(fp, "Patches the executable bits of files in the current source directory.\n"); +} + +void version(FILE* fp, const char* argv0) +{ + help(fp, argv0); +} + +int main(int argc, char* argv[]) +{ + bool check = false; + char* directory = NULL; + + const char* argv0 = argv[0]; + for ( int i = 0; i < argc; i++ ) + { + const char* arg = argv[i]; + if ( arg[0] != '-' ) + continue; + argv[i] = NULL; + if ( !strcmp(arg, "--") ) + break; + if ( arg[1] != '-' ) + { + while ( char c = *++arg ) switch ( c ) + { + case 'c': check = true; break; + default: + fprintf(stderr, "%s: unknown option -- `%c'\n", argv0, c); + help(stderr, argv0); + exit(1); + } + } + else if ( !strcmp(arg, "--help") ) { help(stdout, argv0); exit(0); } + else if ( !strcmp(arg, "--version") ) { version(stdout, argv0); exit(0); } + else if ( !strcmp(arg, "--check") ) { check = true; } + else if ( GET_OPTION_VARIABLE("--directory", &directory) ) { } + else + { + fprintf(stderr, "%s: unknown option: `%s'\n", argv0, arg); + help(stderr, argv0); + exit(1); + } + } + + CompactArguments(&argc, &argv); + + if ( 2 < argc ) + { + fprintf(stderr, "%s: unexpected extra operand `%s'\n", argv0, argv[2]); + help(stderr, argv0); + exit(1); + } + + const char* input_path = ""; + FILE* input = stdin; + + if ( argc == 2 ) + { + input_path = argv[1]; + if ( !(input = fopen(input_path, "r")) ) + error(1, errno, "`%s'", input_path); + } + + if ( directory && chdir(directory) != 0 ) + error(1, errno, "chdir: `%s'", directory); + free(directory); + + return execpatch(input, input_path, check) ? 0 : 1; +} diff --git a/tix/tix-install.cpp b/tix/tix-install.cpp new file mode 100644 index 00000000..3ba9bedd --- /dev/null +++ b/tix/tix-install.cpp @@ -0,0 +1,342 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + This file is part of Tix. + + Tix is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or (at your option) any later + version. + + Tix is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + details. + + You should have received a copy of the GNU General Public License along with + Tix. If not, see . + + tix-install.cpp + Install a tix into a tix collection. + +*******************************************************************************/ + +#define __STDC_CONSTANT_MACROS +#define __STDC_LIMIT_MACROS + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" + +void TipTixCollection(const char* prefix) +{ + error(0, 0, "error: `%s' isn't a tix collection, use tix-collection before " + "installing packages.", prefix); +} + +void TipTixWrongPlatform(const char* prefix, const char* platform) +{ + error(0, 0, "error: `%s' isn't a tix collection for platform `%s', use " + "tix-collection to add this platform before installing " + "packages.", prefix, platform); +} + +void VerifyTixCollection(const char* prefix) +{ + if ( !IsDirectory(prefix) ) + { + if ( errno == ENOENT ) + TipTixCollection(prefix); + error(1, errno, "error: tix collection unavailable: `%s'", prefix); + } +} + +void VerifyTixDirectory(const char* prefix, const char* tix_dir) +{ + if ( !IsDirectory(tix_dir) ) + { + if ( errno == ENOENT ) + TipTixCollection(prefix); + error(1, errno, "error: tix database unavailable: `%s'", tix_dir); + } +} + +void VerifyTixDatabase(const char* prefix, + const char* tixdb_path, + const char* platform) +{ + if ( !IsDirectory(tixdb_path) ) + { + if ( errno == ENOENT ) + TipTixWrongPlatform(prefix, platform); + error(1, errno, "error: tix database for platform `%s' unavailable: " + "`%s'", platform, tixdb_path); + } + char* info_path = join_paths(tixdb_path, "collection.conf"); + if ( !IsFile(info_path) ) + { + if ( errno == ENOENT ) + TipTixCollection(prefix); + error(1, errno, "error: tix collection information unavailable: `%s'", + info_path); + } + char* installed_list_path = join_paths(tixdb_path, "installed.list"); + FILE* installed_list_fp = fopen(installed_list_path, "a"); + if ( !installed_list_fp ) + { + error(0, errno, "error: unable to open `%s' for writing", + installed_list_path); + error(1, 0, "error: `%s': do you have sufficient permissions to " + "administer this tix collection?", prefix); + } + fclose(installed_list_fp); + free(installed_list_path); + free(info_path); +} + +bool IsPackageInstalled(const char* tixdb_path, const char* package) +{ + char* installed_list_path = join_paths(tixdb_path, "installed.list"); + FILE* installed_list_fp = fopen(installed_list_path, "r"); + if ( !installed_list_fp ) + error(1, errno, "`%s'", installed_list_path); + + bool ret = false; + char* line = NULL; + size_t line_size; + ssize_t line_len; + while ( 0 < (line_len = getline(&line, &line_size, installed_list_fp)) ) + { + if ( line_len && line[line_len-1] == '\n' ) + line[--line_len] = '\0'; + if ( !strcmp(line, package) ) + { + ret = true; + break; + } + } + free(line); + + fclose(installed_list_fp); + free(installed_list_path); + return ret; +} + +void MarkPackageAsInstalled(const char* tixdb_path, const char* package) +{ + char* installed_list_path = join_paths(tixdb_path, "installed.list"); + FILE* installed_list_fp = fopen(installed_list_path, "a"); + if ( !installed_list_fp ) + error(1, errno, "`%s'", installed_list_path); + + fprintf(installed_list_fp, "%s\n", package); + fflush(installed_list_fp); + + if ( ferror(installed_list_fp) || fclose(installed_list_fp) == EOF ) + error(1, errno, "`%s'", installed_list_path); + free(installed_list_path); +} + +void Usage(FILE* fp, const char* argv0) +{ + fprintf(fp, "Usage: %s [OPTION]... --collection=PREFIX PACKAGE\n", argv0); + fprintf(fp, "Install a tix into a tix collection.\n"); +} + +void Help(FILE* fp, const char* argv0) +{ + Usage(fp, argv0); +} + +void Version(FILE* fp, const char* argv0) +{ + Usage(fp, argv0); +} + +int main(int argc, char* argv[]) +{ + char* collection = strdup_null(getenv_def("TIX_COLLECTION", NULL)); + char* prefix = strdup_null(getenv_def("TIX_COLLECTION_PREFIX", NULL)); + char* tar = strdup(getenv_def("TAR", "tar")); + bool reinstall = false; + + const char* argv0 = argv[0]; + for ( int i = 0; i < argc; i++ ) + { + const char* arg = argv[i]; + if ( arg[0] != '-' ) + continue; + argv[i] = NULL; + if ( !strcmp(arg, "--") ) + break; + if ( arg[1] != '-' ) + { + while ( char c = *++arg ) switch ( c ) + { + default: + fprintf(stderr, "%s: unknown option -- `%c'\n", argv0, c); + Usage(stderr, argv0); + exit(1); + } + } + else if ( !strcmp(arg, "--help") ) { Help(stdout, argv0); exit(0); } + else if ( !strcmp(arg, "--usage") ) { Usage(stdout, argv0); exit(0); } + else if ( !strcmp(arg, "--version") ) { Version(stdout, argv0); exit(0); } + else if ( GET_OPTION_VARIABLE("--collection", &collection) ) { } + else if ( GET_OPTION_VARIABLE("--prefix", &prefix) ) { } + else if ( GET_OPTION_VARIABLE("--tar", &tar) ) { } + else if ( !strcmp(arg, "--reinstall") ) { reinstall = true; } + else + { + fprintf(stderr, "%s: unknown option: `%s'\n", argv0, arg); + Usage(stderr, argv0); + exit(1); + } + } + + if ( argc == 1 ) + { + Usage(stdout, argv0); + exit(0); + } + + CompactArguments(&argc, &argv); + + if ( argc <= 1 ) + { + fprintf(stderr, "%s: no package specified\n", argv0); + Usage(stderr, argv0); + exit(1); + } + + if ( !collection && prefix ) + collection = strdup(prefix); + + if ( !collection ) + { + fprintf(stderr, "%s: no collection prefix specified, use --collection " + "or TIX_COLLECTION to specify where the package will " + "installed.\n", argv0); + Usage(stderr, argv0); + exit(1); + } + + if ( !prefix ) + prefix = strdup(collection); + + if ( strcmp(collection, prefix) != 0 ) + error(1, 0, "error: desired collection `%s' isn't equal to desired " + "prefix `%s', which isn't supported (and dangerous).\n", + collection, prefix); + + if ( !*collection ) + collection = strdup("/"); + + VerifyTixCollection(collection); + + char* tix_directory_path = join_paths(collection, "tix"); + + VerifyTixDirectory(collection, tix_directory_path); + + char* tix_path = strdup(argv[1]); + if ( !IsFile(tix_path) ) + error(1, errno, "`%s'", tix_path); + + const char* tixinfo_path = "tix/tixinfo"; + if ( !TarContainsFile(tar, tix_path, tixinfo_path) ) + error(1, 0, "`%s' doesn't contain a `%s' file", tix_path, tixinfo_path); + + string_array_t tixinfo = string_array_make(); + FILE* tixinfo_fp = TarOpenFile(tar, tix_path, tixinfo_path); + dictionary_append_file(&tixinfo, tixinfo_fp); + fclose(tixinfo_fp); + + const char* package_name = dictionary_get(&tixinfo, "pkg.name"); + assert(package_name); + + const char* package_prefix = dictionary_get(&tixinfo, "pkg.prefix"); + + const char* package_platform = dictionary_get(&tixinfo, "tix.platform"); + assert(package_platform); + + char* tixdb_path = join_paths(tix_directory_path, package_platform); + free(tix_directory_path); + + VerifyTixDatabase(collection, tixdb_path, package_platform); + + char* coll_conf_path = join_paths(tixdb_path, "collection.conf"); + string_array_t coll_conf = string_array_make(); + if ( !dictionary_append_file_path(&coll_conf, coll_conf_path) ) + error(1, errno, "`%s'", coll_conf_path); + VerifyTixCollectionConfiguration(&coll_conf, coll_conf_path); + free(coll_conf_path); + + const char* coll_prefix = dictionary_get(&coll_conf, "collection.prefix"); + assert(coll_prefix); + const char* coll_platform = dictionary_get(&coll_conf, "collection.platform"); + assert(coll_platform); + + bool already_installed = IsPackageInstalled(tixdb_path, package_name); + if ( already_installed && !reinstall ) + error(1, 0, "error: package `%s' is already installed.", package_name); + + if ( package_prefix && strcmp(coll_prefix, package_prefix) != 0 ) + { + error(0, errno, "error: `%s' is compiled with the prefix `%s', but the " + "destination collection has the prefix `%s'.", tix_path, + package_prefix, coll_prefix); + error(1, errno, "you need to recompile the package with " + "--prefix=\"%s\".", coll_prefix); + } + + if ( strcmp(coll_platform, package_platform) != 0 ) + { + error(0, errno, "error: `%s' is compiled with the platform `%s', but " + "the destination collection has the platform `%s'.", + tix_path, package_platform, coll_platform); + error(1, errno, "you need to recompile the package with " + "--host=%s\".", coll_platform); + } + + printf("Installing `%s' into `%s'...\n", package_name, prefix); + char* data_and_prefix = package_prefix && prefix[0] ? + print_string("data%s", package_prefix) : + strdup("data"); + if ( fork_and_wait_or_death() ) + { + size_t num_strips = count_tar_components(data_and_prefix); + const char* cmd_argv[] = + { + tar, + print_string("--strip-components=%zu", num_strips), + "-C", prefix, + "--extract", + "--file", tix_path, + data_and_prefix, + NULL + }; + execvp(cmd_argv[0], (char* const*) cmd_argv); + error(127, errno, "%s", cmd_argv[0]); + } + free(data_and_prefix); + + if ( !already_installed ) + MarkPackageAsInstalled(tixdb_path, package_name); +} diff --git a/tix/tix-object-insert.cpp b/tix/tix-object-insert.cpp new file mode 100644 index 00000000..1e16a1e6 --- /dev/null +++ b/tix/tix-object-insert.cpp @@ -0,0 +1,427 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + This file is part of Tix. + + Tix is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or (at your option) any later + version. + + Tix is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + details. + + You should have received a copy of the GNU General Public License along with + Tix. If not, see . + + tix-object-insert.cpp + Inserts files into a tix object database. + +*******************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +int create_and_open_directory_at(int dirfd, const char* path, mode_t mode) +{ + + int ret = openat(dirfd, path, O_RDONLY | O_DIRECTORY); + if ( ret < 0 && errno == EEXIST ) + { + if ( mkdirat(dirfd, path, mode) != 0 ) + return -1; + return openat(dirfd, path, O_RDONLY | O_DIRECTORY); + } + return ret; +} + +struct options +{ + bool hash; + bool quiet; + bool link; + bool symlink; +}; + +bool insert_object(struct options* opts, + const char* file_path, + const char* tmp, + int database_fd, const char* database_path, + const char* digest, + int dir_fd, const char* dir_name) +{ + const char* entry_name = digest + 2; + if ( linkat(database_fd, tmp, dir_fd, entry_name, 0) && + errno != EEXIST ) + { + error(0, errno, "`%s/%s' -> `%s/%s/%s'", database_path, dir_name, + database_path, dir_name, entry_name); + return false; + } + + if ( opts->hash ) + printf("%s\n", digest); + else if ( !opts->quiet ) + printf("`%s' -> `%s/%s/%s'\n", file_path, + database_path, dir_name, entry_name); + + return true; +} + +bool insert_object(struct options* opts, + const char* file_path, + const char* tmp, + int database_fd, const char* database_path, + const char* digest) +{ + char dir_name[3] = { digest[0], digest[1], '\0' }; + int dir_fd = create_and_open_directory_at(database_fd, dir_name, 0777); + if ( dir_fd < 0 ) + { + error(0, errno, "`%s/%s'", database_path, dir_name); + return false; + } + + bool success = insert_object(opts, file_path, tmp, database_fd, database_path, digest, dir_fd, dir_name); + + close(dir_fd); + + return success; +} + +bool copy_and_hash(FILE* fpin, const char* file_path, + FILE* fpout, const char* tmp, + int /*database_fd*/, const char* database_path, + char* digest) +{ + struct sha256_ctx shactx; + sha256_init(&shactx); + + uint8_t buffer[SHA256_DATA_SIZE]; + while ( size_t num_bytes = fread(buffer, 1, sizeof(buffer), fpin) ) + { + assert(num_bytes <= UINT_MAX); + sha256_update(&shactx, num_bytes, buffer); + if ( fwrite(buffer, 1, num_bytes, fpout) != num_bytes ) + { + error(0, errno, "`%s/%s'", database_path, tmp); + return false; + } + } + + if ( ferror(fpin) ) + { + error(0, errno, "`%s'", file_path); + return false; + } + + if ( fflush(fpout) != 0 ) + { + error(0, errno, "`%s/%s'", database_path, tmp); + return false; + } + + uint8_t binary_digest[SHA256_DIGEST_SIZE]; + sha256_digest(&shactx, sizeof(binary_digest), binary_digest); + + for ( size_t n = 0; n < SHA256_DIGEST_SIZE; n++ ) + snprintf(digest + 2 * n, 3, "%02x", binary_digest[n]); + + return true; +} + +bool insert_object(struct options* opts, + FILE* fpin, const char* file_path, + FILE* fpout, const char* tmp, + int database_fd, const char* database_path) +{ + char digest[2 * SHA256_DIGEST_SIZE + 1]; + + if ( !copy_and_hash(fpin, file_path, fpout, tmp, database_fd, + database_path, digest) ) + return false; + + return insert_object(opts, file_path, tmp, database_fd, database_path, digest); +} + +// TODO: Preferably use O_TMPFILE to avoid naming the file until we have made a +// copy whose hash we know. +bool insert_object(struct options* opts, + FILE* fpin, const char* file_path, + int database_fd, const char* database_path) +{ + char tmp[8 + 1 + sizeof(pid_t) * 3 + 1]; + snprintf(tmp, sizeof(tmp), "incoming.%ji", (intmax_t) getpid()); + int tmp_fd = openat(database_fd, tmp, O_CREAT | O_WRONLY | O_EXCL, 0444); + if ( tmp_fd < 0 ) + { + error(0, errno, "`%s/%s'", database_path, tmp); + return false; + } + + FILE* fpout = fdopen(tmp_fd, "w"); + if ( !fpout ) + { + error(0, errno, "fdopen(%i)", tmp_fd); + close(tmp_fd); + return false; + } + + bool success = insert_object(opts, fpin, file_path, fpout, tmp, database_fd, database_path); + + fclose(fpout); + + unlinkat(database_fd, tmp, 0); + + return success; +} + +bool insert_object(struct options* opts, + const char* file_path, + int database_fd, const char* database_path) +{ + if ( !strcmp(file_path, "-") ) + return insert_object(opts, stdin, file_path, database_fd, database_path); + + FILE* fpin = fopen(file_path, "r"); + if ( !fpin ) + error(1, errno, "`%s'", file_path); + + bool success = insert_object(opts, fpin, file_path, database_fd, database_path); + + fclose(fpin); + + return success; +} + +void help(FILE* fp, const char* argv0) +{ + fprintf(fp, "Usage: %s [OPTION]... [-l | -s] --database DATABASE FILE...\n", argv0); + fprintf(fp, "Inserts files into a tix object database.\n"); +} + +void usage(FILE* fp, const char* argv0) +{ + help(fp, argv0); +} + +void version(FILE* fp, const char* argv0) +{ + help(fp, argv0); +} + +int main(int argc, char* argv[]) +{ + const char* argv0 = argv[0]; + const char* database_path = NULL; + bool hash = false; + bool quiet = false; + bool do_link = false; + bool do_symlink = false; + + for ( int i = 0; i < argc; i++ ) + { + const char* arg = argv[i]; + if ( arg[0] != '-' ) + continue; + argv[i] = NULL; + if ( !strcmp(arg, "--") ) + break; + if ( arg[1] != '-' ) + { + while ( char c = *++arg ) switch ( c ) + { + case 'h': hash = true; + case 'l': do_link = true; + case 'q': quiet = true; + case 's': do_link = true; + default: + fprintf(stderr, "%s: unknown option -- '%c'\n", argv0, c); + usage(stderr, argv0); + exit(1); + } + } + else if ( !strcmp(arg, "--help") ) + help(stdout, argv0), exit(0); + else if ( !strcmp(arg, "--usage") ) + usage(stdout, argv0), exit(0); + else if ( !strcmp(arg, "--version") ) + version(stdout, argv0), exit(0); + else if ( !strcmp(arg, "--database") ) + { + if ( i + 1 == argc ) + error(1, 0, "`--database' expected argument"); + database_path = argv[++i], argv[i] = NULL; + } + else if ( !strcmp(arg, "--hash") ) + hash = true; + else if ( !strcmp(arg, "--link") ) + do_link = true; + else if ( !strcmp(arg, "--quiet") ) + quiet = true; + else if ( !strcmp(arg, "--symlink") ) + do_symlink = true; + else + { + fprintf(stderr, "%s: unknown option: `%s'\n", argv0, arg); + usage(stderr, argv0); + exit(1); + } + } + + if ( argc == 1 ) + usage(stdout, argv0), exit(0); + + if ( do_link && do_symlink ) + { + error(0, 0, "error: the -l and -s options are mutually exclusive"); + usage(stdout, argv0); + exit(1); + } + + if ( !database_path ) + { + error(1, 0, "no `--database' option given, don't know what database"); + usage(stdout, argv0); + exit(1); + } + + int database_fd = open(database_path, O_RDONLY | O_DIRECTORY); + if ( database_fd < 0 ) + error(1, errno, "`%s'", database_path); + + for ( int i = 1; i < argc; i++ ) + { + const char* file_path = argv[i]; + if ( !file_path ) + continue; + + FILE* fpin = fopen(file_path, "r"); + if ( !fpin ) + error(1, errno, "`%s'", file_path); + + char tmp[8 + 1 + sizeof(pid_t) * 3 + 1]; + snprintf(tmp, sizeof(tmp), "incoming.%ju", (uintmax_t) getpid()); + int tmp_fd = openat(database_fd, tmp, O_CREAT | O_WRONLY | O_EXCL, 0444); + if ( tmp_fd < 0 ) + error(1, errno, "`%s/%s'", database_path, tmp); + FILE* fpout = fdopen(tmp_fd, "w"); + + struct sha256_ctx shactx; + sha256_init(&shactx); + + uint8_t buffer[SHA256_DATA_SIZE]; + while ( size_t num_bytes = fread(buffer, 1, sizeof(buffer), fpin) ) + { + assert(num_bytes <= UINT_MAX); + sha256_update(&shactx, num_bytes, buffer); + if ( fwrite(buffer, 1, num_bytes, fpout) != num_bytes ) + { + unlinkat(database_fd, tmp, 0); + error(1, errno, "`%s/%s'", database_path, tmp); + } + } + + if ( ferror(fpin) ) + { + unlinkat(database_fd, tmp, 0); + error(1, errno, "`%s'", file_path); + } + fclose(fpin); + + if ( fflush(fpout) != 0 ) + { + unlinkat(database_fd, tmp, 0); + error(1, errno, "`%s/%s'", database_path, tmp); + } + + fclose(fpout); + + uint8_t binary_digest[SHA256_DIGEST_SIZE]; + sha256_digest(&shactx, sizeof(binary_digest), binary_digest); + char digest[2 * SHA256_DIGEST_SIZE + 1]; + for ( size_t n = 0; n < SHA256_DIGEST_SIZE; n++ ) + snprintf(digest + 2 * n, 3, "%02x", binary_digest[n]); + + char dir_name[3] = { digest[0], digest[1], '\0' }; + if ( mkdirat(database_fd, dir_name, 0777) != 0 && + errno != EEXIST ) + { + unlinkat(database_fd, tmp, 0); + error(1, errno, "`%s/%s'", database_path, dir_name); + } + + int dir_fd = openat(database_fd, dir_name, O_RDONLY | O_DIRECTORY); + if ( dir_fd < 0 ) + { + unlinkat(database_fd, tmp, 0); + error(1, errno, "`%s/%s'", database_path, dir_name); + } + + const char* entry_name = digest + 2; + if ( linkat(database_fd, tmp, dir_fd, entry_name, 0) && + errno != EEXIST ) + { + unlinkat(database_fd, tmp, 0); + error(1, errno, "`%s/%s' -> `%s/%s/%s'", database_path, dir_name, + database_path, dir_name, entry_name); + } + + close(dir_fd); + + unlinkat(database_fd, tmp, 0); + + if ( hash ) + printf("%s\n", digest); + else if ( !quiet ) + printf("`%s' -> `%s/%s/%s'\n", file_path, + database_path, dir_name, entry_name); + + if ( do_link || do_symlink ) + { + if ( unlink(file_path) < 0 ) + error(1, errno, "cannot unlink: `%s'", file_path); + + size_t link_dest_length = strlen(database_path) + 1 + strlen(dir_name) + strlen(entry_name); + char* link_dest = (char*) malloc(sizeof(char) * (link_dest_length + 1)); + + stpcpy(stpcpy(stpcpy(stpcpy(stpcpy(link_dest, database_path), "/"), dir_name), "/"), entry_name); + + if ( do_symlink ) + { + if ( symlink(link_dest, file_path) < 0 ) + error(1, errno, "cannot symlink `%s' to `%s'", file_path, + link_dest); + } + else + { + if ( link(link_dest, file_path) < 0 ) + error(1, errno, "cannot link `%s' to `%s'", file_path, + link_dest); + } + + free(link_dest); + } + } + + close(database_fd); + + return 0; +} diff --git a/tix/tix-rmpatch.cpp b/tix/tix-rmpatch.cpp new file mode 100644 index 00000000..ae02d3b9 --- /dev/null +++ b/tix/tix-rmpatch.cpp @@ -0,0 +1,240 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + This file is part of Tix. + + Tix is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or (at your option) any later + version. + + Tix is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + details. + + You should have received a copy of the GNU General Public License along with + Tix. If not, see . + + tix-rmpatch.cpp + Removes files from the current source directory. + +*******************************************************************************/ + +#define __STDC_CONSTANT_MACROS +#define __STDC_LIMIT_MACROS + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" + +int fgetc_or_die(FILE* input, const char* input_path, size_t* line, + size_t* column) +{ + int result = fgetc(input); + if ( result == '\n' ) + (*line)++, *column = 0; + else + (*column)++; + if ( result == EOF && ferror(input) ) + error(1, errno, "read: `%s'", input_path); + return result; +} + +int fgetc_or_die_eof(FILE* input, const char* input_path, size_t* line, + size_t* column) +{ + int result = fgetc_or_die(input, input_path, line, column); + if ( result == EOF ) + error(1, errno, "%s:%zu:%zu: unexpected end of file", + input_path, *line, *column); + return result; +} + +bool check_eof(FILE* input, const char* input_path) +{ + size_t line = 0; + size_t column = 0; + int c = fgetc_or_die(input, input_path, &line, &column); + if ( c != EOF ) + { + ungetc(c, input); + return false; + } + return true; +} + +void parse_fixed(const char* text, FILE* input, const char* input_path, + size_t* line, size_t* column) +{ + for ( size_t i = 0; text[i]; i++ ) + { + int ic = fgetc_or_die(input, input_path, line, column); + if ( ic == EOF ) + error(1, errno, "%s:%zu:%zu: unexpected end of file, expected `%s'", + input_path, *line, *column, text + i); + if ( ic != (unsigned char) text[i] ) + error(1, errno, "%s:%zu:%zu: parse error, expected `%s'", input_path, + *line, *column, text + i); + } +} + +bool rmpatch(FILE* input, const char* input_path, bool check) +{ + char* buffer = NULL; + size_t buffer_used = 0; + size_t buffer_length = 0; + + bool result = true; + size_t line = 1; + while ( !check_eof(input, input_path) ) + { + buffer_used = 0; + size_t column = 0; + parse_fixed("rm -rf -- '", input, input_path, &line, &column); + while ( true ) + { + int ic = fgetc_or_die_eof(input, input_path, &line, &column); + if ( ic == '\'' ) + { + ic = fgetc_or_die(input, input_path, &line, &column); + if ( ic == EOF || ic == '\n' ) + break; + ungetc(ic, input); + parse_fixed("\\''", input, input_path, &line, &column); + ic = '\''; + } + if ( buffer_used == buffer_length ) + { + size_t new_length = buffer_length ? 2 * buffer_length : 16; + buffer = (char*) realloc(buffer, sizeof(char) * (new_length + 1)); + buffer_length = new_length; + } + buffer[buffer_used++] = ic; + } + if ( !buffer_used ) + error(1, errno, "%s:%zu: unexpected empty path", input_path, line); + assert(buffer_length); + buffer[buffer_used] = '\0'; + if ( buffer[0] == '/' ) + error(1, errno, "%s:%zu: unexpected absolute path", input_path, line); + if ( does_path_contain_dotdot(buffer) ) + error(1, errno, "%s:%zu: unexpected path with ..", input_path, line); + if ( check ) + continue; + if ( pid_t child_pid = fork_or_death() ) + { + int status; + waitpid(child_pid, &status, 0); + if ( WIFSIGNALED(status) ) + error(128 + WTERMSIG(status), 0, "child with pid %ju was killed by " + "signal %i (%s).", (uintmax_t) child_pid, WTERMSIG(status), + strsignal(WTERMSIG(status))); + if ( WIFEXITED(status) && WEXITSTATUS(status) != 0 ) + result = false; + continue; + } + const char* cmd_argv[] = + { + "rm", + "-rf", + "--", + buffer, + NULL, + }; + execvp(cmd_argv[0], (char* const*) cmd_argv); + error(127, errno, "%s", cmd_argv[0]); + } + return result; +} + +void help(FILE* fp, const char* argv0) +{ + fprintf(fp, "Usage: %s [OPTION]... [PATCH]\n", argv0); + fprintf(fp, "Removes files from the current source directory.\n"); +} + +void version(FILE* fp, const char* argv0) +{ + help(fp, argv0); +} + +int main(int argc, char* argv[]) +{ + bool check = false; + char* directory = NULL; + + const char* argv0 = argv[0]; + for ( int i = 0; i < argc; i++ ) + { + const char* arg = argv[i]; + if ( arg[0] != '-' ) + continue; + argv[i] = NULL; + if ( !strcmp(arg, "--") ) + break; + if ( arg[1] != '-' ) + { + while ( char c = *++arg ) switch ( c ) + { + case 'c': check = true; break; + default: + fprintf(stderr, "%s: unknown option -- `%c'\n", argv0, c); + help(stderr, argv0); + exit(1); + } + } + else if ( !strcmp(arg, "--help") ) { help(stdout, argv0); exit(0); } + else if ( !strcmp(arg, "--version") ) { version(stdout, argv0); exit(0); } + else if ( !strcmp(arg, "--check") ) { check = true; } + else if ( GET_OPTION_VARIABLE("--directory", &directory) ) { } + else + { + fprintf(stderr, "%s: unknown option: `%s'\n", argv0, arg); + help(stderr, argv0); + exit(1); + } + } + + CompactArguments(&argc, &argv); + + if ( 2 < argc ) + { + fprintf(stderr, "%s: unexpected extra operand `%s'\n", argv0, argv[2]); + help(stderr, argv0); + exit(1); + } + + const char* input_path = ""; + FILE* input = stdin; + + if ( argc == 2 ) + { + input_path = argv[1]; + if ( !(input = fopen(input_path, "r")) ) + error(1, errno, "`%s'", input_path); + } + + if ( directory && chdir(directory) != 0 ) + error(1, errno, "chdir: `%s'", directory); + free(directory); + + return rmpatch(input, input_path, check) ? 0 : 1; +} diff --git a/tix/tix.cpp b/tix/tix.cpp new file mode 100644 index 00000000..82f41ff3 --- /dev/null +++ b/tix/tix.cpp @@ -0,0 +1,285 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + This file is part of Tix. + + Tix is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or (at your option) any later + version. + + Tix is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + details. + + You should have received a copy of the GNU General Public License along with + Tix. If not, see . + + tix.cpp + Front end to the Tix package management system. + +*******************************************************************************/ + +#define __STDC_CONSTANT_MACROS +#define __STDC_LIMIT_MACROS + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" + +typedef struct +{ + char* collection; + char* tar; + char* tixdb_path; + char* tix_install; + string_array_t coll_conf; + string_array_t repo_list; + string_array_t inst_list; +} params_t; + +char* FindPackageInRepository(const char* repo, const char* pkg_name) +{ + char* repo_index_path = join_paths(repo, "repository.index"); + string_array_t repo_index = string_array_make(); + if ( !dictionary_append_file_path(&repo_index, repo_index_path) ) + error(1, errno, "bad repository: `%s'", repo_index_path); + free(repo_index_path); + const char* pkg_path_rel = dictionary_get(&repo_index, pkg_name); + char* ret = pkg_path_rel ? join_paths(repo, pkg_path_rel) : NULL; + string_array_reset(&repo_index); + return ret; +} + +char* FindPackage(string_array_t* repositories, const char* pkg_name) +{ + for ( size_t i = 0; i < repositories->length; i++ ) + { + const char* repo = repositories->strings[i]; + char* ret = FindPackageInRepository(repo, pkg_name); + if ( ret ) + return ret; + } + return NULL; +} + +string_array_t GetPackageDependencies(params_t* params, const char* pkg_name) +{ + string_array_t ret = string_array_make(); + + char* pkg_path = FindPackage(¶ms->repo_list, pkg_name); + if ( !pkg_path ) + error(1, errno, "unable to locate package `%s'", pkg_name); + + const char* tixinfo_path = "tix/tixinfo"; + if ( !TarContainsFile(params->tar, pkg_path, tixinfo_path) ) + error(1, 0, "`%s' doesn't contain a `%s' file", pkg_path, tixinfo_path); + + string_array_t tixinfo = string_array_make(); + FILE* tixinfo_fp = TarOpenFile(params->tar, pkg_path, tixinfo_path); + dictionary_append_file(&tixinfo, tixinfo_fp); + fclose(tixinfo_fp); + + VerifyTixInformation(&tixinfo, pkg_path); + + const char* deps = dictionary_get(&tixinfo, "pkg.runtime-deps", ""); + string_array_append_token_string(&ret, deps); + + string_array_reset(&tixinfo); + + free(pkg_path); + + return ret; +} + +void GetPackageRecursiveDependencies(params_t* params, string_array_t* sofar, + const char* pkg_name) +{ + if ( string_array_contains(sofar, pkg_name) ) + return; + + // Avoid endless recursion by adding our package before the recursive call, + // in case we need to satisfy cyclic dependencies. + string_array_append(sofar, pkg_name); + + string_array_t pkg_deps = GetPackageDependencies(params, pkg_name); + for ( size_t i = 0; i < pkg_deps.length; i++ ) + if ( !string_array_contains(sofar, pkg_deps.strings[i]) ) + GetPackageRecursiveDependencies(params, sofar, pkg_deps.strings[i]); + string_array_reset(&pkg_deps); +} + +void InstallPackageOfName(params_t* params, const char* pkg_name) +{ + char* pkg_path = FindPackage(¶ms->repo_list, pkg_name); + if ( !pkg_path ) + error(1, errno, "unable to locate package `%s'", pkg_name); + + if ( fork_and_wait_or_death() ) + { + const char* cmd_argv[] = + { + params->tix_install, + "--collection", params->collection, + "--tar", params->tar, + "--", pkg_path, + NULL + }; + execvp(cmd_argv[0], (char* const*) cmd_argv); + error(127, errno, "`%s'", cmd_argv[0]); + } + + free(pkg_path); +} + +void Usage(FILE* fp, const char* argv0) +{ + fprintf(fp, "Usage: %s PREFIX COMMAND [OPTION]...\n", argv0); + fprintf(fp, "Front end to the Tix package management system.\n"); +} + +void Help(FILE* fp, const char* argv0) +{ + Usage(fp, argv0); +} + +void Version(FILE* fp, const char* argv0) +{ + Usage(fp, argv0); +} + +int main(int argc, char* argv[]) +{ + params_t params; + params.collection = strdup_null(getenv_def("TIX_COLLECTION", NULL)); + params.tar = strdup(getenv_def("TAR", "tar")); + params.tix_install = strdup("tix-install"); + + const char* argv0 = argv[0]; + for ( int i = 0; i < argc; i++ ) + { + const char* arg = argv[i]; + if ( arg[0] != '-' ) + continue; + argv[i] = NULL; + if ( !strcmp(arg, "--") ) + break; + if ( arg[1] != '-' ) + { + while ( char c = *++arg ) switch ( c ) + { + default: + fprintf(stderr, "%s: unknown option -- `%c'\n", argv0, c); + Usage(stderr, argv0); + exit(1); + } + } + else if ( !strcmp(arg, "--help") ) { Help(stdout, argv0); exit(0); } + else if ( !strcmp(arg, "--usage") ) { Usage(stdout, argv0); exit(0); } + else if ( !strcmp(arg, "--version") ) { Version(stdout, argv0); exit(0); } + else if ( GET_OPTION_VARIABLE("--collection", ¶ms.collection) ) { } + else if ( GET_OPTION_VARIABLE("--tar", ¶ms.tar) ) { } + else if ( GET_OPTION_VARIABLE("--tix-install", ¶ms.tix_install) ) { } + else + { + fprintf(stderr, "%s: unknown option: `%s'\n", argv0, arg); + Usage(stderr, argv0); + exit(1); + } + } + + if ( argc == 1 ) + { + Usage(stdout, argv0); + exit(0); + } + + CompactArguments(&argc, &argv); + + ParseOptionalCommandLineCollectionPrefix(¶ms.collection, &argc, &argv); + VerifyCommandLineCollection(¶ms.collection); + + params.tixdb_path = join_paths(params.collection, "tix"); + + char* coll_conf_path = join_paths(params.tixdb_path, "collection.conf"); + params.coll_conf = string_array_make(); + if ( !dictionary_append_file_path(¶ms.coll_conf, coll_conf_path) ) + error(1, errno, "`%s'", coll_conf_path); + VerifyTixCollectionConfiguration(¶ms.coll_conf, coll_conf_path); + free(coll_conf_path); + + char* repo_list_path = join_paths(params.tixdb_path, "repository.list"); + params.repo_list = string_array_make(); + if ( !string_array_append_file_path(¶ms.repo_list, repo_list_path) ) + error(1, errno, "`%s'", repo_list_path); + free(repo_list_path); + + char* inst_list_path = join_paths(params.tixdb_path, "installed.list"); + params.inst_list = string_array_make(); + if ( !string_array_append_file_path(¶ms.inst_list, inst_list_path) ) + error(1, errno, "`%s'", inst_list_path); + free(inst_list_path); + + if ( argc == 1 ) + { + error(0, 0, "error: no command specified."); + Usage(stderr, argv0); + exit(1); + } + + const char* cmd = argv[1]; + if ( !strcmp(cmd, "install") ) + { + if ( argc == 2 ) + { + error(0, 0, "expected list of packages to install after `install'"); + Usage(stderr, argv0); + exit(1); + } + + string_array_t work = string_array_make(); + + for ( int i = 2; i < argc; i++ ) + { + const char* pkg_name = argv[i]; + if ( string_array_contains(¶ms.inst_list, pkg_name) ) + { + printf("Package `%s' is already installed.\n", pkg_name); + continue; + } + + GetPackageRecursiveDependencies(¶ms, &work, pkg_name); + } + + for ( size_t i = 0; i < work.length; i++ ) + InstallPackageOfName(¶ms, work.strings[i]); + + string_array_reset(&work); + + return 0; + } + else + { + fprintf(stderr, "%s: unknown command: `%s'\n", argv0, cmd); + Usage(stderr, argv0); + exit(1); + } +} diff --git a/tix/util.h b/tix/util.h new file mode 100644 index 00000000..ab9aca76 --- /dev/null +++ b/tix/util.h @@ -0,0 +1,1033 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + This file is part of Tix. + + Tix is free software: you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or (at your option) any later + version. + + Tix is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + details. + + You should have received a copy of the GNU General Public License along with + Tix. If not, see . + + util.h + Shared Utility functions for Tix. + +*******************************************************************************/ + +#ifndef UTIL_H +#define UTIL_H + +bool does_path_contain_dotdot(const char* path) +{ + size_t index = 0; + while ( path[index] ) + { + if ( path[index] == '.' && path[index+1] == '.' ) + { + index += 2; + if ( !path[index] || path[index] == '/' ) + return true; + } + while ( path[index] && path[index] != '/' ) + index++; + while ( path[index] == '/' ) + index++; + } + return false; +} + +bool does_path_contain_dot_or_dotdot(const char* path) +{ + size_t index = 0; + while ( path[index] ) + { + if ( path[index] == '.' ) + { + index++; + if ( path[index] == '.' ) + index++; + if ( !path[index] || path[index] == '/' ) + return true; + } + while ( path[index] && path[index] != '/' ) + index++; + while ( path[index] == '/' ) + index++; + } + return false; +} + +bool parse_boolean(const char* str) +{ + return strcmp(str, "true") == 0; +} + +char* strdup_null(const char* src) +{ + return src ? strdup(src) : NULL; +} + +const char* non_modify_basename(const char* path) +{ + const char* last_slash = (char*) strrchr((char*) path, '/'); + if ( !last_slash ) + return path; + return last_slash + 1; +} + +typedef struct +{ + char** strings; + size_t length; + size_t capacity; +} string_array_t; + +string_array_t string_array_make() +{ + string_array_t sa; + sa.strings = NULL; + sa.length = sa.capacity = 0; + return sa; +} + +void string_array_reset(string_array_t* sa) +{ + for ( size_t i = 0; i < sa->length; i++ ) + free(sa->strings[i]); + free(sa->strings); + *sa = string_array_make(); +} + +bool string_array_append(string_array_t* sa, const char* str) +{ + if ( sa->length == sa->capacity ) + { + size_t new_capacity = sa->capacity ? sa->capacity * 2 : 8; + size_t new_size = sizeof(char*) * new_capacity; + char** new_strings = (char**) realloc(sa->strings, new_size); + if ( !new_strings ) + return false; + sa->strings = new_strings; + sa->capacity = new_capacity; + } + char* copy = strdup_null(str); + if ( str && !copy ) + return false; + sa->strings[sa->length++] = copy; + return true; +} + +bool string_array_append_token_string(string_array_t* sa, const char* str) +{ + while ( *str ) + { + if ( isspace(*str) ) + { + str++; + continue; + } + + size_t input_length = 0; + size_t output_length = 0; + bool quoted = false; + bool escaped = false; + while ( str[input_length] && + (escaped || quoted || !isspace(str[input_length])) ) + { + if ( !escaped && str[input_length] == '\\' ) + escaped = true; + else if ( !escaped && str[input_length] == '"' ) + quoted = !quoted; + else + escaped = false, output_length++; + input_length++; + } + + if ( quoted || escaped ) + return false; + + char* output = (char*) malloc(sizeof(char) * (output_length+1)); + if ( !output ) + return false; + + input_length = 0; + output_length = 0; + quoted = false; + escaped = false; + while ( str[input_length] && + (escaped || quoted || !isspace(str[input_length])) ) + { + if ( !escaped && str[input_length] == '\\' ) + escaped = true; + else if ( !escaped && str[input_length] == '"' ) + quoted = !quoted; + else if ( escaped && str[input_length] == 'a' ) + escaped = false, output[output_length++] = '\a'; + else if ( escaped && str[input_length] == 'b' ) + escaped = false, output[output_length++] = '\b'; + else if ( escaped && str[input_length] == 'e' ) + escaped = false, output[output_length++] = '\e'; + else if ( escaped && str[input_length] == 'f' ) + escaped = false, output[output_length++] = '\f'; + else if ( escaped && str[input_length] == 'n' ) + escaped = false, output[output_length++] = '\n'; + else if ( escaped && str[input_length] == 'r' ) + escaped = false, output[output_length++] = '\r'; + else if ( escaped && str[input_length] == 't' ) + escaped = false, output[output_length++] = '\t'; + else if ( escaped && str[input_length] == 'v' ) + escaped = false, output[output_length++] = '\v'; + else + escaped = false, output[output_length++] = str[input_length]; + input_length++; + } + + output[output_length] = '\0'; + + if ( !string_array_append(sa, output) ) + { + free(output); + return false; + } + + free(output); + + str += input_length; + } + + return true; +} + +bool is_token_string_special_character(char c) +{ + return isspace(c) || c == '"' || c == '\\'; +} + +char* token_string_of_string_array(const string_array_t* sa) +{ + size_t result_length = 0; + + for ( size_t i = 0; i < sa->length; i++ ) + { + if ( i ) + result_length++; + + for ( size_t n = 0; sa->strings[i][n]; n++ ) + { + if ( is_token_string_special_character(sa->strings[i][n]) ) + result_length++; + result_length++; + } + } + + char* result = (char*) malloc(sizeof(char) * (result_length + 1)); + if ( !result ) + return NULL; + + result_length = 0; + + for ( size_t i = 0; i < sa->length; i++ ) + { + if ( i ) + result[result_length++] = ' '; + + for ( size_t n = 0; sa->strings[i][n]; n++ ) + { + if ( is_token_string_special_character(sa->strings[i][n]) ) + result[result_length++] = '\\'; + result[result_length++] = sa->strings[i][n]; + } + } + + result[result_length] = '\0'; + + return result; +} + +void string_array_append_file(string_array_t* sa, FILE* fp) +{ + char* entry = NULL; + size_t entry_size; + ssize_t entry_length; + while ( 0 < (entry_length = getline(&entry, &entry_size, fp)) ) + { + if ( entry_length && entry[entry_length-1] == '\n' ) + entry[entry_length-1] = '\0'; + string_array_append(sa, entry); + } +} + +bool string_array_append_file_path(string_array_t* sa, const char* path) +{ + FILE* fp = fopen(path, "r"); + if ( !fp ) + return false; + string_array_append_file(sa, fp); + fclose(fp); + return true; +} + +size_t string_array_find(string_array_t* sa, const char* str) +{ + for ( size_t i = 0; i < sa->length; i++ ) + if ( !strcmp(sa->strings[i], str) ) + return i; + return SIZE_MAX; +} + +bool string_array_contains(string_array_t* sa, const char* str) +{ + return string_array_find(sa, str) != SIZE_MAX; +} + +size_t dictionary_lookup_index(string_array_t* sa, const char* key) +{ + size_t keylen = strlen(key); + for ( size_t i = 0; i < sa->length; i++ ) + { + const char* entry = sa->strings[i]; + if ( strncmp(key, entry, keylen) != 0 ) + continue; + if ( entry[keylen] != '=' ) + continue; + return i; + } + return SIZE_MAX; +} + +const char* dictionary_get_entry(string_array_t* sa, const char* key) +{ + size_t index = dictionary_lookup_index(sa, key); + if ( index == SIZE_MAX ) + return NULL; + return sa->strings[index]; +} + +const char* dictionary_get(string_array_t* sa, const char* key, + const char* def = NULL) +{ + size_t keylen = strlen(key); + const char* entry = dictionary_get_entry(sa, key); + return entry ? entry + keylen + 1 : def; +} + +void dictionary_normalize_entry(char* entry) +{ + bool key = true; + size_t input_off, output_off; + for ( input_off = output_off = 0; entry[input_off]; input_off++ ) + { + if ( key && isspace(entry[input_off]) ) + continue; + if ( key && (entry[input_off] == '=' || entry[input_off] == '#') ) + key = false; + entry[output_off++] = entry[input_off]; + } + entry[output_off] = '\0'; +} + +void dictionary_append_file(string_array_t* sa, FILE* fp) +{ + char* entry = NULL; + size_t entry_size; + ssize_t entry_length; + while ( 0 < (entry_length = getline(&entry, &entry_size, fp)) ) + { + if ( entry_length && entry[entry_length-1] == '\n' ) + entry[entry_length-1] = '\0'; + dictionary_normalize_entry(entry); + if ( entry[0] == '#' ) + continue; + string_array_append(sa, entry); + } +} + +bool dictionary_append_file_path(string_array_t* sa, const char* path) +{ + FILE* fp = fopen(path, "r"); + if ( !fp ) + return false; + dictionary_append_file(sa, fp); + fclose(fp); + return true; +} + +char* print_string(const char* format, ...) +{ + va_list ap; + va_start(ap, format); + char* ret; + if ( vasprintf(&ret, format, ap) < 0 ) + ret = NULL; + va_end(ap); + return ret; +} + +char* read_single_line(FILE* fp) +{ + char* ret = NULL; + size_t ret_size = 0; + ssize_t ret_len = getline(&ret, &ret_size, fp); + if ( ret_len < 0 ) + return NULL; + if ( ret_len && ret[ret_len-1] == '\n' ) + ret[ret_len-1] = '\0'; + return ret; +} + +pid_t fork_or_death() +{ + pid_t child_pid = fork(); + if ( child_pid < 0 ) + error(1, errno, "fork"); + return child_pid; +} + +void waitpid_or_death(pid_t child_pid, bool die_on_error = true) +{ + int status; + waitpid(child_pid, &status, 0); + if ( die_on_error ) + { + if ( WIFEXITED(status) && WEXITSTATUS(status) != 0 ) + exit(WEXITSTATUS(status)); + if ( WIFSIGNALED(status) ) + error(128 + WTERMSIG(status), 0, "child with pid %ju was killed by " + "signal %i (%s).", (uintmax_t) child_pid, WTERMSIG(status), + strsignal(WTERMSIG(status))); + } +} + +bool fork_and_wait_or_death(bool die_on_error = true) +{ + pid_t child_pid = fork_or_death(); + if ( !child_pid ) + return true; + waitpid_or_death(child_pid, die_on_error); + return false; +} + +const char* getenv_def(const char* var, const char* def) +{ + const char* ret = getenv(var); + return ret ? ret : def; +} + +int mkdir_p(const char* path, mode_t mode) +{ + int saved_errno = errno; + if ( mkdir(path, mode) != 0 && errno != EEXIST ) + return -1; + errno = saved_errno; + return 0; +} + +void CompactArguments(int* argc, char*** argv) +{ + for ( int i = 0; i < *argc; i++ ) + while ( i < *argc && !(*argv)[i] ) + { + for ( int n = i; n < *argc; n++ ) + (*argv)[n] = (*argv)[n+1]; + (*argc)--; + } +} + +char* GetBuildTriplet() +{ + const char* env_host = getenv("BUILD"); + if ( env_host ) + return strdup(env_host); + FILE* fp = popen("cc -dumpmachine", "r"); + if ( !fp ) + return NULL; + char* ret = read_single_line(fp); + pclose(fp); + return ret; +} + +bool get_option_variable(const char* option, char** varptr, + const char* arg, int argc, char** argv, int* ip, + const char* argv0) +{ + size_t option_len = strlen(option); + if ( strncmp(option, arg, option_len) != 0 ) + return false; + if ( arg[option_len] == '=' ) + { + *varptr = strdup(arg + option_len + 1); + return true; + } + if ( arg[option_len] != '\0' ) + return false; + if ( *ip + 1 == argc ) + { + fprintf(stderr, "%s: expected operand after `%s'\n", argv0, option); + exit(1); + } + *varptr = strdup(argv[++*ip]), argv[*ip] = NULL; + return true; +} + +char* join_paths(const char* a, const char* b) +{ + size_t a_len = strlen(a); + bool has_slash = (a_len && a[a_len-1] == '/') || b[0] == '/'; + return has_slash ? print_string("%s%s", a, b) : print_string("%s/%s", a, b); +} + +bool IsFile(const char* path) +{ + struct stat st; + return stat(path, &st) == 0 && S_ISREG(st.st_mode); +} + +bool IsDirectory(const char* path) +{ + struct stat st; + return stat(path, &st) == 0 && + (S_ISDIR(st.st_mode) || (errno = ENOTDIR, false)); +} + +size_t count_tar_components(const char* path) +{ + if ( !*path ) + return 0; + size_t slashes = 1; + for ( size_t i = 0; path[i]; i++ ) + if ( path[i] == '/' ) + slashes++; + return slashes; +} + +#define GET_OPTION_VARIABLE(str, varptr) \ + get_option_variable(str, varptr, arg, argc, argv, &i, argv0) + +// TODO: This is a bit inefficient but doing it otherwise would involve looking +// through the error stream for messages such as "file not found", which +// can be hard to distinguish from the common "oh no, an error occured" +// case in which we need to abort as well. +bool TarContainsFile(const char* tar, const char* archive, const char* file) +{ + int pipes[2]; + if ( pipe(pipes) ) + error(1, errno, "pipe"); + pid_t tar_pid = fork_or_death(); + if ( !tar_pid ) + { + dup2(pipes[1], 1); + close(pipes[1]); + close(pipes[0]); + const char* cmd_argv[] = + { + tar, + "--list", + "--file", archive, + NULL + }; + execvp(cmd_argv[0], (char* const*) cmd_argv); + error(127, errno, "%s", cmd_argv[0]); + } + close(pipes[1]); + FILE* fp = fdopen(pipes[0], "r"); + + char* line = NULL; + size_t line_size = 0; + ssize_t line_len; + bool ret = false; + while ( 0 < (line_len = getline(&line, &line_size, fp)) ) + { + if ( line_len && line[line_len-1] == '\n' ) + line[--line_len] = '\0'; + if ( strcmp(line, file) == 0 ) + { + ret = true; + #if !defined(__sortix__) + kill(tar_pid, SIGPIPE); + break; + #endif + } + } + free(line); + + fclose(fp); + int tar_exit_status; + waitpid(tar_pid, &tar_exit_status, 0); + bool sigpiped = WIFSIGNALED(tar_exit_status) && + WTERMSIG(tar_exit_status) == SIGPIPE; + bool errored = !WIFEXITED(tar_exit_status) || + WEXITSTATUS(tar_exit_status) != 0; + if ( errored && !sigpiped ) + { + error(1, 0, "Unable to list contents of `%s'.", archive); + exit(WEXITSTATUS(tar_exit_status)); + } + return ret; +} + +FILE* TarOpenFile(const char* tar, const char* archive, const char* file) +{ + FILE* fp = tmpfile(); + if ( !fp ) + error(1, errno, "tmpfile"); + pid_t tar_pid = fork_or_death(); + if ( !tar_pid ) + { + dup2(fileno(fp), 1); + fclose(fp); + const char* cmd_argv[] = + { + tar, + "--to-stdout", + "--extract", + "--file", archive, + "--", file, + NULL + }; + execvp(cmd_argv[0], (char* const*) cmd_argv); + error(127, errno, "%s", cmd_argv[0]); + } + int tar_exit_status; + waitpid(tar_pid, &tar_exit_status, 0); + if ( !WIFEXITED(tar_exit_status) || WEXITSTATUS(tar_exit_status) != 0 ) + { + error(1, 0, "Unable to extract `%s/%s'", archive, file); + exit(WEXITSTATUS(tar_exit_status)); + } + if ( fseeko(fp, 0, SEEK_SET) < 0 ) + error(1, errno, "fseeko(tmpfile(), 0, SEEK_SET)"); + return fp; +} + +int fopenat_opener(int dirfd, const char* path, const char* mode) +{ + int omode = 0; + int oflags = 0; + char c; + // TODO: This is too hacky and a little buggy. + while ( (c = *mode++) ) + switch ( c ) + { + case 'r': omode = O_RDONLY; break; + case 'a': oflags |= O_APPEND; /* fall-through */ + case 'w': omode = O_WRONLY; oflags |= O_CREAT | O_TRUNC; break; + case '+': omode = O_RDWR; break; + case 'x': omode = O_EXCL; break; + case 'b': break; + case 't': break; + default: + errno = EINVAL; + return -1; + } + return openat(dirfd, path, omode | oflags, 0666); +} + +FILE* fopenat(DIR* dir, const char* path, const char* mode) +{ + int fd = fopenat_opener(dirfd(dir), path, mode); + if ( fd < 0 ) + return NULL; + FILE* ret = fdopen(fd, mode); + if ( !ret ) + return close(fd), (FILE*) NULL; + return ret; +} + +const char* VerifyInfoVariable(string_array_t* info, const char* var, + const char* path) +{ + const char* ret = dictionary_get(info, var); + if ( !ret ) + error(1, 0, "error: `%s': no `%s' variable declared", path, var); + return ret; +} + +void VerifyTixInformation(string_array_t* tixinfo, const char* tix_path) +{ + const char* tix_version = dictionary_get(tixinfo, "tix.version"); + if ( !tix_version ) + error(1, 0, "error: `%s': no `tix.version' variable declared", + tix_path); + if ( atoi(tix_version) != 1 ) + error(1, 0, "error: `%s': tix version `%s' not supported", tix_path, + tix_version); + const char* tix_class = dictionary_get(tixinfo, "tix.class"); + if ( !tix_class ) + error(1, 0, "error: `%s': no `tix.class' variable declared", tix_path); + if ( !strcmp(tix_class, "srctix") ) + error(1, 0, "error: `%s': this object is a source tix and needs to be " + "compiled into a binary tix prior to installation.", + tix_path); + if ( strcmp(tix_class, "tix") ) + error(1, 0, "error: `%s': tix class `%s' is not `tix': this object is " + "not suitable for installation.", tix_path, tix_class); + if ( !(dictionary_get(tixinfo, "tix.platform")) ) + error(1, 0, "error: `%s': no `tix.platform' variable declared", tix_path); + if ( !(dictionary_get(tixinfo, "pkg.name")) ) + error(1, 0, "error: `%s': no `pkg.name' variable declared", tix_path); +} + +bool IsCollectionPrefixRatherThanCommand(const char* arg) +{ + return strchr(arg, '/') || !strcmp(arg, ".") || !strcmp(arg, ".."); +} + +void ParseOptionalCommandLineCollectionPrefix(char** collection, int* argcp, + char*** argvp) +{ + if ( 2 <= *argcp && IsCollectionPrefixRatherThanCommand((*argvp)[1]) ) + { + free(*collection); + *collection = strdup((*argvp)[1]); + (*argvp)[1] = NULL; + CompactArguments(argcp, argvp); + } +} + +void VerifyCommandLineCollection(char** collection) +{ + if ( !*collection ) + error(1, 0, "error: you need to specify which tix collection to " + "administer using --collection or TIX_COLLECTION or giving " + "the prefix as the first argument."); + + if ( !**collection ) + { + free(*collection); + *collection = strdup("/"); + } + + char* collection_rel = *collection; + if ( !(*collection = canonicalize_file_name(collection_rel)) ) + error(1, errno, "canonicalize_file_name(`%s')", collection_rel); +} + +void VerifyTixCollectionConfiguration(string_array_t* info, const char* path) +{ + const char* tix_version = dictionary_get(info, "tix.version"); + if ( !tix_version ) + error(1, 0, "error: `%s': no `tix.version' variable declared", path); + if ( atoi(tix_version) != 1 ) + error(1, 0, "error: `%s': tix version `%s' not supported", path, + tix_version); + const char* tix_class = dictionary_get(info, "tix.class"); + if ( !tix_class ) + error(1, 0, "error: `%s': no `tix.class' variable declared", path); + if ( strcmp(tix_class, "collection") != 0 ) + error(1, 0, "error: `%s': error: unexpected tix class `%s'.", path, + tix_class); + if ( !(dictionary_get(info, "collection.prefix")) ) + error(1, 0, "error: `%s': no `collection.prefix' variable declared", + path); + if ( !(dictionary_get(info, "collection.platform")) ) + error(1, 0, "error: `%s': no `collection.platform' variable declared", + path); +} + +static pid_t original_pid; + +__attribute__((constructor)) +static void initialize_original_pid() +{ + original_pid = getpid(); +} + +void cleanup_file_or_directory(int, void* path_ptr) +{ + if ( original_pid != getpid() ) + return; + const char* path = (const char*) path_ptr; + if ( fork_and_wait_or_death(false) ) + { + const char* cmd_argv[] = + { + "rm", + "-rf", + "--", + path, + NULL, + }; + execvp(cmd_argv[0], (char* const*) cmd_argv); + error(127, errno, "%s", cmd_argv[0]); + } +} + +mode_t get_umask_value() +{ + mode_t result = umask(0); + umask(result); + return result; +} + +int fchmod_plus_x(int fd) +{ + struct stat st; + if ( fstat(fd, &st) != 0 ) + return -1; + mode_t new_mode = st.st_mode | (0111 & ~get_umask_value()); + if ( fchmod(fd, new_mode) != 0 ) + return -1; + return 0; +} + +void fprint_shell_variable_assignment(FILE* fp, const char* variable, const char* value) +{ + if ( value ) + { + fprintf(fp, "export %s='", variable); + for ( size_t i = 0; value[i]; i++ ) + if ( value[i] == '\'' ) + fprintf(fp, "'\\''"); + else + fputc(value[i], fp); + fprintf(fp, "'\n"); + } + else + { + fprintf(fp, "unset %s\n", variable); + } +} + +bool is_success_exit_status(int status) +{ + return WIFEXITED(status) && WEXITSTATUS(status) == 0; +} + +__attribute__((noreturn)) +bool exit_like_exit_status(int status) +{ + if ( WIFEXITED(status) ) + exit(WEXITSTATUS(status)); + if ( WIFSIGNALED(status) ) + exit(128 + WTERMSIG(status)); + exit(1); +} + +enum recovery_state +{ + RECOVERY_STATE_NONE, + RECOVERY_STATE_PRINT_COMMAND, + RECOVERY_STATE_RUN_SHELL, +}; + +enum recovery_state +recovery_configure_state(bool set, + enum recovery_state to_what = RECOVERY_STATE_NONE) +{ + static enum recovery_state recovery_state = RECOVERY_STATE_NONE; + if ( set ) + recovery_state = to_what; + return recovery_state; +} + +bool recovery_print_attempted_execution() +{ + pid_t child_pid = fork(); + if ( child_pid < 0 ) + return false; + + if ( !child_pid ) + { + // Redirect stdout and stderr to /dev/null to prevent duplicate errors. + // The recovery_execvp function below will automatically re-open the + // terminal device when it needs to talk to the terminal. + int dev_null = open("/dev/null", O_WRONLY); + if ( 0 <= dev_null ) + { + dup2(dev_null, 1); + dup2(dev_null, 2); + close(dev_null); + } + + recovery_configure_state(true, RECOVERY_STATE_PRINT_COMMAND); + return true; + } + + int status; + waitpid(child_pid, &status, 0); + + return false; +} + +bool recovery_run_shell() +{ + pid_t child_pid = fork(); + if ( child_pid < 0 ) + return false; + + if ( !child_pid ) + { + recovery_configure_state(true, RECOVERY_STATE_RUN_SHELL); + return true; + } + + int status; + waitpid(child_pid, &status, 0); + + return false; +} + +int recovery_execvp(const char* path, char* const* argv) +{ + if ( recovery_configure_state(false) == RECOVERY_STATE_NONE ) + return execvp(path, argv); + + // Make sure that stdout and stderr go to an interactive terminal. + if ( !isatty(1) ) + { + int dev_tty = open("/dev/tty", O_WRONLY); + if ( 0 <= dev_tty ) + { + dup2(dev_tty, 1); + dup2(dev_tty, 2); + close(dev_tty); + } + } + + printf("Attempted command was: "); + + for ( int i = 0; argv[i]; i++ ) + { + if ( i ) + putchar(' '); + putchar('\''); + for ( size_t n = 0; argv[i][n]; n++ ) + if ( argv[i][n] == '\'' ) + printf("'\\''"); + else + putchar(argv[i][n]); + putchar('\''); + } + + printf("\n"); + + if ( recovery_configure_state(false) == RECOVERY_STATE_PRINT_COMMAND ) + _exit(0); + + const char* cmd_argv[] = + { + getenv_def("SHELL", "sh"), + "-i", + NULL + }; + execvp(cmd_argv[0], (char* const*) cmd_argv); + error(127, errno, "%s", cmd_argv[0]); + + __builtin_unreachable(); +} + +bool fork_and_wait_or_recovery() +{ + int default_selection = 1; + + while ( true ) + { + pid_t child_pid = fork_or_death(); + if ( !child_pid ) + return true; + + int status; + waitpid(child_pid, &status, 0); + + if ( is_success_exit_status(status) ) + return false; + + if ( WIFEXITED(status) ) + error(0, 0, "child with pid %ju exited with status %i.", + (uintmax_t) child_pid, WEXITSTATUS(status)); + else if ( WIFSIGNALED(status) ) + error(0, 0, "child with pid %ju was killed by signal %i (%s).", + (uintmax_t) child_pid, WTERMSIG(status), + strsignal(WTERMSIG(status))); + else + error(0, 0, "child with pid %ju exited in an unusual manner (%i).", + (uintmax_t) child_pid, status); + + if ( recovery_print_attempted_execution() ) + return true; + + if ( !isatty(0) ) + exit_like_exit_status(status); + + FILE* output = fopen("/dev/tty", "we"); + if ( !output ) + exit_like_exit_status(status); + +retry_ask_recovery_method: + + fprintf(output, "\n"); + fprintf(output, "1. Abort\n"); + fprintf(output, "2. Try again\n"); + fprintf(output, "3. Pretend command was successful\n"); + fprintf(output, "4. Run $SHELL -i to investigate\n"); + fprintf(output, "\n"); + fprintf(output, "Please choose one: [%i] ", default_selection); + fflush(output); + + char* input = read_single_line(stdin); + if ( !input ) + { + fprintf(output, "\n"); + fclose(output); + error(0, errno, "can't read line from standard input, aborting."); + exit_like_exit_status(status); + } + + int selection = default_selection; + if ( input[0] ) + { + char* input_end; + selection = (int) strtol(input, &input_end, 0); + if ( *input_end ) + { + error(0, 0, "error: `%s' is not an allowed choice\n", input); + goto retry_ask_recovery_method; + } + + if ( 4 < selection ) + { + error(0, 0, "error: `%i' is not an allowed choice\n", selection); + goto retry_ask_recovery_method; + } + } + + if ( selection == 1 ) + exit_like_exit_status(status); + + if ( selection == 2 ) + { + fprintf(output, "\nTrying to execute command again.\n\n"); + fclose(output); + continue; + } + + if ( selection == 3 ) + { + fprintf(output, "\nPretending the command executed successfully.\n\n"); + fclose(output); + return false; + } + + if ( selection == 4 ) + { + fprintf(output, "\nDropping you to a recovery shell, type `exit' " + "when you are done.\n\n"); + if ( recovery_run_shell() ) + return true; + } + + default_selection = 2; + + goto retry_ask_recovery_method; + } +} + +#endif