diff options
-rw-r--r-- | Makefile | 21 | ||||
-rw-r--r-- | Resources/Dependencies | 3 | ||||
-rwxr-xr-x | bin/InstallPackage-RPM | 311 | ||||
-rwxr-xr-x | bin/RPMFinder | 84 | ||||
-rw-r--r-- | src/Makefile | 21 | ||||
-rw-r--r-- | src/rpminfo.c | 202 |
6 files changed, 642 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d327c73 --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +EXEC_FILES = $(patsubst src/%.c,bin/%,$(wildcard src/*.c)) + +all: $(EXEC_FILES) + +debug: python + cd src; $(MAKE) debug + +clean: + rm -rf Resources/FileHash* + find * -path "*~" -or -path "*/.\#*" -or -path "*.bak" | xargs rm -f + cd src && $(MAKE) clean + rm -f $(EXEC_FILES) + +$(EXEC_FILES): bin/%: src/% + cp -af $< $@ + chmod a+x $@ + +src/%: src/%.c + $(MAKE) -C src + +.PHONY: all debug clean diff --git a/Resources/Dependencies b/Resources/Dependencies new file mode 100644 index 0000000..e04c063 --- /dev/null +++ b/Resources/Dependencies @@ -0,0 +1,3 @@ +Cpio >= 2.12 +RPM >= 5.0.0, < 6.0.0 +XZ-Utils 5.2.2 diff --git a/bin/InstallPackage-RPM b/bin/InstallPackage-RPM new file mode 100755 index 0000000..20a4517 --- /dev/null +++ b/bin/InstallPackage-RPM @@ -0,0 +1,311 @@ +#!/bin/bash + +source ScriptFunctions +Import File +Import GoboLinux +Import Log +Import OptionParser + +### Options ################################################################### + +scriptDescription="Install RPM packages on GoboLinux." +scriptCredits="Copyright (C) Lucas C. Villa Real, 2016 - Released under the GNU GPL." +helpOnNoArguments=yes +scriptUsage="<file.rpm>" +scriptExample="LibreOffice_5.2.3_Linux_x86-64_rpm" + +Add_Option_Entry "l" "symlink" "If symlinks should be created and wether they should be forced on conflicts." "yes" "yes no force" +Parse_Options "$@" + +### Functions ################################################################# + +function uncompress_rpm() { + local payload_compressor=$(rpminfo --compressor "$rpmfile") + local cpiofile=$(basename "$rpmfile").cpio${payload_compressor:+.$payload_compressor} + + Log_Normal "Extracting RPM payload." + rpm2cpio < "$rpmfile" > "$cpiofile" + + if [ "$payload_compressor" = "xz" ] + then + Log_Normal "Decompressing $payload_compressor payload." + xz -d "$cpiofile" + cpiofile=$(basename "$rpmfile").cpio + fi + + Log_Normal "Extracting CPIO archive." + cpio -d -i < "$cpiofile" + rm -f -- "$cpiofile" +} + +function flatten_rpm() { + Log_Normal "Flattening directory structure." + if [ -d "./usr" ] + then + cp -a ./usr/* . + rm -rf -- ./usr + fi + if [ -d "./etc" ] + then + mkdir -p Resources/Defaults/Settings + mv ./etc/* Resources/Defaults/Settings + rm -rf -- ./etc + fi + if [ -d "./opt" ] + then + mkdir -p Resources/Unmanaged/opt + if ls ./opt/* 2> /dev/null | grep -q "bin\|sbin\|lib\|lib64\|libexec\|include" + then + # 1-level dir: opt/pkgname/{bin,sbin,...} + cp -va ./opt/*/* . + for pkgdir in $(basename ./opt/*) + do + ln -s $goboIndex/ Resources/Unmanaged/opt/$pkgdir + done + realpath ./opt/* | sed "s,$(realpath $PWD),,g" >> Resources/UnmanagedFiles + elif ls ./opt/*/* 2> /dev/null | grep -q "bin\|sbin\|lib\|lib64\|libexec\|include" + then + # 2-level dir: opt/vendorname/pkgname/{bin,sbin,...} + # XXX needs testing + cp -va ./opt/*/*/* . + for vendordir in $(basename ./opt/*) + do + mkdir -p Resources/Unmanaged/opt/$vendordir + for pkgdir in $(basename ./opt/*) + do + ln -s $goboIndex/ Resources/Unmanaged/opt/$vendordir/$optdir + done + done + realpath ./opt/*/* | sed "s,$(realpath $PWD),,g" >> Resources/UnmanagedFiles + fi + rm -rf -- ./opt + fi + if [ -d "./var" ] + then + mkdir -p Resources/Unmanaged/$goboVariable + find ./var | sed "s,./var,$goboVariable,g" >> Resources/UnmanagedFiles + mv ./var/* Resources/Unmanaged/$goboVariable + rm -rf -- ./var + fi + rmdir * 2> /dev/null +} + +function populate_resources() { + local arch=$(rpminfo --arch "$rpmfile") + local description=$(rpminfo --description "$rpmfile") + + Log_Normal "Populating Resources." + mkdir -p Resources + if [ "$arch" ] && [ "$arch" != "noarch" ] + then + echo "$arch" > Resources/Architecture + fi + if [ "$description" ] + then + cat /dev/null > Resources/Description + echo "[Name] $(rpminfo --name $rpmfile)" >> Resources/Description + echo "[Summary] $(rpminfo --summary $rpmfile)" >> Resources/Description + echo "[License] $(rpminfo --license $rpmfile)" >> Resources/Description + echo "[Description] $(rpminfo --description $rpmfile)" >> Resources/Description + echo "[Homepage] $(rpminfo --url $rpmfile)" >> Resources/Description + fi +} + +function lookup_symbol() { + local depname="$1" + local testversion="$2" + local arch="$3" + local symbol="$4" + local testarch=$(cat "$goboPrograms/$depname/$testversion/Resources/Architecture" 2> /dev/null) + + if [ "$testarch" ] && [ "$testarch" = "$arch" ] + then + Log_Verbose "Looking for symbol $symbol on $goboPrograms/$depname/$testversion/$path" + if nm "$goboPrograms/$depname/$testversion/$path" 2> /dev/null | grep --max-count=1 -q "$symbol" + then + Log_Verbose "Match: $depname $testversion" + echo "$depname $testversion" + return 0 + fi + fi + return 1 +} + +function take_dependency_from_path() { + local originalpath="$1" + local path="$(echo $1 | sed 's,/usr,,g')" + local symbol="$2" + local fullpath="$(readlink -f $path)" + local arch=$(rpminfo --arch "$rpmfile") + local distro=$(rpminfo --distribution "$rpmfile") + + local depname= + local depversion= + + if echo "$fullpath" | grep -q "^${goboPrograms}" + then + # If given, we search for the presence of @symbol on the given target file. + # We iterate over different installations of the same program looking for + # that symbol. If none of the installations have it, we fallback to printing + # the dependency currently linked on /System/Index. + # + # Note that when iterating over installed programs we skip those entries whose + # Resources/Architecture do not match the output of $(rpminfo --arch). + + depname=$(echo "$fullpath" | cut -d/ -f3) + depversion=$(echo "$fullpath" | cut -d/ -f4) + if [ "$symbol" ] + then + for testversion in $(ls $goboPrograms/$depname/ | grep -v "Settings\|Variable\|Current") + do + lookup_symbol "$depname" "$testversion" "$arch" "$symbol" && return 0 + done + fi + + Log_Verbose "Fallback: $depname $depversion" + echo "$depname $depversion" + else + # We have a path, but we don't have a link to that file under /System/Index. + # Our first attempt is to search over the list of installed programs anyhow, + # because some programs may not be currently activated. + + for fullpath in $(ls $goboPrograms/*/*/$path 2> /dev/null | grep -v "Current") + do + depname=$(echo "$fullpath" | cut -d/ -f3) + testversion=$(echo "$fullpath" | cut -d/ -f4) + [ -z "$depversion" ] && depversion="$testversion" + Log_Verbose "Looking for symbol on candidate file $candidate ($depname, $testversion)" + lookup_symbol "$depname" "$testversion" "$arch" "$symbol" && return 0 + done + + # We don't have a match. If we have a file name that satisfies the path but + # that doesn't contain the requested symbol, we simply return that path. + + if [ "$depname" ] && [ "$depversion" ] + then + echo "$depname $depversion" + return 0 + fi + + # We don't have a matching filename under /System/Index nor under /Programs/*/*. + # What we do now is to query remote RPM databases to find which package hosts the + # dependency file. + + Log_Normal "Searching the remote RPM database for the package hosting $originalpath" + depname=$(RPMFinder --path="$originalpath" --arch="$arch" --distro="$distro") + if [ "$depname" ] + then + # TODO: we could now lookup the GoboLinux recipe store to find whether we + # have it or not + echo "$(GuessProgramCase $depname)" + return 0 + fi + fi +} + +function lookup_pkgname() { + local dependency="$1" + local pkgname=$(echo "$dependency" | cut -d'(' -f1) + + # Do we have a GoboLinux package installed with a matching name? + for testname in $(ls $goboPrograms/*) + do + # Case-insensitive omparison (requires Bash 4) + if [ "${testname,,}" = "{$pkgname,,}" ] + then + echo "$pkgname" && return 0 + fi + done + + # Query the remote RPM database + return 1 +} + +function is_basic_symbol() { + local dependency="$1" + echo "$dependency" | grep -q "^rtld(" && return 0 + return 1 +} + +function is_rpmlib_symbol() { + local dependency="$1" + echo "$dependency" | grep -q "^VersionedDependencies" && return 0 + echo "$dependency" | grep -q "^PayloadFilesHavePrefix" && return 0 + echo "$dependency" | grep -q "^CompressedFileNames" && return 0 + echo "$dependency" | grep -q "^PayloadIs" && return 0 + return 1 +} + +function populate_dependencies_loop() { + rpminfo --dependencies "$rpmfile" | while read dependency + do + if echo "$dependency" | grep -q "^/" + then + depinfo=$(take_dependency_from_path $dependency "") + if [ "$depinfo" ] + then echo "$depinfo" + else echo "# Unresolved dependency: $dependency" + fi + elif echo "$dependency" | grep -q "^lib.*.so*" + then + libname=$(echo "$dependency" | cut -d'(' -f1) + wantedsymbol=$(echo "$dependency" | cut -d'(' -f2 | cut -d')' -f1) + depinfo="$(take_dependency_from_path $goboLibraries/$libname $wantedsymbol)" + if [ "$depinfo" ] + then echo "$depinfo" + else echo "# Unresolved dependency: $dependency" + fi + elif is_basic_symbol "$dependency" + then + Log_Verbose "Skipping basic symbol: $dependency" + elif is_rpmlib_symbol "$dependency" + then + Log_Verbose "Skipping internal symbol: $dependency" + else + depinfo=$(lookup_pkgname "$dependency") + if [ "$depinfo" ] + then echo "$depinfo" + else echo "# Unresolved dependency: $dependency" + fi + fi + done +} + +function populate_dependencies() { + Log_Normal "Processing dependencies." + populate_dependencies_loop | sort -n | uniq +} + +### Operation ################################################################# + +Is_Writable "${goboPrograms}" || Verify_Superuser + +symlink="$(Entry symlink)" +rpmfile="$(readlink -f $(Arg 1))" +programname=$(rpminfo --name "$rpmfile") +programversion=$(printf "%s_%s" $(rpminfo --version "$rpmfile") $(rpminfo --release "$rpmfile")) + +PrepareProgram -t "$programname" "$programversion" + +# Update program name (PrepareProgram may have changed its case) +programname=$(ls $goboPrograms/ | grep -i "^${programname}$") +target="$goboPrograms/$programname/$programversion" + +# Installation pipeline +Quiet pushd "$target" || Die "Could not enter $target" +uncompress_rpm +flatten_rpm +populate_resources +populate_dependencies +Quiet popd + +# Symlinking +if [ "$symlink" = "no" ] +then + Log_Normal "Done." + exit 0 +fi + +[ -d "$target/Resources/Defaults/Settings" ] && UpdateSettings "$programname" "$programversion" +SymlinkProgram "$programname" "$programversion" +Log_Normal "Done." diff --git a/bin/RPMFinder b/bin/RPMFinder new file mode 100755 index 0000000..1797174 --- /dev/null +++ b/bin/RPMFinder @@ -0,0 +1,84 @@ +#!/usr/bin/env python + +# Searches over the network to find out which RPM package distributes +# a given file. +# +# Written by Lucas C. Villa Real <lucasvr@gobolinux.org> +# Released under the GNU GPL version 2 or above. + +import os +import sys +import argparse +import subprocess +from HTMLParser import HTMLParser + + +class RPMFind_Parser(HTMLParser): + ''' + Parses the HTML data output by rpmfind.net + ''' + def __init__(self): + self.tags = [] + self.candidates = [] + HTMLParser.__init__(self) + + def handle_starttag(self, tag, attrs): + self.tags.append(tag) + + def handle_endtag(self, tag): + self.tags.pop() + + def handle_data(self, data): + if len(self.tags) and self.tags[-1] == "a" and data.find(".rpm") >= 0: + self.candidates.append(data) + + def get_pkgname(self): + if len(self.candidates) == 0: + return "" + name = os.path.commonprefix(self.candidates) + if name.endswith("-"): + name = name[:-1] + return name + + +class RPMFinder: + def find(self, path, arch, distro): + ''' + Searches rpmfind.net for a given file. Arch and distro can + be provided to reduce the search scope. Returns the package + name on success or an empty string if no matches were found. + ''' + self.path = path + self.arch = arch + self.distro = distro + return self.__search_rpmfind_net() + + def __search_rpmfind_net(self): + path = self.path.replace("/", "%2F") + arch = self.arch + baseuri = "http://rpmfind.net/linux/rpm2html/search.php" + query = "?query={0}&submit=Search+...&system=&arch={1}".format(path, arch) + html = subprocess.check_output(["wget", "--quiet", "{0}{1}".format(baseuri, query), "-O", "-"]) + + htmlparser = RPMFind_Parser() + htmlparser.feed(html) + return htmlparser.get_pkgname() + + +def main(): + argparser = argparse.ArgumentParser(argument_default="") + argparser.add_argument("--path", type=str, help="File name to search for in the remote RPM databases") + argparser.add_argument("--arch", type=str, help="Architecture (optional)") + argparser.add_argument("--distro", type=str, help="Distribution (optional)") + args = argparser.parse_args() + + if len(args.path) == 0: + argparser.print_help() + sys.exit(1) + + pkgname = RPMFinder().find(args.path, args.arch, args.distro) + if len(pkgname): + print pkgname + +if __name__ == "__main__": + main() diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..5dbc4cb --- /dev/null +++ b/src/Makefile @@ -0,0 +1,21 @@ +CC = gcc +MYCFLAGS = -O3 -Wall +TARGETS = rpminfo + +# first rule +default: all + +.PHONY: all default + +all: $(TARGETS) + +rpminfo: rpminfo.c + $(CC) $(MYCFLAGS) $^ -o $@ -lrpm -lrpmdb -lrpmio -I/usr/include/rpm + +debug: MYCFLAGS = -g -ggdb -O0 -DDEBUG -Wall +debug: all + +clean: + rm -f $(TARGETS) + +.PHONY: all clean debug install diff --git a/src/rpminfo.c b/src/rpminfo.c new file mode 100644 index 0000000..bf20962 --- /dev/null +++ b/src/rpminfo.c @@ -0,0 +1,202 @@ +/** + * Print RPM package name, version, and revision. + * Depends on RPM = 5.3.5 + * + * Build with: + * gcc rpminfo.c -o rpminfo -Wall -lrpm -lrpmdb -lrpmio -I/usr/include/rpm + * + * Written by Lucas C. Villa Real <lucasvr@gobolinux.org> + * Released under the GNU GPL version 2 or above. + */ +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <unistd.h> +#include <errno.h> + +#include <rpm/rpmio.h> +#include <rpm/rpmcli.h> +#include <rpm/pkgio.h> +#include <rpm/rpmds.h> + +struct optinfo { + const char *name; + const char *desc; + void (*fn)(Header *); +}; + +void generic_print_msg(Header *hdr, const char *fmt) +{ + char *value = headerSprintf(*hdr, fmt, NULL, NULL, NULL); + if (value) { + printf("%s\n", value); + free(value); + } +} + +void print_arch(Header *hdr) +{ + generic_print_msg(hdr, "%{arch}"); +} + +void print_compressor(Header *hdr) +{ + generic_print_msg(hdr, "%{payloadcompressor}"); +} + +void print_dependencies(Header *hdr) +{ + rpmds depinfo = rpmdsNew(*hdr, RPMTAG_REQUIRENAME, 0); + + /* init iterator */ + depinfo = rpmdsInit(depinfo); + while (rpmdsNext(depinfo) >= 0) { + const char *name = rpmdsN(depinfo); + const char *version= rpmdsEVR(depinfo); + evrFlags flags = rpmdsFlags(depinfo); + + printf("%s", name); + if (version && strlen(version)) { + if (flags) + printf(" "); + if (flags & RPMSENSE_LESS) + printf("<"); + if (flags & RPMSENSE_GREATER) + printf(">"); + if (RPMSENSE_EQUAL) + printf("="); + printf(" %s", version); + } + printf("\n"); + } + + rpmdsFree(depinfo); +} + +void print_description(Header *hdr) +{ + generic_print_msg(hdr, "%{description}"); +} + +void print_distribution(Header *hdr) +{ + generic_print_msg(hdr, "%{distribution}"); +} + +void print_license(Header *hdr) +{ + generic_print_msg(hdr, "%{license}"); +} + +void print_name(Header *hdr) +{ + generic_print_msg(hdr, "%{name}"); +} + +void print_release(Header *hdr) +{ + generic_print_msg(hdr, "%{release}"); +} + +void print_summary(Header *hdr) +{ + generic_print_msg(hdr, "%{summary}"); +} + +void print_url(Header *hdr) +{ + generic_print_msg(hdr, "%{url}"); +} + +void print_version(Header *hdr) +{ + generic_print_msg(hdr, "%{version}"); +} + +static struct optinfo optinfo[] = { + { "--arch", "architecture", print_arch }, + { "--compressor", "payload compressor", print_compressor }, + { "--dependencies", "package dependencies", print_dependencies }, + { "--description", "package description", print_description }, + { "--distribution", "distribution name", print_distribution }, + { "--license", "package license", print_license }, + { "--name", "package name", print_name }, + { "--release", "release number", print_release }, + { "--summary", "summary information", print_summary }, + { "--url", "project url", print_url }, + { "--version", "package version", print_version }, + { NULL, NULL, NULL } +}; + +void usage(char *appname, int retval) +{ + int i; + fprintf(stderr, "Usage: %s OPTION <file.rpm>\n\n" + "Available (mutually-exclusive) options are:\n", appname); + for (i=0; optinfo[i].name; ++i) + fprintf(stderr, " %-17s %s\n", optinfo[i].name, optinfo[i].desc); + exit(retval); +} + +void parse_args(int argc, char **argv, char **rpmfile, int *opt) +{ + const int argc_option = 1; + const int argc_rpmfile = 2; + int i; + + if (argc != 3) + usage(argv[0], 1); + + *rpmfile = NULL; + *opt = -1; + + for (i=0; optinfo[i].name; ++i) { + if (!strcmp(argv[argc_option], optinfo[i].name)) { + *opt = i; + break; + } + } + + if (*opt == -1) + usage(argv[0], 1); + + *rpmfile = argv[argc_rpmfile]; +} + +int main(int argc, char **argv) +{ + int option = -1; + char *rpmfile = NULL; + parse_args(argc, argv, &rpmfile, &option); + + FD_t fd = Fopen(rpmfile, "r.ufdio"); + if (! fd || Ferror(fd)) { + fprintf(stderr, "Failed to open %s: %s\n", rpmfile, Fstrerror(fd)); + return 1; + } + + rpmVSFlags vsflags = 0; + vsflags |= RPMVSF_NOHDRCHK; + vsflags |= _RPMVSF_NODIGESTS; + vsflags |= _RPMVSF_NOSIGNATURES; + + rpmts ts = rpmtsCreate(); + rpmtsSetVSFlags(ts, vsflags); + + Header hdr; + rpmRC rc = rpmReadPackageFile(ts, fd, rpmfile, &hdr); + if (rc != RPMRC_OK) { + fprintf(stderr, "Failed to read package file %s\n", rpmfile); + return 1; + } + + /* handle option */ + optinfo[option].fn(&hdr); + + headerFree(hdr); + rpmtsFree(ts); + Fclose(fd); + + return 0; +} |