aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLucas C. Villa Real <lucasvr@gmail.com>2016-12-05 20:20:26 +0000
committerLucas C. Villa Real <lucasvr@gmail.com>2016-12-05 20:20:26 +0000
commit5e0d67f455f6b481ef53793d19dd37c8778138f6 (patch)
treecae23d1337e91de517c98aabce4685cb85008766
downloadThirdPartyInstallers-5e0d67f455f6b481ef53793d19dd37c8778138f6.tar.xz
First commit.
-rw-r--r--Makefile21
-rw-r--r--Resources/Dependencies3
-rwxr-xr-xbin/InstallPackage-RPM311
-rwxr-xr-xbin/RPMFinder84
-rw-r--r--src/Makefile21
-rw-r--r--src/rpminfo.c202
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;
+}