diff options
author | kx <kx@radix.pro> | 2023-04-11 01:18:34 +0300 |
---|---|---|
committer | kx <kx@radix.pro> | 2023-04-11 01:18:34 +0300 |
commit | 11c606a6888dc269ef018359469a7276c3ad8f67 (patch) | |
tree | 368294bb7cadcd5c44ccd082187d6a4433401027 | |
parent | 8c55752ed5b29a22fdab9faaa6ff27b7cafa6791 (diff) | |
download | pkgtools-11c606a6888dc269ef018359469a7276c3ad8f67.tar.xz |
Version 0.2.1pkgtools-0.2.1
56 files changed, 39826 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0298281 --- /dev/null +++ b/.gitignore @@ -0,0 +1,45 @@ +# +# Generated by Autotools: +# ---------------------- +# +m4/ +autom4te.cache/ +aclocal.m4 +compile +config.status +config.guess +config.sub +config.log +configure +depcomp +install-sh +missing +stamp-h1 + +config.h +config.h.in + +Makefile +Makefile.in + +src/Makefile +src/Makefile.in +src/.deps/ + +# +# Object files and Binaries: +# ------------------------- +# +*.o +check-db-integrity +check-package +check-requires +chrefs +install-package +install-pkglist +make-package +make-pkglist +pkginfo +pkglog +remove-package +update-package diff --git a/.svnignore b/.svnignore new file mode 100644 index 0000000..d121d24 --- /dev/null +++ b/.svnignore @@ -0,0 +1,42 @@ +# +# Generated by Autotools: +# ---------------------- +# +m4 +autom4te.cache +aclocal.m4 +compile +config.status +config.guess +config.sub +config.log +configure +depcomp +install-sh +missing +stamp-h1 + +config.h +config.h.in + +Makefile +Makefile.in +.deps/ + +# +# Object files and Binaries: +# ------------------------- +# +*.o +check-db-integrity +check-package +check-requires +chrefs +install-package +install-pkglist +make-package +make-pkglist +pkginfo +pkglog +remove-package +update-package diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..9877db3 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,10 @@ + +ACLOCAL_AMFLAGS = -I m4 + +# +# In build order: +# ============== +# +SUBDIRS = src + +EXTRA_DIST = doc LICENSE README.md acsite.m4 bootstrap src/.dialogrc src/pkglist.html.c src/pkglist.html.v3.c @@ -0,0 +1,2 @@ + +see README.md instead diff --git a/README.md b/README.md new file mode 100644 index 0000000..571b711 --- /dev/null +++ b/README.md @@ -0,0 +1,146 @@ + +# [Package Tools](https://radix.pro/build-system/pkgtool/) + +**pkgtools** is a set of programs to create, install, remove, and update +packages on the root file system. + +## Table of contents + +* [Bootstrap script](#bootstrap-script) +* [Install](#install) +* [Configurations](#configurations) +* [Cross compilation example](#cross-compilation-example) +* [Dialog](#dialog) +* [License](#license) + + +## Bootstrap script + +The **bootstrap** script aspecialy created for autotools install automation. +To install autotools into source directory on build machine (i.e. when +**build == host**) the bootstrap script can be run without arguments. In this +case autotools will be installed from current root file system. + +For the cross environment the **--target-dest-dir** options allows to install +some stuf from development root file system: + +```Bash +$ TARGET_DEST_DIR=/home/developer/prog/trunk-672/dist/.s9xx-glibc/enybox-x2 \ + ./bootstrap --target-dest-dir=${TARGET_DEST_DIR} +``` + +For example, in this case the **dialog.m4** script will be taken from the +**${TARGET_DEST_DIR}/usr/share/aclocal** directory. + + +## Install + +On the build machine the installation process seems like that + +```Bash +$ tar xJvf pkgtools-0.2.1.tar.xz +$ mkdir build +$ cd build +$ ../pkgtools-0.2.1/configure --prefix=/usr +$ make +$ make install DESTDIR=$PKG exec_prefix=/ +``` + +Note that the **exec_prefix=/** used for canonical installation of +pkgtools utilities into **${DESTDIR}/sbin/** directory instead of +**${DESTDIR}/usr/sbin/** which is not corresponds to +[**FHS**](https://refspecs.linuxfoundation.org/FHS_3.0/fhs-3.0.pdf). + + +## Configurations + +Pkgtools support **GnuPG** signing of packages and also simple +user interface based on **dialog** library. + +#### OpenPGP support options: + +```Bash + --with-gpg2=no + --with-gpg2=yes + --with-gpg2=${TARGET_DEST_DIR}/usr +``` + +If the **--with-gpg2** option is not specified then **GnuPG** support +is disabled. + +#### Dialog options: + +```Bash + --with-dialog=no + --with-dialog=yes + --with-dialog=${TARGET_DEST_DIR}/usr + --with-dialog-test=no + --with-dialog-test=yes +``` + +Dialog support is enabled by default. The option **--with-dialog=no** +disables the dialog support. + + +#### Distribution options: + +```Bash + --with-distro-name[=NAME] The name of distribution + --with-distro-version[=VERSION] The distribution version +``` + +To show all available options you can make use of + +```Bash +$ ./configure --help +``` + + +## Cross compilation example + +```Bash +TARGET_DEST_DIR=/home/developer/prog/trunk-672/dist/.s9xx-glibc/enybox-x2 +TOOLCHAIN_PATH=/opt/toolchains/aarch64-S9XX-linux-glibc/1.1.4/bin +TARGET=aarch64-s9xx-linux-gnu + +DIALOG_CONFIG=${TARGET_DEST_DIR}/usr/bin/dialog-config \ +STRIP="${TOOLCHAIN_PATH}/${TARGET}-strip" \ +CC="${TOOLCHAIN_PATH}/${TARGET}-gcc --sysroot=${TARGET_DEST_DIR}" \ +./configure --prefix=/usr + --build=x86_64-pc-linux-gnu \ + --host=${TARGET} \ + --with-gpg2=${TARGET_DEST_DIR}/usr \ + --with-dialog=${TARGET_DEST_DIR}/usr \ + --with-dialog-test=yes +``` + +Also we can make use of additional variables such as **CFLAGS**, **LDFLAGS**: + +```Bash +LDFLAGS="-L${TARGET_DEST_DIR}/lib -L${TARGET_DEST_DIR}/usr/lib" +TARGET_INCPATH="-L${TARGET_DEST_DIR}/usr/include" +CFLAGS="${TARGET_INCPATH}" +CPPFLAGS="${TARGET_INCPATH}" +``` + + +## [Dialog](https://invisible-island.net/dialog/dialog.html) + +The original **dialog** sources have some bugs such as memory leaks and also +the **dialog** package doesn't have correct autotools scripts. If you want to +use **libdialog** with **pkgtools** then you have to install the dialog package +with our [patch](doc/dialog/dialog-1.3-20210621.patch). This patch provides +**dialog.m4** and more convenient **dialog-config** script for +[dialog-1.3-20210621.tgz](ftp://ftp.invisible-island.net/dialog/dialog-1.3-20210621.tgz) +source package. + + +## [License](https://radix.pro/legal/licenses/) + +Code and documentation copyright 2009-2023 Andrey V. Kosteltsev.<br/> +Code and documentation released under [the **Radix.pro** License](https://cgit.radix.pro/radix/pkgtools.git/trunk/LICENSE). + +#### The text of this license can be found on our website at: + +> [https://radix.pro/licenses/LICENSE-1.0-en_US.txt](https://radix.pro/licenses/LICENSE-1.0-en_US.txt)<br/> +> [https://radix.pro/licenses/LICENSE-1.0-en_US.txt](https://radix.pro/licenses/LICENSE-1.0-ru_RU.txt) diff --git a/acsite.m4 b/acsite.m4 new file mode 100644 index 0000000..dd81602 --- /dev/null +++ b/acsite.m4 @@ -0,0 +1,56 @@ + +dnl ============================================================ +dnl Display Configuration Headers +dnl +dnl configure.in: +dnl AC_MSG_CFG_PART(<text>) +dnl ============================================================ + +define(AC_MSG_CFG_PART,[dnl + AC_MSG_RESULT() + AC_MSG_RESULT(${TB}$1:${TN}) +])dnl + +AC_DEFUN(AC_PKGTOOLS_HEADLINE,[dnl + # Use a `Quadrigaph'. @<:@ gives you [ and @:>@ gives you ] : + TB=`echo -n -e '\033@<:@1m'` + TN=`echo -n -e '\033@<:@0m'` + echo "" + echo "Configuring ${TB}$1${TN} ($2), Version ${TB}${PACKAGE_VERSION}${TN}" + echo "$3" +])dnl + + +dnl ============================================================ +dnl Test for build_host `ln -s' . +dnl ============================ +dnl +dnl Usage: +dnl ----- +dnl AC_PATH_PROG_LN_S +dnl AC_SUBST(LN) +dnl AC_SUBST(LN_S) +dnl +dnl ============================================================ +AC_DEFUN(AC_PATH_PROG_LN_S, +[AC_PATH_PROG(LN, ln, no, /usr/local/bin:/usr/bin:/bin:$PATH) +AC_MSG_CHECKING(whether ln -s works on build host) +AC_CACHE_VAL(ac_cv_path_prog_LN_S, +[rm -f conftestdata +if $LN -s X conftestdata 2>/dev/null +then + rm -f conftestdata + ac_cv_path_prog_LN_S="$LN -s" +else + ac_cv_path_prog_LN_S="$LN" +fi])dnl +LN_S="$ac_cv_path_prog_LN_S" +if test "$ac_cv_path_prog_LN_S" = "$LN -s"; then + AC_MSG_RESULT(yes) +else + AC_MSG_RESULT(no) +fi +AC_SUBST(LN)dnl +AC_SUBST(LN_S)dnl +]) + diff --git a/bootstrap b/bootstrap new file mode 100755 index 0000000..679cc4b --- /dev/null +++ b/bootstrap @@ -0,0 +1,81 @@ +#!/bin/sh + +CWD=`pwd` + +program=`basename $0` + +usage() { + cat << EOF + +Usage: $program [options] + +Options: + -h,--help Display this message. + -d,--target-dest-dir=DIR The target ROOTFS directory + [default: DIR=/]. + +EOF +} + +TARGET_DEST_DIR=/ +ACDIR=usr/share/aclocal +INCDIR=usr/include +SYSTEM_ACDIR= +SYSTEM_INCDIR= + +while [ 0 ] ; do + if [ "$1" = "-h" -o "$1" = "--help" ] ; then + usage + exit 0 + elif [ "$1" = "-d" -o "$1" = "--target-dest-dir" ] ; then + if [ "$2" = "" ] ; then + echo -e "\n${program}: ERROR: --target-dest-dir is not specified.\n" + usage + exit 1 + fi + TARGET_DEST_DIR="$2" + shift 2 + elif [[ $1 == --target-dest-dir=* ]] ; then + TARGET_DEST_DIR="`echo $1 | cut -f2 -d'='`" + shift 1 + else + if [ "$1" != "" ] ; then + echo -e "\n${program}: ERROR: Unknown argument: $1.\n" + usage + exit 1 + fi + break + fi +done + +if [ ! -d "${TARGET_DEST_DIR}" ] ; then + echo -e "\n${program}: ERROR: --target-dest-dir is not a directory.\n" + usage + exit 1 +fi + +# +# Absolute path: +# +if [ "${TARGET_DEST_DIR:0:1}" != "/" ] ; then + TARGET_DEST_DIR=${CWD}/${TARGET_DEST_DIR} +fi + +# +# Remove last '/' char: +# +if [ "${TARGET_DEST_DIR: -1}" = "/" ] ; then + len=${#TARGET_DEST_DIR} + let "len = len - 1" + tmp="${TARGET_DEST_DIR:0:$len}" + TARGET_DEST_DIR=${tmp} +fi + +SYSTEM_ACDIR="${TARGET_DEST_DIR}/${ACDIR}" +SYSTEM_INCDIR="${TARGET_DEST_DIR}/${INCDIR}" + + +aclocal --install -I m4 --force --system-acdir=${SYSTEM_ACDIR} +autoheader --include=${SYSTEM_INCDIR} +automake --gnu --add-missing --copy --force-missing +autoconf --force diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..89459a0 --- /dev/null +++ b/configure.ac @@ -0,0 +1,251 @@ + +# ============================================================ +# Process this file with autoconf to produce +# a configure script. +# ============================================================ + +AC_PREREQ(2.71) # Minimum Autoconf version required. + + +AC_INIT([pkgtools], [0.2.1], + [support@radix.pro], [pkgtools], [https://radix.pro]) + +# ============================================================ +# m4's diversions: +# --------------- +# +# see: /use/share/autoconf/autoconf/general.m4 +# ============================================================ +m4_divert_push([M4SH-INIT]) +DISTRO_NAME=radix +DISTRO_CAPTION=Radix +DISTRO_VERSION=1.1 +DISTRO_LICENSE=Radix-1.0 +m4_divert_pop([M4SH-INIT]) + +AC_PKGTOOLS_HEADLINE([pkgtools], + [Package Tools], [Copyright (c) 2009-2023 Andrey V.Kosteltsev]) + + +# ============================================================ +# ============================================================ +# ============================================================ +# ============================================================ +# ============================================================ +# $$ $$ +# $$ PART: Init Automake environment $$ +# $$ $$ +# ============================================================ +# ============================================================ +# ============================================================ +# ============================================================ +# ============================================================ +AC_MSG_CFG_PART(Init Automake environment) + +AC_CANONICAL_TARGET + +AM_INIT_AUTOMAKE([subdir-objects foreign no-dist-gzip dist-xz]) + +AC_CONFIG_HEADERS([config.h]) + +AC_PREFIX_DEFAULT(/usr/local) + + +# ============================================================ +# ============================================================ +# ============================================================ +# ============================================================ +# ============================================================ +# $$ $$ +# $$ PART: Test for Build Tools $$ +# $$ $$ +# ============================================================ +# ============================================================ +# ============================================================ +# ============================================================ +# ============================================================ +AC_MSG_CFG_PART(Test for build tools) +AC_CHECK_TOOL([GCC], [gcc], [:]) + + +# ============================================================ +# ============================================================ +# ============================================================ +# ============================================================ +# ============================================================ +# $$ $$ +# $$ PART: Test for Libraries $$ +# $$ $$ +# ============================================================ +# ============================================================ +# ============================================================ +# ============================================================ +# ============================================================ +AC_MSG_CFG_PART(Test for libraries) +AC_CHECK_DIALOG([1.3.20210621],yes,yes,yes,CFLAGS="$CFLAGS -DHAVE_DIALOG") +AM_CONDITIONAL([USE_DIALOG], [test "x$HAVE_DIALOG" = "x1"]) + + +# ============================================================ +# ============================================================ +# ============================================================ +# ============================================================ +# ============================================================ +# $$ $$ +# $$ PART: Test for Runtime Tools $$ +# $$ $$ +# ============================================================ +# ============================================================ +# ============================================================ +# ============================================================ +# ============================================================ +AC_MSG_CFG_PART(Test for runtime tools) + +# ============================================================ +# Check for GPG2 utility: +# ---------------------- +# Usage: +# not defined --with-gpg2 - Disable OpenPGP support by default +# --with-gpg2=no - Disable OpenPGP support by --with-gpg2=no option +# --with-gpg2, --with-gpg2=yes - Check whether the gpg2 program exists in path on the build machine +# --with-gpg2=${TARGET_DEST_DIR}/usr - Check whether the gpg2 program already installed on the target rootfs +# ============================================================ +AC_ARG_WITH([gpg2], [AS_HELP_STRING([--with-gpg2], + [support OpenPGP signatures (default=no)])], + [GPG2= + AS_IF([test "x$with_gpg2" != "xno"], + [AS_IF([test "x$with_gpg2" != "xyes"], + [AC_PATH_PROG([GPG2], [gpg2], [no], [${with_gpg2}/bin])], + [AC_PATH_PROG([GPG2], [gpg2], [no], [/usr/local/bin:/usr/bin:/bin:$PATH])]) + ], + [AC_MSG_CHECKING(for gpg2) + AC_MSG_RESULT([OpenPGP disabled by the --with-gpg2=no option]) + AC_SUBST(GPG2, [no]) + ]) + if test "x$GPG2" != "xno" ; then + AC_DEFINE([HAVE_GPG2], [1], [Define if you have OpenPGP program]) + else + if test "x$with_gpg2" != "xno" ; then + AC_MSG_FAILURE([--with-gpg2 was given, but test for gpg2 program failed], [1]) + fi + fi + ], + [AC_MSG_CHECKING(for gpg2) + AC_MSG_RESULT([OpenPGP disabled by default]) + AC_SUBST(GPG2, [no]) + ]) + +# ============================================================ +# m4's diversions: +# --------------- +# +# see: /use/share/autoconf/autoconf/general.m4 +# ============================================================ +m4_divert_push([HELP_WITH]) +_ACEOF + + cat <<_ACEOF + +Distribution Features: +m4_divert_pop([HELP_WITH]) + +AC_ARG_WITH([distro-name], [AS_HELP_STRING([--with-distro-name@<:@=NAME@:>@], + [Distribution Name @<:@default=${DISTRO_NAME}@:>@. Please note that the distribution NAME should not contain spaces],[30],[74])], + [AS_IF([test "x$with_distro_name" != "x"], + [DISTRO_NAME=${with_distro_name} + DISTRO_CAPTION=`echo ${with_distro_name:0:1} | tr '[a-z]' '[A-Z]'`${with_distro_name:1} + AC_DEFINE_UNQUOTED([DISTRO_NAME], ["$with_distro_name"], [Define the distribution name]) + AC_DEFINE_UNQUOTED([DISTRO_CAPTION], "$DISTRO_CAPTION", [Define the caption of the distribution]) + ], + [AC_DEFINE_UNQUOTED([DISTRO_NAME], ["$DISTRO_NAME"], [Define the distribution name]) + AC_DEFINE_UNQUOTED([DISTRO_CAPTION], ["$DISTRO_CAPTION"], [Define the caption of the distribution]) + ]) + ], + [AC_DEFINE_UNQUOTED([DISTRO_NAME], ["$DISTRO_NAME"], [Define the distribution name]) + AC_DEFINE_UNQUOTED([DISTRO_CAPTION], ["$DISTRO_CAPTION"], [Define the caption of the distribution]) + ]) + +AC_ARG_WITH([distro-version], [AS_HELP_STRING([--with-distro-version@<:@=VERSION@:>@], + [Distribution Version @<:@default=${DISTRO_VERSION}@:>@],[30],[79])], + [AS_IF([test "x$with_distro_version" != "x"], + [AC_DEFINE_UNQUOTED([DISTRO_VERSION], ["$with_distro_version"], [Define the version of distribution]) + ], + [AC_DEFINE_UNQUOTED([DISTRO_VERSION], ["$DISTRO_VERSION"], [Define the version of distribution]) + ]) + ], + [AC_DEFINE_UNQUOTED([DISTRO_VERSION], ["$DISTRO_VERSION"], [Define the version of distribution]) + ]) + +m4_divert_push([HELP_WITH]) +_ACEOF + + cat <<\_ACEOF +m4_divert_pop([HELP_WITH]) + +AC_SUBST(DISTRO_NAME) +AC_SUBST(DISTRO_CAPTION) +AC_SUBST(DISTRO_VERSION) +AC_SUBST(DISTRO_URL,[${PACKAGE_URL}]) +AC_SUBST(DISTRO_LICENSE,[${DISTRO_LICENSE}]) +AC_SUBST(PROGRAM_VERSION,[${PACKAGE_VERSION}]) + +AC_DEFINE_UNQUOTED([DISTRO_URL], ["$DISTRO_URL"], [Define the bug report URL]) +AC_DEFINE_UNQUOTED([DISTRO_LICENSE], ["$DISTRO_LICENSE"], [Define the bug report URL]) +AC_DEFINE_UNQUOTED([PROGRAM_VERSION], ["$PROGRAM_VERSION"], [Define the version of all programs in this package]) + + +# ============================================================ +# Environment Variables: +# --------------------- +# For 'Some influential environment variables:' help section +# ============================================================ +AC_ARG_VAR([STRIP], [strip command]) + + +# ============================================================ +# ============================================================ +# ============================================================ +# ============================================================ +# ============================================================ +# $$ $$ +# $$ PART: Test for Auxiliary (my be version sensitive) $$ +# $$ programs $$ +# $$ $$ +# ============================================================ +# ============================================================ +# ============================================================ +# ============================================================ +# ============================================================ +AC_MSG_CFG_PART(Test for aux programs) +AC_PATH_PROG_LN_S +AC_PATH_PROG([TAR], [tar], [no], [/usr/local/bin:/usr/bin:/bin:$PATH]) +AC_PATH_PROG([SED], [sed], [no], [/usr/local/bin:/usr/bin:/bin:$PATH]) +AC_PATH_PROG([FIND], [find], [no], [/usr/local/bin:/usr/bin:/bin:$PATH]) +AC_PATH_PROG([CAT], [cat], [no], [/usr/local/bin:/usr/bin:/bin:$PATH]) +AC_PATH_PROG([CP], [cp], [no], [/usr/local/bin:/usr/bin:/bin:$PATH]) +AC_PATH_PROG([MV], [mv], [no], [/usr/local/bin:/usr/bin:/bin:$PATH]) +AC_PATH_PROG([RM], [rm], [no], [/usr/local/bin:/usr/bin:/bin:$PATH]) +AC_PATH_PROG([CHMOD], [chmod], [no], [/usr/local/bin:/usr/bin:/bin:$PATH]) +AC_PATH_PROG([SHA256SUM], [sha256sum], [no], [/usr/local/bin:/usr/bin:/bin:$PATH]) + + +# ============================================================ +# ============================================================ +# ============================================================ +# ============================================================ +# ============================================================ +# $$ $$ +# $$ PART: OUTPUT Substitution $$ +# $$ $$ +# ============================================================ +# ============================================================ +# ============================================================ +# ============================================================ +# ============================================================ +AC_MSG_CFG_PART(OUTPUT) + +AC_CONFIG_FILES([ +src/Makefile +Makefile +]) +AC_OUTPUT diff --git a/doc/autogen-examples/auto-clean.sh b/doc/autogen-examples/auto-clean.sh new file mode 100755 index 0000000..079b699 --- /dev/null +++ b/doc/autogen-examples/auto-clean.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +cd ../.. + +if [ -f "Makefile" ] ; then + make distclean +fi + +rm -rf autom4te.cache m4 + +rm -f Makefile +rm -f Makefile.in +rm -f config.h +rm -f config.h.in +rm -f config.log +rm -f config.status +rm -f compile config.guess config.sub +rm -f configure +rm -f install-sh +rm -f missing +rm -f stamp-h1 +rm -f aclocal.m4 +rm -f depcomp + +rm -rf src/.deps +rm -f src/Makefile +rm -f src/Makefile.in diff --git a/doc/autogen-examples/bootstrap-cross.sh b/doc/autogen-examples/bootstrap-cross.sh new file mode 100755 index 0000000..aa5e3f6 --- /dev/null +++ b/doc/autogen-examples/bootstrap-cross.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +TARGET_DEST_DIR=/home/kx/prog/scm/svn/platform/trunk-672/dist/.s9xx-glibc/enybox-x2 + +cd ../.. +./bootstrap --target-dest-dir=${TARGET_DEST_DIR} diff --git a/doc/autogen-examples/bootstrap.sh b/doc/autogen-examples/bootstrap.sh new file mode 100755 index 0000000..59b68f5 --- /dev/null +++ b/doc/autogen-examples/bootstrap.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +cd ../.. +./bootstrap diff --git a/doc/autogen-examples/configure-cross.sh b/doc/autogen-examples/configure-cross.sh new file mode 100755 index 0000000..9c74ac8 --- /dev/null +++ b/doc/autogen-examples/configure-cross.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +cd ../.. + +TARGET_DEST_DIR=/home/kx/prog/scm/svn/platform/trunk-672/dist/.s9xx-glibc/enybox-x2 +TOOLCHAIN_PATH=/opt/toolchain/aarch64-S9XX-linux-glibc/1.1.4/bin +TARGET=aarch64-s9xx-linux-gnu + +TARGET_INCPATH="-L${TARGET_DEST_DIR}/usr/include" +CFLAGS="${TARGET_INCPATH}" +CPPFLAGS="${TARGET_INCPATH}" +LDFLAGS="-L${TARGET_DEST_DIR}/lib -L${TARGET_DEST_DIR}/usr/lib" + + +DIALOG_CONFIG=${TARGET_DEST_DIR}/usr/bin/dialog-config \ +STRIP="${TOOLCHAIN_PATH}/${TARGET}-strip" \ +CC="${TOOLCHAIN_PATH}/${TARGET}-gcc --sysroot=${TARGET_DEST_DIR}" \ +./configure --prefix=/usr \ + --build=x86_64-pc-linux-gnu --host=${TARGET} \ + --with-gpg2=${TARGET_DEST_DIR}/usr \ + --with-dialog=${TARGET_DEST_DIR}/usr \ + --with-dialog-test=yes diff --git a/doc/autogen-examples/configure.sh b/doc/autogen-examples/configure.sh new file mode 100755 index 0000000..7c743fd --- /dev/null +++ b/doc/autogen-examples/configure.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +cd ../.. + +./configure --prefix=/usr \ + --with-distro-name=radix \ + --with-distro-version=1.1 \ + --with-gpg2=yes \ + --with-dialog=yes diff --git a/doc/dialog/dialog-1.3-20190211.patch b/doc/dialog/dialog-1.3-20190211.patch new file mode 100644 index 0000000..3a5ce88 --- /dev/null +++ b/doc/dialog/dialog-1.3-20190211.patch @@ -0,0 +1,453 @@ +diff -b --unified -Nr dialog-1.3-20190211-orig/checklist.c dialog-1.3-20190211/checklist.c +--- dialog-1.3-20190211-orig/checklist.c 2018-06-20 01:57:01.000000000 +0300 ++++ dialog-1.3-20190211/checklist.c 2019-07-20 15:48:25.753332500 +0300 +@@ -29,7 +29,7 @@ + #include <dialog.h> + #include <dlg_keys.h> + +-#define MIN_HIGH (1 + (5 * MARGIN)) ++#define MIN_HIGH 4 + + typedef struct { + /* the outer-window */ +diff -b --unified -Nr dialog-1.3-20190211-orig/dialog-config.in dialog-1.3-20190211/dialog-config.in +--- dialog-1.3-20190211-orig/dialog-config.in 2012-10-06 18:29:45.000000000 +0400 ++++ dialog-1.3-20190211/dialog-config.in 2019-07-20 15:48:25.753332500 +0300 +@@ -73,12 +73,17 @@ + INCS="-I${prefix}/include" + fi + sed -e 's,^[ ]*,,' -e 's, [ ]*, ,g' -e 's,[ ]*$,,' <<-ENDECHO +- $INCS ++ @CFLAGS@ $INCS ++ENDECHO ++ ;; ++ --ldflags) ++ sed -e 's,^[ ]*,,' -e 's, [ ]*, ,g' -e 's,[ ]*$,,' <<-ENDECHO ++ -L@libdir@ + ENDECHO + ;; + --libs) + sed -e 's,^[ ]*,,' -e 's, [ ]*, ,g' -e 's,[ ]*$,,' <<-ENDECHO +- -L${exec_prefix}/lib -l${THIS} @LIBS@ ++ -l${THIS} @LIBS@ + ENDECHO + ;; + # identification +@@ -113,6 +118,7 @@ + --exec-prefix=ARG sets the executable-prefix of ${THIS} + + --cflags echos the C compiler flags needed to compile with ${THIS} ++ --ldflags echos the linker flags needed to link with ${THIS} + --libs echos the libraries needed to link with ${THIS} + + --version echos the release+patchdate version of ${THIS} +diff -b --unified -Nr dialog-1.3-20190211-orig/dialog.m4 dialog-1.3-20190211/dialog.m4 +--- dialog-1.3-20190211-orig/dialog.m4 1970-01-01 03:00:00.000000000 +0300 ++++ dialog-1.3-20190211/dialog.m4 2019-07-20 15:48:25.749332413 +0300 +@@ -0,0 +1,333 @@ ++dnl # ++dnl # /usr/share/aclocal/dialog.m4 ++dnl # ++dnl # Configure paths for dialog ++dnl # Andrew V.Kosteltsev ++ ++dnl ============================================================ ++dnl ++dnl Synopsis: ++dnl AC_CHECK_DIALOG([MIN-VERSION [, # minimum dialog version, e.g. 1.3-20190211 ++dnl DEFAULT-WITH-DIALOG [, # default value for --with-dialog option ++dnl DEFAULT-WITH-DIALOG-TEST [,# default value for --with-dialog-test option ++dnl EXTEND-VARS [, # whether CFLAGS/LDFLAGS/etc are extended ++dnl ACTION-IF-FOUND [, # action to perform if dialog was found ++dnl ACTION-IF-NOT-FOUND # action to perform if dialog was not found ++dnl ]]]]]]) ++dnl Examples: ++dnl AC_CHECK_DIALOG(1.3-20190211) ++dnl AC_CHECK_DIALOG(1.3-20190211,,,no,CFLAGS="$CFLAGS -DHAVE_DIALOG $DIALOG_CFLAGS") ++dnl AC_CHECK_DIALOG(1.3-20190211,yes,yes,yes,CFLAGS="$CFLAGS -DHAVE_DIALOG") ++dnl ++dnl ++dnl If you have to change prefix returned by dialog-config script or change ++dnl location of dialog-config, you may set environment variable DIALOG_CONFIG, ++dnl for example: ++dnl ++dnl # export DIALOG_CONFIG="dialog-config --prefix=/usr/local" ++dnl # export DIALOG_CONFIG="/usr/bin/dialog-config --prefix=/usr/local" ++dnl ++dnl ============================================================ ++dnl ++dnl ============================================================ ++dnl auxilliary macros ++dnl ============================================================ ++AC_DEFUN([_AC_DIALOG_ERROR], [dnl ++AC_MSG_RESULT([*FAILED*]) ++cat <<EOT | sed -e 's/^[[ ]]*/ | /' -e 's/>>/ /' 1>&2 ++$1 ++EOT ++exit 1 ++]) ++ ++AC_DEFUN([_AC_DIALOG_VERBOSE], [dnl ++if test ".$verbose" = .yes; then ++ AC_MSG_RESULT([ $1]) ++fi ++]) ++ ++dnl ============================================================ ++dnl the user macro ++dnl ============================================================ ++AC_DEFUN([AC_CHECK_DIALOG], [dnl ++dnl ++dnl ============================================================ ++dnl prerequisites ++dnl ============================================================ ++AC_REQUIRE([AC_PROG_CC])dnl ++AC_REQUIRE([AC_PROG_CPP])dnl ++dnl ++dnl ============================================================ ++dnl set DIALOG_CONFIG variable ++dnl ============================================================ ++if test -z "$DIALOG_CONFIG"; then ++ DIALOG_CONFIG='dialog-config' ++fi ++dnl ++DIALOG_CFLAGS='' ++DIALOG_LDFLAGS='' ++DIALOG_LIBS='' ++AC_SUBST(DIALOG_CFLAGS) ++AC_SUBST(DIALOG_LDFLAGS) ++AC_SUBST(DIALOG_LIBS) ++dnl ++dnl ============================================================ ++dnl command line options ++dnl ============================================================ ++_AC_DIALOG_VERBOSE([]) ++AC_ARG_WITH(dialog,dnl ++[ --with-dialog[=ARG] Build with dialog Library (default=]ifelse([$2],,yes,$2)[)],dnl ++,dnl ++with_dialog="ifelse([$2],,yes,$2)" ++)dnl ++AC_ARG_WITH(dialog-test,dnl ++[ --with-dialog-test Perform dialog Sanity Test (default=]ifelse([$3],,yes,$3)[)],dnl ++,dnl ++with_dialog_test="ifelse([$3],,yes,$3)" ++)dnl ++_AC_DIALOG_VERBOSE([+ Command Line Options:]) ++_AC_DIALOG_VERBOSE([ o --with-dialog=$with_dialog]) ++_AC_DIALOG_VERBOSE([ o --with-dialog-test=$with_dialog_test]) ++dnl ++dnl ============================================================ ++dnl configuration ++dnl ============================================================ ++if test ".$with_dialog" != .no; then ++ dialog_subdir=no ++ dialog_subdir_opts='' ++ case "$with_dialog" in ++ subdir:* ) ++ dialog_subdir=yes ++ changequote(, )dnl ++ dialog_subdir_opts=`echo $with_dialog | sed -e 's/^subdir:[^ ]*[ ]*//'` ++ with_dialog=`echo $with_dialog | sed -e 's/^subdir:\([^ ]*\).*$/\1/'` ++ changequote([, ])dnl ++ ;; ++ esac ++ dialog_version="" ++ dialog_location="" ++ dialog_type="" ++ dialog_cflags="" ++ dialog_ldflags="" ++ dialog_libs="" ++ if test ".$with_dialog" = .yes; then ++ # via config script in $PATH ++ changequote(, )dnl ++ dialog_version=`($DIALOG_CONFIG --version) 2>/dev/null |\ ++ sed -e 's/^.*\([0-9]\.[0-9]*[-][0-9]*\).*$/\1/'` ++ changequote([, ])dnl ++ if test ".$dialog_version" != .; then ++ dialog_location=`$DIALOG_CONFIG --prefix` ++ dialog_type='installed' ++ dialog_cflags=`$DIALOG_CONFIG --cflags` ++ dialog_ldflags=`$DIALOG_CONFIG --ldflags` ++ dialog_libs=`$DIALOG_CONFIG --libs` ++ fi ++ elif test -d "$with_dialog"; then ++ with_dialog=`echo $with_dialog | sed -e 's;/*$;;'` ++ dialog_found=no ++ # via config script under a specified directory ++ # (a standard installation, but not a source tree) ++ if test ".$dialog_found" = .no; then ++ for _dir in $with_dialog/bin $with_dialog; do ++ if test -f "$_dir/dialog-config"; then ++ test -f "$_dir/dialog-config.in" && continue # dialog-config in source tree! ++ changequote(, )dnl ++ dialog_version=`($_dir/dialog-config --version) 2>/dev/null |\ ++ sed -e 's/^.*\([0-9]\.[0-9]*[.][0-9]*\).*$/\1/'` ++ changequote([, ])dnl ++ if test ".$dialog_version" != .; then ++ dialog_location=`$_dir/dialog-config --prefix` ++ dialog_type="installed" ++ dialog_cflags=`$_dir/dialog-config --cflags` ++ dialog_ldflags=`$_dir/dialog-config --ldflags` ++ dialog_libs=`$_dir/dialog-config --libs` ++ dialog_found=yes ++ break ++ fi ++ fi ++ done ++ fi ++ fi ++ _AC_DIALOG_VERBOSE([+ Determined Location:]) ++ _AC_DIALOG_VERBOSE([ o path: $dialog_location]) ++ _AC_DIALOG_VERBOSE([ o type: $dialog_type]) ++ if test ".$dialog_version" = .; then ++ if test ".$with_dialog" != .yes; then ++ _AC_DIALOG_ERROR([dnl ++ Unable to locate dialog under $with_dialog. ++ Please specify the correct path to either a dialog installation tree ++ (use --with-dialog=DIR if you used --prefix=DIR for installing dialog in ++ the past).]) ++ else ++ _AC_DIALOG_ERROR([dnl ++ Unable to locate dialog in any system-wide location (see \$PATH). ++ Please specify the correct path to either a dialog installation tree ++ (use --with-dialog=DIR if you used --prefix=DIR for installing dialog in ++ the past, or set the DIALOG_CONFIG environment variable to the full path ++ to dialog-config).]) ++ fi ++ fi ++ dnl ======================================================== ++ dnl Check whether the found version is sufficiently new ++ dnl ======================================================== ++ _req_version="ifelse([$1],,1.0.0,$1)" ++ for _var in dialog_version _req_version; do ++ eval "_val=\"\$${_var}\"" ++ _major=`echo $_val | sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\([[.]]\)\([[0-9]]*\)/\1/'` ++ _minor=`echo $_val | sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\([[.]]\)\([[0-9]]*\)/\2/'` ++ _micro=`echo $_val | sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\([[.]]\)\([[0-9]]*\)/\4/'` ++ _hex=`echo dummy | awk '{ printf("%d%02d%02d", major, minor, micro); }' \ ++ "major=$_major" "minor=$_minor" "micro=$_micro"` ++ eval "${_var}_hex=\"\$_hex\"" ++ done ++ _AC_DIALOG_VERBOSE([+ Determined Versions:]) ++ _AC_DIALOG_VERBOSE([ o existing: $dialog_version -> 0x$dialog_version_hex]) ++ _AC_DIALOG_VERBOSE([ o required: $_req_version -> 0x$_req_version_hex]) ++ _ok=0 ++ if test ".$dialog_version_hex" != .; then ++ if test ".$_req_version_hex" != .; then ++ if test $dialog_version_hex -ge $_req_version_hex; then ++ _ok=1 ++ fi ++ fi ++ fi ++ if test ".$_ok" = .0; then ++ _AC_DIALOG_ERROR([dnl ++ Found dialog version $dialog_version, but required at least version $_req_version. ++ Upgrade dialog under $dialog_location to $_req_version or higher first, please.]) ++ fi ++ dnl ======================================================== ++ dnl Perform dialog Sanity Compile Check ++ dnl ======================================================== ++ if test ".$with_dialog_test" = .yes; then ++ _ac_save_CFLAGS="$CFLAGS" ++ _ac_save_LDFLAGS="$LDFLAGS" ++ _ac_save_LIBS="$LIBS" ++ CFLAGS="$CFLAGS $dialog_cflags" ++ LDFLAGS="$LDFLAGS $dialog_ldflags" ++ LIBS="$LIBS $dialog_libs" ++ _AC_DIALOG_VERBOSE([+ Test Build Environment:]) ++ _AC_DIALOG_VERBOSE([ o CFLAGS=\"$CFLAGS\"]) ++ _AC_DIALOG_VERBOSE([ o LDFLAGS=\"$LDFLAGS\"]) ++ _AC_DIALOG_VERBOSE([ o LIBS=\"$LIBS\"]) ++ cross_compile=no ++ define(_code1, [dnl ++ ++#include <stdlib.h> ++#include <stdio.h> ++#include <strings.h> /* index(3) */ ++ ++#include <dialog.h> ++#include <dlg_colors.h> ++#include <dlg_keys.h> ++ ++ ]) ++ define(_code2, [dnl ++ ++int main( void ) ++{ ++ int status = 0; ++ ++ bzero( (void *)&dialog_vars, sizeof(DIALOG_VARS) ); ++ ++ init_dialog(stdin, stdout); ++ ++ dialog_vars.colors = 1; ++ dialog_vars.backtitle = "\\Z7Test\\Zn \\Z1dialog\\Zn \\Z7Library\\Zn"; ++ dialog_vars.dlg_clear_screen = 1; ++ dialog_vars.sleep_secs = 1; ++ ++ ++ dlg_put_backtitle(); ++ ++ /************************************************* ++ Ruler: 68 characters + 2 spaces left and right: ++ ++ | ----handy-ruler----------------------------------------------------- | */ ++ status = dialog_msgbox( " \\Z4Dialog ==>\\Zn\\Z1libdialog\\Zn\\Z4<== [required]\\Zn ", ++ "\nPackage is installed and corect.\n", ++ 5, 72, 0 ); ++ ++ if( dialog_vars.sleep_secs ) ++ (void)napms(dialog_vars.sleep_secs * 1000); ++ ++ if( dialog_vars.dlg_clear_screen ) ++ { ++ dlg_clear(); ++ (void)refresh(); ++ } ++ end_dialog(); ++ ++ exit( 0 ); ++} ++ ]) ++ _AC_DIALOG_VERBOSE([+ Performing Sanity Checks:]) ++ _AC_DIALOG_VERBOSE([ o pre-processor test]) ++ AC_TRY_CPP(_code1, _ok=yes, _ok=no) ++ if test ".$_ok" != .yes; then ++ _AC_DIALOG_ERROR([dnl ++ Found dialog $dialog_version under $dialog_location, but ++ was unable to perform a sanity pre-processor check. This means ++ the dialog header dialog.h was not found. ++ We used the following build environment: ++ >> CPP="$CPP" ++ See config.log for possibly more details.]) ++ fi ++ _AC_DIALOG_VERBOSE([ o link check]) ++ AC_TRY_LINK(_code1, _code2, _ok=yes, _ok=no) ++ if test ".$_ok" != .yes; then ++ _AC_DIALOG_ERROR([dnl ++ Found dialog $dialog_version under $dialog_location, but ++ was unable to perform a sanity linker check. This means ++ the dialog library libdialog.a was not found. ++ We used the following build environment: ++ >> CC="$CC" ++ >> CFLAGS="$CFLAGS" ++ >> LDFLAGS="$LDFLAGS" ++ >> LIBS="$LIBS" ++ See config.log for possibly more details.]) ++ fi ++ _extendvars="ifelse([$4],,yes,$4)" ++ if test ".$_extendvars" != .yes; then ++ CFLAGS="$_ac_save_CFLAGS" ++ LDFLAGS="$_ac_save_LDFLAGS" ++ LIBS="$_ac_save_LIBS" ++ fi ++ else ++ _extendvars="ifelse([$4],,yes,$4)" ++ if test ".$_extendvars" = .yes; then ++ if test ".$dialog_subdir" = .yes; then ++ CFLAGS="$CFLAGS $dialog_cflags" ++ LDFLAGS="$LDFLAGS $dialog_ldflags" ++ LIBS="$LIBS $dialog_libs" ++ fi ++ fi ++ fi ++ DIALOG_CFLAGS="$dialog_cflags" ++ DIALOG_LDFLAGS="$dialog_ldflags" ++ DIALOG_LIBS="$dialog_libs" ++ AC_SUBST(DIALOG_CFLAGS) ++ AC_SUBST(DIALOG_LDFLAGS) ++ AC_SUBST(DIALOG_LIBS) ++ ++ AC_SUBST(HAVE_DIALOG, [1]) ++ ++ AC_CHECK_HEADERS(dialog.h dlg_colors.h dlg_keys.h) ++ ++ _AC_DIALOG_VERBOSE([+ Final Results:]) ++ _AC_DIALOG_VERBOSE([ o DIALOG_CFLAGS=\"$DIALOG_CFLAGS\"]) ++ _AC_DIALOG_VERBOSE([ o DIALOG_LDFLAGS=\"$DIALOG_LDFLAGS\"]) ++ _AC_DIALOG_VERBOSE([ o DIALOG_LIBS=\"$DIALOG_LIBS\"]) ++fi ++if test ".$with_dialog" != .no; then ++ AC_MSG_CHECKING(for libdialog) ++ AC_MSG_RESULT([version $dialog_version, $dialog_type under $dialog_location]) ++ ifelse([$5], , :, [$5]) ++else ++ AC_MSG_CHECKING(for libdialog) ++ AC_MSG_RESULT([no]) ++ ifelse([$6], , :, [$6]) ++fi ++]) ++ +diff -b --unified -Nr dialog-1.3-20190211-orig/fselect.c dialog-1.3-20190211/fselect.c +--- dialog-1.3-20190211-orig/fselect.c 2018-06-22 02:28:04.000000000 +0300 ++++ dialog-1.3-20190211/fselect.c 2019-07-20 15:48:25.753332500 +0300 +@@ -631,7 +631,7 @@ + dlg_print_size(height, width); + dlg_ctl_size(height, width); + +- dialog = dlg_new_window(height, width, ++ dialog = dlg_new_window(height + 1, width, + dlg_box_y_ordinate(height), + dlg_box_x_ordinate(width)); + dlg_register_window(dialog, "fselect", binding); +@@ -639,7 +639,7 @@ + + dlg_mouse_setbase(0, 0); + +- dlg_draw_box2(dialog, 0, 0, height, width, dialog_attr, border_attr, border2_attr); ++ dlg_draw_box2(dialog, 0, 0, height + 1, width, dialog_attr, border_attr, border2_attr); + dlg_draw_bottom_box2(dialog, border_attr, border2_attr, dialog_attr); + dlg_draw_title(dialog, title); + +@@ -648,7 +648,7 @@ + /* Draw the input field box */ + tbox_height = 1; + tbox_width = width - (4 * MARGIN + 2); +- tbox_y = height - (BTN_HIGH * 2) + MARGIN; ++ tbox_y = height - (BTN_HIGH * 2) + MARGIN + 1; + tbox_x = (width - tbox_width) / 2; + + w_text = derwin(dialog, tbox_height, tbox_width, tbox_y, tbox_x); +@@ -675,7 +675,7 @@ + else + dbox_width = (width - (6 * MARGIN + 2 * EXT_WIDE)) / 2; + dbox_height = height - MIN_HIGH; +- dbox_y = (2 * MARGIN + 1); ++ dbox_y = (2 * MARGIN + 2); + dbox_x = tbox_x; + + w_work = derwin(dialog, dbox_height, dbox_width, dbox_y, dbox_x); +@@ -735,7 +735,7 @@ + if (show_buttons) { + show_buttons = FALSE; + button = (state < 0) ? 0 : state; +- dlg_draw_buttons(dialog, height - 2, 0, buttons, button, FALSE, width); ++ dlg_draw_buttons(dialog, height - 1, 0, buttons, button, FALSE, width); + } + + if (first_trace) { +diff -b --unified -Nr dialog-1.3-20190211-orig/guage.c dialog-1.3-20190211/guage.c +--- dialog-1.3-20190211-orig/guage.c 2018-06-21 11:23:43.000000000 +0300 ++++ dialog-1.3-20190211/guage.c 2019-07-20 15:48:25.768332827 +0300 +@@ -377,6 +377,9 @@ + MY_OBJ *obj = (MY_OBJ *) objptr; + + if (valid(obj)) { ++ if (obj->title) free(obj->title); ++ if (obj->prompt) free(obj->prompt); ++ + obj->obj.keep_win = FALSE; + dlg_remove_callback(&(obj->obj)); + delink(obj); +diff -b --unified -Nr dialog-1.3-20190211-orig/menubox.c dialog-1.3-20190211/menubox.c +--- dialog-1.3-20190211-orig/menubox.c 2018-06-22 02:28:56.000000000 +0300 ++++ dialog-1.3-20190211/menubox.c 2019-07-20 15:48:25.753332500 +0300 +@@ -48,7 +48,7 @@ + int item_no; + } ALL_DATA; + +-#define MIN_HIGH (1 + (5 * MARGIN)) ++#define MIN_HIGH 4 + + #define INPUT_ROWS 3 /* rows per inputmenu entry */ + diff --git a/doc/dialog/dialog-1.3-20190724.patch b/doc/dialog/dialog-1.3-20190724.patch new file mode 100644 index 0000000..e29bc55 --- /dev/null +++ b/doc/dialog/dialog-1.3-20190724.patch @@ -0,0 +1,467 @@ +diff -b --unified -Nr dialog-1.3-20190724-orig/checklist.c dialog-1.3-20190724/checklist.c +--- dialog-1.3-20190724-orig/checklist.c 2019-07-25 01:17:14.000000000 +0300 ++++ dialog-1.3-20190724/checklist.c 2019-07-26 14:14:01.063282363 +0300 +@@ -29,7 +29,7 @@ + #include <dialog.h> + #include <dlg_keys.h> + +-#define MIN_HIGH (1 + (5 * MARGIN)) ++#define MIN_HIGH 4 + + typedef struct { + /* the outer-window */ +diff -b --unified -Nr dialog-1.3-20190724-orig/dialog-config.in dialog-1.3-20190724/dialog-config.in +--- dialog-1.3-20190724-orig/dialog-config.in 2019-07-23 11:44:33.000000000 +0300 ++++ dialog-1.3-20190724/dialog-config.in 2019-07-26 14:14:01.063282363 +0300 +@@ -73,7 +73,12 @@ + INCS="-I${prefix}/include" + fi + sed -e 's,^[ ]*,,' -e 's, [ ]*, ,g' -e 's,[ ]*$,,' <<-ENDECHO +- $INCS ++ @CFLAGS@ $INCS ++ENDECHO ++ ;; ++ --ldflags) ++ sed -e 's,^[ ]*,,' -e 's, [ ]*, ,g' -e 's,[ ]*$,,' <<-ENDECHO ++ -L@libdir@ + ENDECHO + ;; + --cflags-only-other) +@@ -81,12 +86,12 @@ + ;; + --libs) + sed -e 's,^[ ]*,,' -e 's, [ ]*, ,g' -e 's,[ ]*$,,' <<-ENDECHO +- -L${exec_prefix}/lib -l${THIS} @LIBS@ ++ -l${THIS} @LIBS@ + ENDECHO + ;; + --libs-only-L) + OPTS= +- for opt in "$LIBS" ++ for opt in -L@libdir@ -l${THIS} @LIBS@ + do + case "x$opt" in + x-L*) +@@ -98,7 +103,7 @@ + ;; + --libs-only-l) + OPTS= +- for opt in "$LIBS" ++ for opt in -L@libdir@ -l${THIS} @LIBS@ + do + case "x$opt" in + x-l*) +@@ -110,7 +115,7 @@ + ;; + --libs-only-other) + OPTS= +- for opt in "$LIBS" ++ for opt in -L@libdir@ -l${THIS} @LIBS@ + do + case "x$opt" in + x-[lL]*) +@@ -154,6 +159,7 @@ + --exec-prefix=ARG sets the executable-prefix of ${THIS} + + --cflags echos the C compiler flags needed to compile with ${THIS} ++ --ldflags echos the linker flags needed to link with ${THIS} + --libs echos the libraries needed to link with ${THIS} + + --libs-only-L echos -L linker options (search path) for ${THIS} +diff -b --unified -Nr dialog-1.3-20190724-orig/dialog.m4 dialog-1.3-20190724/dialog.m4 +--- dialog-1.3-20190724-orig/dialog.m4 1970-01-01 03:00:00.000000000 +0300 ++++ dialog-1.3-20190724/dialog.m4 2019-07-26 14:14:01.063282363 +0300 +@@ -0,0 +1,333 @@ ++dnl # ++dnl # /usr/share/aclocal/dialog.m4 ++dnl # ++dnl # Configure paths for dialog ++dnl # Andrew V.Kosteltsev ++ ++dnl ============================================================ ++dnl ++dnl Synopsis: ++dnl AC_CHECK_DIALOG([MIN-VERSION [, # minimum dialog version, e.g. 1.3-20190211 ++dnl DEFAULT-WITH-DIALOG [, # default value for --with-dialog option ++dnl DEFAULT-WITH-DIALOG-TEST [,# default value for --with-dialog-test option ++dnl EXTEND-VARS [, # whether CFLAGS/LDFLAGS/etc are extended ++dnl ACTION-IF-FOUND [, # action to perform if dialog was found ++dnl ACTION-IF-NOT-FOUND # action to perform if dialog was not found ++dnl ]]]]]]) ++dnl Examples: ++dnl AC_CHECK_DIALOG(1.3-20190211) ++dnl AC_CHECK_DIALOG(1.3-20190211,,,no,CFLAGS="$CFLAGS -DHAVE_DIALOG $DIALOG_CFLAGS") ++dnl AC_CHECK_DIALOG(1.3-20190211,yes,yes,yes,CFLAGS="$CFLAGS -DHAVE_DIALOG") ++dnl ++dnl ++dnl If you have to change prefix returned by dialog-config script or change ++dnl location of dialog-config, you may set environment variable DIALOG_CONFIG, ++dnl for example: ++dnl ++dnl # export DIALOG_CONFIG="dialog-config --prefix=/usr/local" ++dnl # export DIALOG_CONFIG="/usr/bin/dialog-config --prefix=/usr/local" ++dnl ++dnl ============================================================ ++dnl ++dnl ============================================================ ++dnl auxilliary macros ++dnl ============================================================ ++AC_DEFUN([_AC_DIALOG_ERROR], [dnl ++AC_MSG_RESULT([*FAILED*]) ++cat <<EOT | sed -e 's/^[[ ]]*/ | /' -e 's/>>/ /' 1>&2 ++$1 ++EOT ++exit 1 ++]) ++ ++AC_DEFUN([_AC_DIALOG_VERBOSE], [dnl ++if test ".$verbose" = .yes; then ++ AC_MSG_RESULT([ $1]) ++fi ++]) ++ ++dnl ============================================================ ++dnl the user macro ++dnl ============================================================ ++AC_DEFUN([AC_CHECK_DIALOG], [dnl ++dnl ++dnl ============================================================ ++dnl prerequisites ++dnl ============================================================ ++AC_REQUIRE([AC_PROG_CC])dnl ++AC_REQUIRE([AC_PROG_CPP])dnl ++dnl ++dnl ============================================================ ++dnl set DIALOG_CONFIG variable ++dnl ============================================================ ++if test -z "$DIALOG_CONFIG"; then ++ DIALOG_CONFIG='dialog-config' ++fi ++dnl ++DIALOG_CFLAGS='' ++DIALOG_LDFLAGS='' ++DIALOG_LIBS='' ++AC_SUBST(DIALOG_CFLAGS) ++AC_SUBST(DIALOG_LDFLAGS) ++AC_SUBST(DIALOG_LIBS) ++dnl ++dnl ============================================================ ++dnl command line options ++dnl ============================================================ ++_AC_DIALOG_VERBOSE([]) ++AC_ARG_WITH(dialog,dnl ++[ --with-dialog[=ARG] Build with dialog Library (default=]ifelse([$2],,yes,$2)[)],dnl ++,dnl ++with_dialog="ifelse([$2],,yes,$2)" ++)dnl ++AC_ARG_WITH(dialog-test,dnl ++[ --with-dialog-test Perform dialog Sanity Test (default=]ifelse([$3],,yes,$3)[)],dnl ++,dnl ++with_dialog_test="ifelse([$3],,yes,$3)" ++)dnl ++_AC_DIALOG_VERBOSE([+ Command Line Options:]) ++_AC_DIALOG_VERBOSE([ o --with-dialog=$with_dialog]) ++_AC_DIALOG_VERBOSE([ o --with-dialog-test=$with_dialog_test]) ++dnl ++dnl ============================================================ ++dnl configuration ++dnl ============================================================ ++if test ".$with_dialog" != .no; then ++ dialog_subdir=no ++ dialog_subdir_opts='' ++ case "$with_dialog" in ++ subdir:* ) ++ dialog_subdir=yes ++ changequote(, )dnl ++ dialog_subdir_opts=`echo $with_dialog | sed -e 's/^subdir:[^ ]*[ ]*//'` ++ with_dialog=`echo $with_dialog | sed -e 's/^subdir:\([^ ]*\).*$/\1/'` ++ changequote([, ])dnl ++ ;; ++ esac ++ dialog_version="" ++ dialog_location="" ++ dialog_type="" ++ dialog_cflags="" ++ dialog_ldflags="" ++ dialog_libs="" ++ if test ".$with_dialog" = .yes; then ++ # via config script in $PATH ++ changequote(, )dnl ++ dialog_version=`($DIALOG_CONFIG --version) 2>/dev/null |\ ++ sed -e 's/^.*\([0-9]\.[0-9]*[-][0-9]*\).*$/\1/'` ++ changequote([, ])dnl ++ if test ".$dialog_version" != .; then ++ dialog_location=`$DIALOG_CONFIG --prefix` ++ dialog_type='installed' ++ dialog_cflags=`$DIALOG_CONFIG --cflags` ++ dialog_ldflags=`$DIALOG_CONFIG --ldflags` ++ dialog_libs=`$DIALOG_CONFIG --libs` ++ fi ++ elif test -d "$with_dialog"; then ++ with_dialog=`echo $with_dialog | sed -e 's;/*$;;'` ++ dialog_found=no ++ # via config script under a specified directory ++ # (a standard installation, but not a source tree) ++ if test ".$dialog_found" = .no; then ++ for _dir in $with_dialog/bin $with_dialog; do ++ if test -f "$_dir/dialog-config"; then ++ test -f "$_dir/dialog-config.in" && continue # dialog-config in source tree! ++ changequote(, )dnl ++ dialog_version=`($_dir/dialog-config --version) 2>/dev/null |\ ++ sed -e 's/^.*\([0-9]\.[0-9]*[.][0-9]*\).*$/\1/'` ++ changequote([, ])dnl ++ if test ".$dialog_version" != .; then ++ dialog_location=`$_dir/dialog-config --prefix` ++ dialog_type="installed" ++ dialog_cflags=`$_dir/dialog-config --cflags` ++ dialog_ldflags=`$_dir/dialog-config --ldflags` ++ dialog_libs=`$_dir/dialog-config --libs` ++ dialog_found=yes ++ break ++ fi ++ fi ++ done ++ fi ++ fi ++ _AC_DIALOG_VERBOSE([+ Determined Location:]) ++ _AC_DIALOG_VERBOSE([ o path: $dialog_location]) ++ _AC_DIALOG_VERBOSE([ o type: $dialog_type]) ++ if test ".$dialog_version" = .; then ++ if test ".$with_dialog" != .yes; then ++ _AC_DIALOG_ERROR([dnl ++ Unable to locate dialog under $with_dialog. ++ Please specify the correct path to either a dialog installation tree ++ (use --with-dialog=DIR if you used --prefix=DIR for installing dialog in ++ the past).]) ++ else ++ _AC_DIALOG_ERROR([dnl ++ Unable to locate dialog in any system-wide location (see \$PATH). ++ Please specify the correct path to either a dialog installation tree ++ (use --with-dialog=DIR if you used --prefix=DIR for installing dialog in ++ the past, or set the DIALOG_CONFIG environment variable to the full path ++ to dialog-config).]) ++ fi ++ fi ++ dnl ======================================================== ++ dnl Check whether the found version is sufficiently new ++ dnl ======================================================== ++ _req_version="ifelse([$1],,1.0.0,$1)" ++ for _var in dialog_version _req_version; do ++ eval "_val=\"\$${_var}\"" ++ _major=`echo $_val | sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\([[.]]\)\([[0-9]]*\)/\1/'` ++ _minor=`echo $_val | sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\([[.]]\)\([[0-9]]*\)/\2/'` ++ _micro=`echo $_val | sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\([[.]]\)\([[0-9]]*\)/\4/'` ++ _hex=`echo dummy | awk '{ printf("%d%02d%02d", major, minor, micro); }' \ ++ "major=$_major" "minor=$_minor" "micro=$_micro"` ++ eval "${_var}_hex=\"\$_hex\"" ++ done ++ _AC_DIALOG_VERBOSE([+ Determined Versions:]) ++ _AC_DIALOG_VERBOSE([ o existing: $dialog_version -> 0x$dialog_version_hex]) ++ _AC_DIALOG_VERBOSE([ o required: $_req_version -> 0x$_req_version_hex]) ++ _ok=0 ++ if test ".$dialog_version_hex" != .; then ++ if test ".$_req_version_hex" != .; then ++ if test $dialog_version_hex -ge $_req_version_hex; then ++ _ok=1 ++ fi ++ fi ++ fi ++ if test ".$_ok" = .0; then ++ _AC_DIALOG_ERROR([dnl ++ Found dialog version $dialog_version, but required at least version $_req_version. ++ Upgrade dialog under $dialog_location to $_req_version or higher first, please.]) ++ fi ++ dnl ======================================================== ++ dnl Perform dialog Sanity Compile Check ++ dnl ======================================================== ++ if test ".$with_dialog_test" = .yes; then ++ _ac_save_CFLAGS="$CFLAGS" ++ _ac_save_LDFLAGS="$LDFLAGS" ++ _ac_save_LIBS="$LIBS" ++ CFLAGS="$CFLAGS $dialog_cflags" ++ LDFLAGS="$LDFLAGS $dialog_ldflags" ++ LIBS="$LIBS $dialog_libs" ++ _AC_DIALOG_VERBOSE([+ Test Build Environment:]) ++ _AC_DIALOG_VERBOSE([ o CFLAGS=\"$CFLAGS\"]) ++ _AC_DIALOG_VERBOSE([ o LDFLAGS=\"$LDFLAGS\"]) ++ _AC_DIALOG_VERBOSE([ o LIBS=\"$LIBS\"]) ++ cross_compile=no ++ define(_code1, [dnl ++ ++#include <stdlib.h> ++#include <stdio.h> ++#include <strings.h> /* index(3) */ ++ ++#include <dialog.h> ++#include <dlg_colors.h> ++#include <dlg_keys.h> ++ ++ ]) ++ define(_code2, [dnl ++ ++int main( void ) ++{ ++ int status = 0; ++ ++ bzero( (void *)&dialog_vars, sizeof(DIALOG_VARS) ); ++ ++ init_dialog(stdin, stdout); ++ ++ dialog_vars.colors = 1; ++ dialog_vars.backtitle = "\\Z7Test\\Zn \\Z1dialog\\Zn \\Z7Library\\Zn"; ++ dialog_vars.dlg_clear_screen = 1; ++ dialog_vars.sleep_secs = 1; ++ ++ ++ dlg_put_backtitle(); ++ ++ /************************************************* ++ Ruler: 68 characters + 2 spaces left and right: ++ ++ | ----handy-ruler----------------------------------------------------- | */ ++ status = dialog_msgbox( " \\Z4Dialog ==>\\Zn\\Z1libdialog\\Zn\\Z4<== [required]\\Zn ", ++ "\nPackage is installed and corect.\n", ++ 5, 72, 0 ); ++ ++ if( dialog_vars.sleep_secs ) ++ (void)napms(dialog_vars.sleep_secs * 1000); ++ ++ if( dialog_vars.dlg_clear_screen ) ++ { ++ dlg_clear(); ++ (void)refresh(); ++ } ++ end_dialog(); ++ ++ exit( 0 ); ++} ++ ]) ++ _AC_DIALOG_VERBOSE([+ Performing Sanity Checks:]) ++ _AC_DIALOG_VERBOSE([ o pre-processor test]) ++ AC_TRY_CPP(_code1, _ok=yes, _ok=no) ++ if test ".$_ok" != .yes; then ++ _AC_DIALOG_ERROR([dnl ++ Found dialog $dialog_version under $dialog_location, but ++ was unable to perform a sanity pre-processor check. This means ++ the dialog header dialog.h was not found. ++ We used the following build environment: ++ >> CPP="$CPP" ++ See config.log for possibly more details.]) ++ fi ++ _AC_DIALOG_VERBOSE([ o link check]) ++ AC_TRY_LINK(_code1, _code2, _ok=yes, _ok=no) ++ if test ".$_ok" != .yes; then ++ _AC_DIALOG_ERROR([dnl ++ Found dialog $dialog_version under $dialog_location, but ++ was unable to perform a sanity linker check. This means ++ the dialog library libdialog.a was not found. ++ We used the following build environment: ++ >> CC="$CC" ++ >> CFLAGS="$CFLAGS" ++ >> LDFLAGS="$LDFLAGS" ++ >> LIBS="$LIBS" ++ See config.log for possibly more details.]) ++ fi ++ _extendvars="ifelse([$4],,yes,$4)" ++ if test ".$_extendvars" != .yes; then ++ CFLAGS="$_ac_save_CFLAGS" ++ LDFLAGS="$_ac_save_LDFLAGS" ++ LIBS="$_ac_save_LIBS" ++ fi ++ else ++ _extendvars="ifelse([$4],,yes,$4)" ++ if test ".$_extendvars" = .yes; then ++ if test ".$dialog_subdir" = .yes; then ++ CFLAGS="$CFLAGS $dialog_cflags" ++ LDFLAGS="$LDFLAGS $dialog_ldflags" ++ LIBS="$LIBS $dialog_libs" ++ fi ++ fi ++ fi ++ DIALOG_CFLAGS="$dialog_cflags" ++ DIALOG_LDFLAGS="$dialog_ldflags" ++ DIALOG_LIBS="$dialog_libs" ++ AC_SUBST(DIALOG_CFLAGS) ++ AC_SUBST(DIALOG_LDFLAGS) ++ AC_SUBST(DIALOG_LIBS) ++ ++ AC_SUBST(HAVE_DIALOG, [1]) ++ ++ AC_CHECK_HEADERS(dialog.h dlg_colors.h dlg_keys.h) ++ ++ _AC_DIALOG_VERBOSE([+ Final Results:]) ++ _AC_DIALOG_VERBOSE([ o DIALOG_CFLAGS=\"$DIALOG_CFLAGS\"]) ++ _AC_DIALOG_VERBOSE([ o DIALOG_LDFLAGS=\"$DIALOG_LDFLAGS\"]) ++ _AC_DIALOG_VERBOSE([ o DIALOG_LIBS=\"$DIALOG_LIBS\"]) ++fi ++if test ".$with_dialog" != .no; then ++ AC_MSG_CHECKING(for libdialog) ++ AC_MSG_RESULT([version $dialog_version, $dialog_type under $dialog_location]) ++ ifelse([$5], , :, [$5]) ++else ++ AC_MSG_CHECKING(for libdialog) ++ AC_MSG_RESULT([no]) ++ ifelse([$6], , :, [$6]) ++fi ++]) ++ +diff -b --unified -Nr dialog-1.3-20190724-orig/fselect.c dialog-1.3-20190724/fselect.c +--- dialog-1.3-20190724-orig/fselect.c 2019-07-25 02:40:15.000000000 +0300 ++++ dialog-1.3-20190724/fselect.c 2019-07-26 14:14:01.063282363 +0300 +@@ -639,7 +639,7 @@ + dlg_print_size(height, width); + dlg_ctl_size(height, width); + +- dialog = dlg_new_window(height, width, ++ dialog = dlg_new_window(height + 1, width, + dlg_box_y_ordinate(height), + dlg_box_x_ordinate(width)); + dlg_register_window(dialog, "fselect", binding); +@@ -647,7 +647,7 @@ + + dlg_mouse_setbase(0, 0); + +- dlg_draw_box2(dialog, 0, 0, height, width, dialog_attr, border_attr, border2_attr); ++ dlg_draw_box2(dialog, 0, 0, height + 1, width, dialog_attr, border_attr, border2_attr); + dlg_draw_bottom_box2(dialog, border_attr, border2_attr, dialog_attr); + dlg_draw_title(dialog, title); + +@@ -656,7 +656,7 @@ + /* Draw the input field box */ + tbox_height = 1; + tbox_width = width - (4 * MARGIN + 2); +- tbox_y = height - (BTN_HIGH * 2) + MARGIN; ++ tbox_y = height - (BTN_HIGH * 2) + MARGIN + 1; + tbox_x = (width - tbox_width) / 2; + + w_text = derwin(dialog, tbox_height, tbox_width, tbox_y, tbox_x); +@@ -683,7 +683,7 @@ + else + dbox_width = (width - (6 * MARGIN + 2 * EXT_WIDE)) / 2; + dbox_height = height - MIN_HIGH; +- dbox_y = (2 * MARGIN + 1); ++ dbox_y = (2 * MARGIN + 2); + dbox_x = tbox_x; + + w_work = derwin(dialog, dbox_height, dbox_width, dbox_y, dbox_x); +@@ -743,7 +743,7 @@ + if (show_buttons) { + show_buttons = FALSE; + button = (state < 0) ? 0 : state; +- dlg_draw_buttons(dialog, height - 2, 0, buttons, button, FALSE, width); ++ dlg_draw_buttons(dialog, height - 1, 0, buttons, button, FALSE, width); + } + + if (first_trace) { +diff -b --unified -Nr dialog-1.3-20190724-orig/menubox.c dialog-1.3-20190724/menubox.c +--- dialog-1.3-20190724-orig/menubox.c 2019-07-25 02:42:20.000000000 +0300 ++++ dialog-1.3-20190724/menubox.c 2019-07-26 14:14:01.063282363 +0300 +@@ -48,7 +48,7 @@ + int item_no; + } ALL_DATA; + +-#define MIN_HIGH (1 + (5 * MARGIN)) ++#define MIN_HIGH 4 + + #define INPUT_ROWS 3 /* rows per inputmenu entry */ + diff --git a/doc/dialog/dialog-1.3-20190728.patch b/doc/dialog/dialog-1.3-20190728.patch new file mode 100644 index 0000000..4d39715 --- /dev/null +++ b/doc/dialog/dialog-1.3-20190728.patch @@ -0,0 +1,467 @@ +diff -b --unified -Nr dialog-1.3-20190728-orig/checklist.c dialog-1.3-20190728/checklist.c +--- dialog-1.3-20190728-orig/checklist.c 2019-07-25 01:17:14.000000000 +0300 ++++ dialog-1.3-20190728/checklist.c 2019-08-01 22:20:16.712332775 +0300 +@@ -29,7 +29,7 @@ + #include <dialog.h> + #include <dlg_keys.h> + +-#define MIN_HIGH (1 + (5 * MARGIN)) ++#define MIN_HIGH 4 + + typedef struct { + /* the outer-window */ +diff -b --unified -Nr dialog-1.3-20190728-orig/dialog-config.in dialog-1.3-20190728/dialog-config.in +--- dialog-1.3-20190728-orig/dialog-config.in 2019-07-29 02:02:53.000000000 +0300 ++++ dialog-1.3-20190728/dialog-config.in 2019-08-01 22:20:16.712332775 +0300 +@@ -89,7 +89,12 @@ + INCS="-I${prefix}/include" + fi + sed -e 's,^[ ]*,,' -e 's, [ ]*, ,g' -e 's,[ ]*$,,' <<-ENDECHO +- $INCS ++ @CFLAGS@ $INCS ++ENDECHO ++ ;; ++ --ldflags) ++ sed -e 's,^[ ]*,,' -e 's, [ ]*, ,g' -e 's,[ ]*$,,' <<-ENDECHO ++ -L@libdir@ + ENDECHO + ;; + --cflags-only-other) +@@ -97,12 +102,12 @@ + ;; + --libs) + sed -e 's,^[ ]*,,' -e 's, [ ]*, ,g' -e 's,[ ]*$,,' <<-ENDECHO +- -L${exec_prefix}/lib -l${THIS} @LIBS@ ++ -l${THIS} @LIBS@ + ENDECHO + ;; + --libs-only-L) + OPTS= +- for opt in @LIBS@ ++ for opt in -L@libdir@ -l${THIS} @LIBS@ + do + case "x$opt" in + x-L*) +@@ -114,7 +119,7 @@ + ;; + --libs-only-l) + OPTS= +- for opt in @LIBS@ ++ for opt in -L@libdir@ -l${THIS} @LIBS@ + do + case "x$opt" in + x-l*) +@@ -126,7 +131,7 @@ + ;; + --libs-only-other) + OPTS= +- for opt in @LIBS@ ++ for opt in -L@libdir@ -l${THIS} @LIBS@ + do + case "x$opt" in + x-[lL]*) +@@ -170,6 +175,7 @@ + --exec-prefix=ARG sets the executable-prefix of ${THIS} + + --cflags echos the C compiler flags needed to compile with ${THIS} ++ --ldflags echos the linker flags needed to link with ${THIS} + --libs echos the libraries needed to link with ${THIS} + + --libs-only-L echos -L linker options (search path) for ${THIS} +diff -b --unified -Nr dialog-1.3-20190728-orig/dialog.m4 dialog-1.3-20190728/dialog.m4 +--- dialog-1.3-20190728-orig/dialog.m4 1970-01-01 03:00:00.000000000 +0300 ++++ dialog-1.3-20190728/dialog.m4 2019-08-01 22:20:16.712332775 +0300 +@@ -0,0 +1,333 @@ ++dnl # ++dnl # /usr/share/aclocal/dialog.m4 ++dnl # ++dnl # Configure paths for dialog ++dnl # Andrew V.Kosteltsev ++ ++dnl ============================================================ ++dnl ++dnl Synopsis: ++dnl AC_CHECK_DIALOG([MIN-VERSION [, # minimum dialog version, e.g. 1.3-20190211 ++dnl DEFAULT-WITH-DIALOG [, # default value for --with-dialog option ++dnl DEFAULT-WITH-DIALOG-TEST [,# default value for --with-dialog-test option ++dnl EXTEND-VARS [, # whether CFLAGS/LDFLAGS/etc are extended ++dnl ACTION-IF-FOUND [, # action to perform if dialog was found ++dnl ACTION-IF-NOT-FOUND # action to perform if dialog was not found ++dnl ]]]]]]) ++dnl Examples: ++dnl AC_CHECK_DIALOG(1.3-20190211) ++dnl AC_CHECK_DIALOG(1.3-20190211,,,no,CFLAGS="$CFLAGS -DHAVE_DIALOG $DIALOG_CFLAGS") ++dnl AC_CHECK_DIALOG(1.3-20190211,yes,yes,yes,CFLAGS="$CFLAGS -DHAVE_DIALOG") ++dnl ++dnl ++dnl If you have to change prefix returned by dialog-config script or change ++dnl location of dialog-config, you may set environment variable DIALOG_CONFIG, ++dnl for example: ++dnl ++dnl # export DIALOG_CONFIG="dialog-config --prefix=/usr/local" ++dnl # export DIALOG_CONFIG="/usr/bin/dialog-config --prefix=/usr/local" ++dnl ++dnl ============================================================ ++dnl ++dnl ============================================================ ++dnl auxilliary macros ++dnl ============================================================ ++AC_DEFUN([_AC_DIALOG_ERROR], [dnl ++AC_MSG_RESULT([*FAILED*]) ++cat <<EOT | sed -e 's/^[[ ]]*/ | /' -e 's/>>/ /' 1>&2 ++$1 ++EOT ++exit 1 ++]) ++ ++AC_DEFUN([_AC_DIALOG_VERBOSE], [dnl ++if test ".$verbose" = .yes; then ++ AC_MSG_RESULT([ $1]) ++fi ++]) ++ ++dnl ============================================================ ++dnl the user macro ++dnl ============================================================ ++AC_DEFUN([AC_CHECK_DIALOG], [dnl ++dnl ++dnl ============================================================ ++dnl prerequisites ++dnl ============================================================ ++AC_REQUIRE([AC_PROG_CC])dnl ++AC_REQUIRE([AC_PROG_CPP])dnl ++dnl ++dnl ============================================================ ++dnl set DIALOG_CONFIG variable ++dnl ============================================================ ++if test -z "$DIALOG_CONFIG"; then ++ DIALOG_CONFIG='dialog-config' ++fi ++dnl ++DIALOG_CFLAGS='' ++DIALOG_LDFLAGS='' ++DIALOG_LIBS='' ++AC_SUBST(DIALOG_CFLAGS) ++AC_SUBST(DIALOG_LDFLAGS) ++AC_SUBST(DIALOG_LIBS) ++dnl ++dnl ============================================================ ++dnl command line options ++dnl ============================================================ ++_AC_DIALOG_VERBOSE([]) ++AC_ARG_WITH(dialog,dnl ++[ --with-dialog[=ARG] Build with dialog Library (default=]ifelse([$2],,yes,$2)[)],dnl ++,dnl ++with_dialog="ifelse([$2],,yes,$2)" ++)dnl ++AC_ARG_WITH(dialog-test,dnl ++[ --with-dialog-test Perform dialog Sanity Test (default=]ifelse([$3],,yes,$3)[)],dnl ++,dnl ++with_dialog_test="ifelse([$3],,yes,$3)" ++)dnl ++_AC_DIALOG_VERBOSE([+ Command Line Options:]) ++_AC_DIALOG_VERBOSE([ o --with-dialog=$with_dialog]) ++_AC_DIALOG_VERBOSE([ o --with-dialog-test=$with_dialog_test]) ++dnl ++dnl ============================================================ ++dnl configuration ++dnl ============================================================ ++if test ".$with_dialog" != .no; then ++ dialog_subdir=no ++ dialog_subdir_opts='' ++ case "$with_dialog" in ++ subdir:* ) ++ dialog_subdir=yes ++ changequote(, )dnl ++ dialog_subdir_opts=`echo $with_dialog | sed -e 's/^subdir:[^ ]*[ ]*//'` ++ with_dialog=`echo $with_dialog | sed -e 's/^subdir:\([^ ]*\).*$/\1/'` ++ changequote([, ])dnl ++ ;; ++ esac ++ dialog_version="" ++ dialog_location="" ++ dialog_type="" ++ dialog_cflags="" ++ dialog_ldflags="" ++ dialog_libs="" ++ if test ".$with_dialog" = .yes; then ++ # via config script in $PATH ++ changequote(, )dnl ++ dialog_version=`($DIALOG_CONFIG --version) 2>/dev/null |\ ++ sed -e 's/^.*\([0-9]\.[0-9]*[-][0-9]*\).*$/\1/'` ++ changequote([, ])dnl ++ if test ".$dialog_version" != .; then ++ dialog_location=`$DIALOG_CONFIG --prefix` ++ dialog_type='installed' ++ dialog_cflags=`$DIALOG_CONFIG --cflags` ++ dialog_ldflags=`$DIALOG_CONFIG --ldflags` ++ dialog_libs=`$DIALOG_CONFIG --libs` ++ fi ++ elif test -d "$with_dialog"; then ++ with_dialog=`echo $with_dialog | sed -e 's;/*$;;'` ++ dialog_found=no ++ # via config script under a specified directory ++ # (a standard installation, but not a source tree) ++ if test ".$dialog_found" = .no; then ++ for _dir in $with_dialog/bin $with_dialog; do ++ if test -f "$_dir/dialog-config"; then ++ test -f "$_dir/dialog-config.in" && continue # dialog-config in source tree! ++ changequote(, )dnl ++ dialog_version=`($_dir/dialog-config --version) 2>/dev/null |\ ++ sed -e 's/^.*\([0-9]\.[0-9]*[.][0-9]*\).*$/\1/'` ++ changequote([, ])dnl ++ if test ".$dialog_version" != .; then ++ dialog_location=`$_dir/dialog-config --prefix` ++ dialog_type="installed" ++ dialog_cflags=`$_dir/dialog-config --cflags` ++ dialog_ldflags=`$_dir/dialog-config --ldflags` ++ dialog_libs=`$_dir/dialog-config --libs` ++ dialog_found=yes ++ break ++ fi ++ fi ++ done ++ fi ++ fi ++ _AC_DIALOG_VERBOSE([+ Determined Location:]) ++ _AC_DIALOG_VERBOSE([ o path: $dialog_location]) ++ _AC_DIALOG_VERBOSE([ o type: $dialog_type]) ++ if test ".$dialog_version" = .; then ++ if test ".$with_dialog" != .yes; then ++ _AC_DIALOG_ERROR([dnl ++ Unable to locate dialog under $with_dialog. ++ Please specify the correct path to either a dialog installation tree ++ (use --with-dialog=DIR if you used --prefix=DIR for installing dialog in ++ the past).]) ++ else ++ _AC_DIALOG_ERROR([dnl ++ Unable to locate dialog in any system-wide location (see \$PATH). ++ Please specify the correct path to either a dialog installation tree ++ (use --with-dialog=DIR if you used --prefix=DIR for installing dialog in ++ the past, or set the DIALOG_CONFIG environment variable to the full path ++ to dialog-config).]) ++ fi ++ fi ++ dnl ======================================================== ++ dnl Check whether the found version is sufficiently new ++ dnl ======================================================== ++ _req_version="ifelse([$1],,1.0.0,$1)" ++ for _var in dialog_version _req_version; do ++ eval "_val=\"\$${_var}\"" ++ _major=`echo $_val | sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\([[.]]\)\([[0-9]]*\)/\1/'` ++ _minor=`echo $_val | sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\([[.]]\)\([[0-9]]*\)/\2/'` ++ _micro=`echo $_val | sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\([[.]]\)\([[0-9]]*\)/\4/'` ++ _hex=`echo dummy | awk '{ printf("%d%02d%02d", major, minor, micro); }' \ ++ "major=$_major" "minor=$_minor" "micro=$_micro"` ++ eval "${_var}_hex=\"\$_hex\"" ++ done ++ _AC_DIALOG_VERBOSE([+ Determined Versions:]) ++ _AC_DIALOG_VERBOSE([ o existing: $dialog_version -> 0x$dialog_version_hex]) ++ _AC_DIALOG_VERBOSE([ o required: $_req_version -> 0x$_req_version_hex]) ++ _ok=0 ++ if test ".$dialog_version_hex" != .; then ++ if test ".$_req_version_hex" != .; then ++ if test $dialog_version_hex -ge $_req_version_hex; then ++ _ok=1 ++ fi ++ fi ++ fi ++ if test ".$_ok" = .0; then ++ _AC_DIALOG_ERROR([dnl ++ Found dialog version $dialog_version, but required at least version $_req_version. ++ Upgrade dialog under $dialog_location to $_req_version or higher first, please.]) ++ fi ++ dnl ======================================================== ++ dnl Perform dialog Sanity Compile Check ++ dnl ======================================================== ++ if test ".$with_dialog_test" = .yes; then ++ _ac_save_CFLAGS="$CFLAGS" ++ _ac_save_LDFLAGS="$LDFLAGS" ++ _ac_save_LIBS="$LIBS" ++ CFLAGS="$CFLAGS $dialog_cflags" ++ LDFLAGS="$LDFLAGS $dialog_ldflags" ++ LIBS="$LIBS $dialog_libs" ++ _AC_DIALOG_VERBOSE([+ Test Build Environment:]) ++ _AC_DIALOG_VERBOSE([ o CFLAGS=\"$CFLAGS\"]) ++ _AC_DIALOG_VERBOSE([ o LDFLAGS=\"$LDFLAGS\"]) ++ _AC_DIALOG_VERBOSE([ o LIBS=\"$LIBS\"]) ++ cross_compile=no ++ define(_code1, [dnl ++ ++#include <stdlib.h> ++#include <stdio.h> ++#include <strings.h> /* index(3) */ ++ ++#include <dialog.h> ++#include <dlg_colors.h> ++#include <dlg_keys.h> ++ ++ ]) ++ define(_code2, [dnl ++ ++int main( void ) ++{ ++ int status = 0; ++ ++ bzero( (void *)&dialog_vars, sizeof(DIALOG_VARS) ); ++ ++ init_dialog(stdin, stdout); ++ ++ dialog_vars.colors = 1; ++ dialog_vars.backtitle = "\\Z7Test\\Zn \\Z1dialog\\Zn \\Z7Library\\Zn"; ++ dialog_vars.dlg_clear_screen = 1; ++ dialog_vars.sleep_secs = 1; ++ ++ ++ dlg_put_backtitle(); ++ ++ /************************************************* ++ Ruler: 68 characters + 2 spaces left and right: ++ ++ | ----handy-ruler----------------------------------------------------- | */ ++ status = dialog_msgbox( " \\Z4Dialog ==>\\Zn\\Z1libdialog\\Zn\\Z4<== [required]\\Zn ", ++ "\nPackage is installed and corect.\n", ++ 5, 72, 0 ); ++ ++ if( dialog_vars.sleep_secs ) ++ (void)napms(dialog_vars.sleep_secs * 1000); ++ ++ if( dialog_vars.dlg_clear_screen ) ++ { ++ dlg_clear(); ++ (void)refresh(); ++ } ++ end_dialog(); ++ ++ exit( 0 ); ++} ++ ]) ++ _AC_DIALOG_VERBOSE([+ Performing Sanity Checks:]) ++ _AC_DIALOG_VERBOSE([ o pre-processor test]) ++ AC_TRY_CPP(_code1, _ok=yes, _ok=no) ++ if test ".$_ok" != .yes; then ++ _AC_DIALOG_ERROR([dnl ++ Found dialog $dialog_version under $dialog_location, but ++ was unable to perform a sanity pre-processor check. This means ++ the dialog header dialog.h was not found. ++ We used the following build environment: ++ >> CPP="$CPP" ++ See config.log for possibly more details.]) ++ fi ++ _AC_DIALOG_VERBOSE([ o link check]) ++ AC_TRY_LINK(_code1, _code2, _ok=yes, _ok=no) ++ if test ".$_ok" != .yes; then ++ _AC_DIALOG_ERROR([dnl ++ Found dialog $dialog_version under $dialog_location, but ++ was unable to perform a sanity linker check. This means ++ the dialog library libdialog.a was not found. ++ We used the following build environment: ++ >> CC="$CC" ++ >> CFLAGS="$CFLAGS" ++ >> LDFLAGS="$LDFLAGS" ++ >> LIBS="$LIBS" ++ See config.log for possibly more details.]) ++ fi ++ _extendvars="ifelse([$4],,yes,$4)" ++ if test ".$_extendvars" != .yes; then ++ CFLAGS="$_ac_save_CFLAGS" ++ LDFLAGS="$_ac_save_LDFLAGS" ++ LIBS="$_ac_save_LIBS" ++ fi ++ else ++ _extendvars="ifelse([$4],,yes,$4)" ++ if test ".$_extendvars" = .yes; then ++ if test ".$dialog_subdir" = .yes; then ++ CFLAGS="$CFLAGS $dialog_cflags" ++ LDFLAGS="$LDFLAGS $dialog_ldflags" ++ LIBS="$LIBS $dialog_libs" ++ fi ++ fi ++ fi ++ DIALOG_CFLAGS="$dialog_cflags" ++ DIALOG_LDFLAGS="$dialog_ldflags" ++ DIALOG_LIBS="$dialog_libs" ++ AC_SUBST(DIALOG_CFLAGS) ++ AC_SUBST(DIALOG_LDFLAGS) ++ AC_SUBST(DIALOG_LIBS) ++ ++ AC_SUBST(HAVE_DIALOG, [1]) ++ ++ AC_CHECK_HEADERS(dialog.h dlg_colors.h dlg_keys.h) ++ ++ _AC_DIALOG_VERBOSE([+ Final Results:]) ++ _AC_DIALOG_VERBOSE([ o DIALOG_CFLAGS=\"$DIALOG_CFLAGS\"]) ++ _AC_DIALOG_VERBOSE([ o DIALOG_LDFLAGS=\"$DIALOG_LDFLAGS\"]) ++ _AC_DIALOG_VERBOSE([ o DIALOG_LIBS=\"$DIALOG_LIBS\"]) ++fi ++if test ".$with_dialog" != .no; then ++ AC_MSG_CHECKING(for libdialog) ++ AC_MSG_RESULT([version $dialog_version, $dialog_type under $dialog_location]) ++ ifelse([$5], , :, [$5]) ++else ++ AC_MSG_CHECKING(for libdialog) ++ AC_MSG_RESULT([no]) ++ ifelse([$6], , :, [$6]) ++fi ++]) ++ +diff -b --unified -Nr dialog-1.3-20190728-orig/fselect.c dialog-1.3-20190728/fselect.c +--- dialog-1.3-20190728-orig/fselect.c 2019-07-25 02:40:15.000000000 +0300 ++++ dialog-1.3-20190728/fselect.c 2019-08-01 22:20:16.712332775 +0300 +@@ -639,7 +639,7 @@ + dlg_print_size(height, width); + dlg_ctl_size(height, width); + +- dialog = dlg_new_window(height, width, ++ dialog = dlg_new_window(height + 1, width, + dlg_box_y_ordinate(height), + dlg_box_x_ordinate(width)); + dlg_register_window(dialog, "fselect", binding); +@@ -647,7 +647,7 @@ + + dlg_mouse_setbase(0, 0); + +- dlg_draw_box2(dialog, 0, 0, height, width, dialog_attr, border_attr, border2_attr); ++ dlg_draw_box2(dialog, 0, 0, height + 1, width, dialog_attr, border_attr, border2_attr); + dlg_draw_bottom_box2(dialog, border_attr, border2_attr, dialog_attr); + dlg_draw_title(dialog, title); + +@@ -656,7 +656,7 @@ + /* Draw the input field box */ + tbox_height = 1; + tbox_width = width - (4 * MARGIN + 2); +- tbox_y = height - (BTN_HIGH * 2) + MARGIN; ++ tbox_y = height - (BTN_HIGH * 2) + MARGIN + 1; + tbox_x = (width - tbox_width) / 2; + + w_text = derwin(dialog, tbox_height, tbox_width, tbox_y, tbox_x); +@@ -683,7 +683,7 @@ + else + dbox_width = (width - (6 * MARGIN + 2 * EXT_WIDE)) / 2; + dbox_height = height - MIN_HIGH; +- dbox_y = (2 * MARGIN + 1); ++ dbox_y = (2 * MARGIN + 2); + dbox_x = tbox_x; + + w_work = derwin(dialog, dbox_height, dbox_width, dbox_y, dbox_x); +@@ -743,7 +743,7 @@ + if (show_buttons) { + show_buttons = FALSE; + button = (state < 0) ? 0 : state; +- dlg_draw_buttons(dialog, height - 2, 0, buttons, button, FALSE, width); ++ dlg_draw_buttons(dialog, height - 1, 0, buttons, button, FALSE, width); + } + + if (first_trace) { +diff -b --unified -Nr dialog-1.3-20190728-orig/menubox.c dialog-1.3-20190728/menubox.c +--- dialog-1.3-20190728-orig/menubox.c 2019-07-25 02:42:20.000000000 +0300 ++++ dialog-1.3-20190728/menubox.c 2019-08-01 22:20:16.712332775 +0300 +@@ -48,7 +48,7 @@ + int item_no; + } ALL_DATA; + +-#define MIN_HIGH (1 + (5 * MARGIN)) ++#define MIN_HIGH 4 + + #define INPUT_ROWS 3 /* rows per inputmenu entry */ + diff --git a/doc/dialog/dialog-1.3-20190808.patch b/doc/dialog/dialog-1.3-20190808.patch new file mode 100644 index 0000000..6c27a3b --- /dev/null +++ b/doc/dialog/dialog-1.3-20190808.patch @@ -0,0 +1,460 @@ +diff --unified -Nr dialog-1.3-20190808-orig/checklist.c dialog-1.3-20190808/checklist.c +--- dialog-1.3-20190808-orig/checklist.c 2019-08-05 12:14:59.000000000 +0300 ++++ dialog-1.3-20190808/checklist.c 2019-08-15 19:43:02.164788537 +0300 +@@ -29,7 +29,7 @@ + #include <dialog.h> + #include <dlg_keys.h> + +-#define MIN_HIGH (1 + (5 * MARGIN)) ++#define MIN_HIGH 4 + + typedef struct { + /* the outer-window */ +diff --unified -Nr dialog-1.3-20190808-orig/dialog-config.in dialog-1.3-20190808/dialog-config.in +--- dialog-1.3-20190808-orig/dialog-config.in 2019-08-02 03:20:15.000000000 +0300 ++++ dialog-1.3-20190808/dialog-config.in 2019-08-15 19:43:02.164788537 +0300 +@@ -60,7 +60,7 @@ + [ -z "$includedir" ] && includedir="${prefix}/include" + + eval LDFLAGS='"@LDFLAGS@"' +- [ -z "$LDFLAGS" ] && LDFLAGS="-L${exec_prefix}/lib" ++ [ -z "$LDFLAGS" ] && LDFLAGS="-L${libdir}" + + eval LIBS='"@LIBS@"' + LIBS="-l${THIS} $LIBS" +@@ -93,10 +93,7 @@ + ;; + # compile/link + --cflags|--cflags-only-I) +- INCS= +- if test "$includedir" != /usr/include ; then +- INCS="-I$includedir" +- fi ++ INCS="-I$includedir" + sed -e 's,^[ ]*,,' -e 's, [ ]*, ,g' -e 's,[ ]*$,,' <<-ENDECHO + $INCS + ENDECHO +@@ -110,6 +107,18 @@ + $LIBS + ENDECHO + ;; ++ --ldflags) ++ OPTS= ++ for opt in $LDFLAGS $LIBS ++ do ++ case "x$opt" in ++ x-[^l]*) ++ OPTS="$OPTS $opt" ++ ;; ++ esac ++ done ++ printf "%s\n" "$OPTS" ++ ;; + --libs-only-L) + OPTS= + for opt in $LDFLAGS $LIBS +@@ -182,6 +191,7 @@ + --cflags echos the C compiler flags needed to compile with ${THIS} + --libs echos the libraries needed to link with ${THIS} + ++ --ldflags echos the linker flags needed to link with ${THIS} + --libs-only-L echos -L linker options (search path) for ${THIS} + --libs-only-l echos -l linker options (libraries) for ${THIS} + --libs-only-other echos linker options other than -L/-l +diff --unified -Nr dialog-1.3-20190808-orig/dialog.m4 dialog-1.3-20190808/dialog.m4 +--- dialog-1.3-20190808-orig/dialog.m4 1970-01-01 03:00:00.000000000 +0300 ++++ dialog-1.3-20190808/dialog.m4 2019-08-15 19:43:02.164788537 +0300 +@@ -0,0 +1,333 @@ ++dnl # ++dnl # /usr/share/aclocal/dialog.m4 ++dnl # ++dnl # Configure paths for dialog ++dnl # Andrew V.Kosteltsev ++ ++dnl ============================================================ ++dnl ++dnl Synopsis: ++dnl AC_CHECK_DIALOG([MIN-VERSION [, # minimum dialog version, e.g. 1.3-20190211 ++dnl DEFAULT-WITH-DIALOG [, # default value for --with-dialog option ++dnl DEFAULT-WITH-DIALOG-TEST [,# default value for --with-dialog-test option ++dnl EXTEND-VARS [, # whether CFLAGS/LDFLAGS/etc are extended ++dnl ACTION-IF-FOUND [, # action to perform if dialog was found ++dnl ACTION-IF-NOT-FOUND # action to perform if dialog was not found ++dnl ]]]]]]) ++dnl Examples: ++dnl AC_CHECK_DIALOG(1.3-20190211) ++dnl AC_CHECK_DIALOG(1.3-20190211,,,no,CFLAGS="$CFLAGS -DHAVE_DIALOG $DIALOG_CFLAGS") ++dnl AC_CHECK_DIALOG(1.3-20190211,yes,yes,yes,CFLAGS="$CFLAGS -DHAVE_DIALOG") ++dnl ++dnl ++dnl If you have to change prefix returned by dialog-config script or change ++dnl location of dialog-config, you may set environment variable DIALOG_CONFIG, ++dnl for example: ++dnl ++dnl # export DIALOG_CONFIG="dialog-config --prefix=/usr/local" ++dnl # export DIALOG_CONFIG="/usr/bin/dialog-config --prefix=/usr/local" ++dnl ++dnl ============================================================ ++dnl ++dnl ============================================================ ++dnl auxilliary macros ++dnl ============================================================ ++AC_DEFUN([_AC_DIALOG_ERROR], [dnl ++AC_MSG_RESULT([*FAILED*]) ++cat <<EOT | sed -e 's/^[[ ]]*/ | /' -e 's/>>/ /' 1>&2 ++$1 ++EOT ++exit 1 ++]) ++ ++AC_DEFUN([_AC_DIALOG_VERBOSE], [dnl ++if test ".$verbose" = .yes; then ++ AC_MSG_RESULT([ $1]) ++fi ++]) ++ ++dnl ============================================================ ++dnl the user macro ++dnl ============================================================ ++AC_DEFUN([AC_CHECK_DIALOG], [dnl ++dnl ++dnl ============================================================ ++dnl prerequisites ++dnl ============================================================ ++AC_REQUIRE([AC_PROG_CC])dnl ++AC_REQUIRE([AC_PROG_CPP])dnl ++dnl ++dnl ============================================================ ++dnl set DIALOG_CONFIG variable ++dnl ============================================================ ++if test -z "$DIALOG_CONFIG"; then ++ DIALOG_CONFIG='dialog-config' ++fi ++dnl ++DIALOG_CFLAGS='' ++DIALOG_LDFLAGS='' ++DIALOG_LIBS='' ++AC_SUBST(DIALOG_CFLAGS) ++AC_SUBST(DIALOG_LDFLAGS) ++AC_SUBST(DIALOG_LIBS) ++dnl ++dnl ============================================================ ++dnl command line options ++dnl ============================================================ ++_AC_DIALOG_VERBOSE([]) ++AC_ARG_WITH(dialog,dnl ++[ --with-dialog[=ARG] Build with dialog Library (default=]ifelse([$2],,yes,$2)[)],dnl ++,dnl ++with_dialog="ifelse([$2],,yes,$2)" ++)dnl ++AC_ARG_WITH(dialog-test,dnl ++[ --with-dialog-test Perform dialog Sanity Test (default=]ifelse([$3],,yes,$3)[)],dnl ++,dnl ++with_dialog_test="ifelse([$3],,yes,$3)" ++)dnl ++_AC_DIALOG_VERBOSE([+ Command Line Options:]) ++_AC_DIALOG_VERBOSE([ o --with-dialog=$with_dialog]) ++_AC_DIALOG_VERBOSE([ o --with-dialog-test=$with_dialog_test]) ++dnl ++dnl ============================================================ ++dnl configuration ++dnl ============================================================ ++if test ".$with_dialog" != .no; then ++ dialog_subdir=no ++ dialog_subdir_opts='' ++ case "$with_dialog" in ++ subdir:* ) ++ dialog_subdir=yes ++ changequote(, )dnl ++ dialog_subdir_opts=`echo $with_dialog | sed -e 's/^subdir:[^ ]*[ ]*//'` ++ with_dialog=`echo $with_dialog | sed -e 's/^subdir:\([^ ]*\).*$/\1/'` ++ changequote([, ])dnl ++ ;; ++ esac ++ dialog_version="" ++ dialog_location="" ++ dialog_type="" ++ dialog_cflags="" ++ dialog_ldflags="" ++ dialog_libs="" ++ if test ".$with_dialog" = .yes; then ++ # via config script in $PATH ++ changequote(, )dnl ++ dialog_version=`($DIALOG_CONFIG --version) 2>/dev/null |\ ++ sed -e 's/^.*\([0-9]\.[0-9]*[-][0-9]*\).*$/\1/'` ++ changequote([, ])dnl ++ if test ".$dialog_version" != .; then ++ dialog_location=`$DIALOG_CONFIG --prefix` ++ dialog_type='installed' ++ dialog_cflags=`$DIALOG_CONFIG --cflags` ++ dialog_ldflags=`$DIALOG_CONFIG --ldflags` ++ dialog_libs=`$DIALOG_CONFIG --libs` ++ fi ++ elif test -d "$with_dialog"; then ++ with_dialog=`echo $with_dialog | sed -e 's;/*$;;'` ++ dialog_found=no ++ # via config script under a specified directory ++ # (a standard installation, but not a source tree) ++ if test ".$dialog_found" = .no; then ++ for _dir in $with_dialog/bin $with_dialog; do ++ if test -f "$_dir/dialog-config"; then ++ test -f "$_dir/dialog-config.in" && continue # dialog-config in source tree! ++ changequote(, )dnl ++ dialog_version=`($_dir/dialog-config --version) 2>/dev/null |\ ++ sed -e 's/^.*\([0-9]\.[0-9]*[.][0-9]*\).*$/\1/'` ++ changequote([, ])dnl ++ if test ".$dialog_version" != .; then ++ dialog_location=`$_dir/dialog-config --prefix` ++ dialog_type="installed" ++ dialog_cflags=`$_dir/dialog-config --cflags` ++ dialog_ldflags=`$_dir/dialog-config --ldflags` ++ dialog_libs=`$_dir/dialog-config --libs` ++ dialog_found=yes ++ break ++ fi ++ fi ++ done ++ fi ++ fi ++ _AC_DIALOG_VERBOSE([+ Determined Location:]) ++ _AC_DIALOG_VERBOSE([ o path: $dialog_location]) ++ _AC_DIALOG_VERBOSE([ o type: $dialog_type]) ++ if test ".$dialog_version" = .; then ++ if test ".$with_dialog" != .yes; then ++ _AC_DIALOG_ERROR([dnl ++ Unable to locate dialog under $with_dialog. ++ Please specify the correct path to either a dialog installation tree ++ (use --with-dialog=DIR if you used --prefix=DIR for installing dialog in ++ the past).]) ++ else ++ _AC_DIALOG_ERROR([dnl ++ Unable to locate dialog in any system-wide location (see \$PATH). ++ Please specify the correct path to either a dialog installation tree ++ (use --with-dialog=DIR if you used --prefix=DIR for installing dialog in ++ the past, or set the DIALOG_CONFIG environment variable to the full path ++ to dialog-config).]) ++ fi ++ fi ++ dnl ======================================================== ++ dnl Check whether the found version is sufficiently new ++ dnl ======================================================== ++ _req_version="ifelse([$1],,1.0.0,$1)" ++ for _var in dialog_version _req_version; do ++ eval "_val=\"\$${_var}\"" ++ _major=`echo $_val | sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\([[.]]\)\([[0-9]]*\)/\1/'` ++ _minor=`echo $_val | sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\([[.]]\)\([[0-9]]*\)/\2/'` ++ _micro=`echo $_val | sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\([[.]]\)\([[0-9]]*\)/\4/'` ++ _hex=`echo dummy | awk '{ printf("%d%02d%02d", major, minor, micro); }' \ ++ "major=$_major" "minor=$_minor" "micro=$_micro"` ++ eval "${_var}_hex=\"\$_hex\"" ++ done ++ _AC_DIALOG_VERBOSE([+ Determined Versions:]) ++ _AC_DIALOG_VERBOSE([ o existing: $dialog_version -> 0x$dialog_version_hex]) ++ _AC_DIALOG_VERBOSE([ o required: $_req_version -> 0x$_req_version_hex]) ++ _ok=0 ++ if test ".$dialog_version_hex" != .; then ++ if test ".$_req_version_hex" != .; then ++ if test $dialog_version_hex -ge $_req_version_hex; then ++ _ok=1 ++ fi ++ fi ++ fi ++ if test ".$_ok" = .0; then ++ _AC_DIALOG_ERROR([dnl ++ Found dialog version $dialog_version, but required at least version $_req_version. ++ Upgrade dialog under $dialog_location to $_req_version or higher first, please.]) ++ fi ++ dnl ======================================================== ++ dnl Perform dialog Sanity Compile Check ++ dnl ======================================================== ++ if test ".$with_dialog_test" = .yes; then ++ _ac_save_CFLAGS="$CFLAGS" ++ _ac_save_LDFLAGS="$LDFLAGS" ++ _ac_save_LIBS="$LIBS" ++ CFLAGS="$CFLAGS $dialog_cflags" ++ LDFLAGS="$LDFLAGS $dialog_ldflags" ++ LIBS="$LIBS $dialog_libs" ++ _AC_DIALOG_VERBOSE([+ Test Build Environment:]) ++ _AC_DIALOG_VERBOSE([ o CFLAGS=\"$CFLAGS\"]) ++ _AC_DIALOG_VERBOSE([ o LDFLAGS=\"$LDFLAGS\"]) ++ _AC_DIALOG_VERBOSE([ o LIBS=\"$LIBS\"]) ++ cross_compile=no ++ define(_code1, [dnl ++ ++#include <stdlib.h> ++#include <stdio.h> ++#include <strings.h> /* index(3) */ ++ ++#include <dialog.h> ++#include <dlg_colors.h> ++#include <dlg_keys.h> ++ ++ ]) ++ define(_code2, [dnl ++ ++int main( void ) ++{ ++ int status = 0; ++ ++ bzero( (void *)&dialog_vars, sizeof(DIALOG_VARS) ); ++ ++ init_dialog(stdin, stdout); ++ ++ dialog_vars.colors = 1; ++ dialog_vars.backtitle = "\\Z7Test\\Zn \\Z1dialog\\Zn \\Z7Library\\Zn"; ++ dialog_vars.dlg_clear_screen = 1; ++ dialog_vars.sleep_secs = 1; ++ ++ ++ dlg_put_backtitle(); ++ ++ /************************************************* ++ Ruler: 68 characters + 2 spaces left and right: ++ ++ | ----handy-ruler----------------------------------------------------- | */ ++ status = dialog_msgbox( " \\Z4Dialog ==>\\Zn\\Z1libdialog\\Zn\\Z4<== [required]\\Zn ", ++ "\nPackage is installed and corect.\n", ++ 5, 72, 0 ); ++ ++ if( dialog_vars.sleep_secs ) ++ (void)napms(dialog_vars.sleep_secs * 1000); ++ ++ if( dialog_vars.dlg_clear_screen ) ++ { ++ dlg_clear(); ++ (void)refresh(); ++ } ++ end_dialog(); ++ ++ exit( 0 ); ++} ++ ]) ++ _AC_DIALOG_VERBOSE([+ Performing Sanity Checks:]) ++ _AC_DIALOG_VERBOSE([ o pre-processor test]) ++ AC_TRY_CPP(_code1, _ok=yes, _ok=no) ++ if test ".$_ok" != .yes; then ++ _AC_DIALOG_ERROR([dnl ++ Found dialog $dialog_version under $dialog_location, but ++ was unable to perform a sanity pre-processor check. This means ++ the dialog header dialog.h was not found. ++ We used the following build environment: ++ >> CPP="$CPP" ++ See config.log for possibly more details.]) ++ fi ++ _AC_DIALOG_VERBOSE([ o link check]) ++ AC_TRY_LINK(_code1, _code2, _ok=yes, _ok=no) ++ if test ".$_ok" != .yes; then ++ _AC_DIALOG_ERROR([dnl ++ Found dialog $dialog_version under $dialog_location, but ++ was unable to perform a sanity linker check. This means ++ the dialog library libdialog.a was not found. ++ We used the following build environment: ++ >> CC="$CC" ++ >> CFLAGS="$CFLAGS" ++ >> LDFLAGS="$LDFLAGS" ++ >> LIBS="$LIBS" ++ See config.log for possibly more details.]) ++ fi ++ _extendvars="ifelse([$4],,yes,$4)" ++ if test ".$_extendvars" != .yes; then ++ CFLAGS="$_ac_save_CFLAGS" ++ LDFLAGS="$_ac_save_LDFLAGS" ++ LIBS="$_ac_save_LIBS" ++ fi ++ else ++ _extendvars="ifelse([$4],,yes,$4)" ++ if test ".$_extendvars" = .yes; then ++ if test ".$dialog_subdir" = .yes; then ++ CFLAGS="$CFLAGS $dialog_cflags" ++ LDFLAGS="$LDFLAGS $dialog_ldflags" ++ LIBS="$LIBS $dialog_libs" ++ fi ++ fi ++ fi ++ DIALOG_CFLAGS="$dialog_cflags" ++ DIALOG_LDFLAGS="$dialog_ldflags" ++ DIALOG_LIBS="$dialog_libs" ++ AC_SUBST(DIALOG_CFLAGS) ++ AC_SUBST(DIALOG_LDFLAGS) ++ AC_SUBST(DIALOG_LIBS) ++ ++ AC_SUBST(HAVE_DIALOG, [1]) ++ ++ AC_CHECK_HEADERS(dialog.h dlg_colors.h dlg_keys.h) ++ ++ _AC_DIALOG_VERBOSE([+ Final Results:]) ++ _AC_DIALOG_VERBOSE([ o DIALOG_CFLAGS=\"$DIALOG_CFLAGS\"]) ++ _AC_DIALOG_VERBOSE([ o DIALOG_LDFLAGS=\"$DIALOG_LDFLAGS\"]) ++ _AC_DIALOG_VERBOSE([ o DIALOG_LIBS=\"$DIALOG_LIBS\"]) ++fi ++if test ".$with_dialog" != .no; then ++ AC_MSG_CHECKING(for libdialog) ++ AC_MSG_RESULT([version $dialog_version, $dialog_type under $dialog_location]) ++ ifelse([$5], , :, [$5]) ++else ++ AC_MSG_CHECKING(for libdialog) ++ AC_MSG_RESULT([no]) ++ ifelse([$6], , :, [$6]) ++fi ++]) ++ +diff --unified -Nr dialog-1.3-20190808-orig/fselect.c dialog-1.3-20190808/fselect.c +--- dialog-1.3-20190808-orig/fselect.c 2019-08-09 00:28:56.000000000 +0300 ++++ dialog-1.3-20190808/fselect.c 2019-08-15 19:43:02.164788537 +0300 +@@ -639,7 +639,7 @@ + dlg_print_size(height, width); + dlg_ctl_size(height, width); + +- dialog = dlg_new_window(height, width, ++ dialog = dlg_new_window(height + 1, width, + dlg_box_y_ordinate(height), + dlg_box_x_ordinate(width)); + dlg_register_window(dialog, "fselect", binding); +@@ -647,7 +647,7 @@ + + dlg_mouse_setbase(0, 0); + +- dlg_draw_box2(dialog, 0, 0, height, width, dialog_attr, border_attr, border2_attr); ++ dlg_draw_box2(dialog, 0, 0, height + 1, width, dialog_attr, border_attr, border2_attr); + dlg_draw_bottom_box2(dialog, border_attr, border2_attr, dialog_attr); + dlg_draw_title(dialog, title); + +@@ -656,7 +656,7 @@ + /* Draw the input field box */ + tbox_height = 1; + tbox_width = width - (4 * MARGIN + 2); +- tbox_y = height - (BTN_HIGH * 2) + MARGIN; ++ tbox_y = height - (BTN_HIGH * 2) + MARGIN + 1; + tbox_x = (width - tbox_width) / 2; + + w_text = derwin(dialog, tbox_height, tbox_width, tbox_y, tbox_x); +@@ -683,7 +683,7 @@ + else + dbox_width = (width - (6 * MARGIN + 2 * EXT_WIDE)) / 2; + dbox_height = height - MIN_HIGH; +- dbox_y = (2 * MARGIN + 1); ++ dbox_y = (2 * MARGIN + 2); + dbox_x = tbox_x; + + w_work = derwin(dialog, dbox_height, dbox_width, dbox_y, dbox_x); +@@ -743,7 +743,7 @@ + if (show_buttons) { + show_buttons = FALSE; + button = (state < 0) ? 0 : state; +- dlg_draw_buttons(dialog, height - 2, 0, buttons, button, FALSE, width); ++ dlg_draw_buttons(dialog, height - 1, 0, buttons, button, FALSE, width); + } + + if (first_trace) { +diff --unified -Nr dialog-1.3-20190808-orig/menubox.c dialog-1.3-20190808/menubox.c +--- dialog-1.3-20190808-orig/menubox.c 2019-08-09 00:00:23.000000000 +0300 ++++ dialog-1.3-20190808/menubox.c 2019-08-15 19:43:02.164788537 +0300 +@@ -48,7 +48,7 @@ + int item_no; + } ALL_DATA; + +-#define MIN_HIGH (1 + (5 * MARGIN)) ++#define MIN_HIGH 4 + + #define INPUT_ROWS 3 /* rows per inputmenu entry */ + diff --git a/doc/dialog/dialog-1.3-20201126.patch b/doc/dialog/dialog-1.3-20201126.patch new file mode 100644 index 0000000..6832773 --- /dev/null +++ b/doc/dialog/dialog-1.3-20201126.patch @@ -0,0 +1,462 @@ +diff --unified -Nr dialog-1.3-20201126-orig/checklist.c dialog-1.3-20201126/checklist.c +--- dialog-1.3-20201126-orig/checklist.c 2020-11-23 03:37:47.000000000 +0300 ++++ dialog-1.3-20201126/checklist.c 2020-12-18 03:18:05.742635782 +0300 +@@ -29,7 +29,7 @@ + #include <dialog.h> + #include <dlg_keys.h> + +-#define MIN_HIGH (1 + (5 * MARGIN)) ++#define MIN_HIGH 4 + + typedef struct { + /* the outer-window */ +diff --unified -Nr dialog-1.3-20201126-orig/dialog-config.in dialog-1.3-20201126/dialog-config.in +--- dialog-1.3-20201126-orig/dialog-config.in 2019-09-26 03:50:46.000000000 +0300 ++++ dialog-1.3-20201126/dialog-config.in 2020-12-18 03:18:05.742635782 +0300 +@@ -79,7 +79,7 @@ + [ -n "$LFLAGS" ] && LDFLAGS=" $LFDLAGS" + LDFLAGS="-L${libdir}$LDFLAGS" + fi +- [ -z "$LDFLAGS" ] && LDFLAGS="-L${exec_prefix}/lib" ++ [ -z "$LDFLAGS" ] && LDFLAGS="-L${libdir}" + + # Ignore -L options which do not correspond to an actual directory, + # or which are standard library directories (i.e., the linker is +@@ -133,10 +133,7 @@ + ;; + # compile/link + --cflags|--cflags-only-I) +- INCS= +- if test "$includedir" != /usr/include ; then +- INCS="-I$includedir" +- fi ++ INCS="-I$includedir" + sed -e 's,^[ ]*,,' -e 's, [ ]*, ,g' -e 's,[ ]*$,,' <<-ENDECHO + $INCS + ENDECHO +@@ -145,6 +142,18 @@ + # no -D/-U options should be needed + echo + ;; ++ --ldflags) ++ OPTS= ++ for opt in $LDFLAGS $LIBS ++ do ++ case "x$opt" in ++ x-[^l]*) ++ OPTS="$OPTS $opt" ++ ;; ++ esac ++ done ++ printf "%s\n" "$OPTS" ++ ;; + --libs) + OPTS= + for opt in $lib_flags +@@ -227,8 +236,9 @@ + --exec-prefix=ARG sets the executable-prefix of ${THIS} + + --cflags echos the C compiler flags needed to compile with ${THIS} +- --libs echos the libraries needed to link with ${THIS} ++ --ldflags echos the linker flags needed to link with ${THIS} + ++ --libs echos the libraries needed to link with ${THIS} + --libs-only-L echos -L linker options (search path) for ${THIS} + --libs-only-l echos -l linker options (libraries) for ${THIS} + --libs-only-other echos linker options other than -L/-l +diff --unified -Nr dialog-1.3-20201126-orig/dialog.m4 dialog-1.3-20201126/dialog.m4 +--- dialog-1.3-20201126-orig/dialog.m4 1970-01-01 03:00:00.000000000 +0300 ++++ dialog-1.3-20201126/dialog.m4 2020-12-18 03:18:05.742635782 +0300 +@@ -0,0 +1,332 @@ ++dnl # ++dnl # /usr/share/aclocal/dialog.m4 ++dnl # ++dnl # Configure paths for dialog ++dnl # Andrew V.Kosteltsev ++ ++dnl ============================================================ ++dnl ++dnl Synopsis: ++dnl AC_CHECK_DIALOG([MIN-VERSION [, # minimum dialog version, e.g. 1.3-20190211 ++dnl DEFAULT-WITH-DIALOG [, # default value for --with-dialog option ++dnl DEFAULT-WITH-DIALOG-TEST [,# default value for --with-dialog-test option ++dnl EXTEND-VARS [, # whether CFLAGS/LDFLAGS/etc are extended ++dnl ACTION-IF-FOUND [, # action to perform if dialog was found ++dnl ACTION-IF-NOT-FOUND # action to perform if dialog was not found ++dnl ]]]]]]) ++dnl Examples: ++dnl AC_CHECK_DIALOG(1.3-20201126) ++dnl AC_CHECK_DIALOG(1.3-20201126,,,no,CFLAGS="$CFLAGS -DHAVE_DIALOG $DIALOG_CFLAGS") ++dnl AC_CHECK_DIALOG(1.3-20201126,yes,yes,yes,CFLAGS="$CFLAGS -DHAVE_DIALOG") ++dnl ++dnl ++dnl If you have to change prefix returned by dialog-config script or change ++dnl location of dialog-config, you may set environment variable DIALOG_CONFIG, ++dnl for example: ++dnl ++dnl # export DIALOG_CONFIG="dialog-config --prefix=/usr/local" ++dnl # export DIALOG_CONFIG="/usr/bin/dialog-config --prefix=/usr/local" ++dnl ++dnl ============================================================ ++dnl ++dnl ============================================================ ++dnl auxilliary macros ++dnl ============================================================ ++AC_DEFUN([_AC_DIALOG_ERROR], [dnl ++AC_MSG_RESULT([*FAILED*]) ++cat <<EOT | sed -e 's/^[[ ]]*/ | /' -e 's/>>/ /' 1>&2 ++$1 ++EOT ++exit 1 ++]) ++ ++AC_DEFUN([_AC_DIALOG_VERBOSE], [dnl ++if test ".$verbose" = .yes; then ++ AC_MSG_RESULT([ $1]) ++fi ++]) ++ ++dnl ============================================================ ++dnl the user macro ++dnl ============================================================ ++AC_DEFUN([AC_CHECK_DIALOG], [dnl ++dnl ++dnl ============================================================ ++dnl prerequisites ++dnl ============================================================ ++AC_REQUIRE([AC_PROG_CC])dnl ++AC_REQUIRE([AC_PROG_CPP])dnl ++dnl ++dnl ============================================================ ++dnl set DIALOG_CONFIG variable ++dnl ============================================================ ++if test -z "$DIALOG_CONFIG"; then ++ DIALOG_CONFIG='dialog-config' ++fi ++dnl ++DIALOG_CFLAGS='' ++DIALOG_LDFLAGS='' ++DIALOG_LIBS='' ++AC_SUBST(DIALOG_CFLAGS) ++AC_SUBST(DIALOG_LDFLAGS) ++AC_SUBST(DIALOG_LIBS) ++dnl ++dnl ============================================================ ++dnl command line options ++dnl ============================================================ ++_AC_DIALOG_VERBOSE([]) ++AC_ARG_WITH(dialog,dnl ++[ --with-dialog[=ARG] Build with dialog Library (default=]ifelse([$2],,yes,$2)[)],dnl ++,dnl ++with_dialog="ifelse([$2],,yes,$2)" ++)dnl ++AC_ARG_WITH(dialog-test,dnl ++[ --with-dialog-test Perform dialog Sanity Test (default=]ifelse([$3],,yes,$3)[)],dnl ++,dnl ++with_dialog_test="ifelse([$3],,yes,$3)" ++)dnl ++_AC_DIALOG_VERBOSE([+ Command Line Options:]) ++_AC_DIALOG_VERBOSE([ o --with-dialog=$with_dialog]) ++_AC_DIALOG_VERBOSE([ o --with-dialog-test=$with_dialog_test]) ++dnl ++dnl ============================================================ ++dnl configuration ++dnl ============================================================ ++if test ".$with_dialog" != .no; then ++ dialog_subdir=no ++ dialog_subdir_opts='' ++ case "$with_dialog" in ++ subdir:* ) ++ dialog_subdir=yes ++ changequote(, )dnl ++ dialog_subdir_opts=`echo $with_dialog | sed -e 's/^subdir:[^ ]*[ ]*//'` ++ with_dialog=`echo $with_dialog | sed -e 's/^subdir:\([^ ]*\).*$/\1/'` ++ changequote([, ])dnl ++ ;; ++ esac ++ dialog_version="" ++ dialog_location="" ++ dialog_type="" ++ dialog_cflags="" ++ dialog_ldflags="" ++ dialog_libs="" ++ if test ".$with_dialog" = .yes; then ++ # via config script in $PATH ++ changequote(, )dnl ++ dialog_version=`($DIALOG_CONFIG --version) 2>/dev/null |\ ++ sed -e 's/^.*\([0-9]\.[0-9]*[-][0-9]*\).*$/\1/'` ++ changequote([, ])dnl ++ if test ".$dialog_version" != .; then ++ dialog_location=`$DIALOG_CONFIG --prefix` ++ dialog_type='installed' ++ dialog_cflags=`$DIALOG_CONFIG --cflags` ++ dialog_ldflags=`$DIALOG_CONFIG --ldflags` ++ dialog_libs=`$DIALOG_CONFIG --libs` ++ fi ++ elif test -d "$with_dialog"; then ++ with_dialog=`echo $with_dialog | sed -e 's;/*$;;'` ++ dialog_found=no ++ # via config script under a specified directory ++ # (a standard installation, but not a source tree) ++ if test ".$dialog_found" = .no; then ++ for _dir in $with_dialog/bin $with_dialog; do ++ if test -f "$_dir/dialog-config"; then ++ test -f "$_dir/dialog-config.in" && continue # dialog-config in source tree! ++ changequote(, )dnl ++ dialog_version=`($_dir/dialog-config --version) 2>/dev/null |\ ++ sed -e 's/^.*\([0-9]\.[0-9]*[.][0-9]*\).*$/\1/'` ++ changequote([, ])dnl ++ if test ".$dialog_version" != .; then ++ dialog_location=`$_dir/dialog-config --prefix` ++ dialog_type="installed" ++ dialog_cflags=`$_dir/dialog-config --cflags` ++ dialog_ldflags=`$_dir/dialog-config --ldflags` ++ dialog_libs=`$_dir/dialog-config --libs` ++ dialog_found=yes ++ break ++ fi ++ fi ++ done ++ fi ++ fi ++ _AC_DIALOG_VERBOSE([+ Determined Location:]) ++ _AC_DIALOG_VERBOSE([ o path: $dialog_location]) ++ _AC_DIALOG_VERBOSE([ o type: $dialog_type]) ++ if test ".$dialog_version" = .; then ++ if test ".$with_dialog" != .yes; then ++ _AC_DIALOG_ERROR([dnl ++ Unable to locate dialog under $with_dialog. ++ Please specify the correct path to either a dialog installation tree ++ (use --with-dialog=DIR if you used --prefix=DIR for installing dialog in ++ the past).]) ++ else ++ _AC_DIALOG_ERROR([dnl ++ Unable to locate dialog in any system-wide location (see \$PATH). ++ Please specify the correct path to either a dialog installation tree ++ (use --with-dialog=DIR if you used --prefix=DIR for installing dialog in ++ the past, or set the DIALOG_CONFIG environment variable to the full path ++ to dialog-config).]) ++ fi ++ fi ++ dnl ======================================================== ++ dnl Check whether the found version is sufficiently new ++ dnl ======================================================== ++ _req_version="ifelse([$1],,1.0.0,$1)" ++ for _var in dialog_version _req_version; do ++ eval "_val=\"\$${_var}\"" ++ _major=`echo $_val | sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\([[.]]\)\([[0-9]]*\)/\1/'` ++ _minor=`echo $_val | sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\([[.]]\)\([[0-9]]*\)/\2/'` ++ _micro=`echo $_val | sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\([[.]]\)\([[0-9]]*\)/\4/'` ++ _hex=`echo dummy | awk '{ printf("%d%02d%02d", major, minor, micro); }' \ ++ "major=$_major" "minor=$_minor" "micro=$_micro"` ++ eval "${_var}_hex=\"\$_hex\"" ++ done ++ _AC_DIALOG_VERBOSE([+ Determined Versions:]) ++ _AC_DIALOG_VERBOSE([ o existing: $dialog_version -> 0x$dialog_version_hex]) ++ _AC_DIALOG_VERBOSE([ o required: $_req_version -> 0x$_req_version_hex]) ++ _ok=0 ++ if test ".$dialog_version_hex" != .; then ++ if test ".$_req_version_hex" != .; then ++ if test $dialog_version_hex -ge $_req_version_hex; then ++ _ok=1 ++ fi ++ fi ++ fi ++ if test ".$_ok" = .0; then ++ _AC_DIALOG_ERROR([dnl ++ Found dialog version $dialog_version, but required at least version $_req_version. ++ Upgrade dialog under $dialog_location to $_req_version or higher first, please.]) ++ fi ++ dnl ======================================================== ++ dnl Perform dialog Sanity Compile Check ++ dnl ======================================================== ++ if test ".$with_dialog_test" = .yes; then ++ _ac_save_CFLAGS="$CFLAGS" ++ _ac_save_LDFLAGS="$LDFLAGS" ++ _ac_save_LIBS="$LIBS" ++ CFLAGS="$CFLAGS $dialog_cflags" ++ LDFLAGS="$LDFLAGS $dialog_ldflags" ++ LIBS="$LIBS $dialog_libs" ++ _AC_DIALOG_VERBOSE([+ Test Build Environment:]) ++ _AC_DIALOG_VERBOSE([ o CFLAGS=\"$CFLAGS\"]) ++ _AC_DIALOG_VERBOSE([ o LDFLAGS=\"$LDFLAGS\"]) ++ _AC_DIALOG_VERBOSE([ o LIBS=\"$LIBS\"]) ++ cross_compile=no ++ define(_code1, [dnl ++ ++#include <stdlib.h> ++#include <stdio.h> ++#include <strings.h> /* index(3) */ ++ ++#include <dialog.h> ++#include <dlg_colors.h> ++#include <dlg_keys.h> ++ ++ ]) ++ define(_code2, [dnl ++ ++int main( void ) ++{ ++ int status = 0; ++ ++ bzero( (void *)&dialog_vars, sizeof(DIALOG_VARS) ); ++ ++ init_dialog(stdin, stdout); ++ ++ dialog_vars.colors = 1; ++ dialog_vars.backtitle = "\\Z7Test\\Zn \\Z1dialog\\Zn \\Z7Library\\Zn"; ++ dialog_vars.dlg_clear_screen = 1; ++ dialog_vars.sleep_secs = 1; ++ ++ ++ dlg_put_backtitle(); ++ ++ /************************************************* ++ Ruler: 68 characters + 2 spaces left and right: ++ ++ | ----handy-ruler----------------------------------------------------- | */ ++ status = dialog_msgbox( " \\Z4Dialog ==>\\Zn\\Z1libdialog\\Zn\\Z4<== [required]\\Zn ", ++ "\nPackage is installed and corect.\n", ++ 5, 72, 0 ); ++ ++ if( dialog_vars.sleep_secs ) ++ (void)napms(dialog_vars.sleep_secs * 1000); ++ ++ if( dialog_vars.dlg_clear_screen ) ++ { ++ dlg_clear(); ++ (void)refresh(); ++ } ++ end_dialog(); ++ ++ exit( 0 ); ++} ++ ]) ++ _AC_DIALOG_VERBOSE([+ Performing Sanity Checks:]) ++ _AC_DIALOG_VERBOSE([ o pre-processor test]) ++ AC_TRY_CPP(_code1, _ok=yes, _ok=no) ++ if test ".$_ok" != .yes; then ++ _AC_DIALOG_ERROR([dnl ++ Found dialog $dialog_version under $dialog_location, but ++ was unable to perform a sanity pre-processor check. This means ++ the dialog header dialog.h was not found. ++ We used the following build environment: ++ >> CPP="$CPP" ++ See config.log for possibly more details.]) ++ fi ++ _AC_DIALOG_VERBOSE([ o link check]) ++ AC_TRY_LINK(_code1, _code2, _ok=yes, _ok=no) ++ if test ".$_ok" != .yes; then ++ _AC_DIALOG_ERROR([dnl ++ Found dialog $dialog_version under $dialog_location, but ++ was unable to perform a sanity linker check. This means ++ the dialog library libdialog.a was not found. ++ We used the following build environment: ++ >> CC="$CC" ++ >> CFLAGS="$CFLAGS" ++ >> LDFLAGS="$LDFLAGS" ++ >> LIBS="$LIBS" ++ See config.log for possibly more details.]) ++ fi ++ _extendvars="ifelse([$4],,yes,$4)" ++ if test ".$_extendvars" != .yes; then ++ CFLAGS="$_ac_save_CFLAGS" ++ LDFLAGS="$_ac_save_LDFLAGS" ++ LIBS="$_ac_save_LIBS" ++ fi ++ else ++ _extendvars="ifelse([$4],,yes,$4)" ++ if test ".$_extendvars" = .yes; then ++ if test ".$dialog_subdir" = .yes; then ++ CFLAGS="$CFLAGS $dialog_cflags" ++ LDFLAGS="$LDFLAGS $dialog_ldflags" ++ LIBS="$LIBS $dialog_libs" ++ fi ++ fi ++ fi ++ DIALOG_CFLAGS="$dialog_cflags" ++ DIALOG_LDFLAGS="$dialog_ldflags" ++ DIALOG_LIBS="$dialog_libs" ++ AC_SUBST(DIALOG_CFLAGS) ++ AC_SUBST(DIALOG_LDFLAGS) ++ AC_SUBST(DIALOG_LIBS) ++ ++ AC_SUBST(HAVE_DIALOG, [1]) ++ ++ AC_CHECK_HEADERS(dialog.h dlg_colors.h dlg_keys.h) ++ ++ _AC_DIALOG_VERBOSE([+ Final Results:]) ++ _AC_DIALOG_VERBOSE([ o DIALOG_CFLAGS=\"$DIALOG_CFLAGS\"]) ++ _AC_DIALOG_VERBOSE([ o DIALOG_LDFLAGS=\"$DIALOG_LDFLAGS\"]) ++ _AC_DIALOG_VERBOSE([ o DIALOG_LIBS=\"$DIALOG_LIBS\"]) ++fi ++if test ".$with_dialog" != .no; then ++ AC_MSG_CHECKING(for libdialog) ++ AC_MSG_RESULT([version $dialog_version, $dialog_type under $dialog_location]) ++ ifelse([$5], , :, [$5]) ++else ++ AC_MSG_CHECKING(for libdialog) ++ AC_MSG_RESULT([no]) ++ ifelse([$6], , :, [$6]) ++fi ++]) +diff --unified -Nr dialog-1.3-20201126-orig/fselect.c dialog-1.3-20201126/fselect.c +--- dialog-1.3-20201126-orig/fselect.c 2020-11-23 12:03:54.000000000 +0300 ++++ dialog-1.3-20201126/fselect.c 2020-12-18 03:18:05.742635782 +0300 +@@ -649,7 +649,7 @@ + dlg_print_size(height, width); + dlg_ctl_size(height, width); + +- dialog = dlg_new_window(height, width, ++ dialog = dlg_new_window(height + 1, width, + dlg_box_y_ordinate(height), + dlg_box_x_ordinate(width)); + dlg_register_window(dialog, "fselect", binding); +@@ -657,7 +657,7 @@ + + dlg_mouse_setbase(0, 0); + +- dlg_draw_box2(dialog, 0, 0, height, width, dialog_attr, border_attr, border2_attr); ++ dlg_draw_box2(dialog, 0, 0, height + 1, width, dialog_attr, border_attr, border2_attr); + dlg_draw_bottom_box2(dialog, border_attr, border2_attr, dialog_attr); + dlg_draw_title(dialog, title); + +@@ -666,7 +666,7 @@ + /* Draw the input field box */ + tbox_height = 1; + tbox_width = width - (4 * MARGIN + 2); +- tbox_y = height - (BTN_HIGH * 2) + MARGIN; ++ tbox_y = height - (BTN_HIGH * 2) + MARGIN + 1; + tbox_x = (width - tbox_width) / 2; + + w_text = dlg_der_window(dialog, tbox_height, tbox_width, tbox_y, tbox_x); +@@ -692,7 +692,7 @@ + else + dbox_width = (width - (6 * MARGIN + 2 * EXT_WIDE)) / 2; + dbox_height = height - MIN_HIGH; +- dbox_y = (2 * MARGIN + 1); ++ dbox_y = (2 * MARGIN + 2); + dbox_x = tbox_x; + + w_work = dlg_der_window(dialog, dbox_height, dbox_width, dbox_y, dbox_x); +@@ -750,7 +750,7 @@ + if (show_buttons) { + show_buttons = FALSE; + button = (state < 0) ? 0 : state; +- dlg_draw_buttons(dialog, height - 2, 0, buttons, button, FALSE, width); ++ dlg_draw_buttons(dialog, height - 1, 0, buttons, button, FALSE, width); + } + + if (first_trace) { +diff --unified -Nr dialog-1.3-20201126-orig/menubox.c dialog-1.3-20201126/menubox.c +--- dialog-1.3-20201126-orig/menubox.c 2020-11-24 00:03:11.000000000 +0300 ++++ dialog-1.3-20201126/menubox.c 2020-12-18 03:18:05.742635782 +0300 +@@ -48,7 +48,7 @@ + int item_no; + } ALL_DATA; + +-#define MIN_HIGH (1 + (5 * MARGIN)) ++#define MIN_HIGH 4 + + #define INPUT_ROWS 3 /* rows per inputmenu entry */ + diff --git a/doc/dialog/dialog-1.3-20210117.patch b/doc/dialog/dialog-1.3-20210117.patch new file mode 100644 index 0000000..68b1eba --- /dev/null +++ b/doc/dialog/dialog-1.3-20210117.patch @@ -0,0 +1,462 @@ +diff --unified -Nr dialog-1.3-20210117-orig/checklist.c dialog-1.3-20210117/checklist.c +--- dialog-1.3-20210117-orig/checklist.c 2020-11-23 03:37:47.000000000 +0300 ++++ dialog-1.3-20210117/checklist.c 2021-02-14 20:14:51.095326506 +0300 +@@ -29,7 +29,7 @@ + #include <dialog.h> + #include <dlg_keys.h> + +-#define MIN_HIGH (1 + (5 * MARGIN)) ++#define MIN_HIGH 4 + + typedef struct { + /* the outer-window */ +diff --unified -Nr dialog-1.3-20210117-orig/dialog-config.in dialog-1.3-20210117/dialog-config.in +--- dialog-1.3-20210117-orig/dialog-config.in 2019-09-26 03:50:46.000000000 +0300 ++++ dialog-1.3-20210117/dialog-config.in 2021-02-14 20:14:51.095326506 +0300 +@@ -79,7 +79,7 @@ + [ -n "$LFLAGS" ] && LDFLAGS=" $LFDLAGS" + LDFLAGS="-L${libdir}$LDFLAGS" + fi +- [ -z "$LDFLAGS" ] && LDFLAGS="-L${exec_prefix}/lib" ++ [ -z "$LDFLAGS" ] && LDFLAGS="-L${libdir}" + + # Ignore -L options which do not correspond to an actual directory, + # or which are standard library directories (i.e., the linker is +@@ -133,10 +133,7 @@ + ;; + # compile/link + --cflags|--cflags-only-I) +- INCS= +- if test "$includedir" != /usr/include ; then +- INCS="-I$includedir" +- fi ++ INCS="-I$includedir" + sed -e 's,^[ ]*,,' -e 's, [ ]*, ,g' -e 's,[ ]*$,,' <<-ENDECHO + $INCS + ENDECHO +@@ -145,6 +142,18 @@ + # no -D/-U options should be needed + echo + ;; ++ --ldflags) ++ OPTS= ++ for opt in $LDFLAGS $LIBS ++ do ++ case "x$opt" in ++ x-[^l]*) ++ OPTS="$OPTS $opt" ++ ;; ++ esac ++ done ++ printf "%s\n" "$OPTS" ++ ;; + --libs) + OPTS= + for opt in $lib_flags +@@ -227,8 +236,9 @@ + --exec-prefix=ARG sets the executable-prefix of ${THIS} + + --cflags echos the C compiler flags needed to compile with ${THIS} +- --libs echos the libraries needed to link with ${THIS} ++ --ldflags echos the linker flags needed to link with ${THIS} + ++ --libs echos the libraries needed to link with ${THIS} + --libs-only-L echos -L linker options (search path) for ${THIS} + --libs-only-l echos -l linker options (libraries) for ${THIS} + --libs-only-other echos linker options other than -L/-l +diff --unified -Nr dialog-1.3-20210117-orig/dialog.m4 dialog-1.3-20210117/dialog.m4 +--- dialog-1.3-20210117-orig/dialog.m4 1970-01-01 03:00:00.000000000 +0300 ++++ dialog-1.3-20210117/dialog.m4 2021-02-14 20:14:51.095326506 +0300 +@@ -0,0 +1,332 @@ ++dnl # ++dnl # /usr/share/aclocal/dialog.m4 ++dnl # ++dnl # Configure paths for dialog ++dnl # Andrew V.Kosteltsev ++ ++dnl ============================================================ ++dnl ++dnl Synopsis: ++dnl AC_CHECK_DIALOG([MIN-VERSION [, # minimum dialog version, e.g. 1.3-20190211 ++dnl DEFAULT-WITH-DIALOG [, # default value for --with-dialog option ++dnl DEFAULT-WITH-DIALOG-TEST [,# default value for --with-dialog-test option ++dnl EXTEND-VARS [, # whether CFLAGS/LDFLAGS/etc are extended ++dnl ACTION-IF-FOUND [, # action to perform if dialog was found ++dnl ACTION-IF-NOT-FOUND # action to perform if dialog was not found ++dnl ]]]]]]) ++dnl Examples: ++dnl AC_CHECK_DIALOG(1.3-20210117) ++dnl AC_CHECK_DIALOG(1.3-20210117,,,no,CFLAGS="$CFLAGS -DHAVE_DIALOG $DIALOG_CFLAGS") ++dnl AC_CHECK_DIALOG(1.3-20210117,yes,yes,yes,CFLAGS="$CFLAGS -DHAVE_DIALOG") ++dnl ++dnl ++dnl If you have to change prefix returned by dialog-config script or change ++dnl location of dialog-config, you may set environment variable DIALOG_CONFIG, ++dnl for example: ++dnl ++dnl # export DIALOG_CONFIG="dialog-config --prefix=/usr/local" ++dnl # export DIALOG_CONFIG="/usr/bin/dialog-config --prefix=/usr/local" ++dnl ++dnl ============================================================ ++dnl ++dnl ============================================================ ++dnl auxilliary macros ++dnl ============================================================ ++AC_DEFUN([_AC_DIALOG_ERROR], [dnl ++AC_MSG_RESULT([*FAILED*]) ++cat <<EOT | sed -e 's/^[[ ]]*/ | /' -e 's/>>/ /' 1>&2 ++$1 ++EOT ++exit 1 ++]) ++ ++AC_DEFUN([_AC_DIALOG_VERBOSE], [dnl ++if test ".$verbose" = .yes; then ++ AC_MSG_RESULT([ $1]) ++fi ++]) ++ ++dnl ============================================================ ++dnl the user macro ++dnl ============================================================ ++AC_DEFUN([AC_CHECK_DIALOG], [dnl ++dnl ++dnl ============================================================ ++dnl prerequisites ++dnl ============================================================ ++AC_REQUIRE([AC_PROG_CC])dnl ++AC_REQUIRE([AC_PROG_CPP])dnl ++dnl ++dnl ============================================================ ++dnl set DIALOG_CONFIG variable ++dnl ============================================================ ++if test -z "$DIALOG_CONFIG"; then ++ DIALOG_CONFIG='dialog-config' ++fi ++dnl ++DIALOG_CFLAGS='' ++DIALOG_LDFLAGS='' ++DIALOG_LIBS='' ++AC_SUBST(DIALOG_CFLAGS) ++AC_SUBST(DIALOG_LDFLAGS) ++AC_SUBST(DIALOG_LIBS) ++dnl ++dnl ============================================================ ++dnl command line options ++dnl ============================================================ ++_AC_DIALOG_VERBOSE([]) ++AC_ARG_WITH(dialog,dnl ++[ --with-dialog[=ARG] Build with dialog Library (default=]ifelse([$2],,yes,$2)[)],dnl ++,dnl ++with_dialog="ifelse([$2],,yes,$2)" ++)dnl ++AC_ARG_WITH(dialog-test,dnl ++[ --with-dialog-test Perform dialog Sanity Test (default=]ifelse([$3],,yes,$3)[)],dnl ++,dnl ++with_dialog_test="ifelse([$3],,yes,$3)" ++)dnl ++_AC_DIALOG_VERBOSE([+ Command Line Options:]) ++_AC_DIALOG_VERBOSE([ o --with-dialog=$with_dialog]) ++_AC_DIALOG_VERBOSE([ o --with-dialog-test=$with_dialog_test]) ++dnl ++dnl ============================================================ ++dnl configuration ++dnl ============================================================ ++if test ".$with_dialog" != .no; then ++ dialog_subdir=no ++ dialog_subdir_opts='' ++ case "$with_dialog" in ++ subdir:* ) ++ dialog_subdir=yes ++ changequote(, )dnl ++ dialog_subdir_opts=`echo $with_dialog | sed -e 's/^subdir:[^ ]*[ ]*//'` ++ with_dialog=`echo $with_dialog | sed -e 's/^subdir:\([^ ]*\).*$/\1/'` ++ changequote([, ])dnl ++ ;; ++ esac ++ dialog_version="" ++ dialog_location="" ++ dialog_type="" ++ dialog_cflags="" ++ dialog_ldflags="" ++ dialog_libs="" ++ if test ".$with_dialog" = .yes; then ++ # via config script in $PATH ++ changequote(, )dnl ++ dialog_version=`($DIALOG_CONFIG --version) 2>/dev/null |\ ++ sed -e 's/^.*\([0-9]\.[0-9]*[-][0-9]*\).*$/\1/'` ++ changequote([, ])dnl ++ if test ".$dialog_version" != .; then ++ dialog_location=`$DIALOG_CONFIG --prefix` ++ dialog_type='installed' ++ dialog_cflags=`$DIALOG_CONFIG --cflags` ++ dialog_ldflags=`$DIALOG_CONFIG --ldflags` ++ dialog_libs=`$DIALOG_CONFIG --libs` ++ fi ++ elif test -d "$with_dialog"; then ++ with_dialog=`echo $with_dialog | sed -e 's;/*$;;'` ++ dialog_found=no ++ # via config script under a specified directory ++ # (a standard installation, but not a source tree) ++ if test ".$dialog_found" = .no; then ++ for _dir in $with_dialog/bin $with_dialog; do ++ if test -f "$_dir/dialog-config"; then ++ test -f "$_dir/dialog-config.in" && continue # dialog-config in source tree! ++ changequote(, )dnl ++ dialog_version=`($_dir/dialog-config --version) 2>/dev/null |\ ++ sed -e 's/^.*\([0-9]\.[0-9]*[.][0-9]*\).*$/\1/'` ++ changequote([, ])dnl ++ if test ".$dialog_version" != .; then ++ dialog_location=`$_dir/dialog-config --prefix` ++ dialog_type="installed" ++ dialog_cflags=`$_dir/dialog-config --cflags` ++ dialog_ldflags=`$_dir/dialog-config --ldflags` ++ dialog_libs=`$_dir/dialog-config --libs` ++ dialog_found=yes ++ break ++ fi ++ fi ++ done ++ fi ++ fi ++ _AC_DIALOG_VERBOSE([+ Determined Location:]) ++ _AC_DIALOG_VERBOSE([ o path: $dialog_location]) ++ _AC_DIALOG_VERBOSE([ o type: $dialog_type]) ++ if test ".$dialog_version" = .; then ++ if test ".$with_dialog" != .yes; then ++ _AC_DIALOG_ERROR([dnl ++ Unable to locate dialog under $with_dialog. ++ Please specify the correct path to either a dialog installation tree ++ (use --with-dialog=DIR if you used --prefix=DIR for installing dialog in ++ the past).]) ++ else ++ _AC_DIALOG_ERROR([dnl ++ Unable to locate dialog in any system-wide location (see \$PATH). ++ Please specify the correct path to either a dialog installation tree ++ (use --with-dialog=DIR if you used --prefix=DIR for installing dialog in ++ the past, or set the DIALOG_CONFIG environment variable to the full path ++ to dialog-config).]) ++ fi ++ fi ++ dnl ======================================================== ++ dnl Check whether the found version is sufficiently new ++ dnl ======================================================== ++ _req_version="ifelse([$1],,1.0.0,$1)" ++ for _var in dialog_version _req_version; do ++ eval "_val=\"\$${_var}\"" ++ _major=`echo $_val | sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\([[.]]\)\([[0-9]]*\)/\1/'` ++ _minor=`echo $_val | sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\([[.]]\)\([[0-9]]*\)/\2/'` ++ _micro=`echo $_val | sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\([[.]]\)\([[0-9]]*\)/\4/'` ++ _hex=`echo dummy | awk '{ printf("%d%02d%02d", major, minor, micro); }' \ ++ "major=$_major" "minor=$_minor" "micro=$_micro"` ++ eval "${_var}_hex=\"\$_hex\"" ++ done ++ _AC_DIALOG_VERBOSE([+ Determined Versions:]) ++ _AC_DIALOG_VERBOSE([ o existing: $dialog_version -> 0x$dialog_version_hex]) ++ _AC_DIALOG_VERBOSE([ o required: $_req_version -> 0x$_req_version_hex]) ++ _ok=0 ++ if test ".$dialog_version_hex" != .; then ++ if test ".$_req_version_hex" != .; then ++ if test $dialog_version_hex -ge $_req_version_hex; then ++ _ok=1 ++ fi ++ fi ++ fi ++ if test ".$_ok" = .0; then ++ _AC_DIALOG_ERROR([dnl ++ Found dialog version $dialog_version, but required at least version $_req_version. ++ Upgrade dialog under $dialog_location to $_req_version or higher first, please.]) ++ fi ++ dnl ======================================================== ++ dnl Perform dialog Sanity Compile Check ++ dnl ======================================================== ++ if test ".$with_dialog_test" = .yes; then ++ _ac_save_CFLAGS="$CFLAGS" ++ _ac_save_LDFLAGS="$LDFLAGS" ++ _ac_save_LIBS="$LIBS" ++ CFLAGS="$CFLAGS $dialog_cflags" ++ LDFLAGS="$LDFLAGS $dialog_ldflags" ++ LIBS="$LIBS $dialog_libs" ++ _AC_DIALOG_VERBOSE([+ Test Build Environment:]) ++ _AC_DIALOG_VERBOSE([ o CFLAGS=\"$CFLAGS\"]) ++ _AC_DIALOG_VERBOSE([ o LDFLAGS=\"$LDFLAGS\"]) ++ _AC_DIALOG_VERBOSE([ o LIBS=\"$LIBS\"]) ++ cross_compile=no ++ define(_code1, [dnl ++ ++#include <stdlib.h> ++#include <stdio.h> ++#include <strings.h> /* index(3) */ ++ ++#include <dialog.h> ++#include <dlg_colors.h> ++#include <dlg_keys.h> ++ ++ ]) ++ define(_code2, [dnl ++ ++int main( void ) ++{ ++ int status = 0; ++ ++ bzero( (void *)&dialog_vars, sizeof(DIALOG_VARS) ); ++ ++ init_dialog(stdin, stdout); ++ ++ dialog_vars.colors = 1; ++ dialog_vars.backtitle = "\\Z7Test\\Zn \\Z1dialog\\Zn \\Z7Library\\Zn"; ++ dialog_vars.dlg_clear_screen = 1; ++ dialog_vars.sleep_secs = 1; ++ ++ ++ dlg_put_backtitle(); ++ ++ /************************************************* ++ Ruler: 68 characters + 2 spaces left and right: ++ ++ | ----handy-ruler----------------------------------------------------- | */ ++ status = dialog_msgbox( " \\Z4Dialog ==>\\Zn\\Z1libdialog\\Zn\\Z4<== [required]\\Zn ", ++ "\nPackage is installed and corect.\n", ++ 5, 72, 0 ); ++ ++ if( dialog_vars.sleep_secs ) ++ (void)napms(dialog_vars.sleep_secs * 1000); ++ ++ if( dialog_vars.dlg_clear_screen ) ++ { ++ dlg_clear(); ++ (void)refresh(); ++ } ++ end_dialog(); ++ ++ exit( 0 ); ++} ++ ]) ++ _AC_DIALOG_VERBOSE([+ Performing Sanity Checks:]) ++ _AC_DIALOG_VERBOSE([ o pre-processor test]) ++ AC_TRY_CPP(_code1, _ok=yes, _ok=no) ++ if test ".$_ok" != .yes; then ++ _AC_DIALOG_ERROR([dnl ++ Found dialog $dialog_version under $dialog_location, but ++ was unable to perform a sanity pre-processor check. This means ++ the dialog header dialog.h was not found. ++ We used the following build environment: ++ >> CPP="$CPP" ++ See config.log for possibly more details.]) ++ fi ++ _AC_DIALOG_VERBOSE([ o link check]) ++ AC_TRY_LINK(_code1, _code2, _ok=yes, _ok=no) ++ if test ".$_ok" != .yes; then ++ _AC_DIALOG_ERROR([dnl ++ Found dialog $dialog_version under $dialog_location, but ++ was unable to perform a sanity linker check. This means ++ the dialog library libdialog.a was not found. ++ We used the following build environment: ++ >> CC="$CC" ++ >> CFLAGS="$CFLAGS" ++ >> LDFLAGS="$LDFLAGS" ++ >> LIBS="$LIBS" ++ See config.log for possibly more details.]) ++ fi ++ _extendvars="ifelse([$4],,yes,$4)" ++ if test ".$_extendvars" != .yes; then ++ CFLAGS="$_ac_save_CFLAGS" ++ LDFLAGS="$_ac_save_LDFLAGS" ++ LIBS="$_ac_save_LIBS" ++ fi ++ else ++ _extendvars="ifelse([$4],,yes,$4)" ++ if test ".$_extendvars" = .yes; then ++ if test ".$dialog_subdir" = .yes; then ++ CFLAGS="$CFLAGS $dialog_cflags" ++ LDFLAGS="$LDFLAGS $dialog_ldflags" ++ LIBS="$LIBS $dialog_libs" ++ fi ++ fi ++ fi ++ DIALOG_CFLAGS="$dialog_cflags" ++ DIALOG_LDFLAGS="$dialog_ldflags" ++ DIALOG_LIBS="$dialog_libs" ++ AC_SUBST(DIALOG_CFLAGS) ++ AC_SUBST(DIALOG_LDFLAGS) ++ AC_SUBST(DIALOG_LIBS) ++ ++ AC_SUBST(HAVE_DIALOG, [1]) ++ ++ AC_CHECK_HEADERS(dialog.h dlg_colors.h dlg_keys.h) ++ ++ _AC_DIALOG_VERBOSE([+ Final Results:]) ++ _AC_DIALOG_VERBOSE([ o DIALOG_CFLAGS=\"$DIALOG_CFLAGS\"]) ++ _AC_DIALOG_VERBOSE([ o DIALOG_LDFLAGS=\"$DIALOG_LDFLAGS\"]) ++ _AC_DIALOG_VERBOSE([ o DIALOG_LIBS=\"$DIALOG_LIBS\"]) ++fi ++if test ".$with_dialog" != .no; then ++ AC_MSG_CHECKING(for libdialog) ++ AC_MSG_RESULT([version $dialog_version, $dialog_type under $dialog_location]) ++ ifelse([$5], , :, [$5]) ++else ++ AC_MSG_CHECKING(for libdialog) ++ AC_MSG_RESULT([no]) ++ ifelse([$6], , :, [$6]) ++fi ++]) +diff --unified -Nr dialog-1.3-20210117-orig/fselect.c dialog-1.3-20210117/fselect.c +--- dialog-1.3-20210117-orig/fselect.c 2021-01-16 20:19:15.000000000 +0300 ++++ dialog-1.3-20210117/fselect.c 2021-02-14 20:14:51.095326506 +0300 +@@ -650,7 +650,7 @@ + dlg_print_size(height, width); + dlg_ctl_size(height, width); + +- dialog = dlg_new_window(height, width, ++ dialog = dlg_new_window(height + 1, width, + dlg_box_y_ordinate(height), + dlg_box_x_ordinate(width)); + dlg_register_window(dialog, "fselect", binding); +@@ -658,7 +658,7 @@ + + dlg_mouse_setbase(0, 0); + +- dlg_draw_box2(dialog, 0, 0, height, width, dialog_attr, border_attr, border2_attr); ++ dlg_draw_box2(dialog, 0, 0, height + 1, width, dialog_attr, border_attr, border2_attr); + dlg_draw_bottom_box2(dialog, border_attr, border2_attr, dialog_attr); + dlg_draw_title(dialog, title); + +@@ -667,7 +667,7 @@ + /* Draw the input field box */ + tbox_height = 1; + tbox_width = width - (4 * MARGIN + 2); +- tbox_y = height - (BTN_HIGH * 2) + MARGIN; ++ tbox_y = height - (BTN_HIGH * 2) + MARGIN + 1; + tbox_x = (width - tbox_width) / 2; + + w_text = dlg_der_window(dialog, tbox_height, tbox_width, tbox_y, tbox_x); +@@ -693,7 +693,7 @@ + else + dbox_width = (width - (6 * MARGIN + 2 * EXT_WIDE)) / 2; + dbox_height = height - MIN_HIGH; +- dbox_y = (2 * MARGIN + 1); ++ dbox_y = (2 * MARGIN + 2); + dbox_x = tbox_x; + + w_work = dlg_der_window(dialog, dbox_height, dbox_width, dbox_y, dbox_x); +@@ -751,7 +751,7 @@ + if (show_buttons) { + show_buttons = FALSE; + button = (state < 0) ? 0 : state; +- dlg_draw_buttons(dialog, height - 2, 0, buttons, button, FALSE, width); ++ dlg_draw_buttons(dialog, height - 1, 0, buttons, button, FALSE, width); + } + + if (first_trace) { +diff --unified -Nr dialog-1.3-20210117-orig/menubox.c dialog-1.3-20210117/menubox.c +--- dialog-1.3-20210117-orig/menubox.c 2020-11-24 00:03:11.000000000 +0300 ++++ dialog-1.3-20210117/menubox.c 2021-02-14 20:14:51.095326506 +0300 +@@ -48,7 +48,7 @@ + int item_no; + } ALL_DATA; + +-#define MIN_HIGH (1 + (5 * MARGIN)) ++#define MIN_HIGH 4 + + #define INPUT_ROWS 3 /* rows per inputmenu entry */ + diff --git a/doc/dialog/dialog-1.3-20210621.patch b/doc/dialog/dialog-1.3-20210621.patch new file mode 100644 index 0000000..7ffd1ae --- /dev/null +++ b/doc/dialog/dialog-1.3-20210621.patch @@ -0,0 +1,454 @@ +diff --unified -Nr dialog-1.3-20210621-orig/checklist.c dialog-1.3-20210621/checklist.c +--- dialog-1.3-20210621-orig/checklist.c 2020-11-23 03:37:47.000000000 +0300 ++++ dialog-1.3-20210621/checklist.c 2021-10-15 12:06:20.636637175 +0300 +@@ -29,7 +29,7 @@ + #include <dialog.h> + #include <dlg_keys.h> + +-#define MIN_HIGH (1 + (5 * MARGIN)) ++#define MIN_HIGH 4 + + typedef struct { + /* the outer-window */ +diff --unified -Nr dialog-1.3-20210621-orig/dialog-config.in dialog-1.3-20210621/dialog-config.in +--- dialog-1.3-20210621-orig/dialog-config.in 2021-03-06 02:48:36.000000000 +0300 ++++ dialog-1.3-20210621/dialog-config.in 2021-10-15 12:06:20.635637175 +0300 +@@ -79,7 +79,7 @@ + [ -n "$LFLAGS" ] && LDFLAGS=" $LFDLAGS" + LDFLAGS="-L${libdir}$LDFLAGS" + fi +- [ -z "$LDFLAGS" ] && LDFLAGS="-L${exec_prefix}/lib" ++ [ -z "$LDFLAGS" ] && LDFLAGS="-L${libdir}" + + # Ignore -L options which do not correspond to an actual directory, + # or which are standard library directories (i.e., the linker is +@@ -134,10 +134,7 @@ + ;; + # compile/link + --cflags|--cflags-only-I) +- INCS= +- if test "$includedir" != /usr/include ; then +- INCS="-I$includedir" +- fi ++ INCS="-I$includedir" + sed -e 's,^[ ]*,,' -e 's, [ ]*, ,g' -e 's,[ ]*$,,' <<-ENDECHO + $INCS + ENDECHO +@@ -146,6 +143,18 @@ + # no -D/-U options should be needed + echo + ;; ++ --ldflags) ++ OPTS= ++ for opt in $LDFLAGS $LIBS ++ do ++ case "x$opt" in ++ x-[^l]*) ++ OPTS="$OPTS $opt" ++ ;; ++ esac ++ done ++ printf "%s\n" "$OPTS" ++ ;; + --libs) + OPTS= + for opt in $lib_flags +@@ -228,8 +237,9 @@ + --exec-prefix=ARG sets the executable-prefix of ${THIS} + + --cflags echos the C compiler flags needed to compile with ${THIS} +- --libs echos the libraries needed to link with ${THIS} ++ --ldflags echos the linker flags needed to link with ${THIS} + ++ --libs echos the libraries needed to link with ${THIS} + --libs-only-L echos -L linker options (search path) for ${THIS} + --libs-only-l echos -l linker options (libraries) for ${THIS} + --libs-only-other echos linker options other than -L/-l +diff --unified -Nr dialog-1.3-20210621-orig/dialog.m4 dialog-1.3-20210621/dialog.m4 +--- dialog-1.3-20210621-orig/dialog.m4 1970-01-01 03:00:00.000000000 +0300 ++++ dialog-1.3-20210621/dialog.m4 2021-10-15 12:06:20.635637175 +0300 +@@ -0,0 +1,324 @@ ++dnl # ++dnl # /usr/share/aclocal/dialog.m4 ++dnl # ++dnl # Configure paths for dialog ++dnl # Andrew V.Kosteltsev ++ ++dnl ============================================================ ++dnl ++dnl Synopsis: ++dnl AC_CHECK_DIALOG([MIN-VERSION [, # minimum dialog version, e.g. 1.3-20190211 ++dnl DEFAULT-WITH-DIALOG [, # default value for --with-dialog option ++dnl DEFAULT-WITH-DIALOG-TEST [,# default value for --with-dialog-test option ++dnl EXTEND-VARS [, # whether CFLAGS/LDFLAGS/etc are extended ++dnl ACTION-IF-FOUND [, # action to perform if dialog was found ++dnl ACTION-IF-NOT-FOUND # action to perform if dialog was not found ++dnl ]]]]]]) ++dnl Examples: ++dnl AC_CHECK_DIALOG(1.3-20210621) ++dnl AC_CHECK_DIALOG(1.3-20210621,,,no,CFLAGS="$CFLAGS -DHAVE_DIALOG $DIALOG_CFLAGS") ++dnl AC_CHECK_DIALOG(1.3-20210621,yes,yes,yes,CFLAGS="$CFLAGS -DHAVE_DIALOG") ++dnl ++dnl ++dnl If you have to change prefix returned by dialog-config script or change ++dnl location of dialog-config, you may set environment variable DIALOG_CONFIG, ++dnl for example: ++dnl ++dnl # export DIALOG_CONFIG="dialog-config --prefix=/usr/local" ++dnl # export DIALOG_CONFIG="/usr/bin/dialog-config --prefix=/usr/local" ++dnl ++dnl ============================================================ ++dnl ++dnl ============================================================ ++dnl auxilliary macros ++dnl ============================================================ ++AC_DEFUN([_AC_DIALOG_ERROR], [dnl ++AC_MSG_RESULT([*FAILED*]) ++cat <<EOT | sed -e 's/^[[ ]]*/ | /' -e 's/>>/ /' 1>&2 ++$1 ++EOT ++exit 1 ++]) ++ ++AC_DEFUN([_AC_DIALOG_VERBOSE], [dnl ++if test ".$verbose" = .yes; then ++ AC_MSG_RESULT([ $1]) ++fi ++]) ++ ++dnl ============================================================ ++dnl the user macro ++dnl ============================================================ ++AC_DEFUN([AC_CHECK_DIALOG], [dnl ++dnl ++dnl ============================================================ ++dnl prerequisites ++dnl ============================================================ ++AC_REQUIRE([AC_PROG_CC])dnl ++AC_REQUIRE([AC_PROG_CPP])dnl ++dnl ++dnl ============================================================ ++dnl set DIALOG_CONFIG variable ++dnl ============================================================ ++if test -z "$DIALOG_CONFIG"; then ++ DIALOG_CONFIG='dialog-config' ++fi ++dnl ++DIALOG_CFLAGS='' ++DIALOG_LDFLAGS='' ++DIALOG_LIBS='' ++AC_SUBST(DIALOG_CFLAGS) ++AC_SUBST(DIALOG_LDFLAGS) ++AC_SUBST(DIALOG_LIBS) ++dnl ++dnl ============================================================ ++dnl command line options ++dnl ============================================================ ++_AC_DIALOG_VERBOSE([]) ++AC_ARG_WITH(dialog,dnl ++[ --with-dialog[=ARG] Build with dialog Library (default=]ifelse([$2],,yes,$2)[)],dnl ++,dnl ++with_dialog="ifelse([$2],,yes,$2)" ++)dnl ++AC_ARG_WITH(dialog-test,dnl ++[ --with-dialog-test Perform dialog Sanity Test (default=]ifelse([$3],,yes,$3)[)],dnl ++,dnl ++with_dialog_test="ifelse([$3],,yes,$3)" ++)dnl ++_AC_DIALOG_VERBOSE([+ Command Line Options:]) ++_AC_DIALOG_VERBOSE([ o --with-dialog=$with_dialog]) ++_AC_DIALOG_VERBOSE([ o --with-dialog-test=$with_dialog_test]) ++dnl ++dnl ============================================================ ++dnl configuration ++dnl ============================================================ ++if test ".$with_dialog" != .no; then ++ dialog_subdir=no ++ dialog_subdir_opts='' ++ case "$with_dialog" in ++ subdir:* ) ++ dialog_subdir=yes ++ changequote(, )dnl ++ dialog_subdir_opts=`echo $with_dialog | sed -e 's/^subdir:[^ ]*[ ]*//'` ++ with_dialog=`echo $with_dialog | sed -e 's/^subdir:\([^ ]*\).*$/\1/'` ++ changequote([, ])dnl ++ ;; ++ esac ++ dialog_version="" ++ dialog_location="" ++ dialog_type="" ++ dialog_cflags="" ++ dialog_ldflags="" ++ dialog_libs="" ++ if test ".$with_dialog" = .yes; then ++ # via config script in $PATH ++ changequote(, )dnl ++ dialog_version=`($DIALOG_CONFIG --version) 2>/dev/null |\ ++ sed -e 's/^.*\([0-9]\.[0-9]*[-][0-9]*\).*$/\1/'` ++ changequote([, ])dnl ++ if test ".$dialog_version" != .; then ++ dialog_location=`$DIALOG_CONFIG --prefix` ++ dialog_type='installed' ++ dialog_cflags=`$DIALOG_CONFIG --cflags` ++ dialog_ldflags=`$DIALOG_CONFIG --ldflags` ++ dialog_libs=`$DIALOG_CONFIG --libs` ++ fi ++ elif test -d "$with_dialog"; then ++ with_dialog=`echo $with_dialog | sed -e 's;/*$;;'` ++ dialog_found=no ++ # via config script under a specified directory ++ # (a standard installation, but not a source tree) ++ if test ".$dialog_found" = .no; then ++ for _dir in $with_dialog/bin $with_dialog; do ++ if test -f "$_dir/dialog-config"; then ++ test -f "$_dir/dialog-config.in" && continue # dialog-config in source tree! ++ changequote(, )dnl ++ dialog_version=`($_dir/dialog-config --version) 2>/dev/null |\ ++ sed -e 's/^.*\([0-9]\.[0-9]*[.][0-9]*\).*$/\1/'` ++ changequote([, ])dnl ++ if test ".$dialog_version" != .; then ++ dialog_location=`$_dir/dialog-config --prefix` ++ dialog_type="installed" ++ dialog_cflags=`$_dir/dialog-config --cflags` ++ dialog_ldflags=`$_dir/dialog-config --ldflags` ++ dialog_libs=`$_dir/dialog-config --libs` ++ dialog_found=yes ++ break ++ fi ++ fi ++ done ++ fi ++ fi ++ _AC_DIALOG_VERBOSE([+ Determined Location:]) ++ _AC_DIALOG_VERBOSE([ o path: $dialog_location]) ++ _AC_DIALOG_VERBOSE([ o type: $dialog_type]) ++ if test ".$dialog_version" = .; then ++ if test ".$with_dialog" != .yes; then ++ _AC_DIALOG_ERROR([dnl ++ Unable to locate dialog under $with_dialog. ++ Please specify the correct path to either a dialog installation tree ++ (use --with-dialog=DIR if you used --prefix=DIR for installing dialog in ++ the past).]) ++ else ++ _AC_DIALOG_ERROR([dnl ++ Unable to locate dialog in any system-wide location (see \$PATH). ++ Please specify the correct path to either a dialog installation tree ++ (use --with-dialog=DIR if you used --prefix=DIR for installing dialog in ++ the past, or set the DIALOG_CONFIG environment variable to the full path ++ to dialog-config).]) ++ fi ++ fi ++ dnl ======================================================== ++ dnl Check whether the found version is sufficiently new ++ dnl ======================================================== ++ _req_version="ifelse([$1],,1.0.0,$1)" ++ for _var in dialog_version _req_version; do ++ eval "_val=\"\$${_var}\"" ++ _major=`echo $_val | sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\([[.]]\)\([[0-9]]*\)/\1/'` ++ _minor=`echo $_val | sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\([[.]]\)\([[0-9]]*\)/\2/'` ++ _micro=`echo $_val | sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\([[.]]\)\([[0-9]]*\)/\4/'` ++ _hex=`echo dummy | awk '{ printf("%d%02d%02d", major, minor, micro); }' \ ++ "major=$_major" "minor=$_minor" "micro=$_micro"` ++ eval "${_var}_hex=\"\$_hex\"" ++ done ++ _AC_DIALOG_VERBOSE([+ Determined Versions:]) ++ _AC_DIALOG_VERBOSE([ o existing: $dialog_version -> 0x$dialog_version_hex]) ++ _AC_DIALOG_VERBOSE([ o required: $_req_version -> 0x$_req_version_hex]) ++ _ok=0 ++ if test ".$dialog_version_hex" != .; then ++ if test ".$_req_version_hex" != .; then ++ if test $dialog_version_hex -ge $_req_version_hex; then ++ _ok=1 ++ fi ++ fi ++ fi ++ if test ".$_ok" = .0; then ++ _AC_DIALOG_ERROR([dnl ++ Found dialog version $dialog_version, but required at least version $_req_version. ++ Upgrade dialog under $dialog_location to $_req_version or higher first, please.]) ++ fi ++ dnl ======================================================== ++ dnl Perform dialog Sanity Compile Check ++ dnl ======================================================== ++ if test ".$with_dialog_test" = .yes; then ++ _ac_save_CFLAGS="$CFLAGS" ++ _ac_save_LDFLAGS="$LDFLAGS" ++ _ac_save_LIBS="$LIBS" ++ CFLAGS="$CFLAGS $dialog_cflags" ++ LDFLAGS="$LDFLAGS $dialog_ldflags" ++ LIBS="$LIBS $dialog_libs" ++ _AC_DIALOG_VERBOSE([+ Test Build Environment:]) ++ _AC_DIALOG_VERBOSE([ o CFLAGS="$CFLAGS"]) ++ _AC_DIALOG_VERBOSE([ o LDFLAGS="$LDFLAGS"]) ++ _AC_DIALOG_VERBOSE([ o LIBS="$LIBS"]) ++ cross_compile=no ++ define([_code1], [ ++#include <stdlib.h> ++#include <stdio.h> ++#include <strings.h> /* index(3) */ ++ ++#include <dialog.h> ++#include <dlg_colors.h> ++#include <dlg_keys.h> ++ ]) ++ define([_code2], [ ++ int status = 0; ++ ++ bzero( (void *)&dialog_vars, sizeof(DIALOG_VARS) ); ++ ++ init_dialog(stdin, stdout); ++ ++ dialog_vars.colors = 1; ++ dialog_vars.backtitle = "Test dialog Library"; ++ dialog_vars.dlg_clear_screen = 1; ++ dialog_vars.sleep_secs = 1; ++ ++ ++ dlg_put_backtitle(); ++ ++ /************************************************* ++ Ruler: 68 characters + 2 spaces left and right: ++ ++ | ----handy-ruler----------------------------------------------------- | */ ++ status = dialog_msgbox( " Dialog ==>libdialog<== [required] ", ++ "\nPackage is installed and corect.\n", ++ 5, 72, 0 ); ++ ++ if( dialog_vars.sleep_secs ) ++ (void)napms(dialog_vars.sleep_secs * 1000); ++ ++ if( dialog_vars.dlg_clear_screen ) ++ { ++ dlg_clear(); ++ (void)refresh(); ++ } ++ end_dialog(); ++ ]) ++ _AC_DIALOG_VERBOSE([+ Performing Sanity Checks:]) ++ _AC_DIALOG_VERBOSE([ o pre-processor test]) ++ AC_PREPROC_IFELSE([AC_LANG_PROGRAM([_code1], [_code2])], [_ok=yes], [_ok=no]) ++ if test ".$_ok" != .yes; then ++ _AC_DIALOG_ERROR([dnl ++ Found dialog $dialog_version under $dialog_location, but ++ was unable to perform a sanity pre-processor check. This means ++ the dialog header dialog.h was not found. ++ We used the following build environment: ++ >> CPP="$CPP" ++ See config.log for possibly more details.]) ++ fi ++ _AC_DIALOG_VERBOSE([ o link check]) ++ AC_LINK_IFELSE([AC_LANG_PROGRAM([_code1], [_code2])], [_ok=yes], [_ok=no]) ++ if test ".$_ok" != .yes; then ++ _AC_DIALOG_ERROR([dnl ++ Found dialog $dialog_version under $dialog_location, but ++ was unable to perform a sanity linker check. This means ++ the dialog library libdialog.a was not found. ++ We used the following build environment: ++ >> CC="$CC" ++ >> CFLAGS="$CFLAGS" ++ >> LDFLAGS="$LDFLAGS" ++ >> LIBS="$LIBS" ++ See config.log for possibly more details.]) ++ fi ++ _extendvars="ifelse([$4],,yes,$4)" ++ if test ".$_extendvars" != .yes; then ++ CFLAGS="$_ac_save_CFLAGS" ++ LDFLAGS="$_ac_save_LDFLAGS" ++ LIBS="$_ac_save_LIBS" ++ fi ++ else ++ _extendvars="ifelse([$4],,yes,$4)" ++ if test ".$_extendvars" = .yes; then ++ if test ".$dialog_subdir" = .yes; then ++ CFLAGS="$CFLAGS $dialog_cflags" ++ LDFLAGS="$LDFLAGS $dialog_ldflags" ++ LIBS="$LIBS $dialog_libs" ++ fi ++ fi ++ fi ++ DIALOG_CFLAGS="$dialog_cflags" ++ DIALOG_LDFLAGS="$dialog_ldflags" ++ DIALOG_LIBS="$dialog_libs" ++ AC_SUBST(DIALOG_CFLAGS) ++ AC_SUBST(DIALOG_LDFLAGS) ++ AC_SUBST(DIALOG_LIBS) ++ ++ AC_SUBST(HAVE_DIALOG, [1]) ++ ++ AC_CHECK_HEADERS(dialog.h dlg_colors.h dlg_keys.h) ++ ++ _AC_DIALOG_VERBOSE([+ Final Results:]) ++ _AC_DIALOG_VERBOSE([ o DIALOG_CFLAGS="$DIALOG_CFLAGS"]) ++ _AC_DIALOG_VERBOSE([ o DIALOG_LDFLAGS="$DIALOG_LDFLAGS"]) ++ _AC_DIALOG_VERBOSE([ o DIALOG_LIBS="$DIALOG_LIBS"]) ++fi ++if test ".$with_dialog" != .no; then ++ AC_MSG_CHECKING(for libdialog) ++ AC_MSG_RESULT([version $dialog_version, $dialog_type under $dialog_location]) ++ ifelse([$5], , :, [$5]) ++else ++ AC_MSG_CHECKING(for libdialog) ++ AC_MSG_RESULT([no]) ++ ifelse([$6], , :, [$6]) ++fi ++]) +diff --unified -Nr dialog-1.3-20210621-orig/fselect.c dialog-1.3-20210621/fselect.c +--- dialog-1.3-20210621-orig/fselect.c 2021-06-21 22:50:35.000000000 +0300 ++++ dialog-1.3-20210621/fselect.c 2021-10-15 12:06:20.635637175 +0300 +@@ -659,7 +659,7 @@ + dlg_print_size(height, width); + dlg_ctl_size(height, width); + +- dialog = dlg_new_window(height, width, ++ dialog = dlg_new_window(height + 1, width, + dlg_box_y_ordinate(height), + dlg_box_x_ordinate(width)); + dlg_register_window(dialog, "fselect", binding); +@@ -667,7 +667,7 @@ + + dlg_mouse_setbase(0, 0); + +- dlg_draw_box2(dialog, 0, 0, height, width, dialog_attr, border_attr, border2_attr); ++ dlg_draw_box2(dialog, 0, 0, height + 1, width, dialog_attr, border_attr, border2_attr); + dlg_draw_bottom_box2(dialog, border_attr, border2_attr, dialog_attr); + dlg_draw_title(dialog, title); + +@@ -676,7 +676,7 @@ + /* Draw the input field box */ + tbox_height = 1; + tbox_width = width - (4 * MARGIN + 2); +- tbox_y = height - (BTN_HIGH * 2) + MARGIN; ++ tbox_y = height - (BTN_HIGH * 2) + MARGIN + 1; + tbox_x = (width - tbox_width) / 2; + + w_text = dlg_der_window(dialog, tbox_height, tbox_width, tbox_y, tbox_x); +@@ -702,7 +702,7 @@ + else + dbox_width = (width - (6 * MARGIN + 2 * EXT_WIDE)) / 2; + dbox_height = height - MIN_HIGH; +- dbox_y = (2 * MARGIN + 1); ++ dbox_y = (2 * MARGIN + 2); + dbox_x = tbox_x; + + w_work = dlg_der_window(dialog, dbox_height, dbox_width, dbox_y, dbox_x); +@@ -760,7 +760,7 @@ + if (show_buttons) { + show_buttons = FALSE; + button = (state < 0) ? 0 : state; +- dlg_draw_buttons(dialog, height - 2, 0, buttons, button, FALSE, width); ++ dlg_draw_buttons(dialog, height - 1, 0, buttons, button, FALSE, width); + } + + if (first_trace) { +diff --unified -Nr dialog-1.3-20210621-orig/menubox.c dialog-1.3-20210621/menubox.c +--- dialog-1.3-20210621-orig/menubox.c 2020-11-24 00:03:11.000000000 +0300 ++++ dialog-1.3-20210621/menubox.c 2021-10-15 12:06:20.636637175 +0300 +@@ -48,7 +48,7 @@ + int item_no; + } ALL_DATA; + +-#define MIN_HIGH (1 + (5 * MARGIN)) ++#define MIN_HIGH 4 + + #define INPUT_ROWS 3 /* rows per inputmenu entry */ + diff --git a/src/.dialogrc b/src/.dialogrc new file mode 100644 index 0000000..50ed037 --- /dev/null +++ b/src/.dialogrc @@ -0,0 +1,144 @@ +# +# Run-time configuration file for dialog, matches Radix color scheme. +# +# Types of values: +# +# Number - <number> +# String - "string" +# Boolean - <ON|OFF> +# Attribute - (foreground,background,highlight?) + +# Set aspect-ration. +aspect = 0 + +# Set separator (for multiple widgets output). +separate_widget = "" + +# Set tab-length (for textbox tab-conversion). +tab_len = 0 + +# Make tab-traversal for checklist, etc., include the list. +visit_items = OFF + +# Shadow dialog boxes? This also turns on color. +use_shadow = ON + +# Turn color support ON or OFF +use_colors = ON + +# Screen color +screen_color = (WHITE,BLACK,ON) + +# Shadow color +shadow_color = (BLACK,BLACK,OFF) + +# Dialog box color +dialog_color = (BLACK,WHITE,OFF) + +# Dialog box title color +title_color = (BLACK,WHITE,ON) + +# Dialog box border color +border_color = (WHITE,WHITE,ON) + + +# Active button color +button_active_color = (WHITE,BLACK,ON) + +# Inactive button color +button_inactive_color = (BLACK,WHITE,OFF) + +# Active button key color +button_key_active_color = (YELLOW,BLACK,ON) + +# Inactive button key color +button_key_inactive_color = (RED,WHITE,ON) + +# Active button label color +button_label_active_color = (WHITE,BLACK,ON) + +# Inactive button label color +button_label_inactive_color = (BLACK,WHITE,ON) + +# Input box color +inputbox_color = (BLUE,WHITE,ON) + +# Input box border color +inputbox_border_color = (WHITE,WHITE,ON) + +# Search box color +searchbox_color = (YELLOW,WHITE,ON) + +# Search box title color +searchbox_title_color = (WHITE,WHITE,ON) + +# Search box border color +searchbox_border_color = (RED,WHITE,OFF) + +# File position indicator color +position_indicator_color = (RED,WHITE,ON) + +# Menu box color +menubox_color = dialog_color + +# Menu box border color +menubox_border_color = border_color + +# Item color +item_color = (BLACK,WHITE,ON) + +# Selected item color +item_selected_color = (BLACK,WHITE,OFF) + +# Tag color +tag_color = (BLACK,WHITE,ON) + +# Selected tag color +tag_selected_color = (BLACK,WHITE,OFF) + +# Tag key color +tag_key_color = (RED,WHITE,ON) + +# Selected tag key color +tag_key_selected_color = (YELLOW,BLACK,ON) + +# Check box color +check_color = dialog_color + +# Selected check box color +check_selected_color = (RED,WHITE,ON) + + +# Up arrow color +uarrow_color = (RED,WHITE,OFF) + +# Down arrow color +darrow_color = uarrow_color + + +# Item help-text color +itemhelp_color = shadow_color + +# Active form text color +form_active_text_color = inputbox_color + +# Form text color +form_text_color = (BLACK,WHITE,ON) + +# Readonly form item color +form_item_readonly_color = (CYAN,WHITE,ON) + +# Dialog box gauge color +gauge_color = (BLACK,WHITE,ON) + +# Dialog box border2 color +border2_color = dialog_color + +# Input box border2 color +inputbox_border2_color = border2_color + +# Search box border2 color +searchbox_border2_color = border2_color + +# Menu box border2 color +menubox_border2_color = border2_color diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..656ef2f --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,64 @@ + +noinst_HEADERS = defs.h cmpvers.h dlist.h btree.h jsmin.h make-pkglist.h msglog.h wrapper.h \ + pkglist.h system.h dialog-ui.h + +sbin_PROGRAMS = chrefs pkginfo pkglog make-package make-pkglist check-db-integrity check-package check-requires \ + install-package remove-package update-package install-pkglist + + +chrefs_SOURCES = chrefs.c system.c msglog.c wrapper.c +pkginfo_SOURCES = pkginfo.c system.c msglog.c wrapper.c +pkglog_SOURCES = pkglog.c system.c msglog.c wrapper.c + +check_db_integrity_SOURCES = check-db-integrity.c system.c msglog.c wrapper.c cmpvers.c dlist.c btree.c jsmin.c pkglist.c +check_db_integrity_LDADD = -lm + +check_requires_SOURCES = check-requires.c system.c msglog.c wrapper.c cmpvers.c dlist.c btree.c jsmin.c pkglist.c +check_requires_LDADD = -lm + +check_package_SOURCES = check-package.c system.c msglog.c wrapper.c cmpvers.c + +make_pkglist_SOURCES = make-pkglist.c system.c msglog.c wrapper.c cmpvers.c dlist.c btree.c jsmin.c pkglist.c +make_pkglist_LDADD = -lm + +make_package_SOURCES = make-package.c system.c msglog.c wrapper.c dlist.c +make_package_LDADD = -lm + +install_package_SOURCES = install-package.c system.c msglog.c wrapper.c cmpvers.c dlist.c +install_package_LDADD = -lm +if USE_DIALOG + install_package_SOURCES += dialog-ui.c + install_package_CFLAFS = $(DIALOG_CFLAGS) + install_package_LDFLAGS = $(DIALOG_LDFLAGS) + install_package_LDADD += $(DIALOG_LIBS) +endif + +remove_package_SOURCES = remove-package.c system.c msglog.c wrapper.c cmpvers.c dlist.c +remove_package_LDADD = -lm +if USE_DIALOG + remove_package_SOURCES += dialog-ui.c + remove_package_CFLAFS = $(DIALOG_CFLAGS) + remove_package_LDFLAGS = $(DIALOG_LDFLAGS) + remove_package_LDADD += $(DIALOG_LIBS) +endif + +update_package_SOURCES = update-package.c system.c msglog.c wrapper.c cmpvers.c dlist.c +update_package_LDADD = -lm +if USE_DIALOG + update_package_SOURCES += dialog-ui.c + update_package_CFLAFS = $(DIALOG_CFLAGS) + update_package_LDFLAGS = $(DIALOG_LDFLAGS) + update_package_LDADD += $(DIALOG_LIBS) +endif + +install_pkglist_SOURCES = install-pkglist.c system.c msglog.c wrapper.c cmpvers.c dlist.c +install_pkglist_LDADD = -lm -lpthread +if USE_DIALOG + install_pkglist_SOURCES += dialog-ui.c + install_pkglist_CFLAFS = $(DIALOG_CFLAGS) + install_pkglist_LDFLAGS = $(DIALOG_LDFLAGS) + install_pkglist_LDADD += $(DIALOG_LIBS) +endif + + +pkgdata_DATA = .dialogrc diff --git a/src/btree.c b/src/btree.c new file mode 100644 index 0000000..e64ef7e --- /dev/null +++ b/src/btree.c @@ -0,0 +1,1137 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <stddef.h> +#include <linux/limits.h> + +#include <msglog.h> + +#include <btree.h> + +struct btree *__btree_alloc( void *data ) +{ + struct btree *node = NULL; + + node = (struct btree *)malloc( sizeof( struct btree ) ); + if( !node ) { FATAL_ERROR( "Cannot allocate memory" ); } + + bzero( (void *)node, sizeof( struct btree ) ); + node->left = node->right = node; + + if( data ) node->data = data; + + return node; +} + +struct btree *btree_insert_left( struct btree *tree, struct btree *node ) +{ + if( !tree ) return node; + if( !node ) return tree; + + node->left = tree->left; + node->ltag = tree->ltag; + tree->left = node; + tree->ltag = 1; + node->right = tree; + node->rtag = 0; + + node->parent = tree; + + if( node->ltag ) + { + node->left->right = node; + } + + return node; +} + +struct btree *btree_insert_right( struct btree *tree, struct btree *node ) +{ + if( !tree ) return node; + if( !node ) return tree; + + node->right = tree->right; + node->rtag = tree->rtag; + tree->right = node; + tree->rtag = 1; + node->left = tree; + node->ltag = 0; + + node->parent = tree; + + if( node->rtag ) + { + node->right->left = node; + } + + return node; +} + + +static struct btree *__next_preorder( struct btree *node ) +{ + struct btree *next = node; + + if( !next ) return next; + + if( next->ltag ) + return next->left; + + if( next->rtag ) + return next->right; + + while( !next->rtag && next != next->right ) + next = next->right; + + if( next->ltag && next->rtag ) + next = next->right; + + return next; +} + +void btree_preorder_traversal( struct btree *root, TREE_FUNC func, void *user_data ) +{ + struct btree *next = root; + + if( !next ) return; + + do + { + if( func ) { func( next->data, user_data ); } + + next = __next_preorder( next ); + + if( next == root || next == root->right ) break; + + } while( next ); + + if( next == root ) return; + + do + { + if( func ) { func( next->data, user_data ); } + + next = __next_preorder( next ); + + if( next == root || next == root->right ) break; + + } while( next ); +} + + +static struct btree *__next_postorder( struct btree *node ) +{ + struct btree *next = NULL; + + if( !node ) return next; + + next = node->right; + + if( !node->rtag ) + return next; + + while( next->ltag ) + next = next->left; + + return next; +} + +void btree_postorder_traversal( struct btree *root, TREE_FUNC func, void *user_data ) +{ + struct btree *next = root; + + if( !next ) return; + + while( next->ltag ) + next = next->left; + + for( ; next ; next = __next_postorder( next ) ) + { + if( func ) { func( next->data, user_data ); } + + if( next == root ) break; + } + + next = __next_postorder( next ); + + for( ; next ; next = __next_postorder( next ) ) + { + if( next == root ) break; + + if( func ) { func( next->data, user_data ); } + } + +} + + +static struct btree *__start_endorder( struct btree *node ) +{ + struct btree *next = node; + + if( !next ) return next; + + do + { + while( next->ltag ) + next = next->left; + + if( !next->rtag ) + return next; + else + next = next->right; + + while( next->ltag ) + next = next->left; + + } while( next->rtag ); + + return next; +} + +static struct btree *__next_endorder( struct btree *node ) +{ + struct btree *next = node; + + if( !next ) return next; + + if( next->parent->rtag && (next != next->parent->right) ) + next = __start_endorder( next->parent->right ); + else + next = next->parent; + + return next; +} + +void btree_endorder_traversal( struct btree *root, TREE_FUNC func, void *user_data ) +{ + struct btree *next = root; + + if( !next ) return; + + next = __start_endorder( next ); + + do + { + if( func ) { func( next->data, user_data ); } + + if( next == root ) break; + + next = __next_endorder( next ); + + } while( next ); +} + + +#if ! defined( max ) +#define max(a,b) \ + ({ typeof (a) _a = (a); \ + typeof (b) _b = (b); \ + _a > _b ? _a : _b; }) +#endif + +/************************************ + Tree height and width calculation: + ───────────────────────────────── + + height: + ┬ + A │ 1 + | \ ├ + B D │ 2 + | | ├ + C E │ 3 + | | \ ├ + K H F │ 4 + | \ ├ + J G │ 5 + ├──┬──┬──┬──┼ + width: 1 2 3 4 + + ************************************/ + +int btree_height( struct btree *root ) +{ + struct btree *next = root; + int height = 0; + + if( !next ) return height; + + next = __start_endorder( next ); + + do + { + struct btree *p = next; + int h = 0; + + while( p->parent ) { ++h; p = p->parent; } + height = max( height, h ); + + if( next == root ) break; + + next = __next_endorder( next ); + + } while( next ); + + return height + 1; +} + +int btree_width( struct btree *root ) +{ + int ret = 0, lw = 0, rw = 0; + struct btree *next = NULL, *left = NULL, *right = NULL; + + if( !root ) return ret; + + left = next = ( root->ltag ) ? root->left : NULL; + + if( next ) + { + ++lw; + + next = __start_endorder( next ); + + do + { + if( next->ltag && next->rtag ) + ++lw; + + if( next == left ) break; + + next = __next_endorder( next ); + + } while( next ); + } + + right = next = ( root->rtag ) ? root->right : NULL; + + if( next ) + { + ++rw; + + next = __start_endorder( next ); + + do + { + if( next->ltag && next->rtag ) + ++rw; + + if( next == right ) break; + + next = __next_endorder( next ); + + } while( next ); + } + + ret = lw + rw; + + return (ret) ? ret : 1; +} + +int btree_left_width( struct btree *root ) +{ + int lw = 0; + struct btree *next = NULL, *left = NULL; + + if( !root ) return lw; + + left = next = ( root->ltag ) ? root->left : NULL; + + if( next ) + { + ++lw; + + next = __start_endorder( next ); + + do + { + if( next->ltag && next->rtag ) + ++lw; + + if( next == left ) break; + + next = __next_endorder( next ); + + } while( next ); + } + + return (lw) ? lw : 1; +} + +int btree_right_width( struct btree *root ) +{ + int rw = 0; + struct btree *next = NULL, *right = NULL; + + if( !root ) return rw; + + right = next = ( root->rtag ) ? root->right : NULL; + + if( next ) + { + ++rw; + + next = __start_endorder( next ); + + do + { + if( next->ltag && next->rtag ) + ++rw; + + if( next == right ) break; + + next = __next_endorder( next ); + + } while( next ); + } + + return (rw) ? rw : 1; +} + + +struct btree *btree_detach( struct btree *node ) +{ + struct btree *parent = node->parent; + + if( !node ) return node; + + if( parent->right == node ) + { + struct btree *rlink = node; + + while( rlink->rtag ) + rlink = rlink->right; + rlink = rlink->right; + + parent->right = rlink; + parent->rtag = 0; + } + else + { + struct btree *llink = node; + + while( llink->ltag ) + llink = llink->left; + llink = llink->left; + + parent->left = llink; + parent->ltag = 0; + } + + return node; +} + + +void __btree_free( struct btree *root ) +{ + struct btree *next = root; + + if( !next ) return; + + next = __start_endorder( next ); + + do + { + struct btree *tmp = next; + + if( next == root ) + { + free( tmp ); + break; + } + next = __next_endorder( next ); + free( tmp ); + + } while( next ); +} + +void btree_free( struct btree *root, TREE_FUNC free_func ) +{ + btree_endorder_traversal( root, free_func, NULL ); + __btree_free( root ); +} + + +/******************************************************************* + Stack functions: + */ +struct btree_stack *btree_stack_alloc( const size_t n, const size_t u ) +{ + struct btree_stack *stack = NULL; + + if( !n || !u ) return stack; + + stack = (struct btree_stack *)malloc( sizeof( struct btree_stack ) ); + if( !stack ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)stack, sizeof( struct btree_stack ) ); + + stack->__mem_size = n * u; + stack->__unit_size = u; + + stack->__mem = malloc( stack->__mem_size ); + if( !stack->__mem ) { FATAL_ERROR( "Cannot allocate memory" ); } + + bzero( stack->__mem, stack->__mem_size ); + stack->__cur_brk = stack->__mem; + + return stack; +} + +void btree_stack_free( struct btree_stack **pstack ) +{ + struct btree_stack *stack = NULL; + + if( !pstack ) return; + + stack = *pstack; + + if( !stack ) return; + if( stack->__mem ) free( stack->__mem ); + + free( stack ); + + *pstack = (struct btree_stack *)NULL; +} + +int btree_stack_is_empty( struct btree_stack *stack ) +{ + if( !stack ) return 1; + if( stack->__mem == stack->__cur_brk ) return 1; + return 0; +} + +int btree_stack_depth( struct btree_stack *stack ) +{ + if( !stack ) return -1; + if( btree_stack_is_empty( stack ) ) + return 0; + + return (stack->__cur_brk - stack->__mem) / stack->__unit_size; +} + +static int __stack_brk( struct btree_stack *stack, void *end_d ) +{ + void *ptr = NULL; + + if( !stack ) return -1; + + ptr = stack->__mem; + if( !ptr ) return -1; + + if( end_d < ptr ) + { + return -1; + } + if( end_d > ptr + stack->__mem_size ) + { + size_t size = stack->__mem_size + stack->__mem_size; + + if( (end_d - (ptr + stack->__mem_size)) < stack->__mem_size ) + { + ptrdiff_t offset = stack->__cur_brk - stack->__mem; + stack->__mem = realloc( stack->__mem, size ); + if( !stack->__mem ) { FATAL_ERROR( "Cannot allocate memory" ); } + stack->__mem_size = size; + stack->__cur_brk = stack->__mem + offset; + ptr = stack->__mem; + return 0; + } + else + return -1; + } + + /* + __cur_brk = end_d; + + The function __stack_brk() only checks boundaries of + memory. The value of __cur_brk is set by __stack_sbrk() + function. + *********************************************************/ + + return 0; + +} /* End of __stack_brk() */ + +static void *__stack_sbrk( struct btree_stack *stack, int incr ) +{ + void *ptr = NULL; + int rc; + + if( !stack ) return ptr; + + ptr = stack->__cur_brk; + + if( incr == 0 ) return( ptr ); + + rc = __stack_brk( stack, ptr + incr ); + if( rc == -1 ) + { + /* errno is set into __mpu_brk() */ + return NULL; + } + + ptr = stack->__cur_brk; + stack->__cur_brk = ptr + (int)incr; + + return ptr; + +} /* End of __stack_sbrk() */ + + +int btree_stack_push( struct btree_stack *stack, const void *unit ) +{ + void *uptr, *ptr = NULL; + + if( !stack ) return -1; + + ptr = __stack_sbrk( stack, stack->__unit_size ); + + if( ptr ) + { + uptr = memcpy( ptr, unit, stack->__unit_size ); + if( !uptr ) + { + return -1; + } + } + else + { + return -1; + } + + return 0; +} + +int btree_stack_pop( struct btree_stack *stack, void *unit ) +{ + void *uptr, *ptr = NULL; + + if( !stack ) return -1; + + ptr = __stack_sbrk( stack, -(int)stack->__unit_size ); + + if( ptr ) + { + ptr -= stack->__unit_size; + uptr = memcpy( unit, (const void *)ptr, stack->__unit_size ); + if( !uptr ) + { + bzero( unit, stack->__unit_size ); + return -1; + } + } + else + { + bzero( unit, stack->__unit_size ); + return -1; + } + + return 0; +} +/* + End of stack functions. + *******************************************************************/ + +static void btree_print_line( const char *line, int indent, const struct _bctx *ctx ) +{ + char *p, buf[PATH_MAX*2]; + int depth = 0, max_depth = PATH_MAX + PATH_MAX / 2; + + if( !line || !ctx ) return; + + if( !indent ) + { + buf[0] = '\0'; + p = (char *)&buf[0]; + depth = 0; + } + else + { + buf[0] = ' '; + buf[1] = '\0'; + p = (char *)&buf[1]; + depth = ctx->indent; + } + + if( depth < 1 ) depth = 0; + if( depth > max_depth ) depth = max_depth; + + while( depth ) + { + (void)sprintf( p, " " ); --depth; ++p; *p = '\0'; + } + + (void)sprintf( p, "%s", line ); + + fprintf( ctx->output, (char *)&buf[0] ); + fflush( ctx->output ); +} + + +static void __btree_clean_prn_flags( struct btree *root ) +{ + struct btree *next = root; + + if( !next ) return; + + next = __start_endorder( next ); + + do + { + next->lprn = 0; + next->rprn = 0; + + if( next == root ) break; + + next = __next_endorder( next ); + + } while( next ); +} + +void btree_print_json( FILE *output, struct btree *root, TREE_FUNC func ) +{ + struct btree *next = root; + struct _bctx ctx; + + struct btree_stack *stack; + struct btree_stack *lstack; + + if( !output || !next || !func ) return; + + bzero( (void *)&ctx, sizeof(struct _bctx) ); + + stack = btree_stack_alloc( (const size_t)btree_height(next), sizeof(struct _bctx) ); + + ctx.output = output; + ctx.node = next; + ctx.indent = 0; + + __btree_clean_prn_flags( root ); + + btree_stack_push( stack, (const void *)&ctx ); + + do + { + + btree_stack_pop( stack, (void *)&ctx ); + + func( next->data, (void *)&ctx ); + + if( ctx.node->ltag || ctx.node->rtag ) + { + btree_print_line( ",\n", 0, &ctx ); + btree_print_line( "\"children\": [\n", 1, &ctx ); + btree_print_line( " {\n", 1, &ctx ); + ctx.indent += 2; + btree_stack_push( stack, (const void *)&ctx ); + } + else + { + btree_stack_push( stack, (const void *)&ctx ); + + if( !ctx.node->ltag && !ctx.node->rtag ) + { + if( ctx.node->parent ) + { + struct _bctx cctx; + + btree_stack_pop( stack, (void *)&ctx ); + + memcpy( (void *)&cctx, (const void *)&ctx, sizeof(struct _bctx) ); + + if( (ctx.node->lprn == ctx.node->ltag) && (ctx.node->rprn == ctx.node->rtag) ) + { + if( ctx.node->parent->ltag && ctx.node->parent->left == ctx.node ) + ctx.node->parent->lprn = 1; + if( ctx.node->parent->rtag && ctx.node->parent->right == ctx.node ) + ctx.node->parent->rprn = 1; + } + + if( btree_stack_depth( stack ) > 0 ) + { + struct btree_stack *pstack = btree_stack_alloc( (const size_t)btree_height(root), sizeof(struct _bctx) ); + struct _bctx pctx; + + do + { + btree_stack_pop( stack, (void *)&pctx ); + + if( (cctx.node->lprn == cctx.node->ltag) && (cctx.node->rprn == cctx.node->rtag) ) + { + if( cctx.node->parent && cctx.node->parent->ltag && cctx.node->parent->left == cctx.node ) + { + cctx.node->parent->lprn = 1; + + if( cctx.node->parent->rtag ) + { + if( !cctx.node->ltag && !cctx.node->rtag ) + { + btree_print_line( "\n", 0, &ctx ); + } + --ctx.indent; + btree_print_line( "},\n", 1, &ctx ); + btree_print_line( "{\n", 1, &ctx ); + } + else + { + if( !cctx.node->ltag && !cctx.node->rtag ) + { + btree_print_line( "\n", 0, &ctx ); + } + --ctx.indent; + btree_print_line( "}\n", 1, &ctx ); + --ctx.indent; + btree_print_line( "]\n", 1, &ctx ); + } + } + if( cctx.node->parent && cctx.node->parent->rtag && cctx.node->parent->right == cctx.node ) + { + cctx.node->parent->rprn = 1; + + if( !cctx.node->ltag && !cctx.node->rtag ) + { + btree_print_line( "\n", 0, &ctx ); + } + --ctx.indent; + btree_print_line( "}\n", 1, &ctx ); + --ctx.indent; + btree_print_line( "]\n", 1, &ctx ); + } + + } + + memcpy( (void *)&cctx, (const void *)&pctx, sizeof(struct _bctx) ); + + if( (pctx.node->ltag && (pctx.node->lprn < pctx.node->ltag)) || (pctx.node->rtag && (pctx.node->rprn < pctx.node->rtag)) ) + { + btree_stack_push( pstack, (const void *)&pctx ); + } + + } while( !btree_stack_is_empty( stack ) ); + + while( btree_stack_pop( pstack, (void *)&pctx ) == 0 ) + { + btree_stack_push( stack, (const void *)&pctx ); + } + + btree_stack_free( &pstack ); + } + else + { + btree_print_line( "\n", 0, &ctx ); + } + + } /* End if( parent ) */ + else + { + btree_stack_pop( stack, (void *)&ctx ); + } + + } /* End if( no children ) */ + + } /* End if( any child ) */ + + + next = __next_preorder( next ); + + + if( next != root ) + { + btree_stack_pop( stack, (void *)&ctx ); + btree_stack_push( stack, (const void *)&ctx ); + + ctx.output = output; + ctx.node = next; + + if( btree_stack_push( stack, (const void *)&ctx ) == -1 ) FATAL_ERROR( "btree stack is destroyed" ); + } + + if( next == root || next == root->right ) break; + + } while( next ); + + + if( next == root ) + { + struct _bctx ctx; + + ctx.output = output; + ctx.node = root; + ctx.indent = 2; + + /* If we in root node then there is no right subtree */ + if( !root->ltag && !root->rtag ) + { + btree_print_line( "\n", 0, &ctx ); + } + + if( root->ltag && (root->lprn < root->ltag) ) + { + root->lprn = 1; + + --ctx.indent; + btree_print_line( "}\n", 1, &ctx ); + --ctx.indent; + btree_print_line( "]\n", 1, &ctx ); + } + } + + if( next == root ) + { + btree_stack_free( &stack ); + return; + } + + do + { + + btree_stack_pop( stack, (void *)&ctx ); + + func( next->data, (void *)&ctx ); + + if( ctx.node->ltag || ctx.node->rtag ) + { + btree_print_line( ",\n", 0, &ctx ); + btree_print_line( "\"children\": [\n", 1, &ctx ); + btree_print_line( " {\n", 1, &ctx ); + ctx.indent += 2; + btree_stack_push( stack, (const void *)&ctx ); + } + else + { + btree_stack_push( stack, (const void *)&ctx ); + + if( !ctx.node->ltag && !ctx.node->rtag ) + { + if( ctx.node->parent ) + { + struct _bctx cctx; + + btree_stack_pop( stack, (void *)&ctx ); + + memcpy( (void *)&cctx, (const void *)&ctx, sizeof(struct _bctx) ); + + if( (ctx.node->lprn == ctx.node->ltag) && (ctx.node->rprn == ctx.node->rtag) ) + { + if( ctx.node->parent->ltag && ctx.node->parent->left == ctx.node ) + ctx.node->parent->lprn = 1; + if( ctx.node->parent->rtag && ctx.node->parent->right == ctx.node ) + ctx.node->parent->rprn = 1; + } + + if( btree_stack_depth( stack ) > 0 ) + { + struct btree_stack *pstack = btree_stack_alloc( (const size_t)btree_height(root), sizeof(struct _bctx) ); + struct _bctx pctx; + + do + { + btree_stack_pop( stack, (void *)&pctx ); + + if( (cctx.node->lprn == cctx.node->ltag) && (cctx.node->rprn == cctx.node->rtag) ) + { + if( cctx.node->parent && cctx.node->parent->ltag && cctx.node->parent->left == cctx.node ) + { + cctx.node->parent->lprn = 1; + + if( cctx.node->parent->rtag ) + { + if( !cctx.node->ltag && !cctx.node->rtag ) + { + btree_print_line( "\n", 0, &ctx ); + } + --ctx.indent; + btree_print_line( "},\n", 1, &ctx ); + btree_print_line( "{\n", 1, &ctx ); + } + else + { + if( !cctx.node->ltag && !cctx.node->rtag ) + { + btree_print_line( "\n", 0, &ctx ); + } + --ctx.indent; + btree_print_line( "}\n", 1, &ctx ); + --ctx.indent; + btree_print_line( "]\n", 1, &ctx ); + } + } + if( cctx.node->parent && cctx.node->parent->rtag && cctx.node->parent->right == cctx.node ) + { + cctx.node->parent->rprn = 1; + + if( !cctx.node->ltag && !cctx.node->rtag ) + { + btree_print_line( "\n", 0, &ctx ); + } + --ctx.indent; + btree_print_line( "}\n", 1, &ctx ); + --ctx.indent; + btree_print_line( "]\n", 1, &ctx ); + } + + } + + memcpy( (void *)&cctx, (const void *)&pctx, sizeof(struct _bctx) ); + + if( (pctx.node->ltag && (pctx.node->lprn < pctx.node->ltag)) || (pctx.node->rtag && (pctx.node->rprn < pctx.node->rtag)) ) + { + btree_stack_push( pstack, (const void *)&pctx ); + } + + } while( !btree_stack_is_empty( stack ) ); + + while( btree_stack_pop( pstack, (void *)&pctx ) == 0 ) + { + btree_stack_push( stack, (const void *)&pctx ); + } + + btree_stack_free( &pstack ); + } + else + { + btree_print_line( "\n", 0, &ctx ); + } + + } /* End if( parent ) */ + else + { + btree_stack_pop( stack, (void *)&ctx ); + } + + } /* End if( no children ) */ + + } /* End if( any child ) */ + + + next = __next_preorder( next ); + + + if( next != root ) + { + btree_stack_pop( stack, (void *)&ctx ); + btree_stack_push( stack, (const void *)&ctx ); + + ctx.output = output; + ctx.node = next; + + if( btree_stack_push( stack, (const void *)&ctx ) == -1 ) FATAL_ERROR( "btree stack is destroyed" ); + } + + if( next == root || next == root->right ) break; + + } while( next ); + + btree_stack_free( &stack ); +} + + +int btree_compare( struct btree *root_a, struct btree *root_b, TREE_CMPF cmp_func ) +{ + struct btree *next_a = root_a, *next_b = root_b; + + if( !next_a || !next_b || !cmp_func ) return 1; + + next_a = __start_endorder( next_a ); + next_b = __start_endorder( next_b ); + + do + { + if( cmp_func( next_a->data, next_b->data ) ) + { + return 1; + } + + if( next_a == root_a || next_b == root_b ) + { + if( (next_a == root_a) && (next_b == root_b) ) + break; + else + return 1; + } + + next_a = __next_endorder( next_a ); + next_b = __next_endorder( next_b ); + + } while( next_a && next_b ); + + return 0; +} + + +static void __remove_duplicates( struct btree *root, struct btree *node, TREE_CMPF cmp_func, TREE_FUNC free_func ) +{ + struct btree *next = NULL, *rem = NULL; + + if( !root || !node || !cmp_func || node == root ) return; + + + next = __next_endorder( node ); + + if( next == root ) return; + + do + { + if( !cmp_func( node->data, next->data ) ) + rem = next; + else + rem = NULL; + + if( next == root ) break; + + next = __next_endorder( next ); + + if( rem && !rem->ltag && !rem->rtag ) + { + struct btree *node = btree_detach( rem ); + + if( free_func ) + { + btree_free( node, free_func ); + } + else + { + __btree_free( node ); + } + } + + } while( next ); +} + +void btree_reduce( struct btree *root, TREE_CMPF cmp_func, TREE_FUNC free_func ) +{ + struct btree *next = root; + + if( !next || ! cmp_func ) return; + + next = __start_endorder( next ); + + do + { + __remove_duplicates( root, next, cmp_func, free_func ); + + if( next == root ) break; + + next = __next_endorder( next ); + + } while( next ); +} diff --git a/src/btree.h b/src/btree.h new file mode 100644 index 0000000..982e57a --- /dev/null +++ b/src/btree.h @@ -0,0 +1,93 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +#ifndef _BTREE_H_ +#define _BTREE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + + +struct btree { + struct btree *left, *right; + int ltag, rtag; + int lprn, rprn; + + struct btree *parent; + + void *data; +}; + +typedef void (*TREE_FUNC) ( void *data, void *user_data ); +typedef int (*TREE_CMPF) ( const void *a, const void *b ); + + +extern struct btree *__btree_alloc( void *data ); +extern struct btree *btree_insert_left( struct btree *tree, struct btree *node ); +extern struct btree *btree_insert_right( struct btree *tree, struct btree *node ); + +extern void btree_preorder_traversal( struct btree *root, TREE_FUNC func, void *user_data ); +extern void btree_postorder_traversal( struct btree *root, TREE_FUNC func, void *user_data ); +extern void btree_endorder_traversal( struct btree *root, TREE_FUNC func, void *user_data ); + +extern int btree_height( struct btree *root ); +extern int btree_width( struct btree *root ); +extern int btree_left_width( struct btree *root ); +extern int btree_right_width( struct btree *root ); + +extern struct btree *btree_detach( struct btree *node ); + +extern int btree_compare( struct btree *root_a, struct btree *root_b, TREE_CMPF cmp_func ); + +void __btree_free( struct btree *root ); +void btree_free( struct btree *root, TREE_FUNC free_func ); + + +struct btree_stack { + void *__mem; + void *__cur_brk; + size_t __mem_size; + size_t __unit_size; +}; + +extern struct btree_stack *btree_stack_alloc( const size_t n, const size_t u ); +extern void btree_stack_free( struct btree_stack **pstack ); +extern int btree_stack_push( struct btree_stack *stack, const void *unit ); +extern int btree_stack_pop( struct btree_stack *stack, void *unit ); + +extern int btree_stack_is_empty( struct btree_stack *stack ); +extern int btree_stack_depth( struct btree_stack *stack ); + + +struct _bctx +{ + FILE *output; + struct btree *node; + int indent; +}; + +extern void btree_reduce( struct btree *root, TREE_CMPF cmp_func, TREE_FUNC func ); +extern void btree_print_json( FILE *output, struct btree *root, TREE_FUNC func ); + + +#ifdef __cplusplus +} /* ... extern "C" */ +#endif + +#endif /* _BTREE_H_ */ diff --git a/src/check-db-integrity.c b/src/check-db-integrity.c new file mode 100644 index 0000000..87120b8 --- /dev/null +++ b/src/check-db-integrity.c @@ -0,0 +1,3241 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <sys/types.h> +#include <stdint.h> +#include <dirent.h> +#include <sys/stat.h> /* chmod(2) */ +#include <sys/file.h> /* flock(2) */ +#include <fcntl.h> +#include <linux/limits.h> +#include <alloca.h> /* alloca(3) */ +#include <string.h> /* strdup(3) */ +#include <strings.h> /* index(3) */ +#include <libgen.h> /* basename(3) */ +#include <ctype.h> /* tolower(3) */ +#include <errno.h> +#include <time.h> +#include <sys/time.h> +#include <pwd.h> +#include <grp.h> +#include <stdarg.h> +#include <unistd.h> + +#include <math.h> + +#include <sys/wait.h> + +#include <sys/resource.h> + +#include <signal.h> +#if !defined SIGCHLD && defined SIGCLD +# define SIGCHLD SIGCLD +#endif + +#define _GNU_SOURCE +#include <getopt.h> + +#include <msglog.h> +#include <wrapper.h> +#include <system.h> +#include <dlist.h> +#include <pkglist.h> + +#define PROGRAM_NAME "check-db-integrity" + +#include <defs.h> + + +char *program = PROGRAM_NAME; +char *root = NULL, *pkgs_path = NULL, *errlog_fname = NULL, + *tmpdir = NULL; + +int close_log_file = 0; + +int exit_status = EXIT_SUCCESS; /* errors counter */ +char *selfdir = NULL; + +int __done = 0, __child = 0; + +enum _input_type { + IFMT_PKG = 0, + IFMT_LOG, + + IFMT_UNKNOWN +} input_format = IFMT_PKG; + +enum _priority priority = REQUIRED; + + +void free_resources() +{ + if( root ) { free( root ); root = NULL; } + if( pkgs_path ) { free( pkgs_path ); pkgs_path = NULL; } + if( errlog_fname ) { free( errlog_fname ); errlog_fname = NULL; } + + if( selfdir ) { free( selfdir ); selfdir = NULL; } + + if( close_log_file ) + { + (void)fflush( errlog ); + fclose( errlog ); + } + + free_tarballs(); + free_packages(); +} + +void usage() +{ + free_resources(); + + fprintf( stdout, "\n" ); + fprintf( stdout, "Usage: %s [options] [pkglogs path]\n", program ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Check Setup Database integrity - is a procedure for checking data\n" ); + fprintf( stdout, "integrity and correcting errors. This procedure removes invalid\n" ); + fprintf( stdout, "inter-package links, and also outputs the lists of packages that\n" ); + fprintf( stdout, "need to be installed to restore system health.\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Options:\n" ); + fprintf( stdout, " -h,--help Display this information.\n" ); + fprintf( stdout, " -v,--version Display the version of %s utility.\n", program ); + fprintf( stdout, " -r,--root=<DIR> Target rootfs path.\n" ); + fprintf( stdout, " -l,--log=<LOGFILE> Log file name.\n" ); + + fprintf( stdout, "\n" ); + fprintf( stdout, "Optional parameter:\n" ); + fprintf( stdout, " [pkglogs path] The PKGLOGs path in the Setup Database.\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "If the [pkglogs path] is defined, then LOG information outputs to\n" ); + fprintf( stdout, "stderr and options --root, and --log are ignored.\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "By default, the Setup Database is located in the\n" ); + fprintf( stdout, " '/%s/'\n", SETUP_DB_PATH ); + fprintf( stdout, "directory, the PKGLOGs files of installed packages are located in the\n" ); + fprintf( stdout, " '/%s/'\n", PACKAGES_PATH ); + fprintf( stdout, "directory; the log of this procedure is written to the\n" ); + fprintf( stdout, " '/%s/%s.log'\n", LOG_PATH, program ); + fprintf( stdout, "file.\n" ); + fprintf( stdout, "\n" ); +/* + |==================================================================| + Check Setup Database integrity - это процедура проверки целостности + данных и исправления ошибок. Данная процедура удаляет невалидные + межпакетные ссылки, а также выдает список пакетов, которые необходимо + инсталлировать для восстановления работоспособности системы. + + По умолчанию инсталляционная база находится в каталоге + + '/var/log/radix/' , + + описания инсталлированных пакетов находятся в каталоге + + '/var/log/radix/packages/' ; + + лог записывается в файл + + '/var/log/radix/check-db-integrity.log' . + |==================================================================| + */ + + exit( EXIT_FAILURE ); +} + +void to_lowercase( char *s ) +{ + char *p = s; + while( p && *p ) { int c = *p; *p = tolower( c ); ++p; } +} + +void to_uppercase( char *s ) +{ + char *p = s; + while( p && *p ) { int c = *p; *p = toupper( c ); ++p; } +} + +void version() +{ + char *upper = NULL; + + upper = (char *)alloca( strlen( program ) + 1 ); + + strcpy( (char *)upper, (const char *)program ); + to_uppercase( upper ); + + fprintf( stdout, "%s (%s) %s\n", program, upper, PROGRAM_VERSION ); + + fprintf( stdout, "Copyright (C) 2019 Andrey V.Kosteltsev.\n" ); + fprintf( stdout, "This is free software. There is NO warranty; not even\n" ); + fprintf( stdout, "for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" ); + fprintf( stdout, "\n" ); + + free_resources(); + exit( EXIT_SUCCESS ); +} + + +static void remove_trailing_slash( char *dir ) +{ + char *s; + + if( !dir || dir[0] == '\0' ) return; + + s = dir + strlen( dir ) - 1; + while( *s == '/' ) + { + *s = '\0'; --s; + } +} + + +static int _mkdir_p( const char *dir, const mode_t mode ) +{ + char *buf; + char *p = NULL; + struct stat sb; + + if( !dir ) return -1; + + buf = (char *)alloca( strlen( dir ) + 1 ); + strcpy( buf, dir ); + + remove_trailing_slash( buf ); + + /* check if path exists and is a directory */ + if( stat( buf, &sb ) == 0 ) + { + if( S_ISDIR(sb.st_mode) ) + { + return 0; + } + } + + /* mkdir -p */ + for( p = buf + 1; *p; ++p ) + { + if( *p == '/' ) + { + *p = 0; + /* test path */ + if( stat( buf, &sb ) != 0 ) + { + /* path does not exist - create directory */ + if( mkdir( buf, mode ) < 0 ) + { + return -1; + } + } else if( !S_ISDIR(sb.st_mode) ) + { + /* not a directory */ + return -1; + } + *p = '/'; + } + } + + /* test path */ + if( stat( buf, &sb ) != 0 ) + { + /* path does not exist - create directory */ + if( mkdir( buf, mode ) < 0 ) + { + return -1; + } + } else if( !S_ISDIR(sb.st_mode) ) + { + /* not a directory */ + return -1; + } + + return 0; +} + + +static void _rm_tmpdir( const char *dirpath ) +{ + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + if( stat( dirpath, &path_sb ) == -1 ) + { + return; /* stat returns error code; errno is set */ + } + + if( S_ISDIR(path_sb.st_mode) == 0 ) + { + return; /* dirpath is not a directory */ + } + + if( (dir = opendir(dirpath) ) == NULL ) + { + return; /* Cannot open direcroty; errno is set */ + } + + len = strlen( dirpath ); + + while( (entry = readdir( dir )) != NULL) + { + + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( stat( path, &entry_sb ) == 0 ) + { + if( S_ISDIR(entry_sb.st_mode) ) + { + /* recursively remove a nested directory */ + _rm_tmpdir( path ); + } + else + { + /* remove a file object */ + (void)unlink( path ); + } + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + + } + + /* remove the devastated directory and close the object of this directory */ + (void)rmdir( dirpath ); + + closedir( dir ); +} + + +static char *_mk_tmpdir( void ) +{ + char *buf = NULL, *p, *tmp = "/tmp"; + size_t len = 0, size = 0; + + (void)umask( S_IWGRP | S_IWOTH ); /* octal 022 */ + + /* Get preferred directory for tmp files */ + if( (p = getenv( "TMP" )) != NULL ) { + tmp = p; + } + else if( (p = getenv( "TEMP" )) != NULL ) { + tmp = p; + } + + size = strlen( tmp ) + strlen( DISTRO_NAME ) + strlen( program ) + 12; + + buf = (char *)malloc( size ); + if( !buf ) return NULL; + + len = snprintf( buf, size, (const char *)"%s/%s/%s-%.7u", tmp, DISTRO_NAME, program, getpid() ); + if( len == 0 || len == size - 1 ) + { + free( buf ); return NULL; + } + + _rm_tmpdir( (const char *)&buf[0] ); + + if( _mkdir_p( buf, S_IRWXU | S_IRWXG | S_IRWXO ) == 0 ) + { + return buf; + } + + free( buf ); return NULL; +} + + +/******************************************** + LOCK FILE functions: + */ +static int __lock_file( FILE *fp ) +{ + int fd = fileno( fp ); + + if( flock( fd, LOCK_EX ) == -1 ) + { + return -1; + /* + Мы не проверяем errno == EWOULDBLOCK, так какданная ошибка + говорит о том что файл заблокирован другим процессом с флагом + LOCK_NB, а мы не собираемся циклически проверять блокировку. + У нас все просто: процесс просто ждет освобождения дескриптора + и не пытается во время ожидания выполнять другие задачи. + */ + } + return fd; +} + +static void __unlock_file( int fd ) +{ + if( fd != -1 ) flock( fd, LOCK_UN ); + /* + Здесь, в случае ошибки, мы не будем выводить + никаких сообщений. Наш процесс выполняет простую + атомарную задачу и, наверное, завершится в скором + времени, освободив все дескрипторы. + */ +} +/* + End of LOCK FILE functions. + ********************************************/ + + +void fatal_error_actions( void ) +{ + logmsg( errlog, MSG_NOTICE, "Free resources on FATAL error..." ); + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); +} + +void sigint( int signum ) +{ + (void)signum; + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); +} + +void sigchld( int signum ) +{ + pid_t pid = 0; + int status; + + (void)signum; + + while( (pid = waitpid( -1, &status, WNOHANG )) > 0 ) + { + ; /* One of children with 'pid' is terminated */ + + if( WIFEXITED( status ) ) + { + if( (int) WEXITSTATUS (status) > 0 ) + { + ++exit_status; /* printf( "Child %d returned non zero status: %d\n", pid, (int)WEXITSTATUS (status) ); */ + } + else + { + ; /* printf( "Child %d terminated with status: %d\n", pid, (int)WEXITSTATUS (status) ); */ + } + } + else if( WIFSIGNALED( status ) ) + { + ++exit_status; /* printf( "Child %d terminated on signal: %d\n", pid, WTERMSIG( status ) ); */ + } + else + { + ++exit_status; /* printf( "Child %d terminated on unknown reason\n", pid ); */ + } + + } + + if( pid == -1 && errno == ECHILD ) + { + /* No child processes: */ + __done = 1; + } + return; +} + + +static void set_signal_handlers() +{ + struct sigaction sa; + sigset_t set; + + memset( &sa, 0, sizeof( sa ) ); + sa.sa_handler = sigint; /* TERM, INT */ + sa.sa_flags = SA_RESTART; + sigemptyset( &set ); + sigaddset( &set, SIGTERM ); + sigaddset( &set, SIGINT ); + sa.sa_mask = set; + sigaction( SIGTERM, &sa, NULL ); + sigaction( SIGINT, &sa, NULL ); + + /* System V fork+wait does not work if SIGCHLD is ignored */ + memset( &sa, 0, sizeof( sa ) ); + sa.sa_handler = sigchld; /* CHLD */ + sa.sa_flags = SA_RESTART; + sigemptyset( &set ); + sigaddset( &set, SIGCHLD ); + sa.sa_mask = set; + sigaction( SIGCHLD, &sa, NULL ); + + memset( &sa, 0, sizeof( sa ) ); /* ignore SIGPIPE */ + sa.sa_handler = SIG_IGN; + sa.sa_flags = 0; + sigaction( SIGPIPE, &sa, NULL ); +} + + +static enum _input_type check_input_file( char *uncompress, const char *fname ) +{ + struct stat st; + size_t pkglog_size = 0; + unsigned char buf[8]; + int rc, fd; + + /* SIGNATURES: https://www.garykessler.net/library/file_sigs.html */ + + if( uncompress ) + { + *uncompress = '\0'; + } + + if( stat( fname, &st ) == -1 ) + { + FATAL_ERROR( "Cannot access %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + + pkglog_size = st.st_size; + + if( (fd = open( fname, O_RDONLY )) == -1 ) + { + FATAL_ERROR( "Cannot open %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + + rc = (int)read( fd, (void *)&buf[0], 7 ); + if( rc != 7 ) + { + close( fd ); return IFMT_UNKNOWN; + } + buf[7] = '\0'; + + /* TEXT */ + if( !strncmp( (const char *)&buf[0], "PACKAGE", 7 ) ) + { + close( fd ); return IFMT_LOG; + } + + /* GZ */ + if( buf[0] == 0x1F && buf[1] == 0x8B && buf[2] == 0x08 ) + { + if( uncompress ) { *uncompress = 'x'; } + close( fd ); return IFMT_PKG; + } + + /* BZ2 */ + if( buf[0] == 0x42 && buf[1] == 0x5A && buf[2] == 0x68 ) + { + if( uncompress ) { *uncompress = 'j'; } + close( fd ); return IFMT_PKG; + } + + /* XZ */ + if( buf[0] == 0xFD && buf[1] == 0x37 && buf[2] == 0x7A && + buf[3] == 0x58 && buf[4] == 0x5A && buf[5] == 0x00 ) + { + if( uncompress ) { *uncompress = 'J'; } + close( fd ); return IFMT_PKG; + } + + if( pkglog_size > 262 ) + { + if( lseek( fd, 257, SEEK_SET ) == -1 ) + { + FATAL_ERROR( "Cannot check signature of %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + rc = (int)read( fd, &buf[0], 5 ); + if( rc != 5 ) + { + FATAL_ERROR( "Cannot read signature of %s file", basename( (char *)fname ) ); + } + /* TAR */ + if( buf[0] == 0x75 && buf[1] == 0x73 && buf[2] == 0x74 && buf[3] == 0x61 && buf[4] == 0x72 ) + { + close( fd ); return IFMT_PKG; + } + } + + close( fd ); return IFMT_UNKNOWN; +} + + +void get_args( int argc, char *argv[] ) +{ + const char* short_options = "hvr:l:"; + + const struct option long_options[] = + { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'v' }, + { "root", required_argument, NULL, 'r' }, + { "log", required_argument, NULL, 'l' }, + { NULL, 0, NULL, 0 } + }; + + int ret; + int option_index = 0; + + while( (ret = getopt_long( argc, argv, short_options, long_options, &option_index )) != -1 ) + { + switch( ret ) + { + case 'h': + { + usage(); + break; + } + case 'v': + { + version(); + break; + } + + case 'r': + { + if( optarg != NULL ) + { + root = xstrdup( (const char *)optarg ); + remove_trailing_slash( root ); + } + else + /* option is present but without value */ + usage(); + break; + } + case 'l': + { + if( optarg != NULL ) + { + errlog_fname = xstrdup( (const char *)optarg ); + } + else + /* option is present but without value */ + usage(); + break; + } + + case '?': default: + { + usage(); + break; + } + } + } + + + if( optind < argc ) + { + /* + last command line argument assumes as the packages directory + in the SETUP_DB_PATH. If this argument is defined then we + ignore --root, --log options. + */ + pkgs_path = xstrdup( (const char *)argv[optind] ); + remove_trailing_slash( pkgs_path ); + + /* output LOG into stderr*/ + if( root ) { free( root ); root = NULL; } + if( errlog_fname ) { free( errlog_fname ); errlog_fname = NULL; } + errlog_fname = xstrdup( "-" ); + } + + + if( !pkgs_path ) + { + struct stat st; + char *buf = NULL; + + bzero( (void *)&st, sizeof( struct stat ) ); + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + if( !root ) + { + buf[0] = '/'; buf[1] = '\0'; + root = xstrdup( (const char *)&buf[0] ); + } + else + { + int len = strlen( root ); + + (void)strcpy( buf, (const char *)root ); + if( buf[ len - 1 ] != '/' ) + { + buf[len] = '/'; buf[len+1] = '\0'; + } + } + + (void)strcat( buf, PACKAGES_PATH ); + if( stat( (const char *)&buf[0], &st ) == -1 ) + { + FATAL_ERROR( "Cannot access '%s' file or directory: %s", buf, strerror( errno ) ); + } + + if( S_ISDIR(st.st_mode) ) + { + pkgs_path = xstrdup( (const char *)&buf[0] ); + free( buf ); + } + else + { + FATAL_ERROR( "Defined --root '%s' is not a directory", buf ); + } + + } /* End if( !pkgs_path ) */ + + if( !errlog_fname ) + { + struct stat st; + char *buf = NULL; + + bzero( (void *)&st, sizeof( struct stat ) ); + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + if( !root ) + { + buf[0] = '/'; buf[1] = '\0'; + } + else + { + int len = strlen( root ); + + (void)strcpy( buf, (const char *)root ); + if( buf[ len - 1 ] != '/' ) + { + buf[len] = '/'; buf[len+1] = '\0'; + } + } + + (void)strcat( buf, LOG_PATH ); + if( stat( (const char *)&buf[0], &st ) == -1 ) + { + FATAL_ERROR( "Cannot access '%s' file or directory: %s", buf, strerror( errno ) ); + } + + if( S_ISDIR(st.st_mode) ) + { + (void)strcat( buf, LOG_FILE ); + errlog_fname = xstrdup( (const char *)&buf[0] ); + free( buf ); + } + else + { + FATAL_ERROR( "The path '%s' is not a directory", buf ); + } + } + else /* errlog_fname is defined */ + { + struct stat st; + char *buf = NULL, *dir = NULL; + + bzero( (void *)&st, sizeof( struct stat ) ); + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + strncpy( buf, (const char *)errlog_fname, (size_t)PATH_MAX ); + buf[ PATH_MAX - 1] = '\0'; + + dir = dirname( buf ); + + if( _mkdir_p( (const char *)dir, S_IRWXU | S_IRWXG | S_IRWXO ) != 0 ) + { + FATAL_ERROR( "Cannot access '%s' directory", buf ); + } + + free( buf ); + + } /* End if( !errlog_fname ) */ + +} + + +/*************************************************************** + Copy functions: + */ +static void _copy_pkglog( const char *group, const char *fname ) +{ + enum _input_type type = IFMT_UNKNOWN; + char uncompress = '\0'; + + type = check_input_file( &uncompress, fname ); + + if( type == IFMT_LOG ) + { + int len = 0; + char *tmp= NULL, *cmd = NULL; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + if( group ) { (void)sprintf( &tmp[0], "%s/%s", tmpdir, group ); } + else { (void)sprintf( &tmp[0], "%s", tmpdir ); } + + if( _mkdir_p( tmp, S_IRWXU | S_IRWXG | S_IRWXO ) != 0 ) + { + LOG( "ERROR: Cannot copy '%s' PKGLOG file", basename( (char *)fname ) ); + exit_status += 1; + free( tmp ); + return; + } + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, "cp %s %s/ > /dev/null 2>&1", fname, tmp ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot copy %s PKGLOG file", basename( (char *)fname ) ); + } + (void)sys_exec_command( cmd ); + ++__child; + + free( tmp ); + free( cmd ); + } +} + +static void _search_pkglogs( const char *dirpath, const char *grp ) +{ + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + if( stat( dirpath, &path_sb ) == -1 ) + { + FATAL_ERROR( "%s: Cannot stat Setup Database or destination directory", dirpath ); + } + + if( S_ISDIR(path_sb.st_mode) == 0 ) + { + FATAL_ERROR( "%s: Setup Database or destination is not a directory", dirpath ); + } + + if( (dir = opendir(dirpath) ) == NULL ) + { + FATAL_ERROR( "Canot access %s directory: %s", dirpath, strerror( errno ) ); + } + + len = strlen( dirpath ); + + while( (entry = readdir( dir )) != NULL) + { + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( stat( path, &entry_sb ) == 0 ) + { + if( S_ISREG(entry_sb.st_mode) ) + { + _copy_pkglog( grp, (const char *)path ); + } + if( S_ISDIR(entry_sb.st_mode) && grp == NULL ) + { + _search_pkglogs( (const char *)path, (const char *)entry->d_name ); + } + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + } + + closedir( dir ); +} + +/*********************************************************** + copy_pkglogs() - returns number of copied PKGLOGS or 0 if + no PKGLOGS found in the destination + directory (SETUP_DB_PATH). + The exit_status has been set. + */ +int copy_pkglogs( void ) +{ + int ret = 0; + + __done = 0; __child = 0; + + _search_pkglogs( (const char *)pkgs_path, NULL ); + + if( __child > 0 ) + { + while( !__done ) usleep( 1 ); + ret = __child; + } + + __done = 0; __child = 0; + + return ret; +} +/* + Enf of Copy functions. + ***************************************************************/ + + +/*********************************************************** + Remove leading spaces and take non-space characters only: + (Especialy for pkginfo lines) + */ +static char *skip_spaces( char *s ) +{ + char *q, *p = (char *)0; + + if( !s || *s == '\0' ) return p; + + p = s; + + while( (*p == ' ' || *p == '\t') && *p != '\0' ) { ++p; } q = p; + while( *q != ' ' && *q != '\t' && *q != '\0' ) { ++q; } *q = '\0'; + + if( *p == '\0' ) return (char *)0; + + return( xstrdup( (const char *)p ) ); +} + + +/******************************* + remove spaces at end of line: + */ +static void skip_eol_spaces( char *s ) +{ + char *p = (char *)0; + + if( !s || *s == '\0' ) return; + + p = s + strlen( s ) - 1; + while( isspace( *p ) ) { *p-- = '\0'; } +} + +static size_t read_usize( char *s ) +{ + size_t size = 0; + size_t mult = 1; + double sz = 0.0; + + char suffix; + char *q, *p = (char *)0; + + if( !s || *s == '\0' ) return size; + + p = s; + + while( (*p == ' ' || *p == '\t') && *p != '\0' ) { ++p; } q = p; + while( *q != ' ' && *q != '\t' && *q != '\0' ) { ++q; } *q = '\0'; + + if( *p == '\0' ) return size; + + --q; + suffix = *q; + switch( suffix ) + { + /* by default size calculates in KiB - 1024 Bytes (du -s -h .) */ + case 'G': + case 'g': + mult = 1024 * 1024; + *q = '\0'; + break; + case 'M': + case 'm': + mult = 1024; + *q = '\0'; + break; + case 'K': + case 'k': + *q = '\0'; + break; + default: + break; + } + + if( sscanf( p, "%lg", &sz ) != 1 ) return size; + + return (size_t)round( sz * (double)mult ); +} + +static int read_total_files( char *s ) +{ + int n = 0; + char *q, *p = (char *)0; + + if( !s || *s == '\0' ) return n; + + p = s; + + while( (*p == ' ' || *p == '\t') && *p != '\0' ) { ++p; } q = p; + while( *q != ' ' && *q != '\t' && *q != '\0' ) { ++q; } *q = '\0'; + + if( *p == '\0' ) return n; + + if( sscanf( p, "%u", &n ) != 1 ) return 0; + + return n; +} + +static void get_short_description( char *buf, const char *line ) +{ + char *s, *p, *q; + + if( buf ) { buf[0] = '\0'; s = buf; } + if( !line || line[0] == '\0' ) return; + + p = index( line, '(' ); + q = index( line, ')' ); + if( p && q && q > p ) + { + ++p; + while( *p && p < q ) + { + *s = *p; + ++p; ++s; + } + *s = '\0'; + } + else + { + /* + If short description declaration is incorrect at first line + of description; then we take whole first line of description: + */ + p = index( line, ':' ); ++p; + while( (*p == ' ' || *p == '\t') && *p != '\0' ) { ++p; } + strcpy( buf, p ); + } +} + + +static int get_references_section( int *start, int *stop, unsigned int *cnt, FILE *log ) +{ + int ret = -1, found = 0; + + if( !start || !stop || !cnt ) return ret; + + if( log != NULL ) + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + *start = 0; *stop = 0; + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + char *match = NULL; + + if( (match = strstr( ln, "REFERENCE COUNTER:" )) && match == ln ) /* at start of line only */ + { + *start = ret + 1; + ++found; + + /* Get reference counter */ + { + unsigned int count; + int rc; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + rc = sscanf( ln, "REFERENCE COUNTER: %u", &count ); + if( rc == 1 && cnt != NULL ) + { + *cnt = count; + } + } + } + if( (match = strstr( ln, "REQUIRES:" )) && match == ln ) + { + *stop = ret + 1; + ++found; + } + + ++ret; + } + + free( line ); + + ret = ( found == 2 ) ? 0 : 1; /* 0 - success; 1 - not found. */ + + fseek( log, 0, SEEK_SET ); + } + + return( ret ); +} + +static int get_requires_section( int *start, int *stop, FILE *log ) +{ + int ret = -1, found = 0; + + if( !start || !stop ) return ret; + + if( log != NULL ) + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + *start = 0; *stop = 0; + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + char *match = NULL; + + if( (match = strstr( ln, "REQUIRES:" )) && match == ln ) /* at start of line only */ + { + *start = ret + 1; + ++found; + } + if( (match = strstr( ln, "PACKAGE DESCRIPTION:" )) && match == ln ) + { + *stop = ret + 1; + ++found; + } + + ++ret; + } + + free( line ); + + ret = ( found == 2 ) ? 0 : 1; /* 0 - success; 1 - not found. */ + + fseek( log, 0, SEEK_SET ); + } + + return( ret ); +} + +static int get_description_section( int *start, int *stop, FILE *log ) +{ + int ret = -1, found = 0; + + if( !start || !stop ) return ret; + + if( log != NULL ) + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + *start = 0; *stop = 0; + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + char *match = NULL; + + if( (match = strstr( ln, "PACKAGE DESCRIPTION:" )) && match == ln ) /* at start of line only */ + { + *start = ret + 1; + ++found; + } + if( (match = strstr( ln, "RESTORE LINKS:" )) && match == ln ) + { + *stop = ret + 1; + ++found; + } + + ++ret; + } + + free( line ); + + ret = ( found == 2 ) ? 0 : 1; /* 0 - success; 1 - not found. */ + + fseek( log, 0, SEEK_SET ); + } + + return( ret ); +} + +static int get_restore_links_section( int *start, int *stop, FILE *log ) +{ + int ret = -1, found = 0; + + if( !start || !stop ) return ret; + + if( log != NULL ) + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + *start = 0; *stop = 0; + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + char *match = NULL; + + if( (match = strstr( ln, "RESTORE LINKS:" )) && match == ln ) /* at start of line only */ + { + *start = ret + 1; + ++found; + } + if( (match = strstr( ln, "INSTALL SCRIPT:" )) && match == ln ) + { + *stop = ret + 1; + ++found; + } + + ++ret; + } + + free( line ); + + ret = ( found == 2 ) ? 0 : 1; /* 0 - success; 1 - not found. */ + + fseek( log, 0, SEEK_SET ); + } + + return( ret ); +} + + +static int get_install_script_section( int *start, int *stop, FILE *log ) +{ + int ret = -1, found = 0; + + if( !start || !stop ) return ret; + + if( log != NULL ) + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + *start = 0; *stop = 0; + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + char *match = NULL; + + if( (match = strstr( ln, "INSTALL SCRIPT:" )) && match == ln ) /* at start of line only */ + { + *start = ret + 1; + ++found; + } + if( (match = strstr( ln, "FILE LIST:" )) && match == ln ) + { + *stop = ret + 1; + ++found; + } + + ++ret; + } + + free( line ); + + ret = ( found == 2 ) ? 0 : 1; /* 0 - success; 1 - not found. */ + + fseek( log, 0, SEEK_SET ); + } + + return( ret ); +} + + +static int get_file_list_section( int *start, int *stop, FILE *log ) +{ + int ret = -1, found = 0; + + if( !start || !stop ) return ret; + + if( log != NULL ) + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + *start = 0; *stop = 0; + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + char *match = NULL; + + if( (match = strstr( ln, "FILE LIST:" )) && match == ln ) /* at start of line only */ + { + *start = ret + 1; + ++found; + } + + ++ret; + } + + free( line ); + + ret = ( found == 1 ) ? 0 : 1; /* 0 - success; 1 - not found. */ + + fseek( log, 0, SEEK_SET ); + } + + return( ret ); +} + + +int read_pkginfo( FILE *log, struct package *package ) +{ + int ret = -1; + + char *ln = NULL; + char *line = NULL; + + char *pkgname_pattern = "PACKAGE NAME:", + *pkgver_pattern = "PACKAGE VERSION:", + *arch_pattern = "ARCH:", + *distroname_pattern = "DISTRO:", + *distrover_pattern = "DISTRO VERSION:", + *group_pattern = "GROUP:", + *url_pattern = "URL:", + *license_pattern = "LICENSE:", + *uncompressed_size_pattern = "UNCOMPRESSED SIZE:", + *total_files_pattern = "TOTAL FILES:"; + + + if( !log || !package ) return ret; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + char *match = NULL; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( (match = strstr( ln, pkgname_pattern )) && match == ln ) /* at start of line only */ + { + package->pkginfo->name = skip_spaces( ln + strlen( pkgname_pattern ) ); + } + if( (match = strstr( ln, pkgver_pattern )) && match == ln ) + { + package->pkginfo->version = skip_spaces( ln + strlen( pkgver_pattern ) ); + } + if( (match = strstr( ln, arch_pattern )) && match == ln ) + { + package->pkginfo->arch = skip_spaces( ln + strlen( arch_pattern ) ); + } + if( (match = strstr( ln, distroname_pattern )) && match == ln ) + { + package->pkginfo->distro_name = skip_spaces( ln + strlen( distroname_pattern ) ); + } + if( (match = strstr( ln, distrover_pattern )) && match == ln ) + { + package->pkginfo->distro_version = skip_spaces( ln + strlen( distrover_pattern ) ); + } + if( (match = strstr( ln, group_pattern )) && match == ln ) + { + package->pkginfo->group = skip_spaces( ln + strlen( group_pattern ) ); + } + if( (match = strstr( ln, url_pattern )) && match == ln ) + { + package->pkginfo->url = skip_spaces( ln + strlen( url_pattern ) ); + } + if( (match = strstr( ln, license_pattern )) && match == ln ) + { + package->pkginfo->license = skip_spaces( ln + strlen( license_pattern ) ); + } + if( (match = strstr( ln, uncompressed_size_pattern )) && match == ln ) + { + package->pkginfo->uncompressed_size = read_usize( ln + strlen( uncompressed_size_pattern ) ); + } + if( (match = strstr( ln, total_files_pattern )) && match == ln ) + { + package->pkginfo->total_files = read_total_files( ln + strlen( total_files_pattern ) ); + } + + if( (match = strstr( ln, "PACKAGE DESCRIPTION:" )) && match == ln ) + { + char *buf = NULL; + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + /* Get short_description from PACKAGE DESCRIPTION */ + ln = fgets( line, PATH_MAX, log ); + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + + bzero( (void *)buf, PATH_MAX ); + get_short_description( buf, (const char *)line ); + if( buf[0] != '\0' ) + { + package->pkginfo->short_description = xstrdup( (const char *)buf ); + } + free( buf ); + } + + } /* End of while() */ + + free( line ); + + if( package->pkginfo->name == NULL ) ++ret; + if( package->pkginfo->version == NULL ) ++ret; + if( package->pkginfo->arch == NULL ) ++ret; + if( package->pkginfo->distro_name == NULL ) ++ret; + if( package->pkginfo->distro_version == NULL ) ++ret; + /* group can be equal to NULL */ + + fseek( log, 0, SEEK_SET ); + + return( ret ); +} + + +static unsigned int read_references( FILE *log, int start, unsigned int *cnt, struct package *package ) +{ + char *ln = NULL; + char *line = NULL; + char *p = NULL, *group = NULL, *name = NULL, *version = NULL; + int n = 1; + + unsigned int counter, pkgs = 0; + + struct pkg *pkg = NULL; + + if( !log || !cnt || *cnt == 0 || !package ) return pkgs; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + counter = *cnt; + + while( (ln = fgets( line, PATH_MAX, log )) && (n < start) ) ++n; + + n = 0; + while( (ln = fgets( line, PATH_MAX, log )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( strstr( ln, "REQUIRES:" ) ) break; /* if cnt greater than real number of references */ + + if( n < counter ) + { + if( (p = index( (const char *)ln, '=' )) ) + { + *p = '\0'; version = ++p; + if( (p = index( (const char *)ln, '/' )) ) + { + *p = '\0'; name = ++p; group = (char *)&ln[0]; + } + else + { + name = (char *)&ln[0]; group = NULL; + } + + pkg = pkg_alloc(); + + if( group ) pkg->group = xstrdup( (const char *)group ); + pkg->name = xstrdup( (const char *)name ); + pkg->version = xstrdup( (const char *)version ); + + add_reference( package, pkg ); + ++pkgs; + } + ++n; + } + else + break; + } + + free( line ); + + fseek( log, 0, SEEK_SET ); + + *cnt = pkgs; + + return pkgs; +} + + +static unsigned int read_requires( FILE *log, int start, int stop, struct package *package ) +{ + char *ln = NULL; + char *line = NULL; + char *p = NULL, *group = NULL, *name = NULL, *version = NULL; + int n = 1; + + unsigned int pkgs = 0; + + struct pkg *pkg = NULL; + + if( !log || !package ) return pkgs; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + while( (ln = fgets( line, PATH_MAX, log )) && (n < start) ) ++n; + + if( start && start < stop ) + { + ++n; /* skip section header */ + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( strstr( ln, "PACKAGE DESCRIPTION:" ) ) break; /* if (stop - start - 1) greater than real number of requiress */ + + if( (n > start) && (n < stop) ) + { + if( (p = index( (const char *)ln, '=' )) ) + { + *p = '\0'; version = ++p; + if( (p = index( (const char *)ln, '/' )) ) + { + *p = '\0'; name = ++p; group = (char *)&ln[0]; + } + else + { + name = (char *)&ln[0]; group = NULL; + } + + pkg = pkg_alloc(); + + if( group ) pkg->group = xstrdup( (const char *)group ); + pkg->name = xstrdup( (const char *)name ); + pkg->version = xstrdup( (const char *)version ); + + add_required( package, pkg ); + ++pkgs; + } + + } + ++n; + } + + } /* End if( start && start < stop ) */ + + free( line ); + + fseek( log, 0, SEEK_SET ); + + return pkgs; +} + + +static unsigned int read_description( FILE *log, int start, int stop, struct package *package ) +{ + char *ln = NULL; + char *line = NULL; + char *pattern = NULL; + int n = 1; + + char *tmp_fname = NULL; + FILE *tmp = NULL; + + unsigned int lines = 0; + + if( !log || !package ) return lines; + + tmp_fname = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp_fname ) { FATAL_ERROR( "Cannot allocate memory" ); } + + bzero( (void *)tmp_fname, PATH_MAX ); + (void)sprintf( (char *)&tmp_fname[0], "%s/.DESCRIPTION", tmpdir ); + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) { FATAL_ERROR( "Cannot allocate memory" ); } + + pattern = (char *)malloc( (size_t)strlen( package->pkginfo->name ) + 2 ); + if( !pattern ) { FATAL_ERROR( "Cannot allocate memory" ); } + + (void)sprintf( pattern, "%s:", package->pkginfo->name ); + + + while( (ln = fgets( line, PATH_MAX, log )) && (n < start) ) ++n; + + if( start && start < stop ) + { + ++n; /* skip section header */ + + tmp = fopen( (const char *)&tmp_fname[0], "w" ); + if( !tmp ) + { + FATAL_ERROR( "Cannot create temporary %s file", basename( (char *)&tmp_fname[0] ) ); + } + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + char *match = NULL; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( strstr( ln, "RESTORE LINKS:" ) ) break; /* if (stop - start - 1) greater than real number of lines */ + + if( (n > start) && (n < stop) ) + { + /* + skip non-significant spaces at beginning of line + and print lines started with 'pkgname:' + */ + if( (match = strstr( ln, pattern )) && lines < DESCRIPTION_NUMBER_OF_LINES ) + { + int mlen = strlen( match ), plen = strlen( pattern ); + int length = ( mlen > plen ) ? (mlen - plen - 1) : 0 ; + + if( length > DESCRIPTION_LENGTH_OF_LINE ) + { + /* WARNING( "Package DESCRIPTION contains lines with length greater than %d characters", DESCRIPTION_LENGTH_OF_LINE ); */ + match[plen + 1 + DESCRIPTION_LENGTH_OF_LINE] = '\0'; /* truncating description line */ + skip_eol_spaces( match ); /* remove spaces at end-of-line */ + } + fprintf( tmp, "%s\n", match ); + ++lines; + } + + } + ++n; + } + + if( lines < (unsigned int)DESCRIPTION_NUMBER_OF_LINES ) + { + /* WARNING( "Package DESCRIPTION contains less than %d lines", DESCRIPTION_NUMBER_OF_LINES ); */ + while( lines < (unsigned int)DESCRIPTION_NUMBER_OF_LINES ) + { + fprintf( tmp, "%s\n", pattern ); + ++lines; + } + } + + fflush( tmp ); + fclose( tmp ); + + } /* End if( start && start < stop ) */ + + free( pattern ); + free( line ); + + fseek( log, 0, SEEK_SET ); + + /* read temporary saved description */ + { + struct stat sb; + size_t size = 0; + int fd; + + char *desc = NULL; + + if( stat( tmp_fname, &sb ) == -1 ) + { + FATAL_ERROR( "Cannot stat temporary %s file", basename( (char *)&tmp_fname[0] ) ); + } + size = (size_t)sb.st_size; + + if( size ) + { + ssize_t rc = 0; + + desc = (char *)malloc( size + 1 ); + if( !desc ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)desc, size + 1 ); + + if( (fd = open( (const char *)&tmp_fname[0], O_RDONLY )) == -1 ) + { + FATAL_ERROR( "Canot access temporary %s file: %s", basename( (char *)&tmp_fname[0] ), strerror( errno ) ); + } + + rc = read( fd, (void *)desc, size ); + if( rc != (ssize_t)size ) + { + LOG( "ERROR: The %s file is not fully read", basename( (char *)&tmp_fname[0] ) ); + exit_status += 1; + } + + package->description = desc; + + close( fd ); + } + + } + + (void)unlink( tmp_fname ); + free( tmp_fname ); + + return lines; +} + + +static unsigned int read_restore_links( FILE *log, int start, int stop, struct package *package ) +{ + char *ln = NULL; + char *line = NULL; + int n = 1; + + char *tmp_fname = NULL; + FILE *tmp = NULL; + + unsigned int lines = 0; + + if( !log || !package ) return lines; + + tmp_fname = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp_fname ) { FATAL_ERROR( "Cannot allocate memory" ); } + + bzero( (void *)tmp_fname, PATH_MAX ); + (void)sprintf( (char *)&tmp_fname[0], "%s/.RESTORELINKS", tmpdir ); + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) { FATAL_ERROR( "Cannot allocate memory" ); } + + while( (ln = fgets( line, PATH_MAX, log )) && (n < start) ) ++n; + + if( start && start < stop ) + { + ++n; /* skip section header */ + + tmp = fopen( (const char *)&tmp_fname[0], "w" ); + if( !tmp ) + { + FATAL_ERROR( "Cannot create temporary %s file", basename( (char *)&tmp_fname[0] ) ); + } + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( strstr( ln, "INSTALL SCRIPT:" ) ) break; /* if (stop - start - 1) greater than real number of lines */ + + if( (n > start) && (n < stop) ) + { + fprintf( tmp, "%s\n", ln ); + ++lines; + } + ++n; + } + + fflush( tmp ); + fclose( tmp ); + + } /* End if( start && start < stop ) */ + + free( line ); + + fseek( log, 0, SEEK_SET ); + + /* read temporary saved description */ + { + struct stat sb; + size_t size = 0; + int fd; + + char *links = NULL; + + if( stat( tmp_fname, &sb ) == -1 ) + { + FATAL_ERROR( "Cannot stat temporary %s file", basename( (char *)&tmp_fname[0] ) ); + } + size = (size_t)sb.st_size; + + if( size ) + { + ssize_t rc = 0; + + links = (char *)malloc( size + 1 ); + if( !links ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)links, size + 1 ); + + if( (fd = open( (const char *)&tmp_fname[0], O_RDONLY )) == -1 ) + { + FATAL_ERROR( "Canot access temporary %s file: %s", basename( (char *)&tmp_fname[0] ), strerror( errno ) ); + } + + rc = read( fd, (void *)links, size ); + if( rc != (ssize_t)size ) + { + LOG( "ERROR: The %s file is not fully read", basename( (char *)&tmp_fname[0] ) ); + exit_status += 1; + } + + package->restore_links = links; + + close( fd ); + } + + } + + (void)unlink( tmp_fname ); + free( tmp_fname ); + + return lines; +} + + +static unsigned int read_install_script( FILE *log, int start, int stop, struct package *package ) +{ + char *ln = NULL; + char *line = NULL; + int n = 1; + + char *tmp_fname = NULL; + FILE *tmp = NULL; + + unsigned int lines = 0; + + if( !log || !package ) return lines; + + tmp_fname = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp_fname ) { FATAL_ERROR( "Cannot allocate memory" ); } + + bzero( (void *)tmp_fname, PATH_MAX ); + (void)sprintf( (char *)&tmp_fname[0], "%s/.INSTALL", tmpdir ); + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) { FATAL_ERROR( "Cannot allocate memory" ); } + + while( (ln = fgets( line, PATH_MAX, log )) && (n < start) ) ++n; + + if( start && start < stop ) + { + ++n; /* skip section header */ + + tmp = fopen( (const char *)&tmp_fname[0], "w" ); + if( !tmp ) + { + FATAL_ERROR( "Cannot create temporary %s file", basename( (char *)&tmp_fname[0] ) ); + } + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( strstr( ln, "FILE LIST:" ) ) break; /* if (stop - start - 1) greater than real number of lines */ + + if( (n > start) && (n < stop) ) + { + fprintf( tmp, "%s\n", ln ); + ++lines; + } + ++n; + } + + fflush( tmp ); + fclose( tmp ); + + } /* End if( start && start < stop ) */ + + free( line ); + + fseek( log, 0, SEEK_SET ); + + /* read temporary saved description */ + { + struct stat sb; + size_t size = 0; + int fd; + + char *install = NULL; + + if( stat( tmp_fname, &sb ) == -1 ) + { + FATAL_ERROR( "Cannot stat temporary %s file", basename( (char *)&tmp_fname[0] ) ); + } + size = (size_t)sb.st_size; + + if( size ) + { + ssize_t rc = 0; + + install = (char *)malloc( size + 1 ); + if( !install ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)install, size + 1 ); + + if( (fd = open( (const char *)&tmp_fname[0], O_RDONLY )) == -1 ) + { + FATAL_ERROR( "Canot access temporary %s file: %s", basename( (char *)&tmp_fname[0] ), strerror( errno ) ); + } + + rc = read( fd, (void *)install, size ); + if( rc != (ssize_t)size ) + { + LOG( "ERROR: The %s file is not fully read", basename( (char *)&tmp_fname[0] ) ); + exit_status += 1; + } + + package->install_script = install; + + close( fd ); + } + + } + + (void)unlink( tmp_fname ); + free( tmp_fname ); + + return lines; +} + + +static unsigned int read_file_list( FILE *log, int start, struct package *package ) +{ + char *ln = NULL; + char *line = NULL; + int n = 1; + + unsigned int files = 0; + + if( !log || !package ) return files; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + while( (ln = fgets( line, PATH_MAX, log )) && (n < start) ) ++n; + + if( start ) + { + while( (ln = fgets( line, PATH_MAX, log )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + add_file( package, (const char *)ln ); + ++files; + } + + } /* End if( start && start < stop ) */ + + free( line ); + + fseek( log, 0, SEEK_SET ); + + return files; +} + + + +static void _read_pkglog( const char *group, const char *fname ) +{ + FILE *log = NULL; + char *bname = NULL; + + if( fname != NULL ) + { + log = fopen( (const char *)fname, "r" ); + if( !log ) + { + FATAL_ERROR( "Cannot open %s file", fname ); + } + bname = (char *)fname + strlen( tmpdir ) + 1; + } + + if( log != NULL ) + { + struct package *package = NULL; + int rc, start, stop; + unsigned int counter; + + package = package_alloc(); + + if( read_pkginfo( log, package ) != 0 ) + { + LOG( "ERROR: %s: Invalid PKGLOG file", bname ); + exit_status += 1; + package_free( package ); + fclose( log ); + return; + } + + if( hardware ) package->hardware = xstrdup( (const char *)hardware ); + if( tarballs ) /* find tarball and allocate package->tarball */ + { + struct pkginfo *info = package->pkginfo; + const char *tgz = NULL; + char *buf = NULL; + struct stat sb; + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + + if( info->group ) + { + (void)sprintf( buf, "%s/%s-%s-%s-%s-%s", + info->group, info->name, info->version, info->arch, + info->distro_name, info->distro_version ); + } + else + { + (void)sprintf( buf, "%s-%s-%s-%s-%s", + info->name, info->version, info->arch, + info->distro_name, info->distro_version ); + } + tgz = find_tarball( (const char *)&buf[0] ); + if( tgz ) + { + package->tarball = xstrdup( (const char *)tgz ); + + bzero( (void *)&buf[0], PATH_MAX ); + (void)sprintf( buf, "%s/%s", pkgs_path, tgz ); + if( stat( buf, &sb ) != -1 ) + { + info->compressed_size = (size_t)sb.st_size; + } + } + free( buf ); + } + package->procedure = INSTALL; + package->priority = priority; + + if( package->pkginfo->group && group && strcmp( package->pkginfo->group, group ) != 0 ) + { + char *tgz; + + if( package->tarball ) { tgz = package->tarball; } + else { tgz = basename( (char *)fname ); } + + WARNING( "%s: Should be moved into '%s' subdir", tgz, package->pkginfo->group ); + } + + /****************** + read references: + */ + rc = get_references_section( &start, &stop, &counter, log ); + if( rc != 0 ) + { + LOG( "ERROR: %s: PKGLOG doesn't contains REFERENCE COUNTER section", bname ); + exit_status += 1; + package_free( package ); + fclose( log ); + return; + } + if( counter > 0 ) + { + unsigned int pkgs = counter; + + if( read_references( log, start, &counter, package ) != pkgs ) + { + LOG( "ERROR: %s: Invalid REFERENCE COUNTER section", bname ); + exit_status += 1; + package_free( package ); + fclose( log ); + return; + } + } + + /****************** + read requires: + */ + rc = get_requires_section( &start, &stop, log ); + if( rc != 0 ) + { + LOG( "ERROR: %s: PKGLOG doesn't contains REQUIRES section", bname ); + exit_status += 1; + package_free( package ); + fclose( log ); + return; + } + if( (stop - start) > 1 ) + { + unsigned int pkgs = (unsigned int)(stop - start - 1); /* -1 skips section header */ + + if( read_requires( log, start, stop, package ) != pkgs ) + { + LOG( "ERROR: %s: Invalid REQUIRES section", bname ); + exit_status += 1; + package_free( package ); + fclose( log ); + return; + } + } + + /******************* + read description: + */ + rc = get_description_section( &start, &stop, log ); + if( rc != 0 ) + { + LOG( "ERROR: %s: PKGLOG doesn't contains PACKAGE DESCRIPTION section", bname ); + exit_status += 1; + package_free( package ); + fclose( log ); + return; + } + if( (stop - start) > 1 ) + { + if( read_description( log, start, stop, package ) != (unsigned int)DESCRIPTION_NUMBER_OF_LINES ) + { + LOG( "ERROR: %s: Invalid DESCRIPTION section", bname ); + exit_status += 1; + package_free( package ); + fclose( log ); + return; + } + } + + /********************* + read restore links: + */ + rc = get_restore_links_section( &start, &stop, log ); + if( rc != 0 ) + { + LOG( "ERROR: %s: PKGLOG doesn't contains RESTORE LINKS section", bname ); + exit_status += 1; + package_free( package ); + fclose( log ); + return; + } + if( (stop - start) > 1 ) + { + (void)read_restore_links( log, start, stop, package ); + } + + /********************* + read install script: + */ + rc = get_install_script_section( &start, &stop, log ); + if( rc != 0 ) + { + LOG( "ERROR: %s: PKGLOG doesn't contains INSTALL SCRIPT section", bname ); + exit_status += 1; + package_free( package ); + fclose( log ); + return; + } + if( (stop - start) > 1 ) + { + (void)read_install_script( log, start, stop, package ); + } + + /***************** + read file_list: + */ + rc = get_file_list_section( &start, &stop, log ); + if( rc != 0 ) + { + LOG( "ERROR: %s: PKGLOG doesn't contains FILE LIST section", bname ); + exit_status += 1; + package_free( package ); + fclose( log ); + return; + } + if( start ) + { + unsigned int files = read_file_list( log, start, package ); + if( files == (unsigned int)0 ) + { + /* + Packages that do not contain regular files are ignored. + For example, service package base/init-devices-1.2.3-s9xx-glibc-radix-1.1.txz + */ + if( ! DO_NOT_PRINTOUT_INFO ) + { + LOG( "INFO: %s: PKGLOG contains empty FILE LIST section", bname ); + } + package_free( package ); + fclose( log ); + return; + } + package->pkginfo->total_files = (int)files; + } + + /* + Здесь можно организовать проверку пакета на предмет его + целостности и правильности установки (когда будет готова + утилита check-package). + */ + add_package( package ); + + ++__child; + fclose( log ); + + /*************************************************** + Incremet REFERENCE COUNTERs of required packages: + */ + { + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *buf = NULL; + char *cmd = NULL, *errmsg = NULL, *wmsg = NULL; + + struct pkginfo *info = package->pkginfo; + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + + errmsg = (char *)malloc( (size_t)PATH_MAX ); + if( !errmsg ) { FATAL_ERROR( "Cannot allocate memory" ); } + + wmsg = (char *)malloc( (size_t)PATH_MAX ); + if( !wmsg ) { FATAL_ERROR( "Cannot allocate memory" ); } + + if( info->group ) + { + (void)sprintf( &buf[0], "%s/%s-%s-%s-%s-%s", info->group, + info->name, info->version, info->arch, + info->distro_name, info->distro_version ); + } + else + { + (void)sprintf( &buf[0], "%s-%s-%s-%s-%s", + info->name, info->version, info->arch, + info->distro_name, info->distro_version ); + } + + (void)sprintf( &errmsg[0], "Cannot update REFERENCE COUNTERs for '%s' package", buf ); + + len = sprintf( &cmd[0], "%s/chrefs -d %s -o inc %s > /dev/null 2>&1", selfdir, pkgs_path, buf ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( errmsg ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)&wmsg[0], PATH_MAX ); + if( rc != 0 ) + { + LOG( "WARNING: %s", errmsg ); + } + + if( buf ) free( buf ); + if( cmd ) free( cmd ); + if( errmsg ) free( errmsg ); + if( wmsg ) free( wmsg ); + } + /* + End of Incremet REFERENCE COUNTERs. + ***************************************************/ + } +} + +static void _read_pkglogs( const char *dirpath, const char *grp ) +{ + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + if( stat( dirpath, &path_sb ) == -1 ) + { + FATAL_ERROR( "%s: Cannot stat Setup Database or destination directory", dirpath ); + } + + if( S_ISDIR(path_sb.st_mode) == 0 ) + { + FATAL_ERROR( "%s: Setup Database or destination is not a directory", dirpath ); + } + + if( (dir = opendir(dirpath) ) == NULL ) + { + FATAL_ERROR( "Canot access %s directory: %s", dirpath, strerror( errno ) ); + } + + len = strlen( dirpath ); + + while( (entry = readdir( dir )) != NULL) + { + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( stat( path, &entry_sb ) == 0 ) + { + if( S_ISREG(entry_sb.st_mode) ) + { + if( check_input_file( NULL, (const char *)path ) == IFMT_LOG ) + { + _read_pkglog( grp, (const char *)path ); + } + } + if( S_ISDIR(entry_sb.st_mode) && grp == NULL ) + { + _read_pkglogs( (const char *)path, (const char *)entry->d_name ); + } + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + } + + closedir( dir ); +} + +int read_pkglogs( void ) +{ + int ret = 0; + + __child = 0; + + _read_pkglogs( (const char *)tmpdir, NULL ); + + ret = __child; + + __child = 0; + + return ret; +} + +/*************************************************** + Decremet REFERENCE COUNTERs functions: + */ +static int save_tmp_head( FILE *log, int stop, const char *fname ) +{ + FILE *fp; + int ret = -1; + + char *ln = NULL; + char *line = NULL; + int n = 1, lines = 0; + + if( !stop || !log || !fname || *fname == '\0' ) return ret; + + fp = fopen( fname, "w" ); + if( !fp ) + { + return ret; + } + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( n < stop ) + { + fprintf( fp, "%s\n", ln ); + ++n; ++lines; + } + else + break; + } + + ret = lines; /* number of lines in the HEAD */ + + free( line ); + + fseek( log, 0, SEEK_SET ); + fclose( fp ); + + return ret; +} + +static int save_tmp_tail( FILE *log, int start, const char *fname ) +{ + FILE *fp; + int ret = -1; + + char *ln = NULL; + char *line = NULL; + int n = 1, lines = 0; + + if( !start || !log || !fname || *fname == '\0' ) return ret; + + fp = fopen( fname, "w" ); + if( !fp ) + { + return ret; + } + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + + while( (ln = fgets( line, PATH_MAX, log )) && (n < start) ) ++n; + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + fprintf( fp, "%s\n", ln ); + ++lines; + } + + ret = lines; /* number of lines in the TAIL */ + + free( line ); + + fseek( log, 0, SEEK_SET ); + fclose( fp ); + + return ret; +} + +static int write_tmp_part( FILE *log, const char *fname ) +{ + FILE *fp; + int ret = -1; + + char *ln = NULL; + char *line = NULL; + int lines = 0; + + if( !log || !fname || *fname == '\0' ) return ret; + + fp = fopen( fname, "r" ); + if( !fp ) + { + return ret; + } + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + + while( (ln = fgets( line, PATH_MAX, fp )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + fprintf( log, "%s\n", ln ); + ++lines; + } + + ret = lines; /* number of written lines */ + + free( line ); + + fclose( fp ); + + return ret; +} + + + +static char **create_references( size_t size ) +{ + char **references = (char **)0; + + if( size > 0 ) + { + references = (char **)malloc( size * sizeof(char *) ); + bzero( (void *)references, size * sizeof(char *) ); + } + + return( references ); +} + +static void free_references( char **references ) +{ + if( references ) + { + char **ptr = references; + + while( *ptr ) + { + if( *ptr ) free( *ptr ); + ptr++; + } + free( references ); + } +} + + +static char **get_references( FILE *log, int start, unsigned int *cnt, char *grp, char *name, char *version ) +{ + char **refs = (char **)0; + char **ptr; + + char *ln = NULL; + char *line = NULL; + int n = 1; + + size_t len = 0; + + unsigned int counter, pkgs; + + char *pkg = NULL; + + if( !log || !cnt || *cnt == 0 || !name || !version ) return refs; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + pkg = (char *)malloc( (size_t)PATH_MAX ); + if( !pkg ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + counter = *cnt; + + if( grp && *grp != '\0' ) { (void)sprintf( pkg, "%s/%s=", grp, name ); } + else { (void)sprintf( pkg, "%s=", name ); } + + len = strlen( pkg ); + + refs = ptr = create_references( counter + 1 ); /* null terminated char *references[] */ + + while( (ln = fgets( line, PATH_MAX, log )) && (n < start) ) ++n; + + n = 0; pkgs = 0; + while( (ln = fgets( line, PATH_MAX, log )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( strstr( ln, "REQUIRES:" ) ) break; /* if cnt greater than real number of references */ + + if( n < counter ) + { + if( strncmp( ln, pkg, len ) ) /* always remove 'name=version' from list */ + { + if( refs ) + { + *ptr = xstrdup( (const char *)ln ); ++ptr; + *ptr = (char *)0; + ++pkgs; + } + } + ++n; + } + else + break; + } + + free( line ); free( pkg ); + + fseek( log, 0, SEEK_SET ); + + if( pkgs == 0 ) + { + free_references( refs ); + refs = (char **)0; + } + + *cnt = pkgs; + + return refs; +} + +static void _change_references( char *grp, char *name, char *version, const char *log_fname ) +{ + int fd; + FILE *log; + + char uncompress = '\0'; + + char *head_fname = NULL, *tail_fname = NULL; + int head_lines, tail_lines; + + int rc, start, stop; + unsigned int counter; + + int inc = 0; + + char **references = NULL; + + char *bname = (char *)log_fname + strlen( pkgs_path ) + 1; + + if( !name || !version || log_fname == NULL ) return; + if( check_input_file( &uncompress, log_fname ) != IFMT_LOG ) return; + + log = fopen( (const char *)log_fname, "r+" ); + if( !log ) + { + LOG( "ERROR: Cannot access %s file: %s", bname, strerror( errno ) ); + exit_status += 1; + return; + } + + fd = __lock_file( log ); + + rc = get_references_section( &start, &stop, &counter, log ); + if( rc != 0 ) + { + LOG( "ERROR: %s: PKGLOG doesn't contains REFERENCE COUNTER section", bname ); + exit_status += 1; + __unlock_file( fd ); fclose( log ); + return; + } + + head_fname = (char *)alloca( strlen( tmpdir ) + 7 ); + (void)sprintf( head_fname, "%s/.HEAD", tmpdir ); + + tail_fname = (char *)alloca( strlen( tmpdir ) + 7 ); + (void)sprintf( tail_fname, "%s/.TAIL", tmpdir ); + + head_lines = save_tmp_head( log, start, (const char *)head_fname ); + tail_lines = save_tmp_tail( log, stop - 1, (const char *)tail_fname ); + + if( head_lines < 10 && tail_lines < 12 ) + { + LOG( "ERROR: %s: Invalid PKGLOG file", bname ); + exit_status += 1; + __unlock_file( fd ); fclose( log ); + return; + } + + references = get_references( log, start, &counter, grp, name, version ); + + if( ftruncate( fd, 0 ) != 0 ) + { + LOG( "ERROR: Cannot change REFERENCE COUNTER in the %s file: %s", bname, strerror( errno ) ); + exit_status += 1; + free_references( references ); + __unlock_file( fd ); fclose( log ); + return; + } + + head_lines = write_tmp_part( log, (const char *)head_fname ); + + if( inc ) ++counter; + fprintf( log, "REFERENCE COUNTER: %u\n", counter ); + if( inc ) + { + if( grp && *grp != '\0' ) + { + fprintf( log, "%s/%s=%s\n", grp, name, version ); + } + else + { + fprintf( log, "%s=%s\n", name, version ); + } + } + + if( references ) + { + char **ptr = references; + + while( *ptr ) + { + if( *ptr ) fprintf( log, "%s\n", *ptr ); + ptr++; + } + + free_references( references ); + } + + tail_lines = write_tmp_part( log, (const char *)tail_fname ); + + __unlock_file( fd ); + fclose( log ); +} + +static void _decrement_references( void *data, void *user_data ) +{ + struct pkg *pkg = (struct pkg *)data; + const char *fname = (const char *)user_data; + + if( pkg && fname ) + { + _change_references( pkg->group, pkg->name, pkg->version, fname ); + } +} + +static void decrement_references( const char *fname ) +{ + dlist_foreach( extern_requires, _decrement_references, (void *)fname ); +} + +static void _decrement_reference_counters( void *data, void *user_data ) +{ + struct package *package = (struct package *)data; + + if( package && package->pkginfo ) + { + char *buf = NULL; + struct pkginfo *info = package->pkginfo; + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + + if( info->group ) + { + (void)sprintf( &buf[0], "%s/%s/%s-%s-%s-%s-%s", pkgs_path, + info->group, + info->name, info->version, info->arch, + info->distro_name, info->distro_version ); + } + else + { + (void)sprintf( &buf[0], "%s/%s-%s-%s-%s-%s", pkgs_path, + info->name, info->version, info->arch, + info->distro_name, info->distro_version ); + } + + decrement_references( (const char *)&buf[0] ); + + free( buf ); + } +} + +static void decrement_reference_counters( void ) +{ + dlist_foreach( provides, _decrement_reference_counters, NULL ); +} + +/**************************************************************** + Если после апдейта пакет просто сменил группу, то его надо + удалить из списка внешних зависимостей, ведь он предоставляет + нужную функциональность. + + Например: libs/libspectre требует libs/cairo, а xlibs/cairo + требует libs/libspectre, однако libs/libspectre может + использовать как libs/cairo так и xlibs/cairo . + + Search installed package with specified name within any group: + */ +static char *pkglog_fname = NULL; + +static void _probe_pkglog( const char *pname, const char *dirpath, const char *grp ) +{ + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + if( pkglog_fname ) return; + + if( stat( dirpath, &path_sb ) == -1 ) + { + FATAL_ERROR( "%s: Cannot stat Setup Database or destination directory", dirpath ); + } + + if( S_ISDIR(path_sb.st_mode) == 0 ) + { + FATAL_ERROR( "%s: Setup Database or destination is not a directory", dirpath ); + } + + if( (dir = opendir(dirpath) ) == NULL ) + { + FATAL_ERROR( "Canot access %s directory: %s", dirpath, strerror( errno ) ); + } + + len = strlen( dirpath ); + + while( (entry = readdir( dir )) != NULL) + { + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( stat( path, &entry_sb ) == 0 ) + { + if( S_ISREG(entry_sb.st_mode) ) + { + char *match = NULL; + char *pkglog = basename( path ); + + if( (match = strstr( pkglog, pname )) && match == pkglog ) + { + char *buf = NULL, *p = NULL, *q = NULL; + + p = q = buf = xstrdup( (const char *)pkglog ); + ++p; + while( *p != '\0' && !isblank(*p) && !(*q == '-' && isdigit(*p)) ) + { + /* package version starts with a number and separated by '-' */ + ++p; ++q; + } + *(--p) = '\0'; + + /******************************************************* + We have to make sure that the name we are looking for + is not shorter than the name of the found package. + */ + if( strlen(pname) >= strlen(buf) ) + { + + pkglog_fname = xstrdup( (const char *)path ); + free( buf ); + closedir( dir ); + return; + } + free( buf ); + } + } + if( S_ISDIR(entry_sb.st_mode) && grp == NULL ) + { + _probe_pkglog( pname, (const char *)path, (const char *)entry->d_name ); + } + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + } + + closedir( dir ); +} + +/****************** + probe_package(): + --------------- + */ +static char *probe_package( const char *name ) +{ + char *ret = NULL; + + _probe_pkglog( name, (const char *)pkgs_path, NULL ); + if( pkglog_fname ) + { + ret = pkglog_fname; + } + + return ret; +} + +static int check_installed_pkgname( const char *name ) +{ + int ret = 0; + char *fname = NULL; + + if( !name ) return ret; + + fname = probe_package( name ); + if( fname ) + { + ret = 1; + free( pkglog_fname ); + pkglog_fname = NULL; + } + + return ret; +} +/* + End of search installed package. + ****************************************************************/ + +static int logged_out = 0; + +static void _log_extern_requires( void *data, void *user_data ) +{ + struct pkg *pkg = (struct pkg *)data; + + if( pkg && !check_installed_pkgname( (const char *)pkg->name ) ) + { + if( !logged_out ) + { + /* LOG( "Система требует инсталляции следующих пакетов:" ); */ + LOG( "The System requires following packages:" ); + ++logged_out; + } + if( pkg->group ) + LOG( " %s/%s-%s : have to be %s", pkg->group, pkg->name, pkg->version, strproc( pkg->procedure ) ); + else + LOG( " %s-%s : have to be %s", pkg->name, pkg->version, strproc( pkg->procedure ) ); + } +} + +static void log_extern_requires( void ) +{ + dlist_foreach( extern_requires, _log_extern_requires, NULL ); + + if( logged_out ) { LOG( "End of requires list." ); } +} +/* + End of decremet REFERENCE COUNTERs functions. + ***************************************************/ + +/*************************************************** + Remove empty DB directories: + */ +static int is_dir_empty( const char *dirpath ) +{ + int ret = 0; + + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + if( stat( dirpath, &path_sb ) == -1 ) return ret; /* stat returns error code; errno is set */ + if( S_ISDIR(path_sb.st_mode) == 0 ) return ret; /* dirpath is not a directory */ + if( (dir = opendir(dirpath) ) == NULL ) return ret; /* Cannot open direcroty; errno is set */ + + ret = 1; + + len = strlen( dirpath ); + while( (entry = readdir( dir )) != NULL) + { + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( stat( path, &entry_sb ) == 0 ) + { + ret = 0; + break; + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + } + closedir( dir ); + + return ret; +} + +static void _remove_empty_dirs( const char *dirpath ) +{ + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + char *pname = (char *)dirpath + (( root ) ? strlen( root ) : 0) ; /* do not remove leading '/' */ + + if( stat( dirpath, &path_sb ) == -1 ) + { + FATAL_ERROR( "%s: Cannot stat Setup Database or group directory", pname ); + } + + if( S_ISDIR(path_sb.st_mode) == 0 ) + { + FATAL_ERROR( "%s: Setup Database or group is not a directory", pname ); + } + + if( (dir = opendir(dirpath) ) == NULL ) + { + FATAL_ERROR( "Canot access %s directory: %s", pname, strerror( errno ) ); + } + + len = strlen( dirpath ); + + while( (entry = readdir( dir )) != NULL) + { + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( stat( path, &entry_sb ) == 0 ) + { + if( S_ISDIR(entry_sb.st_mode) ) + { + if( is_dir_empty( (const char *)path ) ) + { + (void)rmdir( (const char *)path ); + } + } + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + } + + closedir( dir ); +} + +static void remove_empty_dirs( const char *path ) +{ + char *buf = NULL; + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + if( root ) + { + int len = strlen( root ); + + (void)strcpy( buf, (const char *)root ); + if( buf[ len - 1 ] != '/' ) + { + buf[len] = '/'; buf[len+1] = '\0'; + } + (void)strcat( buf, (const char *)path ); + } + else + { + (void)strcpy( buf, path ); + } + + _remove_empty_dirs( (const char *)&buf[0] ); + + free( buf ); +} +/* + End of remove empty DB directories. + ***************************************************/ + + +/********************************************* + Get directory where this program is placed: + */ +char *get_selfdir( void ) +{ + char *buf = NULL; + ssize_t len; + + buf = (char *)malloc( PATH_MAX ); + if( !buf ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + bzero( (void *)buf, PATH_MAX ); + len = readlink( "/proc/self/exe", buf, (size_t)PATH_MAX ); + if( len > 0 && len < PATH_MAX ) + { + char *p = xstrdup( (const char *)dirname( buf ) ); + free( buf ); + return p; + } + FATAL_ERROR( "Cannot determine self directory. Please mount /proc filesystem" ); +} + +void set_stack_size( void ) +{ + const rlim_t stack_size = 16 * 1024 * 1024; /* min stack size = 16 MB */ + struct rlimit rl; + int ret; + + ret = getrlimit( RLIMIT_STACK, &rl ); + if( ret == 0 ) + { + if( rl.rlim_cur < stack_size ) + { + rl.rlim_cur = stack_size; + ret = setrlimit( RLIMIT_STACK, &rl ); + if( ret != 0 ) + { + fprintf(stderr, "setrlimit returned result = %d\n", ret); + FATAL_ERROR( "Cannot set stack size" ); + } + } + } +} + +static void open_errlog_file( void ) +{ + if( errlog_fname && (strcmp( errlog_fname, "-" ) != 0) ) + { + errlog = fopen( (const char *)errlog_fname, "w" ); + if( !errlog ) + { + FATAL_ERROR( "Cannot create LOG '%s' file", basename( errlog_fname ) ); + } + close_log_file = 1; + } + else + { + errlog = stderr; + close_log_file = 0; + } +} + + +int main( int argc, char *argv[] ) +{ + gid_t gid; + + set_signal_handlers(); + + gid = getgid(); + setgroups( 1, &gid ); + + fatal_error_hook = fatal_error_actions; + + selfdir = get_selfdir(); + + errlog = stderr; + + program = basename( argv[0] ); + get_args( argc, argv ); + + /* set_stack_size(); */ + + open_errlog_file(); + + tmpdir = _mk_tmpdir(); + if( !tmpdir ) + { + FATAL_ERROR( "Cannot create temporary directory" ); + } + + LOG( "Check Setup Database Integrity:" ); + + /* Copy PKGLOGs into TMPDIR: */ + { + int pkgs = copy_pkglogs(); + if( pkgs == 0 ) { FATAL_ERROR( "There are no PKGLOG files in the '%s' directory", pkgs_path ); } + if( exit_status > 0 ) { FATAL_ERROR( "Cannot copy some PKGLOG file" ); } + if( ! DO_NOT_PRINTOUT_INFO ) + { + INFO( "Found %d PKGLOG files in the '%s' directory", pkgs, pkgs_path ); + } + } + + /* Read PKGLOGs from TMPDIR and create Double Linked List of PACKAGES: */ + { + int pkgs = read_pkglogs(); + if( pkgs == 0 ) { FATAL_ERROR( "There are no PKGLOG files in the '%s' directory", tmpdir ); } + if( exit_status > 0 ) { FATAL_ERROR( "Cannot read some PKGLOG file" ); } + if( ! DO_NOT_PRINTOUT_INFO ) + { + /* INFO( "Found %d PKGLOG files in the '%s' directory", pkgs, tmpdir ); */ + } + } + + + { + int extern_pkgs = create_provides_list( NULL ); + + /* + На данном этапе, для каждого пакета, добавленного в список + packages, уже выполнена операция 'chrefs -d pkgs_path -o inc PKGLOG' + остается только обработать внешние зависимости, если таковые есть. + */ + if( extern_pkgs ) + { + /* + 1. для каждого элемента сиска provides проходим по extern_requires и делаем + crefs dec (функцию надо взять из chrefs.c так как в списке extern_requires + структуры имеют только 3 поля: group, name, version) + */ + decrement_reference_counters(); + + /* + 2. напечатать список требуемых пакетов: + */ + log_extern_requires(); + } + + free_provides_list(); + } + + + if( exit_status == 0 ) + { + LOG( "Setup Database is clean." ); + } + else + { + LOG( "Setup Database is not fully clean. See the LOG mesages above." ); + } + + if( root ) + { + remove_empty_dirs( PACKAGES_PATH ); + remove_empty_dirs( REMOVED_PKGS_PATH ); + } + else + { + remove_empty_dirs( (const char *)pkgs_path ); + } + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + + exit( exit_status ); +} diff --git a/src/check-package.c b/src/check-package.c new file mode 100644 index 0000000..8997d64 --- /dev/null +++ b/src/check-package.c @@ -0,0 +1,1511 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <sys/types.h> +#include <stdint.h> +#include <dirent.h> +#include <sys/stat.h> /* chmod(2) */ +#include <sys/file.h> /* flock(2) */ +#include <fcntl.h> +#include <linux/limits.h> +#include <alloca.h> /* alloca(3) */ +#include <string.h> /* strdup(3) */ +#include <strings.h> /* index(3) */ +#include <libgen.h> /* basename(3) */ +#include <ctype.h> /* tolower(3) */ +#include <errno.h> +#include <time.h> +#include <sys/time.h> +#include <pwd.h> +#include <grp.h> +#include <stdarg.h> +#include <unistd.h> + +#include <sys/wait.h> + +#include <sys/resource.h> + +#include <signal.h> +#if !defined SIGCHLD && defined SIGCLD +# define SIGCHLD SIGCLD +#endif + +#define _GNU_SOURCE +#include <getopt.h> + +#include <msglog.h> +#include <wrapper.h> +#include <system.h> +#include <cmpvers.h> + +#define PROGRAM_NAME "check-package" + +#include <defs.h> + + +char *program = PROGRAM_NAME; +char *root = NULL, *pkgs_path = NULL, *pkg_fname = NULL, *pkg_found = NULL, + *tmpdir = NULL; + +int quiet = 0, print_broken_files = 0; + +int exit_status = EXIT_SUCCESS; /* errors counter */ +char *selfdir = NULL; + +static char *pkgname = NULL, + *pkgver = NULL, + *arch = NULL, + *distroname = NULL, + *distrover = NULL, + *group = NULL; + +static char *installed_version = NULL; + +enum _input_type { + IFMT_PKG = 0, + IFMT_LOG, + + IFMT_UNKNOWN +} input_format = IFMT_PKG; + + +#define FREE_PKGINFO_VARIABLES() \ + if( pkgname ) { free( pkgname ); } pkgname = NULL; \ + if( pkgver ) { free( pkgver ); } pkgver = NULL; \ + if( arch ) { free( arch ); } arch = NULL; \ + if( distroname ) { free( distroname ); } distroname = NULL; \ + if( distrover ) { free( distrover ); } distrover = NULL; \ + if( group ) { free( group ); } group = NULL; \ + if( installed_version ) { free( installed_version ); } installed_version = NULL + +void free_resources() +{ + if( root ) { free( root ); root = NULL; } + if( pkgs_path ) { free( pkgs_path ); pkgs_path = NULL; } + if( pkg_fname ) { free( pkg_fname ); pkg_fname = NULL; } + + if( selfdir ) { free( selfdir ); selfdir = NULL; } + + FREE_PKGINFO_VARIABLES(); +} + +void usage() +{ + free_resources(); + + fprintf( stdout, "\n" ); + fprintf( stdout, "Usage: %s [options] <package|pkglog|pkgname>\n", program ); + fprintf( stdout, "\n" ); + fprintf( stdout, "This utility checks if specified package is installed. If package\n" ); + fprintf( stdout, "or some another version of this package is already installed then\n" ); + fprintf( stdout, "this utility checks if installed package is correct.\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Options:\n" ); + fprintf( stdout, " -h,--help Display this information.\n" ); + fprintf( stdout, " -v,--version Display the version of %s utility.\n", program ); + fprintf( stdout, " -p,--print-broken-files Print the list of broken directories,\n" ); + fprintf( stdout, " files, or symbolic links to stdout.\n" ); + fprintf( stdout, " -q,--quiet Do not display explanations for\n" ); + fprintf( stdout, " return codes.\n" ); + fprintf( stdout, " -r,--root=<DIR> Target rootfs path.\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Parameter:\n" ); + fprintf( stdout, " <package|pkglog|pkgname> The PKGNAME, PACKAGE tarball or PKGLOG.\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Return codes:\n" ); + fprintf( stdout, " ------+---------------------------+--------------------\n" ); + fprintf( stdout, " code | status | what can be done\n" ); + fprintf( stdout, " ------+---------------------------+--------------------\n" ); + fprintf( stdout, " 30 | not installed | install\n" ); + fprintf( stdout, " 31 | installed correctly | nothing to do\n" ); + fprintf( stdout, " 32 | installed but not correct | repair, re-install\n" ); + fprintf( stdout, " 33 | installed correctly | upgrade\n" ); + fprintf( stdout, " 34 | installed but not correct | repair, upgrade\n" ); + fprintf( stdout, " 35 | installed correctly | downgrade\n" ); + fprintf( stdout, " 36 | installed but not correct | repair, downgrade\n" ); + fprintf( stdout, " ------+---------------------------+--------------------\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Other non-zero codes assumes that this utility faced to errors not\n" ); + fprintf( stdout, "related to quality of installed package.\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "If package specified by short name (probably with version) instead\n" ); + fprintf( stdout, "of regular files such as package tarball or pkglog file then this\n" ); + fprintf( stdout, "utility doesn't check other installed versions of this package.\n" ); + fprintf( stdout, "\n" ); + + exit( EXIT_FAILURE ); +} + +void to_lowercase( char *s ) +{ + char *p = s; + while( p && *p ) { int c = *p; *p = tolower( c ); ++p; } +} + +void to_uppercase( char *s ) +{ + char *p = s; + while( p && *p ) { int c = *p; *p = toupper( c ); ++p; } +} + +void version() +{ + char *upper = NULL; + + upper = (char *)alloca( strlen( program ) + 1 ); + + strcpy( (char *)upper, (const char *)program ); + to_uppercase( upper ); + + fprintf( stdout, "%s (%s) %s\n", program, upper, PROGRAM_VERSION ); + + fprintf( stdout, "Copyright (C) 2019 Andrey V.Kosteltsev.\n" ); + fprintf( stdout, "This is free software. There is NO warranty; not even\n" ); + fprintf( stdout, "for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" ); + fprintf( stdout, "\n" ); + + free_resources(); + exit( EXIT_SUCCESS ); +} + + +static void remove_trailing_slash( char *dir ) +{ + char *s; + + if( !dir || dir[0] == '\0' ) return; + + s = dir + strlen( dir ) - 1; + while( *s == '/' ) + { + *s = '\0'; --s; + } +} + + +static int _mkdir_p( const char *dir, const mode_t mode ) +{ + char *buf; + char *p = NULL; + struct stat sb; + + if( !dir ) return -1; + + buf = (char *)alloca( strlen( dir ) + 1 ); + strcpy( buf, dir ); + + remove_trailing_slash( buf ); + + /* check if path exists and is a directory */ + if( stat( buf, &sb ) == 0 ) + { + if( S_ISDIR(sb.st_mode) ) + { + return 0; + } + } + + /* mkdir -p */ + for( p = buf + 1; *p; ++p ) + { + if( *p == '/' ) + { + *p = 0; + /* test path */ + if( stat( buf, &sb ) != 0 ) + { + /* path does not exist - create directory */ + if( mkdir( buf, mode ) < 0 ) + { + return -1; + } + } else if( !S_ISDIR(sb.st_mode) ) + { + /* not a directory */ + return -1; + } + *p = '/'; + } + } + + /* test path */ + if( stat( buf, &sb ) != 0 ) + { + /* path does not exist - create directory */ + if( mkdir( buf, mode ) < 0 ) + { + return -1; + } + } else if( !S_ISDIR(sb.st_mode) ) + { + /* not a directory */ + return -1; + } + + return 0; +} + +static void _rm_tmpdir( const char *dirpath ) +{ + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + if( stat( dirpath, &path_sb ) == -1 ) + { + return; /* stat returns error code; errno is set */ + } + + if( S_ISDIR(path_sb.st_mode) == 0 ) + { + return; /* dirpath is not a directory */ + } + + if( (dir = opendir(dirpath) ) == NULL ) + { + return; /* Cannot open direcroty; errno is set */ + } + + len = strlen( dirpath ); + + while( (entry = readdir( dir )) != NULL) + { + + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( stat( path, &entry_sb ) == 0 ) + { + if( S_ISDIR(entry_sb.st_mode) ) + { + /* recursively remove a nested directory */ + _rm_tmpdir( path ); + } + else + { + /* remove a file object */ + (void)unlink( path ); + } + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + + } + + /* remove the devastated directory and close the object of this directory */ + (void)rmdir( dirpath ); + + closedir( dir ); +} + +static char *_mk_tmpdir( void ) +{ + char *buf = NULL, *p, *tmp = "/tmp"; + size_t len = 0, size = 0; + + (void)umask( S_IWGRP | S_IWOTH ); /* octal 022 */ + + /* Get preferred directory for tmp files */ + if( (p = getenv( "TMP" )) != NULL ) { + tmp = p; + } + else if( (p = getenv( "TEMP" )) != NULL ) { + tmp = p; + } + + size = strlen( tmp ) + strlen( DISTRO_NAME ) + strlen( program ) + 12; + + buf = (char *)malloc( size ); + if( !buf ) return NULL; + + len = snprintf( buf, size, (const char *)"%s/%s/%s-%.7u", tmp, DISTRO_NAME, program, getpid() ); + if( len == 0 || len == size - 1 ) + { + free( buf ); return NULL; + } + + _rm_tmpdir( (const char *)&buf[0] ); + + if( _mkdir_p( buf, S_IRWXU | S_IRWXG | S_IRWXO ) == 0 ) + { + return buf; + } + + free( buf ); return NULL; +} + + +void fatal_error_actions( void ) +{ + logmsg( errlog, MSG_NOTICE, "Free resources on FATAL error..." ); + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); +} + +void sigint( int signum ) +{ + (void)signum; + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); +} + +static void set_signal_handlers() +{ + struct sigaction sa; + sigset_t set; + + memset( &sa, 0, sizeof( sa ) ); + sa.sa_handler = sigint; /* TERM, INT */ + sa.sa_flags = SA_RESTART; + sigemptyset( &set ); + sigaddset( &set, SIGTERM ); + sigaddset( &set, SIGINT ); + sa.sa_mask = set; + sigaction( SIGTERM, &sa, NULL ); + sigaction( SIGINT, &sa, NULL ); + + memset( &sa, 0, sizeof( sa ) ); /* ignore SIGPIPE */ + sa.sa_handler = SIG_IGN; + sa.sa_flags = 0; + sigaction( SIGPIPE, &sa, NULL ); + + /* System V fork+wait does not work if SIGCHLD is ignored */ + signal( SIGCHLD, SIG_DFL ); +} + + +static enum _input_type check_input_file( char *uncompress, const char *fname ) +{ + struct stat st; + size_t pkglog_size = 0; + unsigned char buf[8]; + int rc, fd; + + /* SIGNATURES: https://www.garykessler.net/library/file_sigs.html */ + + if( uncompress ) + { + *uncompress = '\0'; + } + + if( stat( fname, &st ) == -1 ) + { + FATAL_ERROR( "Cannot access %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + + pkglog_size = st.st_size; + + if( (fd = open( fname, O_RDONLY )) == -1 ) + { + FATAL_ERROR( "Cannot open %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + + rc = (int)read( fd, (void *)&buf[0], 7 ); + if( rc != 7 ) + { + close( fd ); return IFMT_UNKNOWN; + } + buf[7] = '\0'; + + /* TEXT */ + if( !strncmp( (const char *)&buf[0], "PACKAGE", 7 ) ) + { + close( fd ); return IFMT_LOG; + } + + /* GZ */ + if( buf[0] == 0x1F && buf[1] == 0x8B && buf[2] == 0x08 ) + { + if( uncompress ) { *uncompress = 'x'; } + close( fd ); return IFMT_PKG; + } + + /* BZ2 */ + if( buf[0] == 0x42 && buf[1] == 0x5A && buf[2] == 0x68 ) + { + if( uncompress ) { *uncompress = 'j'; } + close( fd ); return IFMT_PKG; + } + + /* XZ */ + if( buf[0] == 0xFD && buf[1] == 0x37 && buf[2] == 0x7A && + buf[3] == 0x58 && buf[4] == 0x5A && buf[5] == 0x00 ) + { + if( uncompress ) { *uncompress = 'J'; } + close( fd ); return IFMT_PKG; + } + + if( pkglog_size > 262 ) + { + if( lseek( fd, 257, SEEK_SET ) == -1 ) + { + FATAL_ERROR( "Cannot check signature of %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + rc = (int)read( fd, &buf[0], 5 ); + if( rc != 5 ) + { + FATAL_ERROR( "Cannot read signature of %s file", basename( (char *)fname ) ); + } + /* TAR */ + if( buf[0] == 0x75 && buf[1] == 0x73 && buf[2] == 0x74 && buf[3] == 0x61 && buf[4] == 0x72 ) + { + close( fd ); return IFMT_PKG; + } + } + + close( fd ); return IFMT_UNKNOWN; +} + + +void get_args( int argc, char *argv[] ) +{ + const char* short_options = "hvpqr:"; + + const struct option long_options[] = + { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'v' }, + { "print-broken-files", no_argument, NULL, 'p' }, + { "quiet", no_argument, NULL, 'q' }, + { "root", required_argument, NULL, 'r' }, + { NULL, 0, NULL, 0 } + }; + + int ret; + int option_index = 0; + + while( (ret = getopt_long( argc, argv, short_options, long_options, &option_index )) != -1 ) + { + switch( ret ) + { + case 'h': + { + usage(); + break; + } + case 'v': + { + version(); + break; + } + case 'p': + { + print_broken_files = 1; + break; + } + case 'q': + { + quiet = 1; + break; + } + + case 'r': + { + if( optarg != NULL ) + { + root = xstrdup( (const char *)optarg ); + remove_trailing_slash( root ); + } + else + /* option is present but without value */ + usage(); + break; + } + + case '?': default: + { + usage(); + break; + } + } + } + + + if( optind < argc ) + { + pkg_fname = xstrdup( (const char *)argv[optind] ); + } + else + { + usage(); + } + + + if( !pkgs_path ) + { + struct stat st; + char *buf = NULL; + + bzero( (void *)&st, sizeof( struct stat ) ); + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + if( !root ) + { + buf[0] = '/'; buf[1] = '\0'; + } + else + { + int len = strlen( root ); + + (void)strcpy( buf, (const char *)root ); + if( buf[ len - 1 ] != '/' ) + { + buf[len] = '/'; buf[len+1] = '\0'; + } + } + + (void)strcat( buf, PACKAGES_PATH ); + if( stat( (const char *)&buf[0], &st ) == -1 ) + { + FATAL_ERROR( "Cannot access '%s' file or directory: %s", buf, strerror( errno ) ); + } + + if( S_ISDIR(st.st_mode) ) + { + pkgs_path = xstrdup( (const char *)&buf[0] ); + free( buf ); + } + else + { + FATAL_ERROR( "Defined --root '%s' is not a directory", buf ); + } + + } /* End if( !pkgs_path ) */ +} + + +/*********************************************************** + Remove leading spaces and take non-space characters only: + (Especialy for pkginfo lines) + */ +static char *skip_spaces( char *s ) +{ + char *q, *p = (char *)0; + + if( !s || *s == '\0' ) return p; + + p = s; + + while( (*p == ' ' || *p == '\t') && *p != '\0' ) { ++p; } q = p; + while( *q != ' ' && *q != '\t' && *q != '\0' ) { ++q; } *q = '\0'; + + if( *p == '\0' ) return (char *)0; + + return( xstrdup( (const char *)p ) ); +} + + +/******************************* + remove spaces at end of line: + */ +static void skip_eol_spaces( char *s ) +{ + char *p = (char *)0; + + if( !s || *s == '\0' ) return; + + p = s + strlen( s ) - 1; + while( isspace( *p ) ) { *p-- = '\0'; } +} + + +/*************************************************************** + Probe functions: + */ +static void _probe_pkglog( const char *dirpath, const char *grp ) +{ + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + if( pkg_found ) return; + + if( stat( dirpath, &path_sb ) == -1 ) + { + FATAL_ERROR( "%s: Cannot stat Setup Database or destination directory", dirpath ); + } + + if( S_ISDIR(path_sb.st_mode) == 0 ) + { + FATAL_ERROR( "%s: Setup Database or destination is not a directory", dirpath ); + } + + if( (dir = opendir(dirpath) ) == NULL ) + { + FATAL_ERROR( "Canot access %s directory: %s", dirpath, strerror( errno ) ); + } + + len = strlen( dirpath ); + + while( (entry = readdir( dir )) != NULL) + { + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( stat( path, &entry_sb ) == 0 ) + { + if( S_ISREG(entry_sb.st_mode) ) + { + char *match = NULL; + char *pkglog = basename( path ); + + if( (match = strstr( pkglog, (const char *)basename( pkg_fname ) )) && match == pkglog ) + { + char *buf = NULL, *p = NULL, *q = NULL; + + p = q = buf = xstrdup( (const char *)pkglog ); + ++p; + while( *p != '\0' && !isblank(*p) && !(*q == '-' && isdigit(*p)) ) + { + /* package version starts with a number and separated by '-' */ + ++p; ++q; + } + *(--p) = '\0'; + + /******************************************************* + We have to make sure that the name we are looking for + is not shorter than the name of the found package. + */ + if( strlen(pkg_fname) >= strlen(buf) ) + { + + pkg_found = xstrdup( (const char *)path ); + free( buf ); + closedir( dir ); + return; + } + free( buf ); + } + } + if( S_ISDIR(entry_sb.st_mode) && grp == NULL ) + { + _probe_pkglog( (const char *)path, (const char *)entry->d_name ); + } + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + } + + closedir( dir ); +} + +/*********************************************************** + probe_package(): + --------------- + */ +char *probe_package( void ) +{ + char *ret = NULL; + + _probe_pkglog( (const char *)pkgs_path, NULL ); + if( pkg_found ) + { + free( pkg_fname ); + ret = pkg_fname = pkg_found; + } + + return ret; +} +/* + Enf of Probe functions. + ***********************************************************/ + + + +static void read_input_pkginfo( const char *pkginfo_fname ) +{ + char *ln = NULL; + char *line = NULL; + + FILE *pkginfo = NULL; + + if( pkginfo_fname != NULL ) + { + pkginfo = fopen( (const char *)pkginfo_fname, "r" ); + if( !pkginfo ) + { + FATAL_ERROR( "Cannot open %s file", pkginfo_fname ); + } + } + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + while( (ln = fgets( line, PATH_MAX, pkginfo )) ) + { + char *match = NULL; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( (match = strstr( ln, "pkgname" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) pkgname = skip_spaces( p ); + } + if( (match = strstr( ln, "pkgver" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) pkgver = skip_spaces( p ); + } + if( (match = strstr( ln, "arch" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) arch = skip_spaces( p ); + } + if( (match = strstr( ln, "distroname" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) distroname = skip_spaces( p ); + } + if( (match = strstr( ln, "distrover" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) distrover = skip_spaces( p ); + } + + if( (match = strstr( ln, "group" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) group = skip_spaces( p ); + } + } + + free( line ); + + if( !pkgname || !pkgver || !arch || !distroname || !distrover ) + { + FATAL_ERROR( "Invalid input .PKGINFO file" ); + } + + fclose( pkginfo ); +} + +static void read_found_pkginfo( const char *pkginfo_fname ) +{ + char *ln = NULL; + char *line = NULL; + + FILE *pkginfo = NULL; + + char *pn = NULL, *pv = NULL, *ar = NULL, *dn = NULL, *dv = NULL; + + if( pkginfo_fname != NULL ) + { + pkginfo = fopen( (const char *)pkginfo_fname, "r" ); + if( !pkginfo ) + { + FATAL_ERROR( "Cannot open %s file", pkginfo_fname ); + } + } + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + while( (ln = fgets( line, PATH_MAX, pkginfo )) ) + { + char *match = NULL; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( (match = strstr( ln, "pkgname" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) pn = skip_spaces( p ); + } + if( (match = strstr( ln, "pkgver" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) pv = skip_spaces( p ); + } + if( (match = strstr( ln, "arch" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) ar = skip_spaces( p ); + } + if( (match = strstr( ln, "distroname" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) dn = skip_spaces( p ); + } + if( (match = strstr( ln, "distrover" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) dv = skip_spaces( p ); + } + } + + free( line ); + + if( !pn || !pv || !ar || !dn || !dv ) + { + FATAL_ERROR( "Invalid input .PKGINFO file" ); + } + else + { + if( pn ) free( pn ); + if( pv ) installed_version = pv; + if( ar ) free( ar ); + if( dn ) free( dn ); + if( dv ) free( dv ); + } + + fclose( pkginfo ); +} + +static void _get_found_pkginfo( const char *pkglog ) +{ + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *tmp= NULL, *cmd = NULL; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + (void)sprintf( &tmp[0], "%s", tmpdir ); + if( _mkdir_p( tmp, S_IRWXU | S_IRWXG | S_IRWXO ) != 0 ) + { + FATAL_ERROR( "Cannot get PKGINFO from '%s' file", basename( (char *)pkglog ) ); + } + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, + "%s/pkginfo -d %s -o pkginfo,restore-links,filelist %s > /dev/null 2>&1", + selfdir, tmp, pkglog ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot get PKGINFO from %s file", basename( (char *)pkglog ) ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + if( rc != 0 ) + { + FATAL_ERROR( "Cannot get PKGINFO from '%s' file", basename( (char *)pkglog ) ); + } + + (void)strcat( tmp, "/.PKGINFO" ); + read_found_pkginfo( (const char *)&tmp[0] ); + *(strstr( tmp, "/.PKGINFO" )) = '\0'; /* :restore tmpdir in tmp[] buffer */ + + free( tmp ); + free( cmd ); +} + +static void _search_pkglog( const char *dirpath, const char *grp ) +{ + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + char *pname = (char *)dirpath + strlen( root ); /* do not remove leading '/' */ + + if( stat( dirpath, &path_sb ) == -1 ) + { + FATAL_ERROR( "%s: Cannot stat Setup Database or group directory", pname ); + } + + if( S_ISDIR(path_sb.st_mode) == 0 ) + { + FATAL_ERROR( "%s: Setup Database or group is not a directory", pname ); + } + + if( (dir = opendir(dirpath) ) == NULL ) + { + FATAL_ERROR( "Canot access %s directory: %s", pname, strerror( errno ) ); + } + + len = strlen( dirpath ); + + while( (entry = readdir( dir )) != NULL) + { + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( stat( path, &entry_sb ) == 0 ) + { + if( S_ISREG(entry_sb.st_mode) ) + { + char *match = NULL, *name = basename( path ); + + if( (match = strstr( name, pkgname )) && match == name ) + { + /**************************************************************** + Здесь мы еще должны проверить, что найденный пакет не имеет + более длинное имя, которое начинается с имени искомого пакета. + Полагаясь на факт, что версия может начинаться только с цифры, + мы пропускаем символ '-', разделяющий имя и версию пакета, + а затем проверяем начальный символ версии: + */ + if( *(name + strlen( pkgname )) == '-' && isdigit( *(name + strlen( pkgname ) + 1) ) ) + { + _get_found_pkginfo( (const char *)path ); + } + } + } + if( S_ISDIR(entry_sb.st_mode) && grp == NULL ) + { + /************************************************************************** + NOTE: + In the Setup Database can be only one package with the same pkgname + but in different groups. For example, the package named 'cairo' + has two instance: libs/cairo-1.14.6 and xlibs/cairo-1.14.6. During + system installation the package libs/cairo-1.14.6 installed first + and then updated by xlibs/cairo-1.14.6 and PKGLOG of libs/cairo-1.14.6 + moved from /var/log/radix/packages to /var/log/radix/removed-packages. + + So here we have to look for the PKGLOG in all group directories: + */ + _search_pkglog( (const char *)path, (const char *)entry->d_name ); + } + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + } + + closedir( dir ); +} + +static void find_installed_package( void ) +{ + char *tmp = NULL; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + (void)sprintf( &tmp[0], "%s", pkgs_path ); + + _search_pkglog( (const char *)&tmp[0], NULL ); + + free( tmp ); +} + +/*************************************************************** + check_input_package(): + --------------------- + + Возвращает: + -1 если пакет установлен, но его версия меньше + запрашиваемого, + 0 если пакет не установлен или версия установленного и + запрашиваемого равны, + 1 если пакет установлен, но его версия больше + запрашиваемого. + + В случае возврата -1 или 1, устанавливается переменная + installed_version, равная версии уже установленного пакета. + + В случае возврата нуля есть два варанта: + а) пакет установлен и его надо проверить на целостность + (переменная installed_version != NULL ); + б) пакет не установлен. + + Если installed_version != NULL, проверка целостности + пакета будет осуществлена, вне зависимости от его версии. + */ +static int check_input_package( void ) +{ + struct stat st; + char *fname = pkg_fname; + + enum _input_type type = IFMT_UNKNOWN; + char uncompress = '\0'; + + int ret = 0; + + bzero( (void *)&st, sizeof( struct stat ) ); + + if( stat( (const char *)fname, &st ) == -1 ) + { + /************************************************* + Specified pkg_fname is not a file or directory. + Try to find installed package with name equal + to pkg_fname: + */ + fname = NULL; + fname = probe_package(); + if( !fname ) + { + if( !quiet ) fprintf( stdout, "Specified package '%s' is not installed.\n\n", pkg_fname ); + + exit_status = 30; /* Package is not installed: install */ + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + + exit( exit_status ); + } + } + + /* check pkg_fname again: */ + if( stat( (const char *)fname, &st ) == -1 ) + { + FATAL_ERROR( "Cannot access input '%s' file: %s", fname, strerror( errno ) ); + } + + type = check_input_file( &uncompress, fname ); + if( type == IFMT_UNKNOWN ) + { + FATAL_ERROR( "Unknown format of input '%s' file", fname ); + } + + if( S_ISREG(st.st_mode) ) + { + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *tmp= NULL, *cmd = NULL; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + (void)sprintf( &tmp[0], "%s", tmpdir ); + if( _mkdir_p( tmp, S_IRWXU | S_IRWXG | S_IRWXO ) != 0 ) + { + FATAL_ERROR( "Cannot get PKGINFO from '%s' file", basename( (char *)fname ) ); + } + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, + "%s/pkginfo -d %s -o pkginfo,restore-links,filelist %s > /dev/null 2>&1", + selfdir, tmp, fname ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot get PKGINFO from %s file", basename( (char *)fname ) ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + if( rc != 0 ) + { + FATAL_ERROR( "Cannot get PKGINFO from '%s' file", basename( (char *)fname ) ); + } + + (void)strcat( tmp, "/.PKGINFO" ); + read_input_pkginfo( (const char *)&tmp[0] ); + *(strstr( tmp, "/.PKGINFO" )) = '\0'; /* :restore tmpdir in tmp[] buffer */ + + find_installed_package(); + + free( cmd ); + free( tmp ); + + if( installed_version ) + { + ret = cmp_version( (const char *)installed_version, (const char *)pkgver ); + } + } + else + { + FATAL_ERROR( "Input %s file is not a regular file", basename( (char *)fname ) ); + } + + return ret; +} + +static int check_package_integrity( void ) +{ + struct stat st; + FILE *fp = NULL; + + char *ln = NULL; + char *line = NULL; + + char *buf = NULL, *tmp = NULL; + + int restore_links = 0; + int ret = 1; + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + /* Check if .RESTORELINKS is present */ + (void)sprintf( &tmp[0], "%s/.RESTORELINKS", tmpdir ); + bzero( (void *)&st, sizeof( struct stat ) ); + if( (stat( (const char *)&tmp[0], &st ) == 0) && (st.st_size > 8) ) + { + restore_links = 1; + } + + (void)sprintf( &tmp[0], "%s/.FILELIST", tmpdir ); + fp = fopen( (const char *)&tmp[0], "r" ); + if( !fp ) + { + FATAL_ERROR( "Cannot open .FILELIST file" ); + } + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)line, PATH_MAX ); + + while( (ln = fgets( line, PATH_MAX, fp )) ) + { + int dir = 0; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( *(ln + strlen(ln) - 1) == '/' ) { dir = 1; *(ln + strlen(ln) - 1) = '\0'; } + else { dir = 0; } + + if( !dir ) + { + char *p = rindex( ln, '.' ); + if( p && !strncmp( (const char *)p, ".new", 4 ) ) + { + /************************** + Do not check .new files: + */ + *p = '\0'; + } + } + + (void)sprintf( &buf[0], "%s/%s", root, ln ); + bzero( (void *)&st, sizeof( struct stat ) ); + + if( lstat( (const char *)&buf[0], &st ) == -1 ) + { + /* cannot access file list entry */ + if( dir ) + { + if( print_broken_files ) + fprintf( stdout, "%s-%s: /%s: no such directory\n", pkgname, installed_version, ln ); + } + else + { + if( print_broken_files ) + fprintf( stdout, "%s-%s: /%s: no such file\n", pkgname, installed_version, ln ); + } + + ret = 0; continue; + } + + if( dir ) + { + if( S_ISDIR(st.st_mode) == 0 ) + { + /* not a directory */ + if( print_broken_files ) + fprintf( stdout, "%s-%s: /%s: not a directory\n", pkgname, installed_version, ln ); + ret = 0; continue; + } + } + else + { + if( S_ISREG(st.st_mode) == 0 ) + { + /* not a regular file */ + if( print_broken_files ) + fprintf( stdout, "%s-%s: /%s: not a regular file\n", pkgname, installed_version, ln ); + ret = 0; continue; + } + if( !restore_links ) + { + if( S_ISLNK(st.st_mode) == 0 ) + { + /* not a symbolic link */ + if( print_broken_files ) + fprintf( stdout, "%s-%s: /%s: not a symbolic link\n", pkgname, installed_version, ln ); + ret = 0; continue; + } + } + } + } /* End of while( file list entry ) */ + fclose( fp ); + + + (void)sprintf( &tmp[0], "%s/.RESTORELINKS", tmpdir ); + bzero( (void *)&st, sizeof( struct stat ) ); + + if( stat( (const char *)&tmp[0], &st ) == 0 ) + { + fp = fopen( (const char *)&tmp[0], "r" ); + if( !fp ) + { + FATAL_ERROR( "Cannot open .RESTORELINKS file" ); + } + + while( (ln = fgets( line, PATH_MAX, fp )) ) + { + char *match = NULL; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( (match = strstr( ln, "; rm -rf " )) ) + { + char *q = NULL; + char *p = strstr( ln, "cd" ) + 2; + char *f = strstr( ln, "; rm -rf" ) + 8; + + if( !p || !f ) continue; + + while( (*p == ' ' || *p == '\t') && *p != '\0' ) ++p; + while( (*f == ' ' || *f == '\t') && *f != '\0' ) ++f; + + q = p; while( *q != ' ' && *q != '\t' && *q != ';' && *q != '\0' ) ++q; *q = '\0'; + q = f; while( *q != ' ' && *q != '\t' && *q != ';' && *q != '\0' ) ++q; *q = '\0'; + + if( p && f ) + { + (void)sprintf( &buf[0], "%s/%s/%s", root, p, f ); + bzero( (void *)&st, sizeof( struct stat ) ); + + if( lstat( (const char *)&buf[0], &st ) == -1 ) + { + /* cannot access restore links entry */ + if( print_broken_files ) + fprintf( stdout, "%s-%s: /%s/%s: no such file or directory\n", pkgname, installed_version, p, f ); + ret = 0; continue; + } + + if( S_ISLNK(st.st_mode) == 0 ) + { + /* not a symbolic link */ + if( print_broken_files ) + fprintf( stdout, "%s-%s: /%s/%s: not a symbolic link\n", pkgname, installed_version, p, f ); + ret = 0; continue; + } + } + } + } /* End of while( restore links entry ) */ + fclose( fp ); + } + + free( line ); + free( tmp ); + free( buf ); + + return ret; +} + + +/********************************************* + Get directory where this program is placed: + */ +char *get_selfdir( void ) +{ + char *buf = NULL; + ssize_t len; + + buf = (char *)malloc( PATH_MAX ); + if( !buf ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + bzero( (void *)buf, PATH_MAX ); + len = readlink( "/proc/self/exe", buf, (size_t)PATH_MAX ); + if( len > 0 && len < PATH_MAX ) + { + char *p = xstrdup( (const char *)dirname( buf ) ); + free( buf ); + return p; + } + FATAL_ERROR( "Cannot determine self directory. Please mount /proc filesystem" ); +} + +void set_stack_size( void ) +{ + const rlim_t stack_size = 16 * 1024 * 1024; /* min stack size = 16 MB */ + struct rlimit rl; + int ret; + + ret = getrlimit( RLIMIT_STACK, &rl ); + if( ret == 0 ) + { + if( rl.rlim_cur < stack_size ) + { + rl.rlim_cur = stack_size; + ret = setrlimit( RLIMIT_STACK, &rl ); + if( ret != 0 ) + { + fprintf(stderr, "setrlimit returned result = %d\n", ret); + FATAL_ERROR( "Cannot set stack size" ); + } + } + } +} + + +int main( int argc, char *argv[] ) +{ + gid_t gid; + + set_signal_handlers(); + + gid = getgid(); + setgroups( 1, &gid ); + + fatal_error_hook = fatal_error_actions; + + selfdir = get_selfdir(); + + errlog = stderr; + + program = basename( argv[0] ); + get_args( argc, argv ); + + /* set_stack_size(); */ + + tmpdir = _mk_tmpdir(); + if( !tmpdir ) + { + FATAL_ERROR( "Cannot create temporary directory" ); + } + + + { + int status = 0, correctly = 0; + + /********************************************************** + Fill pkginfo data and put or replace pkglog into tmpdir: + */ + status = check_input_package(); + + if( installed_version ) + { + /* In this case we have to check package integrity */ + correctly = check_package_integrity(); /* returns 1 if correct */ + } + + if( exit_status == EXIT_SUCCESS ) + { + if( status < 0 ) + { + if( !correctly ) + { + if( !quiet ) + fprintf( stdout, + "Previous version '%s' of specified package '%s-%s' is installed but not correct.\n\n", + installed_version, pkgname, pkgver ); + exit_status = 34; /* Package is installed but not correct: repair, upgrade */ + } + else + { + if( !quiet ) + fprintf( stdout, + "Previous version '%s' of specified package '%s-%s' is installed.\n\n", + installed_version, pkgname, pkgver ); + exit_status = 33; /* Package is installed correctly: upgrade */ + } + } + else if( status > 0 ) + { + if( !correctly ) + { + if( !quiet ) + fprintf( stdout, + "A newer version '%s' of specified package '%s-%s' is installed but not correct.\n\n", + installed_version, pkgname, pkgver ); + exit_status = 36; /* Package is installed but not correct: repair, downgrade */ + } + else + { + if( !quiet ) + fprintf( stdout, + "A newer version '%s' of specified package '%s-%s' is installed.\n\n", + installed_version, pkgname, pkgver ); + exit_status = 35; /* Package is installed correctly: downgrade */ + } + } + else + { + if( installed_version ) + { + if( !correctly ) + { + if( !quiet ) + fprintf( stdout, + "Specified package '%s-%s' is already installed but not correct.\n\n", + pkgname, pkgver ); + exit_status = 32; /* Package is installed but not correct: repair, re-install */ + } + else + { + if( !quiet ) + fprintf( stdout, + "Specified package '%s-%s' is already installed.\n\n", + pkgname, pkgver ); + exit_status = 31; /* Package is installed correctly: nothing to do */ + } + } + else + { + if( !quiet ) + fprintf( stdout, + "Specified package '%s-%s' is not installed.\n\n", + pkgname, pkgver ); + exit_status = 30; /* Package is not installed: install */ + } + } + } + + } + + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + + exit( exit_status ); +} diff --git a/src/check-requires.c b/src/check-requires.c new file mode 100644 index 0000000..396b491 --- /dev/null +++ b/src/check-requires.c @@ -0,0 +1,2616 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <sys/types.h> +#include <stdint.h> +#include <dirent.h> +#include <sys/stat.h> /* chmod(2) */ +#include <sys/file.h> /* flock(2) */ +#include <fcntl.h> +#include <linux/limits.h> +#include <alloca.h> /* alloca(3) */ +#include <string.h> /* strdup(3) */ +#include <strings.h> /* index(3) */ +#include <libgen.h> /* basename(3) */ +#include <ctype.h> /* tolower(3) */ +#include <errno.h> +#include <time.h> +#include <sys/time.h> +#include <pwd.h> +#include <grp.h> +#include <stdarg.h> +#include <unistd.h> + +#include <math.h> + +#include <sys/wait.h> + +#include <sys/resource.h> + +#include <signal.h> +#if !defined SIGCHLD && defined SIGCLD +# define SIGCHLD SIGCLD +#endif + +#define _GNU_SOURCE +#include <getopt.h> + +#include <msglog.h> +#include <wrapper.h> +#include <system.h> +#include <dlist.h> +#include <pkglist.h> + +#define PROGRAM_NAME "check-requires" + +#include <defs.h> + + +char *program = PROGRAM_NAME; +char *root = NULL, *pkgs_path = NULL, *pkg_fname = NULL, + *tmpdir = NULL; + +int exit_status = EXIT_SUCCESS; /* errors counter */ +char *selfdir = NULL; + +int __done = 0, __child = 0; + +enum _input_type { + IFMT_PKG = 0, + IFMT_LOG, + + IFMT_UNKNOWN +} input_format = IFMT_PKG; + +enum _priority priority = REQUIRED; + + +void free_resources() +{ + if( root ) { free( root ); root = NULL; } + if( pkgs_path ) { free( pkgs_path ); pkgs_path = NULL; } + if( pkg_fname ) { free( pkg_fname ); pkg_fname = NULL; } + + if( selfdir ) { free( selfdir ); selfdir = NULL; } + + free_tarballs(); + free_packages(); + free_srcpkgs(); +} + +void usage() +{ + free_resources(); + + fprintf( stdout, "\n" ); + fprintf( stdout, "Usage: %s [options] <package|pkglog>\n", program ); + fprintf( stdout, "\n" ); + fprintf( stdout, "This utility checks if packages required by requested package are\n" ); + fprintf( stdout, "instaled. If there are non-installed packages or packages have to\n" ); + fprintf( stdout, "updated then the list of non-installed packages is output to the\n" ); + fprintf( stdout, "standard error stream.\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Options:\n" ); + fprintf( stdout, " -h,--help Display this information.\n" ); + fprintf( stdout, " -v,--version Display the version of %s utility.\n", program ); + fprintf( stdout, " -r,--root=<DIR> Target rootfs path.\n" ); + + fprintf( stdout, "\n" ); + fprintf( stdout, "Parameter:\n" ); + fprintf( stdout, " <package|pkglog> The PACKAGE tarball or PKGLOG.\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "The list of required packages prints out in following format:\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "app/attr:2.4.47:PROCEDURE\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "where PROCEDURE is:\n" ); + fprintf( stdout, " install - package should be installed; or\n" ); + fprintf( stdout, " update - the old version of required package already instaled\n" ); + fprintf( stdout, " but should be updated to the new version presented\n" ); + fprintf( stdout, " between colon characters.\n" ); + fprintf( stdout, "\n" ); + + exit( EXIT_FAILURE ); +} + +void to_lowercase( char *s ) +{ + char *p = s; + while( p && *p ) { int c = *p; *p = tolower( c ); ++p; } +} + +void to_uppercase( char *s ) +{ + char *p = s; + while( p && *p ) { int c = *p; *p = toupper( c ); ++p; } +} + +void version() +{ + char *upper = NULL; + + upper = (char *)alloca( strlen( program ) + 1 ); + + strcpy( (char *)upper, (const char *)program ); + to_uppercase( upper ); + + fprintf( stdout, "%s (%s) %s\n", program, upper, PROGRAM_VERSION ); + + fprintf( stdout, "Copyright (C) 2019 Andrey V.Kosteltsev.\n" ); + fprintf( stdout, "This is free software. There is NO warranty; not even\n" ); + fprintf( stdout, "for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" ); + fprintf( stdout, "\n" ); + + free_resources(); + exit( EXIT_SUCCESS ); +} + + +static void remove_trailing_slash( char *dir ) +{ + char *s; + + if( !dir || dir[0] == '\0' ) return; + + s = dir + strlen( dir ) - 1; + while( *s == '/' ) + { + *s = '\0'; --s; + } +} + + +static int _mkdir_p( const char *dir, const mode_t mode ) +{ + char *buf; + char *p = NULL; + struct stat sb; + + if( !dir ) return -1; + + buf = (char *)alloca( strlen( dir ) + 1 ); + strcpy( buf, dir ); + + remove_trailing_slash( buf ); + + /* check if path exists and is a directory */ + if( stat( buf, &sb ) == 0 ) + { + if( S_ISDIR(sb.st_mode) ) + { + return 0; + } + } + + /* mkdir -p */ + for( p = buf + 1; *p; ++p ) + { + if( *p == '/' ) + { + *p = 0; + /* test path */ + if( stat( buf, &sb ) != 0 ) + { + /* path does not exist - create directory */ + if( mkdir( buf, mode ) < 0 ) + { + return -1; + } + } else if( !S_ISDIR(sb.st_mode) ) + { + /* not a directory */ + return -1; + } + *p = '/'; + } + } + + /* test path */ + if( stat( buf, &sb ) != 0 ) + { + /* path does not exist - create directory */ + if( mkdir( buf, mode ) < 0 ) + { + return -1; + } + } else if( !S_ISDIR(sb.st_mode) ) + { + /* not a directory */ + return -1; + } + + return 0; +} + +static void _rm_tmpdir( const char *dirpath ) +{ + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + if( stat( dirpath, &path_sb ) == -1 ) + { + return; /* stat returns error code; errno is set */ + } + + if( S_ISDIR(path_sb.st_mode) == 0 ) + { + return; /* dirpath is not a directory */ + } + + if( (dir = opendir(dirpath) ) == NULL ) + { + return; /* Cannot open direcroty; errno is set */ + } + + len = strlen( dirpath ); + + while( (entry = readdir( dir )) != NULL) + { + + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( stat( path, &entry_sb ) == 0 ) + { + if( S_ISDIR(entry_sb.st_mode) ) + { + /* recursively remove a nested directory */ + _rm_tmpdir( path ); + } + else + { + /* remove a file object */ + (void)unlink( path ); + } + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + + } + + /* remove the devastated directory and close the object of this directory */ + (void)rmdir( dirpath ); + + closedir( dir ); +} + +static char *_mk_tmpdir( void ) +{ + char *buf = NULL, *p, *tmp = "/tmp"; + size_t len = 0, size = 0; + + (void)umask( S_IWGRP | S_IWOTH ); /* octal 022 */ + + /* Get preferred directory for tmp files */ + if( (p = getenv( "TMP" )) != NULL ) { + tmp = p; + } + else if( (p = getenv( "TEMP" )) != NULL ) { + tmp = p; + } + + size = strlen( tmp ) + strlen( DISTRO_NAME ) + strlen( program ) + 12; + + buf = (char *)malloc( size ); + if( !buf ) return NULL; + + len = snprintf( buf, size, (const char *)"%s/%s/%s-%.7u", tmp, DISTRO_NAME, program, getpid() ); + if( len == 0 || len == size - 1 ) + { + free( buf ); return NULL; + } + + _rm_tmpdir( (const char *)&buf[0] ); + + if( _mkdir_p( buf, S_IRWXU | S_IRWXG | S_IRWXO ) == 0 ) + { + return buf; + } + + free( buf ); return NULL; +} + + +void fatal_error_actions( void ) +{ + logmsg( errlog, MSG_NOTICE, "Free resources on FATAL error..." ); + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); +} + +void sigint( int signum ) +{ + (void)signum; + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); +} + +void sigchld( int signum ) +{ + pid_t pid = 0; + int status; + + (void)signum; + + while( (pid = waitpid( -1, &status, WNOHANG )) > 0 ) + { + ; /* One of children with 'pid' is terminated */ + + if( WIFEXITED( status ) ) + { + if( (int) WEXITSTATUS (status) > 0 ) + { + ++exit_status; /* printf( "Child %d returned non zero status: %d\n", pid, (int)WEXITSTATUS (status) ); */ + } + else + { + ; /* printf( "Child %d terminated with status: %d\n", pid, (int)WEXITSTATUS (status) ); */ + } + } + else if( WIFSIGNALED( status ) ) + { + ++exit_status; /* printf( "Child %d terminated on signal: %d\n", pid, WTERMSIG( status ) ); */ + } + else + { + ++exit_status; /* printf( "Child %d terminated on unknown reason\n", pid ); */ + } + + } + + if( pid == -1 && errno == ECHILD ) + { + /* No child processes: */ + __done = 1; + } + return; +} + + +static void set_signal_handlers() +{ + struct sigaction sa; + sigset_t set; + + memset( &sa, 0, sizeof( sa ) ); + sa.sa_handler = sigint; /* TERM, INT */ + sa.sa_flags = SA_RESTART; + sigemptyset( &set ); + sigaddset( &set, SIGTERM ); + sigaddset( &set, SIGINT ); + sa.sa_mask = set; + sigaction( SIGTERM, &sa, NULL ); + sigaction( SIGINT, &sa, NULL ); + + /* System V fork+wait does not work if SIGCHLD is ignored */ + memset( &sa, 0, sizeof( sa ) ); + sa.sa_handler = sigchld; /* CHLD */ + sa.sa_flags = SA_RESTART; + sigemptyset( &set ); + sigaddset( &set, SIGCHLD ); + sa.sa_mask = set; + sigaction( SIGCHLD, &sa, NULL ); + + memset( &sa, 0, sizeof( sa ) ); /* ignore SIGPIPE */ + sa.sa_handler = SIG_IGN; + sa.sa_flags = 0; + sigaction( SIGPIPE, &sa, NULL ); +} + + +static enum _input_type check_input_file( char *uncompress, const char *fname ) +{ + struct stat st; + size_t pkglog_size = 0; + unsigned char buf[8]; + int rc, fd; + + /* SIGNATURES: https://www.garykessler.net/library/file_sigs.html */ + + if( uncompress ) + { + *uncompress = '\0'; + } + + if( stat( fname, &st ) == -1 ) + { + FATAL_ERROR( "Cannot access %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + + pkglog_size = st.st_size; + + if( (fd = open( fname, O_RDONLY )) == -1 ) + { + FATAL_ERROR( "Cannot open %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + + rc = (int)read( fd, (void *)&buf[0], 7 ); + if( rc != 7 ) + { + close( fd ); return IFMT_UNKNOWN; + } + buf[7] = '\0'; + + /* TEXT */ + if( !strncmp( (const char *)&buf[0], "PACKAGE", 7 ) ) + { + close( fd ); return IFMT_LOG; + } + + /* GZ */ + if( buf[0] == 0x1F && buf[1] == 0x8B && buf[2] == 0x08 ) + { + if( uncompress ) { *uncompress = 'x'; } + close( fd ); return IFMT_PKG; + } + + /* BZ2 */ + if( buf[0] == 0x42 && buf[1] == 0x5A && buf[2] == 0x68 ) + { + if( uncompress ) { *uncompress = 'j'; } + close( fd ); return IFMT_PKG; + } + + /* XZ */ + if( buf[0] == 0xFD && buf[1] == 0x37 && buf[2] == 0x7A && + buf[3] == 0x58 && buf[4] == 0x5A && buf[5] == 0x00 ) + { + if( uncompress ) { *uncompress = 'J'; } + close( fd ); return IFMT_PKG; + } + + if( pkglog_size > 262 ) + { + if( lseek( fd, 257, SEEK_SET ) == -1 ) + { + FATAL_ERROR( "Cannot check signature of %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + rc = (int)read( fd, &buf[0], 5 ); + if( rc != 5 ) + { + FATAL_ERROR( "Cannot read signature of %s file", basename( (char *)fname ) ); + } + /* TAR */ + if( buf[0] == 0x75 && buf[1] == 0x73 && buf[2] == 0x74 && buf[3] == 0x61 && buf[4] == 0x72 ) + { + close( fd ); return IFMT_PKG; + } + } + + close( fd ); return IFMT_UNKNOWN; +} + + +void get_args( int argc, char *argv[] ) +{ + const char* short_options = "hvr:"; + + const struct option long_options[] = + { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'v' }, + { "root", required_argument, NULL, 'r' }, + { NULL, 0, NULL, 0 } + }; + + int ret; + int option_index = 0; + + while( (ret = getopt_long( argc, argv, short_options, long_options, &option_index )) != -1 ) + { + switch( ret ) + { + case 'h': + { + usage(); + break; + } + case 'v': + { + version(); + break; + } + + case 'r': + { + if( optarg != NULL ) + { + root = xstrdup( (const char *)optarg ); + remove_trailing_slash( root ); + } + else + /* option is present but without value */ + usage(); + break; + } + + case '?': default: + { + usage(); + break; + } + } + } + + + if( optind < argc ) + { + struct stat st; + char *buf = NULL; + + bzero( (void *)&st, sizeof( struct stat ) ); + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + (void)strcpy( buf, (const char *)argv[optind] ); + if( stat( (const char *)&buf[0], &st ) == -1 ) + { + FATAL_ERROR( "Cannot access '%s' file: %s", buf, strerror( errno ) ); + } + + if( S_ISREG(st.st_mode) ) + { + pkg_fname = xstrdup( (const char *)&buf[0] ); + free( buf ); + } + else + { + FATAL_ERROR( "Input package '%s' is not a regular file", buf ); + } + } + else + { + usage(); + } + + + if( !pkgs_path ) + { + struct stat st; + char *buf = NULL; + + bzero( (void *)&st, sizeof( struct stat ) ); + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + if( !root ) + { + buf[0] = '/'; buf[1] = '\0'; + } + else + { + int len = strlen( root ); + + (void)strcpy( buf, (const char *)root ); + if( buf[ len - 1 ] != '/' ) + { + buf[len] = '/'; buf[len+1] = '\0'; + } + } + + (void)strcat( buf, PACKAGES_PATH ); + if( stat( (const char *)&buf[0], &st ) == -1 ) + { + FATAL_ERROR( "Cannot access '%s' file or directory: %s", buf, strerror( errno ) ); + } + + if( S_ISDIR(st.st_mode) ) + { + pkgs_path = xstrdup( (const char *)&buf[0] ); + free( buf ); + } + else + { + FATAL_ERROR( "Defined --root '%s' is not a directory", buf ); + } + + } /* End if( !pkgs_path ) */ +} + + +/*************************************************************** + Copy functions: + */ +static void _copy_pkglog( const char *group, const char *fname ) +{ + enum _input_type type = IFMT_UNKNOWN; + char uncompress = '\0'; + + type = check_input_file( &uncompress, fname ); + + if( type == IFMT_LOG ) + { + int len = 0; + char *tmp= NULL, *cmd = NULL; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + if( group ) { (void)sprintf( &tmp[0], "%s/%s", tmpdir, group ); } + else { (void)sprintf( &tmp[0], "%s", tmpdir ); } + + if( _mkdir_p( tmp, S_IRWXU | S_IRWXG | S_IRWXO ) != 0 ) + { + ERROR( "Cannot copy '%s' PKGLOG file", basename( (char *)fname ) ); + free( tmp ); + return; + } + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, "cp %s %s/ > /dev/null 2>&1", fname, tmp ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot copy %s PKGLOG file", basename( (char *)fname ) ); + } + (void)sys_exec_command( cmd ); + ++__child; + + free( tmp ); + free( cmd ); + } +} + +static void _search_pkglogs( const char *dirpath, const char *grp ) +{ + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + if( stat( dirpath, &path_sb ) == -1 ) + { + FATAL_ERROR( "%s: Cannot stat Setup Database or destination directory", dirpath ); + } + + if( S_ISDIR(path_sb.st_mode) == 0 ) + { + FATAL_ERROR( "%s: Setup Database or destination is not a directory", dirpath ); + } + + if( (dir = opendir(dirpath) ) == NULL ) + { + FATAL_ERROR( "Canot access %s directory: %s", dirpath, strerror( errno ) ); + } + + len = strlen( dirpath ); + + while( (entry = readdir( dir )) != NULL) + { + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( stat( path, &entry_sb ) == 0 ) + { + if( S_ISREG(entry_sb.st_mode) ) + { + _copy_pkglog( grp, (const char *)path ); + } + if( S_ISDIR(entry_sb.st_mode) && grp == NULL ) + { + _search_pkglogs( (const char *)path, (const char *)entry->d_name ); + } + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + } + + closedir( dir ); +} + +/*********************************************************** + copy_pkglogs() - returns number of copied PKGLOGS or 0 if + no PKGLOGS found in the destination + directory (SETUP_DB_PATH). + The exit_status has been set. + */ +int copy_pkglogs( void ) +{ + int ret = 0; + + __done = 0; __child = 0; + + _search_pkglogs( (const char *)pkgs_path, NULL ); + + if( __child > 0 ) + { + while( !__done ) usleep( 1 ); + ret = __child; + } + + __done = 0; __child = 0; + + return ret; +} +/* + Enf of Copy functions. + ***********************************************************/ + + +/*********************************************************** + Remove leading spaces and take non-space characters only: + (Especialy for pkginfo lines) + */ +static char *skip_spaces( char *s ) +{ + char *q, *p = (char *)0; + + if( !s || *s == '\0' ) return p; + + p = s; + + while( (*p == ' ' || *p == '\t') && *p != '\0' ) { ++p; } q = p; + while( *q != ' ' && *q != '\t' && *q != '\0' ) { ++q; } *q = '\0'; + + if( *p == '\0' ) return (char *)0; + + return( xstrdup( (const char *)p ) ); +} + + +/******************************* + remove spaces at end of line: + */ +static void skip_eol_spaces( char *s ) +{ + char *p = (char *)0; + + if( !s || *s == '\0' ) return; + + p = s + strlen( s ) - 1; + while( isspace( *p ) ) { *p-- = '\0'; } +} + +static size_t read_usize( char *s ) +{ + size_t size = 0; + size_t mult = 1; + double sz = 0.0; + + char suffix; + char *q, *p = (char *)0; + + if( !s || *s == '\0' ) return size; + + p = s; + + while( (*p == ' ' || *p == '\t') && *p != '\0' ) { ++p; } q = p; + while( *q != ' ' && *q != '\t' && *q != '\0' ) { ++q; } *q = '\0'; + + if( *p == '\0' ) return size; + + --q; + suffix = *q; + switch( suffix ) + { + /* by default size calculates in KiB - 1024 Bytes (du -s -h .) */ + case 'G': + case 'g': + mult = 1024 * 1024; + *q = '\0'; + break; + case 'M': + case 'm': + mult = 1024; + *q = '\0'; + break; + case 'K': + case 'k': + *q = '\0'; + break; + default: + break; + } + + if( sscanf( p, "%lg", &sz ) != 1 ) return size; + + return (size_t)round( sz * (double)mult ); +} + +static int read_total_files( char *s ) +{ + int n = 0; + char *q, *p = (char *)0; + + if( !s || *s == '\0' ) return n; + + p = s; + + while( (*p == ' ' || *p == '\t') && *p != '\0' ) { ++p; } q = p; + while( *q != ' ' && *q != '\t' && *q != '\0' ) { ++q; } *q = '\0'; + + if( *p == '\0' ) return n; + + if( sscanf( p, "%u", &n ) != 1 ) return 0; + + return n; +} + +static struct pkg *input_package( const char *pkginfo_fname ) +{ + char *ln = NULL; + char *line = NULL; + + FILE *pkginfo = NULL; + + struct pkg *pkg = NULL; + char *pkgname = NULL, *pkgver = NULL, *group = NULL; + + if( pkginfo_fname != NULL ) + { + pkginfo = fopen( (const char *)pkginfo_fname, "r" ); + if( !pkginfo ) + { + FATAL_ERROR( "Cannot open %s file", pkginfo_fname ); + } + } + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + while( (ln = fgets( line, PATH_MAX, pkginfo )) ) + { + char *match = NULL; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( (match = strstr( ln, "pkgname" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) pkgname = skip_spaces( p ); + } + if( (match = strstr( ln, "pkgver" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) pkgver = skip_spaces( p ); + } + if( (match = strstr( ln, "group" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) group = skip_spaces( p ); + } + } + + free( line ); + + if( pkgname && pkgver ) + { + pkg = pkg_alloc(); + if( pkg ) + { + if( group ) + { + pkg->group = group; + } + pkg->name = pkgname; + pkg->version = pkgver; + } + + } + else + { + if( group ) free( group ); + if( pkgname ) free( pkgname ); + if( pkgver ) free( pkgver ); + + FATAL_ERROR( "Invalid input .PKGINFO file" ); + } + + fclose( pkginfo ); + + return( pkg ); +} + + +static void get_short_description( char *buf, const char *line ) +{ + char *s, *p, *q; + + if( buf ) { buf[0] = '\0'; s = buf; } + if( !line || line[0] == '\0' ) return; + + p = index( line, '(' ); + q = index( line, ')' ); + if( p && q && q > p ) + { + ++p; + while( *p && p < q ) + { + *s = *p; + ++p; ++s; + } + *s = '\0'; + } + else + { + /* + If short description declaration is incorrect at first line + of description; then we take whole first line of description: + */ + p = index( line, ':' ); ++p; + while( (*p == ' ' || *p == '\t') && *p != '\0' ) { ++p; } + strcpy( buf, p ); + } +} + + +static int get_references_section( int *start, int *stop, unsigned int *cnt, FILE *log ) +{ + int ret = -1, found = 0; + + if( !start || !stop || !cnt ) return ret; + + if( log != NULL ) + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + *start = 0; *stop = 0; + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + char *match = NULL; + + if( (match = strstr( ln, "REFERENCE COUNTER:" )) && match == ln ) /* at start of line only */ + { + *start = ret + 1; + ++found; + + /* Get reference counter */ + { + unsigned int count; + int rc; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + rc = sscanf( ln, "REFERENCE COUNTER: %u", &count ); + if( rc == 1 && cnt != NULL ) + { + *cnt = count; + } + } + } + if( (match = strstr( ln, "REQUIRES:" )) && match == ln ) + { + *stop = ret + 1; + ++found; + } + + ++ret; + } + + free( line ); + + ret = ( found == 2 ) ? 0 : 1; /* 0 - success; 1 - not found. */ + + fseek( log, 0, SEEK_SET ); + } + + return( ret ); +} + +static int get_requires_section( int *start, int *stop, FILE *log ) +{ + int ret = -1, found = 0; + + if( !start || !stop ) return ret; + + if( log != NULL ) + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + *start = 0; *stop = 0; + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + char *match = NULL; + + if( (match = strstr( ln, "REQUIRES:" )) && match == ln ) /* at start of line only */ + { + *start = ret + 1; + ++found; + } + if( (match = strstr( ln, "PACKAGE DESCRIPTION:" )) && match == ln ) + { + *stop = ret + 1; + ++found; + } + + ++ret; + } + + free( line ); + + ret = ( found == 2 ) ? 0 : 1; /* 0 - success; 1 - not found. */ + + fseek( log, 0, SEEK_SET ); + } + + return( ret ); +} + +static int get_description_section( int *start, int *stop, FILE *log ) +{ + int ret = -1, found = 0; + + if( !start || !stop ) return ret; + + if( log != NULL ) + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + *start = 0; *stop = 0; + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + char *match = NULL; + + if( (match = strstr( ln, "PACKAGE DESCRIPTION:" )) && match == ln ) /* at start of line only */ + { + *start = ret + 1; + ++found; + } + if( (match = strstr( ln, "RESTORE LINKS:" )) && match == ln ) + { + *stop = ret + 1; + ++found; + } + + ++ret; + } + + free( line ); + + ret = ( found == 2 ) ? 0 : 1; /* 0 - success; 1 - not found. */ + + fseek( log, 0, SEEK_SET ); + } + + return( ret ); +} + +static int get_restore_links_section( int *start, int *stop, FILE *log ) +{ + int ret = -1, found = 0; + + if( !start || !stop ) return ret; + + if( log != NULL ) + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + *start = 0; *stop = 0; + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + char *match = NULL; + + if( (match = strstr( ln, "RESTORE LINKS:" )) && match == ln ) /* at start of line only */ + { + *start = ret + 1; + ++found; + } + if( (match = strstr( ln, "INSTALL SCRIPT:" )) && match == ln ) + { + *stop = ret + 1; + ++found; + } + + ++ret; + } + + free( line ); + + ret = ( found == 2 ) ? 0 : 1; /* 0 - success; 1 - not found. */ + + fseek( log, 0, SEEK_SET ); + } + + return( ret ); +} + + +static int get_install_script_section( int *start, int *stop, FILE *log ) +{ + int ret = -1, found = 0; + + if( !start || !stop ) return ret; + + if( log != NULL ) + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + *start = 0; *stop = 0; + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + char *match = NULL; + + if( (match = strstr( ln, "INSTALL SCRIPT:" )) && match == ln ) /* at start of line only */ + { + *start = ret + 1; + ++found; + } + if( (match = strstr( ln, "FILE LIST:" )) && match == ln ) + { + *stop = ret + 1; + ++found; + } + + ++ret; + } + + free( line ); + + ret = ( found == 2 ) ? 0 : 1; /* 0 - success; 1 - not found. */ + + fseek( log, 0, SEEK_SET ); + } + + return( ret ); +} + + +static int get_file_list_section( int *start, int *stop, FILE *log ) +{ + int ret = -1, found = 0; + + if( !start || !stop ) return ret; + + if( log != NULL ) + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + *start = 0; *stop = 0; + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + char *match = NULL; + + if( (match = strstr( ln, "FILE LIST:" )) && match == ln ) /* at start of line only */ + { + *start = ret + 1; + ++found; + } + + ++ret; + } + + free( line ); + + ret = ( found == 1 ) ? 0 : 1; /* 0 - success; 1 - not found. */ + + fseek( log, 0, SEEK_SET ); + } + + return( ret ); +} + + +int read_pkginfo( FILE *log, struct package *package ) +{ + int ret = -1; + + char *ln = NULL; + char *line = NULL; + + char *pkgname_pattern = "PACKAGE NAME:", + *pkgver_pattern = "PACKAGE VERSION:", + *arch_pattern = "ARCH:", + *distroname_pattern = "DISTRO:", + *distrover_pattern = "DISTRO VERSION:", + *group_pattern = "GROUP:", + *url_pattern = "URL:", + *license_pattern = "LICENSE:", + *uncompressed_size_pattern = "UNCOMPRESSED SIZE:", + *total_files_pattern = "TOTAL FILES:"; + + + if( !log || !package ) return ret; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + char *match = NULL; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( (match = strstr( ln, pkgname_pattern )) && match == ln ) /* at start of line only */ + { + package->pkginfo->name = skip_spaces( ln + strlen( pkgname_pattern ) ); + } + if( (match = strstr( ln, pkgver_pattern )) && match == ln ) + { + package->pkginfo->version = skip_spaces( ln + strlen( pkgver_pattern ) ); + } + if( (match = strstr( ln, arch_pattern )) && match == ln ) + { + package->pkginfo->arch = skip_spaces( ln + strlen( arch_pattern ) ); + } + if( (match = strstr( ln, distroname_pattern )) && match == ln ) + { + package->pkginfo->distro_name = skip_spaces( ln + strlen( distroname_pattern ) ); + } + if( (match = strstr( ln, distrover_pattern )) && match == ln ) + { + package->pkginfo->distro_version = skip_spaces( ln + strlen( distrover_pattern ) ); + } + if( (match = strstr( ln, group_pattern )) && match == ln ) + { + package->pkginfo->group = skip_spaces( ln + strlen( group_pattern ) ); + } + if( (match = strstr( ln, url_pattern )) && match == ln ) + { + package->pkginfo->url = skip_spaces( ln + strlen( url_pattern ) ); + } + if( (match = strstr( ln, license_pattern )) && match == ln ) + { + package->pkginfo->license = skip_spaces( ln + strlen( license_pattern ) ); + } + if( (match = strstr( ln, uncompressed_size_pattern )) && match == ln ) + { + package->pkginfo->uncompressed_size = read_usize( ln + strlen( uncompressed_size_pattern ) ); + } + if( (match = strstr( ln, total_files_pattern )) && match == ln ) + { + package->pkginfo->total_files = read_total_files( ln + strlen( total_files_pattern ) ); + } + + if( (match = strstr( ln, "PACKAGE DESCRIPTION:" )) && match == ln ) + { + char *buf = NULL; + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + /* Get short_description from PACKAGE DESCRIPTION */ + ln = fgets( line, PATH_MAX, log ); + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + + bzero( (void *)buf, PATH_MAX ); + get_short_description( buf, (const char *)line ); + if( buf[0] != '\0' ) + { + package->pkginfo->short_description = xstrdup( (const char *)buf ); + } + free( buf ); + } + + } /* End of while() */ + + free( line ); + + if( package->pkginfo->name == NULL ) ++ret; + if( package->pkginfo->version == NULL ) ++ret; + if( package->pkginfo->arch == NULL ) ++ret; + if( package->pkginfo->distro_name == NULL ) ++ret; + if( package->pkginfo->distro_version == NULL ) ++ret; + /* group can be equal to NULL */ + + fseek( log, 0, SEEK_SET ); + + return( ret ); +} + + +static unsigned int read_references( FILE *log, int start, unsigned int *cnt, struct package *package ) +{ + char *ln = NULL; + char *line = NULL; + char *p = NULL, *group = NULL, *name = NULL, *version = NULL; + int n = 1; + + unsigned int counter, pkgs = 0; + + struct pkg *pkg = NULL; + + if( !log || !cnt || *cnt == 0 || !package ) return pkgs; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + counter = *cnt; + + while( (ln = fgets( line, PATH_MAX, log )) && (n < start) ) ++n; + + n = 0; + while( (ln = fgets( line, PATH_MAX, log )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( strstr( ln, "REQUIRES:" ) ) break; /* if cnt greater than real number of references */ + + if( n < counter ) + { + if( (p = index( (const char *)ln, '=' )) ) + { + *p = '\0'; version = ++p; + if( (p = index( (const char *)ln, '/' )) ) + { + *p = '\0'; name = ++p; group = (char *)&ln[0]; + } + else + { + name = (char *)&ln[0]; group = NULL; + } + + pkg = pkg_alloc(); + + if( group ) pkg->group = xstrdup( (const char *)group ); + pkg->name = xstrdup( (const char *)name ); + pkg->version = xstrdup( (const char *)version ); + + add_reference( package, pkg ); + ++pkgs; + } + ++n; + } + else + break; + } + + free( line ); + + fseek( log, 0, SEEK_SET ); + + *cnt = pkgs; + + return pkgs; +} + + +static unsigned int read_requires( FILE *log, int start, int stop, struct package *package ) +{ + char *ln = NULL; + char *line = NULL; + char *p = NULL, *group = NULL, *name = NULL, *version = NULL; + int n = 1; + + unsigned int pkgs = 0; + + struct pkg *pkg = NULL; + + if( !log || !package ) return pkgs; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + while( (ln = fgets( line, PATH_MAX, log )) && (n < start) ) ++n; + + if( start && start < stop ) + { + ++n; /* skip section header */ + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( strstr( ln, "PACKAGE DESCRIPTION:" ) ) break; /* if (stop - start - 1) greater than real number of requiress */ + + if( (n > start) && (n < stop) ) + { + if( (p = index( (const char *)ln, '=' )) ) + { + *p = '\0'; version = ++p; + if( (p = index( (const char *)ln, '/' )) ) + { + *p = '\0'; name = ++p; group = (char *)&ln[0]; + } + else + { + name = (char *)&ln[0]; group = NULL; + } + + pkg = pkg_alloc(); + + if( group ) pkg->group = xstrdup( (const char *)group ); + pkg->name = xstrdup( (const char *)name ); + pkg->version = xstrdup( (const char *)version ); + + add_required( package, pkg ); + ++pkgs; + } + + } + ++n; + } + + } /* End if( start && start < stop ) */ + + free( line ); + + fseek( log, 0, SEEK_SET ); + + return pkgs; +} + + +static unsigned int read_description( FILE *log, int start, int stop, struct package *package ) +{ + char *ln = NULL; + char *line = NULL; + char *pattern = NULL; + int n = 1; + + char *tmp_fname = NULL; + FILE *tmp = NULL; + + unsigned int lines = 0; + + if( !log || !package ) return lines; + + tmp_fname = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp_fname ) { FATAL_ERROR( "Cannot allocate memory" ); } + + bzero( (void *)tmp_fname, PATH_MAX ); + (void)sprintf( (char *)&tmp_fname[0], "%s/.DESCRIPTION", tmpdir ); + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) { FATAL_ERROR( "Cannot allocate memory" ); } + + pattern = (char *)malloc( (size_t)strlen( package->pkginfo->name ) + 2 ); + if( !pattern ) { FATAL_ERROR( "Cannot allocate memory" ); } + + (void)sprintf( pattern, "%s:", package->pkginfo->name ); + + + while( (ln = fgets( line, PATH_MAX, log )) && (n < start) ) ++n; + + if( start && start < stop ) + { + ++n; /* skip section header */ + + tmp = fopen( (const char *)&tmp_fname[0], "w" ); + if( !tmp ) + { + FATAL_ERROR( "Cannot create temporary %s file", basename( (char *)&tmp_fname[0] ) ); + } + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + char *match = NULL; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( strstr( ln, "RESTORE LINKS:" ) ) break; /* if (stop - start - 1) greater than real number of lines */ + + if( (n > start) && (n < stop) ) + { + /* + skip non-significant spaces at beginning of line + and print lines started with 'pkgname:' + */ + if( (match = strstr( ln, pattern )) && lines < DESCRIPTION_NUMBER_OF_LINES ) + { + int mlen = strlen( match ), plen = strlen( pattern ); + int length = ( mlen > plen ) ? (mlen - plen - 1) : 0 ; + + if( length > DESCRIPTION_LENGTH_OF_LINE ) + { + /* WARNING( "Package DESCRIPTION contains lines with length greater than %d characters", DESCRIPTION_LENGTH_OF_LINE ); */ + match[plen + 1 + DESCRIPTION_LENGTH_OF_LINE] = '\0'; /* truncating description line */ + skip_eol_spaces( match ); /* remove spaces at end-of-line */ + } + fprintf( tmp, "%s\n", match ); + ++lines; + } + + } + ++n; + } + + if( lines < (unsigned int)DESCRIPTION_NUMBER_OF_LINES ) + { + /* WARNING( "Package DESCRIPTION contains less than %d lines", DESCRIPTION_NUMBER_OF_LINES ); */ + while( lines < (unsigned int)DESCRIPTION_NUMBER_OF_LINES ) + { + fprintf( tmp, "%s\n", pattern ); + ++lines; + } + } + + fflush( tmp ); + fclose( tmp ); + + } /* End if( start && start < stop ) */ + + free( pattern ); + free( line ); + + fseek( log, 0, SEEK_SET ); + + /* read temporary saved description */ + { + struct stat sb; + size_t size = 0; + int fd; + + char *desc = NULL; + + if( stat( tmp_fname, &sb ) == -1 ) + { + FATAL_ERROR( "Cannot stat temporary %s file", basename( (char *)&tmp_fname[0] ) ); + } + size = (size_t)sb.st_size; + + if( size ) + { + ssize_t rc = 0; + + desc = (char *)malloc( size + 1 ); + if( !desc ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)desc, size + 1 ); + + if( (fd = open( (const char *)&tmp_fname[0], O_RDONLY )) == -1 ) + { + FATAL_ERROR( "Canot access temporary %s file: %s", basename( (char *)&tmp_fname[0] ), strerror( errno ) ); + } + + rc = read( fd, (void *)desc, size ); + if( rc != (ssize_t)size ) + { + ERROR( "The %s file is not fully read", basename( (char *)&tmp_fname[0] ) ); + } + + package->description = desc; + + close( fd ); + } + + } + + (void)unlink( tmp_fname ); + free( tmp_fname ); + + return lines; +} + + +static unsigned int read_restore_links( FILE *log, int start, int stop, struct package *package ) +{ + char *ln = NULL; + char *line = NULL; + int n = 1; + + char *tmp_fname = NULL; + FILE *tmp = NULL; + + unsigned int lines = 0; + + if( !log || !package ) return lines; + + tmp_fname = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp_fname ) { FATAL_ERROR( "Cannot allocate memory" ); } + + bzero( (void *)tmp_fname, PATH_MAX ); + (void)sprintf( (char *)&tmp_fname[0], "%s/.RESTORELINKS", tmpdir ); + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) { FATAL_ERROR( "Cannot allocate memory" ); } + + while( (ln = fgets( line, PATH_MAX, log )) && (n < start) ) ++n; + + if( start && start < stop ) + { + ++n; /* skip section header */ + + tmp = fopen( (const char *)&tmp_fname[0], "w" ); + if( !tmp ) + { + FATAL_ERROR( "Cannot create temporary %s file", basename( (char *)&tmp_fname[0] ) ); + } + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( strstr( ln, "INSTALL SCRIPT:" ) ) break; /* if (stop - start - 1) greater than real number of lines */ + + if( (n > start) && (n < stop) ) + { + fprintf( tmp, "%s\n", ln ); + ++lines; + } + ++n; + } + + fflush( tmp ); + fclose( tmp ); + + } /* End if( start && start < stop ) */ + + free( line ); + + fseek( log, 0, SEEK_SET ); + + /* read temporary saved description */ + { + struct stat sb; + size_t size = 0; + int fd; + + char *links = NULL; + + if( stat( tmp_fname, &sb ) == -1 ) + { + FATAL_ERROR( "Cannot stat temporary %s file", basename( (char *)&tmp_fname[0] ) ); + } + size = (size_t)sb.st_size; + + if( size ) + { + ssize_t rc = 0; + + links = (char *)malloc( size + 1 ); + if( !links ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)links, size + 1 ); + + if( (fd = open( (const char *)&tmp_fname[0], O_RDONLY )) == -1 ) + { + FATAL_ERROR( "Canot access temporary %s file: %s", basename( (char *)&tmp_fname[0] ), strerror( errno ) ); + } + + rc = read( fd, (void *)links, size ); + if( rc != (ssize_t)size ) + { + ERROR( "The %s file is not fully read", basename( (char *)&tmp_fname[0] ) ); + } + + package->restore_links = links; + + close( fd ); + } + + } + + (void)unlink( tmp_fname ); + free( tmp_fname ); + + return lines; +} + + +static unsigned int read_install_script( FILE *log, int start, int stop, struct package *package ) +{ + char *ln = NULL; + char *line = NULL; + int n = 1; + + char *tmp_fname = NULL; + FILE *tmp = NULL; + + unsigned int lines = 0; + + if( !log || !package ) return lines; + + tmp_fname = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp_fname ) { FATAL_ERROR( "Cannot allocate memory" ); } + + bzero( (void *)tmp_fname, PATH_MAX ); + (void)sprintf( (char *)&tmp_fname[0], "%s/.INSTALL", tmpdir ); + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) { FATAL_ERROR( "Cannot allocate memory" ); } + + while( (ln = fgets( line, PATH_MAX, log )) && (n < start) ) ++n; + + if( start && start < stop ) + { + ++n; /* skip section header */ + + tmp = fopen( (const char *)&tmp_fname[0], "w" ); + if( !tmp ) + { + FATAL_ERROR( "Cannot create temporary %s file", basename( (char *)&tmp_fname[0] ) ); + } + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( strstr( ln, "FILE LIST:" ) ) break; /* if (stop - start - 1) greater than real number of lines */ + + if( (n > start) && (n < stop) ) + { + fprintf( tmp, "%s\n", ln ); + ++lines; + } + ++n; + } + + fflush( tmp ); + fclose( tmp ); + + } /* End if( start && start < stop ) */ + + free( line ); + + fseek( log, 0, SEEK_SET ); + + /* read temporary saved description */ + { + struct stat sb; + size_t size = 0; + int fd; + + char *install = NULL; + + if( stat( tmp_fname, &sb ) == -1 ) + { + FATAL_ERROR( "Cannot stat temporary %s file", basename( (char *)&tmp_fname[0] ) ); + } + size = (size_t)sb.st_size; + + if( size ) + { + ssize_t rc = 0; + + install = (char *)malloc( size + 1 ); + if( !install ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)install, size + 1 ); + + if( (fd = open( (const char *)&tmp_fname[0], O_RDONLY )) == -1 ) + { + FATAL_ERROR( "Canot access temporary %s file: %s", basename( (char *)&tmp_fname[0] ), strerror( errno ) ); + } + + rc = read( fd, (void *)install, size ); + if( rc != (ssize_t)size ) + { + ERROR( "The %s file is not fully read", basename( (char *)&tmp_fname[0] ) ); + } + + package->install_script = install; + + close( fd ); + } + + } + + (void)unlink( tmp_fname ); + free( tmp_fname ); + + return lines; +} + + +static unsigned int read_file_list( FILE *log, int start, struct package *package ) +{ + char *ln = NULL; + char *line = NULL; + int n = 1; + + unsigned int files = 0; + + if( !log || !package ) return files; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + while( (ln = fgets( line, PATH_MAX, log )) && (n < start) ) ++n; + + if( start ) + { + while( (ln = fgets( line, PATH_MAX, log )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + add_file( package, (const char *)ln ); + ++files; + } + + } /* End if( start && start < stop ) */ + + free( line ); + + fseek( log, 0, SEEK_SET ); + + return files; +} + + + +static void _read_pkglog( const char *group, const char *fname ) +{ + FILE *log = NULL; + char *bname = NULL; + + if( fname != NULL ) + { + log = fopen( (const char *)fname, "r" ); + if( !log ) + { + FATAL_ERROR( "Cannot open %s file", fname ); + } + bname = (char *)fname + strlen( tmpdir ) + 1; + } + + if( log != NULL ) + { + struct package *package = NULL; + int rc, start, stop; + unsigned int counter; + + package = package_alloc(); + + if( read_pkginfo( log, package ) != 0 ) + { + ERROR( "%s: Invalid PKGLOG file", bname ); + package_free( package ); + fclose( log ); + return; + } + + if( hardware ) package->hardware = xstrdup( (const char *)hardware ); + if( tarballs ) /* find tarball and allocate package->tarball */ + { + struct pkginfo *info = package->pkginfo; + const char *tgz = NULL; + char *buf = NULL; + struct stat sb; + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + + if( info->group ) + { + (void)sprintf( buf, "%s/%s-%s-%s-%s-%s", + info->group, info->name, info->version, info->arch, + info->distro_name, info->distro_version ); + } + else + { + (void)sprintf( buf, "%s-%s-%s-%s-%s", + info->name, info->version, info->arch, + info->distro_name, info->distro_version ); + } + tgz = find_tarball( (const char *)&buf[0] ); + if( tgz ) + { + package->tarball = xstrdup( (const char *)tgz ); + + bzero( (void *)&buf[0], PATH_MAX ); + (void)sprintf( buf, "%s/%s", pkgs_path, tgz ); + if( stat( buf, &sb ) != -1 ) + { + info->compressed_size = (size_t)sb.st_size; + } + } + free( buf ); + } + package->procedure = INSTALL; + package->priority = priority; + + if( package->pkginfo->group && group && strcmp( package->pkginfo->group, group ) != 0 ) + { + char *tgz; + + if( package->tarball ) { tgz = package->tarball; } + else { tgz = basename( (char *)fname ); } + + WARNING( "%s: Should be moved into '%s' subdir", tgz, package->pkginfo->group ); + } + + /****************** + read references: + */ + rc = get_references_section( &start, &stop, &counter, log ); + if( rc != 0 ) + { + ERROR( "%s: PKGLOG doesn't contains REFERENCE COUNTER section", bname ); + package_free( package ); + fclose( log ); + return; + } + if( counter > 0 ) + { + unsigned int pkgs = counter; + + if( read_references( log, start, &counter, package ) != pkgs ) + { + ERROR( "%s: Invalid REFERENCE COUNTER section", bname ); + package_free( package ); + fclose( log ); + return; + } + } + + /****************** + read requires: + */ + rc = get_requires_section( &start, &stop, log ); + if( rc != 0 ) + { + ERROR( "%s: PKGLOG doesn't contains REQUIRES section", bname ); + package_free( package ); + fclose( log ); + return; + } + if( (stop - start) > 1 ) + { + unsigned int pkgs = (unsigned int)(stop - start - 1); /* -1 skips section header */ + + if( read_requires( log, start, stop, package ) != pkgs ) + { + ERROR( "%s: Invalid REQUIRES section", bname ); + package_free( package ); + fclose( log ); + return; + } + } + + /******************* + read description: + */ + rc = get_description_section( &start, &stop, log ); + if( rc != 0 ) + { + ERROR( "%s: PKGLOG doesn't contains PACKAGE DESCRIPTION section", bname ); + package_free( package ); + fclose( log ); + return; + } + if( (stop - start) > 1 ) + { + if( read_description( log, start, stop, package ) != (unsigned int)DESCRIPTION_NUMBER_OF_LINES ) + { + ERROR( "%s: Invalid DESCRIPTION section", bname ); + package_free( package ); + fclose( log ); + return; + } + } + + /********************* + read restore links: + */ + rc = get_restore_links_section( &start, &stop, log ); + if( rc != 0 ) + { + ERROR( "%s: PKGLOG doesn't contains RESTORE LINKS section", bname ); + package_free( package ); + fclose( log ); + return; + } + if( (stop - start) > 1 ) + { + (void)read_restore_links( log, start, stop, package ); + } + + /********************* + read install script: + */ + rc = get_install_script_section( &start, &stop, log ); + if( rc != 0 ) + { + ERROR( "%s: PKGLOG doesn't contains INSTALL SCRIPT section", bname ); + package_free( package ); + fclose( log ); + return; + } + if( (stop - start) > 1 ) + { + (void)read_install_script( log, start, stop, package ); + } + + /***************** + read file_list: + */ + rc = get_file_list_section( &start, &stop, log ); + if( rc != 0 ) + { + ERROR( "%s: PKGLOG doesn't contains FILE LIST section", bname ); + package_free( package ); + fclose( log ); + return; + } + if( start ) + { + unsigned int files = read_file_list( log, start, package ); + if( files == (unsigned int)0 ) + { + /* + Packages that do not contain regular files are ignored. + For example, service package base/init-devices-1.2.3-s9xx-glibc-radix-1.1.txz + */ + if( ! DO_NOT_PRINTOUT_INFO ) + { + INFO( "%s: PKGLOG contains empty FILE LIST section", bname ); + } + package_free( package ); + fclose( log ); + return; + } + package->pkginfo->total_files = (int)files; + } + + /* + Здесь можно организовать проверку пакета на предмет его + целостности и правильности установки (когда будет готова + утилита check-package). + */ + add_package( package ); + + ++__child; + fclose( log ); + } +} + +static void _read_pkglogs( const char *dirpath, const char *grp ) +{ + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + if( stat( dirpath, &path_sb ) == -1 ) + { + FATAL_ERROR( "%s: Cannot stat Setup Database or destination directory", dirpath ); + } + + if( S_ISDIR(path_sb.st_mode) == 0 ) + { + FATAL_ERROR( "%s: Setup Database or destination is not a directory", dirpath ); + } + + if( (dir = opendir(dirpath) ) == NULL ) + { + FATAL_ERROR( "Canot access %s directory: %s", dirpath, strerror( errno ) ); + } + + len = strlen( dirpath ); + + while( (entry = readdir( dir )) != NULL) + { + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( stat( path, &entry_sb ) == 0 ) + { + if( S_ISREG(entry_sb.st_mode) ) + { + if( check_input_file( NULL, (const char *)path ) == IFMT_LOG ) + { + _read_pkglog( grp, (const char *)path ); + } + } + if( S_ISDIR(entry_sb.st_mode) && grp == NULL ) + { + _read_pkglogs( (const char *)path, (const char *)entry->d_name ); + } + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + } + + closedir( dir ); +} + +int read_pkglogs( void ) +{ + int ret = 0; + + __child = 0; + + _read_pkglogs( (const char *)tmpdir, NULL ); + + ret = __child; + + __child = 0; + + return ret; +} + + +static void check_pkg_fname( void ) +{ + struct stat st; + char *fname = pkg_fname; + + bzero( (void *)&st, sizeof( struct stat ) ); + + if( stat( (const char *)fname, &st ) == -1 ) + { + FATAL_ERROR( "Cannot access input '%s' file: %s", fname, strerror( errno ) ); + } + + if( S_ISREG(st.st_mode) ) + { + struct pkg *srcpkg = NULL; + + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *tmp= NULL, *cmd = NULL; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + (void)sprintf( &tmp[0], "%s", tmpdir ); + if( _mkdir_p( tmp, S_IRWXU | S_IRWXG | S_IRWXO ) != 0 ) + { + FATAL_ERROR( "Cannot get PKGINFO from '%s' file", basename( (char *)fname ) ); + } + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, "%s/pkginfo -d %s -o pkginfo %s > /dev/null 2>&1", selfdir, tmp, fname ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot get PKGINFO from %s file", basename( (char *)fname ) ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + if( rc != 0 ) + { + FATAL_ERROR( "Cannot get PKGINFO from '%s' file", basename( (char *)fname ) ); + } + + (void)strcat( tmp, "/.PKGINFO" ); + srcpkg = input_package( (const char *)&tmp[0] ); + add_srcpkg( srcpkg ); + *(strstr( tmp, "/.PKGINFO" )) = '\0'; /* :restore tmpdir in tmp[] buffer */ + + if( check_input_file( NULL, (const char *)fname ) == IFMT_PKG ) + { + bzero( (void *)cmd, PATH_MAX ); + len = snprintf( &cmd[0], PATH_MAX, "%s/pkglog -m -d %s %s > /dev/null 2>&1", selfdir, tmp, fname ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot get PKGLOG from %s file", basename( (char *)fname ) ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + if( rc != 0 ) + { + FATAL_ERROR( "Cannot get PKGLOG from '%s' file", basename( (char *)fname ) ); + } + } + else + { + char *buf = NULL; + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + if( srcpkg->group ) { (void)sprintf( &buf[0], "%s/%s", tmp, srcpkg->group ); } + else { (void)sprintf( &buf[0], "%s", tmp ); } + + if( _mkdir_p( buf, S_IRWXU | S_IRWXG | S_IRWXO ) != 0 ) + { + FATAL_ERROR( "Cannot copy '%s' PKGLOG file", basename( (char *)fname ) ); + } + + bzero( (void *)cmd, PATH_MAX ); + len = snprintf( &cmd[0], PATH_MAX, "cp %s %s/ > /dev/null 2>&1", fname, buf ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot copy %s PKGLOG file", basename( (char *)fname ) ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + if( rc != 0 ) + { + FATAL_ERROR( "Cannot copy %s PKGLOG file", basename( (char *)fname ) ); + } + + free( buf ); + } + + free( tmp ); + free( cmd ); + } + else + { + FATAL_ERROR( "Input %s file is not a regular file", basename( (char *)fname ) ); + } +} + +/**************************************************************** + Если после апдейта пакет просто сменил группу, то его надо + удалить из списка внешних зависимостей, ведь он предоставляет + нужную функциональность. + + Например: libs/libspectre требует libs/cairo, а xlibs/cairo + требует libs/libspectre, однако libs/libspectre может + использовать как libs/cairo так и xlibs/cairo . + + Search installed package with specified name within any group: + */ +static char *pkglog_fname = NULL; + +static void _probe_pkglog( const char *pname, const char *dirpath, const char *grp ) +{ + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + if( pkglog_fname ) return; + + if( stat( dirpath, &path_sb ) == -1 ) + { + FATAL_ERROR( "%s: Cannot stat Setup Database or destination directory", dirpath ); + } + + if( S_ISDIR(path_sb.st_mode) == 0 ) + { + FATAL_ERROR( "%s: Setup Database or destination is not a directory", dirpath ); + } + + if( (dir = opendir(dirpath) ) == NULL ) + { + FATAL_ERROR( "Canot access %s directory: %s", dirpath, strerror( errno ) ); + } + + len = strlen( dirpath ); + + while( (entry = readdir( dir )) != NULL) + { + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( stat( path, &entry_sb ) == 0 ) + { + if( S_ISREG(entry_sb.st_mode) ) + { + char *match = NULL; + char *pkglog = basename( path ); + + if( (match = strstr( pkglog, pname )) && match == pkglog ) + { + char *buf = NULL, *p = NULL, *q = NULL; + + p = q = buf = xstrdup( (const char *)pkglog ); + ++p; + while( *p != '\0' && !isblank(*p) && !(*q == '-' && isdigit(*p)) ) + { + /* package version starts with a number and separated by '-' */ + ++p; ++q; + } + *(--p) = '\0'; + + /******************************************************* + We have to make sure that the name we are looking for + is not shorter than the name of the found package. + */ + if( strlen(pname) >= strlen(buf) ) + { + + pkglog_fname = xstrdup( (const char *)path ); + free( buf ); + closedir( dir ); + return; + } + free( buf ); + } + } + if( S_ISDIR(entry_sb.st_mode) && grp == NULL ) + { + _probe_pkglog( pname, (const char *)path, (const char *)entry->d_name ); + } + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + } + + closedir( dir ); +} + +/****************** + probe_package(): + --------------- + */ +static char *probe_package( const char *name ) +{ + char *ret = NULL; + + _probe_pkglog( name, (const char *)pkgs_path, NULL ); + if( pkglog_fname ) + { + ret = pkglog_fname; + } + + return ret; +} + +static int check_installed_pkgname( const char *name ) +{ + int ret = 0; + char *fname = NULL; + + if( !name ) return ret; + + fname = probe_package( name ); + if( fname ) + { + ret = 1; + free( pkglog_fname ); + pkglog_fname = NULL; + } + + return ret; +} +/* + End of search installed package. + ****************************************************************/ + +static int __extern_requires = 0; + +static void _print_extern_requires( void *data, void *user_data ) +{ + struct pkg *pkg = (struct pkg *)data; + + if( pkg && !check_installed_pkgname( (const char *)pkg->name ) ) + { + if( pkg->group ) + fprintf( stderr, "%s/%s:%s:%s\n", pkg->group, pkg->name, pkg->version, strproc( pkg->procedure ) ); + else + fprintf( stderr, "%s:%s:%s\n", pkg->name, pkg->version, strproc( pkg->procedure ) ); + + ++__extern_requires; + } +} + +static void print_extern_requires( void ) +{ + dlist_foreach( extern_requires, _print_extern_requires, NULL ); +} + + +/********************************************* + Get directory where this program is placed: + */ +char *get_selfdir( void ) +{ + char *buf = NULL; + ssize_t len; + + buf = (char *)malloc( PATH_MAX ); + if( !buf ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + bzero( (void *)buf, PATH_MAX ); + len = readlink( "/proc/self/exe", buf, (size_t)PATH_MAX ); + if( len > 0 && len < PATH_MAX ) + { + char *p = xstrdup( (const char *)dirname( buf ) ); + free( buf ); + return p; + } + FATAL_ERROR( "Cannot determine self directory. Please mount /proc filesystem" ); +} + +void set_stack_size( void ) +{ + const rlim_t stack_size = 16 * 1024 * 1024; /* min stack size = 16 MB */ + struct rlimit rl; + int ret; + + ret = getrlimit( RLIMIT_STACK, &rl ); + if( ret == 0 ) + { + if( rl.rlim_cur < stack_size ) + { + rl.rlim_cur = stack_size; + ret = setrlimit( RLIMIT_STACK, &rl ); + if( ret != 0 ) + { + fprintf(stderr, "setrlimit returned result = %d\n", ret); + FATAL_ERROR( "Cannot set stack size" ); + } + } + } +} + + +int main( int argc, char *argv[] ) +{ + gid_t gid; + + set_signal_handlers(); + + gid = getgid(); + setgroups( 1, &gid ); + + fatal_error_hook = fatal_error_actions; + + selfdir = get_selfdir(); + + errlog = stderr; + + program = basename( argv[0] ); + get_args( argc, argv ); + + /* set_stack_size(); */ + + tmpdir = _mk_tmpdir(); + if( !tmpdir ) + { + FATAL_ERROR( "Cannot create temporary directory" ); + } + + /* Copy PKGLOGs into TMPDIR: */ + { + int pkgs = copy_pkglogs(); + if( pkgs == 0 ) { FATAL_ERROR( "There are no PKGLOG files in the '%s' directory", pkgs_path ); } + if( exit_status > 0 ) { FATAL_ERROR( "Cannot copy some PKGLOG file" ); } + if( ! DO_NOT_PRINTOUT_INFO ) + { + INFO( "Found %d PKGLOG files in the '%s' directory", pkgs, pkgs_path ); + } + } + + /*********************************************************** + Fill srcpkg struct and put or replace pkglog into tmpdir: + */ + check_pkg_fname(); + + /* Read PKGLOGs from TMPDIR and create Double Linked List of PACKAGES: */ + { + int pkgs = read_pkglogs(); + if( pkgs == 0 ) { FATAL_ERROR( "There are no PKGLOG files in the '%s' directory", tmpdir ); } + if( exit_status > 0 ) { FATAL_ERROR( "Cannot read some PKGLOG file" ); } + if( ! DO_NOT_PRINTOUT_INFO ) + { + /* INFO( "Found %d PKGLOG files in the '%s' directory", pkgs, tmpdir ); */ + } + } + + { + int extern_pkgs = create_provides_list( srcpkgs ); + if( extern_pkgs ) + { + print_extern_requires(); + if( __extern_requires ) exit_status += 1; + } + free_provides_list(); + } + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + + exit( exit_status ); +} diff --git a/src/chrefs.c b/src/chrefs.c new file mode 100644 index 0000000..8820aea --- /dev/null +++ b/src/chrefs.c @@ -0,0 +1,1933 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <sys/types.h> +#include <stdint.h> +#include <dirent.h> +#include <sys/stat.h> /* chmod(2) */ +#include <sys/file.h> /* flock(2) */ +#include <fcntl.h> +#include <linux/limits.h> +#include <alloca.h> /* alloca(3) */ +#include <string.h> /* strdup(3) */ +#include <strings.h> /* index(3) */ +#include <libgen.h> /* basename(3) */ +#include <ctype.h> /* tolower(3) */ +#include <errno.h> +#include <time.h> +#include <sys/time.h> +#include <pwd.h> +#include <grp.h> +#include <stdarg.h> +#include <unistd.h> + +#include <sys/resource.h> + +#include <signal.h> +#if !defined SIGCHLD && defined SIGCLD +# define SIGCHLD SIGCLD +#endif + +#define _GNU_SOURCE +#include <getopt.h> + +#include <msglog.h> +#include <wrapper.h> +#include <system.h> + +#define PROGRAM_NAME "chrefs" + +#include <defs.h> + + +char *program = PROGRAM_NAME; +char *destination = NULL, *operation = NULL, *pkglog_fname = NULL, + *tmpdir = NULL, *requires_fname = NULL; + +int exit_status = EXIT_SUCCESS; /* errors counter */ +char *selfdir = NULL; + +char *pkgname = NULL, + *pkgver = NULL, + *arch = NULL, + *distroname = NULL, + *distrover = NULL, + *group = NULL; + + +/******************************************** + *requires[] declarations: + */ +struct package { + char *name; + char *version; + char *arch; + char *distro_name; + char *distro_version; +}; + +static struct package *create_package( char *name, char *version, + char *arch, char *distro_name, + char *distro_version ); +static void free_package( struct package *pkg ); +static struct package **create_requires( size_t size ); +static void free_requires( struct package **requires ); +/* + End of *requires[] declarations. + ********************************************/ + +struct package **requires = NULL; + + +/******************************************** + LOCK FILE declarations: + */ +static int __lock_file( FILE *fp ); +static void __unlock_file( int fd ); +/* + End of LOCK FILE declarations. + ********************************************/ + + +#define FREE_PKGINFO_VARIABLES() \ + if( pkgname ) { free( pkgname ); } pkgname = NULL; \ + if( pkgver ) { free( pkgver ); } pkgver = NULL; \ + if( arch ) { free( arch ); } arch = NULL; \ + if( distroname ) { free( distroname ); } distroname = NULL; \ + if( distrover ) { free( distrover ); } distrover = NULL; \ + if( group ) { free( group ); } group = NULL; \ + if( requires ) { free_requires( requires ); } requires = NULL + +void free_resources() +{ + if( selfdir ) { free( selfdir ); selfdir = NULL; } + if( destination ) { free( destination ); destination = NULL; } + if( operation ) { free( operation ); operation = NULL; } + if( pkglog_fname ) { free( pkglog_fname ); pkglog_fname = NULL; } + if( requires_fname ) { free( requires_fname ); requires_fname = NULL; } + + FREE_PKGINFO_VARIABLES(); +} + +void usage() +{ + free_resources(); + + fprintf( stdout, "\n" ); + fprintf( stdout, "Usage: %s [options] <pkglog>\n", program ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Increment or Decrement reference counters in required PKGLOG files\n" ); + fprintf( stdout, "in the Setup Database packages directory, which is determined by\n" ); + fprintf( stdout, "option --destination OR by the directory where the input <pkglog>\n" ); + fprintf( stdout, "file is located. If destination is defined then <pkglog> file name\n" ); + fprintf( stdout, "should be defined relative to destination.\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Options:\n" ); + fprintf( stdout, " -h,--help Display this information.\n" ); + fprintf( stdout, " -v,--version Display the version of %s utility.\n", program ); + fprintf( stdout, " -d,--destination=<DIR> Setup Database packages directory.\n" ); + fprintf( stdout, " -o,--operation=<inc|dec> Operation: Increment or Decrement.\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Parameter:\n" ); + fprintf( stdout, " <pkglog> Input PKGLOG file (TEXT format).\n" ); + fprintf( stdout, "\n" ); + + exit( EXIT_FAILURE ); +} + +void to_lowercase( char *s ) +{ + char *p = s; + while( p && *p ) { int c = *p; *p = tolower( c ); ++p; } +} + +void to_uppercase( char *s ) +{ + char *p = s; + while( p && *p ) { int c = *p; *p = toupper( c ); ++p; } +} + +void version() +{ + char *upper = NULL; + + upper = (char *)alloca( strlen( program ) + 1 ); + + strcpy( (char *)upper, (const char *)program ); + to_uppercase( upper ); + + fprintf( stdout, "%s (%s) %s\n", program, upper, PROGRAM_VERSION ); + + fprintf( stdout, "Copyright (C) 2019 Andrey V.Kosteltsev.\n" ); + fprintf( stdout, "This is free software. There is NO warranty; not even\n" ); + fprintf( stdout, "for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" ); + fprintf( stdout, "\n" ); + + free_resources(); + exit( EXIT_SUCCESS ); +} + + +static void remove_trailing_slash( char *dir ) +{ + char *s; + + if( !dir || dir[0] == '\0' ) return; + + s = dir + strlen( dir ) - 1; + while( *s == '/' ) + { + *s = '\0'; --s; + } +} + + +static int _mkdir_p( const char *dir, const mode_t mode ) +{ + char *buf; + char *p = NULL; + struct stat sb; + + if( !dir ) return -1; + + buf = (char *)alloca( strlen( dir ) + 1 ); + strcpy( buf, dir ); + + remove_trailing_slash( buf ); + + /* check if path exists and is a directory */ + if( stat( buf, &sb ) == 0 ) + { + if( S_ISDIR(sb.st_mode) ) + { + return 0; + } + } + + /* mkdir -p */ + for( p = buf + 1; *p; ++p ) + { + if( *p == '/' ) + { + *p = 0; + /* test path */ + if( stat( buf, &sb ) != 0 ) + { + /* path does not exist - create directory */ + if( mkdir( buf, mode ) < 0 ) + { + return -1; + } + } else if( !S_ISDIR(sb.st_mode) ) + { + /* not a directory */ + return -1; + } + *p = '/'; + } + } + + /* test path */ + if( stat( buf, &sb ) != 0 ) + { + /* path does not exist - create directory */ + if( mkdir( buf, mode ) < 0 ) + { + return -1; + } + } else if( !S_ISDIR(sb.st_mode) ) + { + /* not a directory */ + return -1; + } + + return 0; +} + + +static void _rm_tmpdir( const char *dirpath ) +{ + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + if( stat( dirpath, &path_sb ) == -1 ) + { + return; /* stat returns error code; errno is set */ + } + + if( S_ISDIR(path_sb.st_mode) == 0 ) + { + return; /* dirpath is not a directory */ + } + + if( (dir = opendir(dirpath) ) == NULL ) + { + return; /* Cannot open direcroty; errno is set */ + } + + len = strlen( dirpath ); + + while( (entry = readdir( dir )) != NULL) + { + + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( stat( path, &entry_sb ) == 0 ) + { + if( S_ISDIR(entry_sb.st_mode) ) + { + /* recursively remove a nested directory */ + _rm_tmpdir( path ); + } + else + { + /* remove a file object */ + (void)unlink( path ); + } + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + + } + + /* remove the devastated directory and close the object of this directory */ + (void)rmdir( dirpath ); + + closedir( dir ); +} + + +static char *_mk_tmpdir( void ) +{ + char *buf = NULL, *p, *tmp = "/tmp"; + size_t len = 0, size = 0; + + (void)umask( S_IWGRP | S_IWOTH ); /* octal 022 */ + + /* Get preferred directory for tmp files */ + if( (p = getenv( "TMP" )) != NULL ) { + tmp = p; + } + else if( (p = getenv( "TEMP" )) != NULL ) { + tmp = p; + } + + size = strlen( tmp ) + strlen( DISTRO_NAME ) + strlen( program ) + 12; + + buf = (char *)malloc( size ); + if( !buf ) return NULL; + + len = snprintf( buf, size, (const char *)"%s/%s/%s-%.7u", tmp, DISTRO_NAME, program, getpid() ); + if( len == 0 || len == size - 1 ) + { + free( buf ); return NULL; + } + + _rm_tmpdir( (const char *)&buf[0] ); + + if( _mkdir_p( buf, S_IRWXU | S_IRWXG | S_IRWXO ) == 0 ) + { + return buf; + } + + free( buf ); return NULL; +} + + +/******************************************** + *requires[] functions: + */ +static struct package *create_package( char *name, char *version, + char *arch, char *distro_name, + char *distro_version ) +{ + struct package *pkg = (struct package *)0; + + if( !name || *name == '\0' ) return pkg; + if( !version || *version == '\0' ) return pkg; + if( !arch || *arch == '\0' ) return pkg; + if( !distro_name || *distro_name == '\0' ) return pkg; + if( !distro_version || *distro_version == '\0' ) return pkg; + + pkg = (struct package *)malloc( sizeof(struct package) ); + if( pkg ) + { + pkg->name = xstrdup( (const char *)name ); + pkg->version = xstrdup( (const char *)version ); + pkg->arch = xstrdup( (const char *)arch ); + pkg->distro_name = xstrdup( (const char *)distro_name ); + pkg->distro_version = xstrdup( (const char *)distro_version ); + } + + return pkg; +} + +static void free_package( struct package *pkg ) +{ + if( pkg ) + { + if( pkg->name ) free( pkg->name ); + if( pkg->version ) free( pkg->version ); + if( pkg->arch ) free( pkg->arch ); + if( pkg->distro_name ) free( pkg->distro_name ); + if( pkg->distro_version ) free( pkg->distro_version ); + + free( pkg ); + } +} + +static struct package **create_requires( size_t size ) +{ + struct package **requires = (struct package **)0; + + if( size > 0 ) + { + requires = (struct package **)malloc( size * sizeof(struct package *) ); + bzero( (void *)requires, size * sizeof(struct package *) ); + } + + return( requires ); +} + +static void free_requires( struct package **requires ) +{ + if( requires ) + { + struct package **ptr = requires; + + while( *ptr ) + { + if( *ptr ) free_package( *ptr ); + ptr++; + } + free( requires ); + } +} +/* + End of *requires[] functions. + ********************************************/ + + +/******************************************** + LOCK FILE functions: + */ +static int __lock_file( FILE *fp ) +{ + int fd = fileno( fp ); + + if( flock( fd, LOCK_EX ) == -1 ) + { + return -1; + /* + Мы не проверяем errno == EWOULDBLOCK, так какданная ошибка + говорит о том что файл заблокирован другим процессом с флагом + LOCK_NB, а мы не собираемся циклически проверять блокировку. + У нас все просто: процесс просто ждет освобождения дескриптора + и не пытается во время ожидания выполнять другие задачи. + */ + } + return fd; +} + +static void __unlock_file( int fd ) +{ + if( fd != -1 ) flock( fd, LOCK_UN ); + /* + Здесь, в случае ошибки, мы не будем выводить + никаких сообщений. Наш процесс выполняет простую + атомарную задачу и, наверное, завершится в скором + времени, освободив все дескрипторы. + */ +} +/* + End of LOCK FILE functions. + ********************************************/ + + +void fatal_error_actions( void ) +{ + logmsg( errlog, MSG_NOTICE, "Free resources on FATAL error..." ); + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); +} + +void sigint( int signum ) +{ + (void)signum; + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); +} + +static void set_signal_handlers() +{ + struct sigaction sa; + sigset_t set; + + memset( &sa, 0, sizeof( sa ) ); + sa.sa_handler = sigint; /* TERM, INT */ + sa.sa_flags = SA_RESTART; + sigemptyset( &set ); + sigaddset( &set, SIGTERM ); + sigaddset( &set, SIGINT ); + sa.sa_mask = set; + sigaction( SIGTERM, &sa, NULL ); + sigaction( SIGINT, &sa, NULL ); + + memset( &sa, 0, sizeof( sa ) ); /* ignore SIGPIPE */ + sa.sa_handler = SIG_IGN; + sa.sa_flags = 0; + sigaction( SIGPIPE, &sa, NULL ); + + /* System V fork+wait does not work if SIGCHLD is ignored */ + signal( SIGCHLD, SIG_DFL ); +} + +enum _pkglog_type +{ + PKGLOG_TEXT = 0, + PKGLOG_GZ, + PKGLOG_BZ2, + PKGLOG_XZ, + PKGLOG_TAR, + + PKGLOG_UNKNOWN +}; + +static enum _pkglog_type pkglog_type = PKGLOG_UNKNOWN; +static char uncompress[2] = { 0, 0 }; + + +static enum _pkglog_type check_pkglog_file( const char *fname ) +{ + struct stat st; + size_t pkglog_size = 0; + unsigned char buf[8]; + int rc, fd; + + /* SIGNATURES: https://www.garykessler.net/library/file_sigs.html */ + + uncompress[0] = '\0'; + + if( stat( fname, &st ) == -1 ) + { + FATAL_ERROR( "Cannot access %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + + pkglog_size = st.st_size; + + if( (fd = open( fname, O_RDONLY )) == -1 ) + { + FATAL_ERROR( "Cannot open %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + + rc = (int)read( fd, (void *)&buf[0], 7 ); + if( rc != 7 ) + { + FATAL_ERROR( "Unknown type of input file %s", basename( (char *)fname ) ); + } + buf[7] = '\0'; + + /* TEXT */ + if( !strncmp( (const char *)&buf[0], "PACKAGE", 7 ) ) + { + close( fd ); return PKGLOG_TEXT; + } + + /* GZ */ + if( buf[0] == 0x1F && buf[1] == 0x8B && buf[2] == 0x08 ) + { + uncompress[0] = 'x'; + close( fd ); return PKGLOG_GZ; + } + + /* BZ2 */ + if( buf[0] == 0x42 && buf[1] == 0x5A && buf[2] == 0x68 ) + { + uncompress[0] = 'j'; + close( fd ); return PKGLOG_BZ2; + } + + /* XZ */ + if( buf[0] == 0xFD && buf[1] == 0x37 && buf[2] == 0x7A && + buf[3] == 0x58 && buf[4] == 0x5A && buf[5] == 0x00 ) + { + uncompress[0] = 'J'; + close( fd ); return PKGLOG_XZ; + } + + if( pkglog_size > 262 ) + { + if( lseek( fd, 257, SEEK_SET ) == -1 ) + { + FATAL_ERROR( "Cannot check signature of %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + rc = (int)read( fd, &buf[0], 5 ); + if( rc != 5 ) + { + FATAL_ERROR( "Cannot read signature of %s file", basename( (char *)fname ) ); + } + /* TAR */ + if( buf[0] == 0x75 && buf[1] == 0x73 && buf[2] == 0x74 && buf[3] == 0x61 && buf[4] == 0x72 ) + { + close( fd ); return PKGLOG_TAR; + } + } + + close( fd ); return PKGLOG_UNKNOWN; +} + + +void get_args( int argc, char *argv[] ) +{ + const char* short_options = "hvd:o:"; + + const struct option long_options[] = + { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'v' }, + { "destination", required_argument, NULL, 'd' }, + { "operation", required_argument, NULL, 'o' }, + { NULL, 0, NULL, 0 } + }; + + int ret; + int option_index = 0; + + while( (ret = getopt_long( argc, argv, short_options, long_options, &option_index )) != -1 ) + { + switch( ret ) + { + case 'h': + { + usage(); + break; + } + case 'v': + { + version(); + break; + } + + case 'd': + { + if( optarg != NULL ) + { + destination = xstrdup( (const char *)optarg ); + remove_trailing_slash( destination ); + } + else + /* option is present but without value */ + usage(); + break; + } + case 'o': + { + operation = xstrdup( (const char *)optarg ); + to_lowercase( operation ); + if( strcmp( operation, "inc" ) != 0 && strcmp( operation, "dec" ) != 0 ) + { + ERROR( "Invalid '%s' operation requested", operation ); + usage(); + } + break; + } + + case '?': default: + { + usage(); + break; + } + } + } + + + if( operation == NULL ) + { + usage(); + } + + /* last command line argument is the LOGFILE */ + if( optind < argc ) + { + char *buf = NULL; + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + + if( destination == NULL ) + { + pkglog_fname = xstrdup( (const char *)argv[optind++] ); + if( pkglog_fname == NULL ) + { + FATAL_ERROR( "Unable to set input PKGLOG file name" ); + } + + bzero( (void *)buf, PATH_MAX ); + (void)sprintf( buf, "%s", pkglog_fname ); + destination = xstrdup( (const char *)dirname( buf ) ); + if( destination == NULL ) + { + FATAL_ERROR( "Unable to set destination directory" ); + } + + } + else + { + bzero( (void *)buf, PATH_MAX ); + (void)sprintf( buf, "%s/%s", destination, argv[optind++] ); + pkglog_fname = xstrdup( (const char *)buf ); + if( pkglog_fname == NULL ) + { + FATAL_ERROR( "Unable to set inpit PKGLOG file name" ); + } + } + + free( buf ); + + pkglog_type = check_pkglog_file( (const char *)pkglog_fname ); + if( pkglog_type != PKGLOG_TEXT ) + { + ERROR( "%s: Unknown input file format", basename( pkglog_fname ) ); + usage(); + } + + } + else + { + usage(); + } +} + + +/* + Especialy for pkginfo lines. + Remove leading spaces and take non-space characters only: + */ +static char *skip_spaces( char *s ) +{ + char *q, *p = (char *)0; + + if( !s || *s == '\0' ) return p; + + p = s; + + while( (*p == ' ' || *p == '\t') && *p != '\0' ) { ++p; } q = p; + while( *q != ' ' && *q != '\t' && *q != '\0' ) { ++q; } *q = '\0'; + + if( *p == '\0' ) return (char *)0; + + return( xstrdup( (const char *)p ) ); +} + + +/* + remove spaces at end of line: + */ +static void skip_eol_spaces( char *s ) +{ + char *p = (char *)0; + + if( !s || *s == '\0' ) return; + + p = s + strlen( s ) - 1; + while( isspace( *p ) ) { *p-- = '\0'; } +} + + + +int get_pkginfo() +{ + int ret = -1; + FILE *log = NULL; + + if( pkglog_fname != NULL ) + { + log = fopen( (const char *)pkglog_fname, "r" ); + if( !log ) + { + FATAL_ERROR( "Cannot open %s file", pkglog_fname ); + } + } + + if( log != NULL ) + { + char *ln = NULL; + char *line = NULL; + + char *pkgname_pattern = "PACKAGE NAME:", + *pkgver_pattern = "PACKAGE VERSION:", + *group_pattern = "GROUP:", + *arch_pattern = "ARCH:", + *distroname_pattern = "DISTRO:", + *distrover_pattern = "DISTRO VERSION:"; + + int last = 10; /* read first 10 lines only */ + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + + if( last ) + { + int n = 1; + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + char *match = NULL; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( (match = strstr( ln, pkgname_pattern )) && match == ln ) /* at start of line only */ + { + pkgname = skip_spaces( ln + strlen( pkgname_pattern ) ); + } + if( (match = strstr( ln, pkgver_pattern )) && match == ln ) + { + pkgver = skip_spaces( ln + strlen( pkgver_pattern ) ); + } + if( (match = strstr( ln, arch_pattern )) && match == ln ) + { + arch = skip_spaces( ln + strlen( arch_pattern ) ); + } + if( (match = strstr( ln, distroname_pattern )) && match == ln ) + { + distroname = skip_spaces( ln + strlen( distroname_pattern ) ); + } + if( (match = strstr( ln, distrover_pattern )) && match == ln ) + { + distrover = skip_spaces( ln + strlen( distrover_pattern ) ); + } + if( (match = strstr( ln, group_pattern )) && match == ln ) + { + group = skip_spaces( ln + strlen( group_pattern ) ); + } + + if( n < last ) ++n; + else break; + + } /* End of while() */ + + } + + free( line ); + + if( pkgname == NULL ) ++ret; + if( pkgver == NULL ) ++ret; + if( arch == NULL ) ++ret; + if( distroname == NULL ) ++ret; + if( distrover == NULL ) ++ret; + /* group can be equal to NULL */ + + fclose( log ); + } + + return( ret ); +} + + + +int get_references_section( int *start, int *stop, unsigned int *cnt, FILE *log ) +{ + int ret = -1, found = 0; + + if( !start || !stop || !cnt ) return ret; + + if( log != NULL ) + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + char *match = NULL; + + if( (match = strstr( ln, "REFERENCE COUNTER:" )) && match == ln ) /* at start of line only */ + { + *start = ret + 1; + ++found; + + /* Get reference counter */ + { + unsigned int count; + int rc; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + rc = sscanf( ln, "REFERENCE COUNTER: %u", &count ); + if( rc == 1 && cnt != NULL ) + { + *cnt = count; + } + } + } + if( (match = strstr( ln, "REQUIRES:" )) && match == ln ) + { + *stop = ret + 1; + ++found; + } + + ++ret; + } + + free( line ); + + ret = ( found == 2 ) ? 0 : 1; /* 0 - success; 1 - not found. */ + + fseek( log, 0, SEEK_SET ); + } + + return( ret ); +} + + + +int get_requires_section( int *start, int *stop, const char *log_fname ) +{ + int ret = -1, found = 0; + + FILE *log = NULL; + + if( !start || !stop ) return ret; + + if( log_fname != NULL ) + { + log = fopen( (const char *)log_fname, "r" ); + if( !log ) + { + return ret; + } + } + + if( log != NULL ) + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + char *match = NULL; + + if( (match = strstr( ln, "REQUIRES:" )) && match == ln ) /* at start of line only */ + { + *start = ret + 1; + ++found; + } + if( (match = strstr( ln, "PACKAGE DESCRIPTION:" )) && match == ln ) + { + *stop = ret + 1; + ++found; + } + + ++ret; + } + + free( line ); + + ret = ( found == 2 ) ? 0 : 1; /* 0 - success; 1 - not found. */ + + fclose( log ); + } + + return( ret ); +} + + +int get_description_section( int *start, int *stop, const char *log_fname ) +{ + int ret = -1, found = 0; + + FILE *log = NULL; + + if( !start || !stop ) return ret; + + if( log_fname != NULL ) + { + log = fopen( (const char *)log_fname, "r" ); + if( !log ) + { + return ret; + } + } + + if( log != NULL ) + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + char *match = NULL; + + if( (match = strstr( ln, "PACKAGE DESCRIPTION:" )) && match == ln ) /* at start of line only */ + { + *start = ret + 1; + ++found; + } + if( (match = strstr( ln, "RESTORE LINKS:" )) && match == ln ) + { + *stop = ret + 1; + ++found; + } + + ++ret; + } + + free( line ); + + ret = ( found == 2 ) ? 0 : 1; /* 0 - success; 1 - not found. */ + + fclose( log ); + } + + return( ret ); +} + + +int get_restore_links_section( int *start, int *stop, const char *log_fname ) +{ + int ret = -1, found = 0; + + FILE *log = NULL; + + if( !start || !stop ) return ret; + + if( log_fname != NULL ) + { + log = fopen( (const char *)log_fname, "r" ); + if( !log ) + { + return ret; + } + } + + if( log != NULL ) + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + char *match = NULL; + + if( (match = strstr( ln, "RESTORE LINKS:" )) && match == ln ) /* at start of line only */ + { + *start = ret + 1; + ++found; + } + if( (match = strstr( ln, "INSTALL SCRIPT:" )) && match == ln ) + { + *stop = ret + 1; + ++found; + } + + ++ret; + } + + free( line ); + + ret = ( found == 2 ) ? 0 : 1; /* 0 - success; 1 - not found. */ + + fclose( log ); + } + + return( ret ); +} + + +int get_install_script_section( int *start, int *stop, const char *log_fname ) +{ + int ret = -1, found = 0; + + FILE *log = NULL; + + if( !start || !stop ) return ret; + + if( log_fname != NULL ) + { + log = fopen( (const char *)log_fname, "r" ); + if( !log ) + { + return ret; + } + } + + if( log != NULL ) + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + char *match = NULL; + + if( (match = strstr( ln, "INSTALL SCRIPT:" )) && match == ln ) /* at start of line only */ + { + *start = ret + 1; + ++found; + } + if( (match = strstr( ln, "FILE LIST:" )) && match == ln ) + { + *stop = ret + 1; + ++found; + } + + ++ret; + } + + free( line ); + + ret = ( found == 2 ) ? 0 : 1; /* 0 - success; 1 - not found. */ + + fclose( log ); + } + + return( ret ); +} + + +int get_file_list_section( int *start, int *stop, const char *log_fname ) +{ + int ret = -1, found = 0; + + FILE *log = NULL; + + if( !start || !stop ) return ret; + + if( log_fname != NULL ) + { + log = fopen( (const char *)log_fname, "r" ); + if( !log ) + { + return ret; + } + } + + if( log != NULL ) + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + *stop = 0; + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + char *match = NULL; + + if( (match = strstr( ln, "FILE LIST:" )) && match == ln ) /* at start of line only */ + { + *start = ret + 1; + ++found; + } + + ++ret; + } + + free( line ); + + ret = ( found == 1 ) ? 0 : 1; /* 0 - success; 1 - not found. */ + + fclose( log ); + } + + return( ret ); +} + + + + +/*********************************************************** + get_requires(): + -------------- + if( success ) - returns number of requires + if( error ) - returns -1 + */ +int get_requires( const char *requires, const char *log_fname ) +{ + int ret = -1; + FILE *req = NULL, *log = NULL; + + int start = 0, stop = 0; + + if( get_requires_section( &start, &stop, log_fname ) != 0 ) + { + return ret; + } + + if( log_fname != NULL ) + { + log = fopen( (const char *)log_fname, "r" ); + if( ! log ) + { + return ret; + } + } + + if( requires != NULL ) + { + req = fopen( (const char *)requires, "w" ); + if( ! req ) + { + return ret; + } + } + + /* get PKGLOG sections */ + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + + if( start && start < stop ) + { + int n = 1, lines = 0; + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( (n > start) && (n < stop) ) + { + fprintf( req, "%s\n", ln ); + ++lines; + } + ++n; + } + + ret = lines; /* number of lines in the LIST */ + } + + free( line ); + + fclose( log ); + fflush( req ); fclose( req ); + } + + return( ret ); +} + + +struct package **read_requires( const char *fname, int n ) +{ + struct package **requires = (struct package **)0; + struct package **ptr = (struct package **)0; + + FILE *file; + + if( ! fname ) return NULL; + + requires = create_requires( n + 1 ); + if( requires ) + { + int i; + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + struct package *package; + + file = fopen( fname, "r" ); + if( !file ) + { + free( line ); + free_requires( requires ); + return NULL; + } + + ptr = requires; + for( i = 0; i < n; ++i ) + { + if( (ln = fgets( line, PATH_MAX, file )) ) + { + char *d, *name, *version; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + /* сut 'name=version' by delimiter '=': */ + d = index( ln, '=' ); *d = '\0'; + version = ++d; name = ln; + + package = create_package( name, version, arch, DISTRO_NAME, DISTRO_VERSION ); + if( package ) + { + *ptr = package; + ptr++; + } + } + } + *ptr = (struct package *)0; + + free( line ); + } + + return requires; +} + +int find_requires( char *pname, const char *grp ) +{ + char *name = NULL; + char *buf = NULL; + + struct package **ptr = requires; + + buf = (char *)malloc( (size_t)PATH_MAX ); + + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + + while( *ptr ) + { + if( grp && *grp != '\0' ) + { + char *p = NULL; + size_t len = strlen( (*ptr)->name ); + + if( (p = strstr( (*ptr)->name, grp )) && (p == (*ptr)->name) ) + { + /* if group is equal to required package's group then remove group from the name */ + name = alloca( len + 1 ); + strcpy( name, (const char *)(*ptr)->name ); + + name = index( name, '/' ) + 1; + } + else + { + /* if group is not equal to required package's group then add group to the name */ + name = alloca( len + strlen( grp ) + 2 ); + (void)sprintf( name, "%s/%s", grp, (const char *)(*ptr)->name ); + } + } + else + { + name = (*ptr)->name; + } + + (void)sprintf( buf, "%s-%s-%s-%s-%s", + name, (*ptr)->version, (*ptr)->arch, + (*ptr)->distro_name, (*ptr)->distro_version ); + + if( ! strcmp( buf, pname ) ) + { + free( buf ); return 1; + } + ptr++; + } + + free( buf ); + + return 0; +} + + +int save_tmp_head( FILE *log, int stop, const char *fname ) +{ + FILE *fp; + int ret = -1; + + char *ln = NULL; + char *line = NULL; + int n = 1, lines = 0; + + if( !stop || !log || !fname || *fname == '\0' ) return ret; + + fp = fopen( fname, "w" ); + if( !fp ) + { + return ret; + } + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( n < stop ) + { + fprintf( fp, "%s\n", ln ); + ++n; ++lines; + } + else + break; + } + + ret = lines; /* number of lines in the HEAD */ + + free( line ); + + fseek( log, 0, SEEK_SET ); + fclose( fp ); + + return ret; +} + +int save_tmp_tail( FILE *log, int start, const char *fname ) +{ + FILE *fp; + int ret = -1; + + char *ln = NULL; + char *line = NULL; + int n = 1, lines = 0; + + if( !start || !log || !fname || *fname == '\0' ) return ret; + + fp = fopen( fname, "w" ); + if( !fp ) + { + return ret; + } + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + + while( (ln = fgets( line, PATH_MAX, log )) && (n < start) ) ++n; + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + fprintf( fp, "%s\n", ln ); + ++lines; + } + + ret = lines; /* number of lines in the TAIL */ + + free( line ); + + fseek( log, 0, SEEK_SET ); + fclose( fp ); + + return ret; +} + +int write_tmp_part( FILE *log, const char *fname ) +{ + FILE *fp; + int ret = -1; + + char *ln = NULL; + char *line = NULL; + int lines = 0; + + if( !log || !fname || *fname == '\0' ) return ret; + + fp = fopen( fname, "r" ); + if( !fp ) + { + return ret; + } + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + + while( (ln = fgets( line, PATH_MAX, fp )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + fprintf( log, "%s\n", ln ); + ++lines; + } + + ret = lines; /* number of written lines */ + + free( line ); + + fclose( fp ); + + return ret; +} + + + +static char **create_references( size_t size ) +{ + char **references = (char **)0; + + if( size > 0 ) + { + references = (char **)malloc( size * sizeof(char *) ); + bzero( (void *)references, size * sizeof(char *) ); + } + + return( references ); +} + +static void free_references( char **references ) +{ + if( references ) + { + char **ptr = references; + + while( *ptr ) + { + if( *ptr ) free( *ptr ); + ptr++; + } + free( references ); + } +} + + +static char **get_references( FILE *log, int start, unsigned int *cnt, char *grp, char *name, char *version ) +{ + char **refs = (char **)0; + char **ptr; + + char *ln = NULL; + char *line = NULL; + int n = 1; + + size_t len = 0; + + unsigned int counter, pkgs; + + char *pkg = NULL; + + if( !log || !cnt || *cnt == 0 || !name || !version ) return refs; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + pkg = (char *)malloc( (size_t)PATH_MAX ); + if( !pkg ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + counter = *cnt; + + if( grp && *grp != '\0' ) { (void)sprintf( pkg, "%s/%s=", grp, name ); } + else { (void)sprintf( pkg, "%s=", name ); } + + len = strlen( pkg ); + + refs = ptr = create_references( counter + 1 ); /* null terminated char *references[] */ + + while( (ln = fgets( line, PATH_MAX, log )) && (n < start) ) ++n; + + n = 0; pkgs = 0; + while( (ln = fgets( line, PATH_MAX, log )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( strstr( ln, "REQUIRES:" ) ) break; /* if cnt greater than real number of references */ + + if( n < counter ) + { + if( strncmp( ln, pkg, len ) ) /* always remove 'name=version' from list */ + { + if( refs ) + { + *ptr = xstrdup( (const char *)ln ); ++ptr; + *ptr = (char *)0; + ++pkgs; + } + } + ++n; + } + else + break; + } + + free( line ); free( pkg ); + + fseek( log, 0, SEEK_SET ); + + if( pkgs == 0 ) + { + free_references( refs ); + refs = (char **)0; + } + + *cnt = pkgs; + + return refs; +} + + +static void _change_references( char *grp, char *name, char *version, const char *log_fname ) +{ + int fd; + FILE *log; + + char *head_fname = NULL, *tail_fname = NULL; + int head_lines, tail_lines; + + int rc, start, stop; + unsigned int counter; + + int inc = ( strcmp( operation, "inc" ) == 0 ) ? 1 : 0; + + char **references = NULL; + + if( !name || !version || log_fname == NULL ) return; + if( check_pkglog_file( log_fname ) != PKGLOG_TEXT ) return; + + log = fopen( (const char *)log_fname, "r+" ); + if( !log ) + { + ERROR( "Cannot access %s file: %s", log_fname, strerror( errno ) ); + return; + } + + fd = __lock_file( log ); + + rc = get_references_section( &start, &stop, &counter, log ); + if( rc != 0 ) + { + ERROR( "%s: PKGLOG doesn't contains REFERENCE COUNTER section", log_fname ); + __unlock_file( fd ); fclose( log ); + return; + } + + head_fname = (char *)alloca( strlen( tmpdir ) + 7 ); + (void)sprintf( head_fname, "%s/.HEAD", tmpdir ); + + tail_fname = (char *)alloca( strlen( tmpdir ) + 7 ); + (void)sprintf( tail_fname, "%s/.TAIL", tmpdir ); + + head_lines = save_tmp_head( log, start, (const char *)head_fname ); + tail_lines = save_tmp_tail( log, stop - 1, (const char *)tail_fname ); + + if( head_lines < 10 && tail_lines < 12 ) + { + ERROR( "%s: Invalid PKGLOG file", log_fname ); + __unlock_file( fd ); fclose( log ); + return; + } + + references = get_references( log, start, &counter, grp, name, version ); + + if( ftruncate( fd, 0 ) != 0 ) + { + ERROR( "Cannot change REFERENCE COUNTER in the %s file: %s", log_fname, strerror( errno ) ); + free_references( references ); + __unlock_file( fd ); fclose( log ); + return; + } + + head_lines = write_tmp_part( log, (const char *)head_fname ); + + if( inc ) ++counter; + fprintf( log, "REFERENCE COUNTER: %u\n", counter ); + if( inc ) + { + if( grp && *grp != '\0' ) + { + fprintf( log, "%s/%s=%s\n", grp, name, version ); + } + else + { + fprintf( log, "%s=%s\n", name, version ); + } + } + + if( references ) + { + char **ptr = references; + + while( *ptr ) + { + if( *ptr ) fprintf( log, "%s\n", *ptr ); + ptr++; + } + + free_references( references ); + } + + tail_lines = write_tmp_part( log, (const char *)tail_fname ); + + __unlock_file( fd ); + fclose( log ); +} + + +static void _search_required_packages( const char *dirpath, const char *grp ) +{ + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + if( stat( dirpath, &path_sb ) == -1 ) + { + FATAL_ERROR( "%s: Cannot stat Setup Database or destination directory", dirpath ); + } + + if( S_ISDIR(path_sb.st_mode) == 0 ) + { + FATAL_ERROR( "%s: Setup Database or destination is not a directory", dirpath ); + } + + if( (dir = opendir(dirpath) ) == NULL ) + { + FATAL_ERROR( "Canot access %s directory: %s", dirpath, strerror( errno ) ); + } + + len = strlen( dirpath ); + + while( (entry = readdir( dir )) != NULL) + { + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( stat( path, &entry_sb ) == 0 ) + { + if( S_ISREG(entry_sb.st_mode) ) + { + if( find_requires( entry->d_name, grp ) ) + { + _change_references( group, pkgname, pkgver, (const char *)path ); + } + } + if( S_ISDIR(entry_sb.st_mode) && grp == NULL ) + { + _search_required_packages( (const char *)path, (const char *)entry->d_name ); + } + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + + } + + closedir( dir ); +} + + + + +/********************************************* + Get directory where this program is placed: + */ +char *get_selfdir( void ) +{ + char *buf = NULL; + ssize_t len; + + buf = (char *)malloc( PATH_MAX ); + if( !buf ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + bzero( (void *)buf, PATH_MAX ); + len = readlink( "/proc/self/exe", buf, (size_t)PATH_MAX ); + if( len > 0 && len < PATH_MAX ) + { + char *p = xstrdup( (const char *)dirname( buf ) ); + free( buf ); + return p; + } + FATAL_ERROR( "Cannot determine self directory. Please mount /proc filesystem" ); +} + +void set_stack_size( void ) +{ + const rlim_t stack_size = 16 * 1024 * 1024; /* min stack size = 16 MB */ + struct rlimit rl; + int ret; + + ret = getrlimit( RLIMIT_STACK, &rl ); + if( ret == 0 ) + { + if( rl.rlim_cur < stack_size ) + { + rl.rlim_cur = stack_size; + ret = setrlimit( RLIMIT_STACK, &rl ); + if( ret != 0 ) + { + fprintf(stderr, "setrlimit returned result = %d\n", ret); + FATAL_ERROR( "Cannot set stack size" ); + } + } + } +} + + +int main( int argc, char *argv[] ) +{ + gid_t gid; + + int ret; + + set_signal_handlers(); + + gid = getgid(); + setgroups( 1, &gid ); + + fatal_error_hook = fatal_error_actions; + + selfdir = get_selfdir(); + + errlog = stderr; + + program = basename( argv[0] ); + get_args( argc, argv ); + + /* set_stack_size(); */ + + exit_status = get_pkginfo(); + if( exit_status != 0 ) + { + FATAL_ERROR( "%s: Invalid input PKGLOG file", basename( pkglog_fname ) ); + } + + tmpdir = _mk_tmpdir(); + if( !tmpdir ) + { + FATAL_ERROR( "Cannot create temporary directory" ); + } + else + { + char *buf = NULL; + + buf = (char *)malloc( (size_t)strlen( tmpdir ) + 11 ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + + (void)sprintf( (char *)&buf[0], "%s/.REQUIRES", tmpdir ); + requires_fname = xstrdup( (const char *)&buf[0] ); + free( buf ); + } + + /******************* + Getting REQUIRES: + */ + if( (ret = get_requires( (const char *)requires_fname, (const char *)pkglog_fname )) > 0 ) + { + /* We have non-empty list of REQUIRES in the 'requires_fname' file */ + requires = read_requires( (const char *)requires_fname, ret ); + _search_required_packages( (const char *)destination, NULL ); + } + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + + exit( exit_status ); +} diff --git a/src/cmpvers.c b/src/cmpvers.c new file mode 100644 index 0000000..42014e8 --- /dev/null +++ b/src/cmpvers.c @@ -0,0 +1,110 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +#include <config.h> + +#include <stdint.h> +#include <string.h> +#include <ctype.h> + +/* states: S_N: normal, S_I: comparing integral part, S_F: comparing + fractionnal parts, S_Z: idem but with leading Zeroes only */ +#define S_N 0x0 +#define S_I 0x3 +#define S_F 0x6 +#define S_Z 0x9 + +/* result_type: CMP: return diff; LEN: compare using len_diff/diff */ +#define CMP 2 +#define LEN 3 + + +/* Compare S1 and S2 as strings holding indices/version numbers, + returning less than, equal to or greater than zero if S1 is less than, + equal to or greater than S2 (for more info, see the texinfo doc). +*/ + +int cmp_version( const char *s1, const char *s2 ) +{ + const unsigned char *p1 = (const unsigned char *)s1; + const unsigned char *p2 = (const unsigned char *)s2; + + /* Symbol(s) 0 [1-9] others + Transition (10) 0 (01) d (00) x */ + static const uint8_t next_state[] = + { + /* state x d 0 */ + /* S_N */ S_N, S_I, S_Z, + /* S_I */ S_N, S_I, S_I, + /* S_F */ S_N, S_F, S_F, + /* S_Z */ S_N, S_F, S_Z + }; + + static const int8_t result_type[] = + { + /* state x/x x/d x/0 d/x d/d d/0 0/x 0/d 0/0 */ + + /* S_N */ CMP, CMP, CMP, CMP, LEN, CMP, CMP, CMP, CMP, + /* S_I */ CMP, -1, -1, +1, LEN, LEN, +1, LEN, LEN, + /* S_F */ CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP, CMP, + /* S_Z */ CMP, +1, +1, -1, CMP, CMP, -1, CMP, CMP + }; + + if( p1 == p2 ) return 0; + + unsigned char c1 = *p1++; + unsigned char c2 = *p2++; + /* Hint: '0' is a digit too. */ + int state = S_N + ((c1 == '0') + (isdigit (c1) != 0)); + + int diff; + while( (diff = c1 - c2) == 0 ) + { + if( c1 == '\0' ) return diff; + + state = next_state[state]; + c1 = *p1++; + c2 = *p2++; + state += (c1 == '0') + (isdigit (c1) != 0); + } + + state = result_type[state * 3 + (((c2 == '0') + (isdigit (c2) != 0)))]; + + switch (state) + { + case CMP: + return diff; + + case LEN: + while( isdigit (*p1++) ) + if( !isdigit (*p2++) ) + return 1; + + return isdigit (*p2) ? -1 : diff; + + default: + return state; + } +} + + +const char *max_version( const char *s1, const char *s2 ) +{ + if( cmp_version( s1, s2 ) < 0 ) return s2; + else return s1; +} diff --git a/src/cmpvers.h b/src/cmpvers.h new file mode 100644 index 0000000..1d3c642 --- /dev/null +++ b/src/cmpvers.h @@ -0,0 +1,35 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +#ifndef _CMP_VERSION_H_ +#define _CMP_VERSION_H_ + +#ifdef __cplusplus +extern "C" { +#endif + + +extern int cmp_version( const char *s1, const char *s2 ); +extern const char *max_version( const char *s1, const char *s2 ); + + +#ifdef __cplusplus +} /* ... extern "C" */ +#endif + +#endif /* _CMP_VERSION_H_ */ diff --git a/src/defs.h b/src/defs.h new file mode 100644 index 0000000..08fe4b2 --- /dev/null +++ b/src/defs.h @@ -0,0 +1,51 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +#ifndef _DEFS_H_ +#define _DEFS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + + +#define SETUP_DB_PATH "var/lib/" DISTRO_NAME +#define PACKAGES_PATH SETUP_DB_PATH "/packages" +#define REMOVED_PKGS_PATH SETUP_DB_PATH "/removed-packages" +#define SETUP_PATH SETUP_DB_PATH "/setup" +#define LOG_PATH SETUP_DB_PATH +#define SETUP_LOG_FILE "/setup.log" /* : used by: update-package, remove-package, install-package. */ +#define LOG_FILE "/" PROGRAM_NAME ".log" /* : used by: check-requires, check-package, check-db-integrity. */ + + +#define DO_NOT_WARN_ABOUT_EMPTY_REQUIRES (1) +#define DO_NOT_WARN_ABOUT_EMPTY_RESTORE_LINKS (1) +#define DO_NOT_WARN_ABOUT_SERVICE_FILES (1) +#define DO_NOT_WARN_ABOUT_OPT_PKGINFO_ITEMS (1) + +#define DO_NOT_PRINTOUT_INFO (1) + +#define DESCRIPTION_NUMBER_OF_LINES 11 +#define DESCRIPTION_LENGTH_OF_LINE 68 + + +#ifdef __cplusplus +} /* ... extern "C" */ +#endif + +#endif /* _DEFS_H_ */ diff --git a/src/dialog-ui.c b/src/dialog-ui.c new file mode 100644 index 0000000..01c5686 --- /dev/null +++ b/src/dialog-ui.c @@ -0,0 +1,401 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +#include <stdlib.h> +#include <stdio.h> +#include <linux/limits.h> +#include <strings.h> /* index(3) */ + +#include <dialog.h> +#include <dlg_colors.h> +#include <dlg_keys.h> + +#include <msglog.h> +#include <wrapper.h> + + /************************************************* + Ruler: 68 characters + 2 spaces left and right: + + | ----handy-ruler----------------------------------------------------- | */ + +int info_box( const char *title, const char *message, int height, int sleep, int clear_screen ) +{ + int status = 0; + + FILE *in = stdin, *out = stdout; + + bzero( (void *)&dialog_vars, sizeof(DIALOG_VARS) ); + + init_dialog( in, out ); + + dialog_vars.colors = 1; + dialog_vars.backtitle = "\\Z7Radix\\Zn \\Z1cross\\Zn \\Z7Linux\\Zn"; + if( clear_screen ) dialog_vars.dlg_clear_screen = 1; + dialog_vars.sleep_secs = sleep; + + dlg_put_backtitle(); + + status = dialog_msgbox( title, message, height, 74, 0 ); + + if( dialog_vars.sleep_secs ) + (void)napms(dialog_vars.sleep_secs * 1000); + + if( dialog_vars.dlg_clear_screen ) + { + dlg_clear(); + (void)refresh(); + } + end_dialog(); + + return status; +} + +int info_pkg_box( const char *title, const char *pkgname, const char *pkgver, const char *priority, + const char *message, int height, int sleep, int clear_screen ) +{ + int status = 0; + + char *tmp = NULL; + + FILE *in = stdin, *out = stdout; + + bzero( (void *)&dialog_vars, sizeof(DIALOG_VARS) ); + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + init_dialog( in, out ); + + dialog_vars.colors = 1; + dialog_vars.backtitle = "\\Z7Radix\\Zn \\Z1cross\\Zn \\Z7Linux\\Zn"; + if( clear_screen ) dialog_vars.dlg_clear_screen = 1; + dialog_vars.sleep_secs = sleep; + + dlg_put_backtitle(); + + if( pkgver ) + (void)sprintf( &tmp[0], " %s \\Z1%s-%s\\Zn ", title, pkgname, pkgver ); + else + (void)sprintf( &tmp[0], " %s \\Z1%s\\Zn ",title, pkgname ); + + if( priority ) + { + (void)strcat( &tmp[0], "[" ); + (void)strcat( &tmp[0], priority ); + (void)strcat( &tmp[0], "] " ); + } + status = dialog_msgbox( (const char *)&tmp[0], message, height, 74, 0 ); + + free( tmp ); + + if( dialog_vars.sleep_secs ) + (void)napms(dialog_vars.sleep_secs * 1000); + + if( dialog_vars.dlg_clear_screen ) + { + dlg_clear(); + (void)refresh(); + } + end_dialog(); + + return status; +} + +int ask_install_box( const char *title, const char *pkgname, const char *pkgver, const char *priority, + const char *message, int height, int sleep, int clear_screen ) +{ + int status = 0; + + char *tmp = NULL; + + FILE *in = stdin, *out = stdout; + + bzero( (void *)&dialog_vars, sizeof(DIALOG_VARS) ); + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + init_dialog( in, out ); + + dialog_vars.colors = 1; + dialog_vars.backtitle = "\\Z7Radix\\Zn \\Z1cross\\Zn \\Z7Linux\\Zn"; + if( clear_screen ) dialog_vars.dlg_clear_screen = 1; + dialog_vars.sleep_secs = sleep; + + dialog_vars.yes_label = "Install"; + dialog_vars.no_label = "Cancel"; + + dlg_put_backtitle(); + + (void)sprintf( &tmp[0], + " %s \\Z1%s-%s\\Zn [%s] ", + title, pkgname, pkgver, priority ); + status = dialog_yesno( (const char *)&tmp[0], message, height, 74 ); + + free( tmp ); + + if( dialog_vars.sleep_secs ) + (void)napms(dialog_vars.sleep_secs * 1000); + + if( dialog_vars.dlg_clear_screen ) + { + dlg_clear(); + (void)refresh(); + } + end_dialog(); + + return status; +} + +int ask_remove_box( const char *title, const char *pkgname, const char *pkgver, + const char *message, int height, int sleep, int clear_screen ) +{ + int status = 0; + + char *tmp = NULL; + + FILE *in = stdin, *out = stdout; + + bzero( (void *)&dialog_vars, sizeof(DIALOG_VARS) ); + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + init_dialog( in, out ); + + dialog_vars.colors = 1; + dialog_vars.backtitle = "\\Z7Radix\\Zn \\Z1cross\\Zn \\Z7Linux\\Zn"; + if( clear_screen ) dialog_vars.dlg_clear_screen = 1; + dialog_vars.sleep_secs = sleep; + + dialog_vars.yes_label = "Remove"; + dialog_vars.no_label = "Cancel"; + + dlg_put_backtitle(); + + (void)sprintf( &tmp[0], + " %s \\Z1%s-%s\\Zn ", + title, pkgname, pkgver ); + status = dialog_yesno( (const char *)&tmp[0], message, height, 74 ); + + free( tmp ); + + if( dialog_vars.sleep_secs ) + (void)napms(dialog_vars.sleep_secs * 1000); + + if( dialog_vars.dlg_clear_screen ) + { + dlg_clear(); + (void)refresh(); + } + end_dialog(); + + return status; +} + +int ask_reinstall_box( const char *title, const char *pkgname, const char *pkgver, + const char *message, int height, int sleep, int clear_screen ) +{ + int status = 0; + + char *tmp = NULL; + + FILE *in = stdin, *out = stdout; + + bzero( (void *)&dialog_vars, sizeof(DIALOG_VARS) ); + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + init_dialog( in, out ); + + dialog_vars.colors = 1; + dialog_vars.backtitle = "\\Z7Radix\\Zn \\Z1cross\\Zn \\Z7Linux\\Zn"; + if( clear_screen ) dialog_vars.dlg_clear_screen = 1; + dialog_vars.sleep_secs = sleep; + + dialog_vars.yes_label = "Re-install"; + dialog_vars.no_label = "Cancel"; + + dlg_put_backtitle(); + + (void)sprintf( &tmp[0], + " %s \\Z1%s-%s\\Zn ", + title, pkgname, pkgver ); + status = dialog_yesno( (const char *)&tmp[0], message, height, 74 ); + + free( tmp ); + + if( dialog_vars.sleep_secs ) + (void)napms(dialog_vars.sleep_secs * 1000); + + if( dialog_vars.dlg_clear_screen ) + { + dlg_clear(); + (void)refresh(); + } + end_dialog(); + + return status; +} + +int ask_update_box( const char *title, const char *pkgname, const char *pkgver, const char *priority, + const char *message, int height, int sleep, int clear_screen ) +{ + int status = 0; + + char *tmp = NULL; + + FILE *in = stdin, *out = stdout; + + bzero( (void *)&dialog_vars, sizeof(DIALOG_VARS) ); + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + init_dialog( in, out ); + + dialog_vars.colors = 1; + dialog_vars.backtitle = "\\Z7Radix\\Zn \\Z1cross\\Zn \\Z7Linux\\Zn"; + if( clear_screen ) dialog_vars.dlg_clear_screen = 1; + dialog_vars.sleep_secs = sleep; + + dialog_vars.yes_label = "Update"; + dialog_vars.no_label = "Cancel"; + + dlg_put_backtitle(); + + (void)sprintf( &tmp[0], + " %s \\Z1%s-%s\\Zn [%s] ", + title, pkgname, pkgver, priority ); + status = dialog_yesno( (const char *)&tmp[0], message, height, 74 ); + + free( tmp ); + + if( dialog_vars.sleep_secs ) + (void)napms(dialog_vars.sleep_secs * 1000); + + if( dialog_vars.dlg_clear_screen ) + { + dlg_clear(); + (void)refresh(); + } + end_dialog(); + + return status; +} + +int select_packages_box( DIALOG_LISTITEM *items, int items_num, int sleep, int clear_screen ) +{ + int status = 0; + + int current = 0; + const char *states = " *"; + FILE *in = stdin, *out = stdout; + + bzero( (void *)&dialog_vars, sizeof(DIALOG_VARS) ); + + init_dialog( in, out ); + + dialog_vars.colors = 1; + dialog_vars.column_separator = " "; + dialog_vars.backtitle = "\\Z7Radix\\Zn \\Z1cross\\Zn \\Z7Linux\\Zn"; + if( clear_screen ) dialog_vars.dlg_clear_screen = 1; + dialog_vars.sleep_secs = sleep; + + dialog_vars.yes_label = "Install"; + dialog_vars.no_label = "Cancel"; + + dlg_put_backtitle(); + + status = dlg_checklist( " \\Z0SELECT PACKAGES TO INSTALL\\Zn ", + "\n" + " Please confirm the packages you wish to install. Use the UP/DOWN\n" + " keys to scroll through the list, and the SPACE key to deselect any\n" + " items you don't want to install.\n" + "\n" + " Press ENTER when you are done.\n", + 19, 74 /* min 73 */, 7, items_num, items, states, 1, ¤t ); + + if( dialog_vars.sleep_secs ) + (void)napms(dialog_vars.sleep_secs * 1000); + + if( dialog_vars.dlg_clear_screen ) + { + dlg_clear(); + (void)refresh(); + } + end_dialog(); + + return status; +} + + + +void show_install_dlg_progress( int percent ) +{ + static void *gauge = NULL; + + + if( percent < 1 ) + { + if( gauge ) return; /* only one instance of progress box */ + + bzero( (void *)&dialog_vars, sizeof(DIALOG_VARS) ); + + init_dialog( stdin, stdout ); + + dialog_vars.colors = 1; + dialog_vars.backtitle = "\\Z7Radix\\Zn \\Z1cross\\Zn \\Z7Linux\\Zn"; + dialog_vars.dlg_clear_screen = 0; + dialog_vars.sleep_secs = 0; + + dlg_put_backtitle(); + gauge = dlg_allocate_gauge( " \\Z0INSTALL PACKAGES\\Zn ", + "\n" + " Please wait for install all specified packages:\n" + "\n\n", 8, 74, 0 ); + } + + if( gauge ) + dlg_update_gauge( gauge, percent ); + + if( percent > 99 ) + { + if( gauge ) + { + dlg_free_gauge( gauge ); + gauge = NULL; + + if( dialog_vars.sleep_secs ) + (void)napms(dialog_vars.sleep_secs * 1000); + + if( dialog_vars.dlg_clear_screen ) + { + dlg_clear(); + (void)refresh(); + } + end_dialog(); + } + } +} diff --git a/src/dialog-ui.h b/src/dialog-ui.h new file mode 100644 index 0000000..fb135da --- /dev/null +++ b/src/dialog-ui.h @@ -0,0 +1,59 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +#ifndef _DIALOG_UI_H_ +#define _DIALOG_UI_H_ + +#include <dialog.h> + +#ifdef __cplusplus +extern "C" { +#endif + + /************************************************* + Ruler: 68 characters + 2 spaces left and right: + + | ----handy-ruler----------------------------------------------------- | */ + +extern int info_box( const char *title, const char *message, int height, int sleep, int clear_screen ); + +extern int info_pkg_box( const char *title, const char *pkgname, const char *pkgver, const char *priority, + const char *message, int height, int sleep, int clear_screen ); + +extern int ask_install_box( const char *title, const char *pkgname, const char *pkgver, const char *priority, + const char *message, int height, int sleep, int clear_screen ); + +extern int ask_remove_box( const char *title, const char *pkgname, const char *pkgver, + const char *message, int height, int sleep, int clear_screen ); + +extern int ask_reinstall_box( const char *title, const char *pkgname, const char *pkgver, + const char *message, int height, int sleep, int clear_screen ); + +extern int ask_update_box( const char *title, const char *pkgname, const char *pkgver, const char *priority, + const char *message, int height, int sleep, int clear_screen ); + +extern int select_packages_box( DIALOG_LISTITEM *items, int items_num, int sleep, int clear_screen ); + +extern void show_install_dlg_progress( int percent ); + + +#ifdef __cplusplus +} /* ... extern "C" */ +#endif + +#endif /* _DIALOG_UI_H_ */ diff --git a/src/dlist.c b/src/dlist.c new file mode 100644 index 0000000..3c97c5b --- /dev/null +++ b/src/dlist.c @@ -0,0 +1,680 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include <msglog.h> + +#include <dlist.h> + +struct dlist *__dlist_alloc( void ) +{ + struct dlist *list = NULL; + + list = (struct dlist *)malloc( sizeof( struct dlist ) ); + if( !list ) { FATAL_ERROR( "Cannot allocate memory" ); } + + bzero( (void *)list, sizeof( struct dlist ) ); + + return list; +} + + + +struct dlist *dlist_first( struct dlist *list ) +{ + while( list && dlist_prev( list ) ) list = dlist_prev( list ); + + return list; +} + +struct dlist *dlist_last( struct dlist *list ) +{ + while( list && dlist_next( list ) ) list = dlist_next( list ); + + return list; +} + +int dlist_length( struct dlist *list ) +{ + int n = 0; + + while( list ) + { + list = dlist_next( list ); + ++n; + } + + return n; +} + +struct dlist *dlist_nth( struct dlist *list, int n ) +{ + while( list && (n-- > 0) ) + { + list = dlist_next( list ); + } + + return list; +} + +void *dlist_nth_data( struct dlist *list, int n ) +{ + while( list && (n-- > 0) ) + { + list = dlist_next( list ); + } + + return list ? list->data : NULL; +} + +int dlist_position( struct dlist *list, struct dlist *link ) +{ + int n = 0; + + while( list ) + { + if( list == link ) return n; + ++n; + list = dlist_next( list ); + } + + return -1; +} + +int dlist_index( struct dlist *list, const void *data ) +{ + int n = 0; + + while( list ) + { + if( list->data == data ) return n; + ++n; + list = dlist_next( list ); + } + + return -1; +} + +struct dlist *dlist_find( struct dlist *list, const void *data ) +{ + while( list ) + { + if( list->data == data ) { break; } + list = dlist_next( list ); + } + + return list; +} + + +struct dlist *dlist_find_data( struct dlist *list, DLCMPF cmp_func, const void *data ) +{ + if( !list || !cmp_func ) return NULL; + + while( list ) + { + if( ! cmp_func( list->data, data ) ) { return list; } + list = dlist_next( list ); + } + + return NULL; +} + + +struct dlist *dlist_append( struct dlist *list, void *data ) +{ + struct dlist *node = NULL; + struct dlist *last = NULL; + + node = __dlist_alloc(); + node->data = data; + + if( list ) + { + last = dlist_last( list ); + + dlist_next( last ) = node; + dlist_prev( node ) = last; + + return list; + } + + return node; +} + +struct dlist *dlist_prepend( struct dlist *list, void *data ) +{ + struct dlist *node = NULL; + struct dlist *first = NULL; + + node = __dlist_alloc(); + node->data = data; + + if( list ) + { + first = dlist_first( list ); + + dlist_prev( first ) = node; + dlist_next( node ) = first; + + return node; + } + + return node; +} + +struct dlist *dlist_insert( struct dlist *list, void *data, int position ) +{ + struct dlist *node = NULL; + struct dlist *ptr = NULL; + + if( position < 0 ) { return dlist_append( list, data ); } + if( position == 0 ) { return dlist_prepend( list, data ); } + + ptr = dlist_nth( list, position ); + if( !ptr ) { return dlist_append( list, data ); } + + node = __dlist_alloc(); + node->data = data; + + node->prev = ptr->prev; + ptr->prev->next = node; + node->next = ptr; + ptr->prev = node; + + return list; +} + +struct dlist *dlist_insert_sorted( struct dlist *list, DLCMPF cmp_func, void *data ) +{ + struct dlist *node = NULL; + struct dlist *ptr = list; + int cmp; + + if( !cmp_func ) return list; + + if( !list ) + { + node = __dlist_alloc(); + node->data = data; + return node; + } + + cmp = cmp_func( data, ptr->data ); + + while( (ptr->next) && (cmp > 0) ) + { + ptr = ptr->next; + cmp = cmp_func( data, ptr->data ); + } + + node = __dlist_alloc(); + node->data = data; + + if( (!ptr->next) && (cmp > 0) ) + { + ptr->next = node; + node->prev = ptr; + return list; + } + + if( ptr->prev ) + { + ptr->prev->next = node; + node->prev = ptr->prev; + } + node->next = ptr; + ptr->prev = node; + + if( ptr == list ) + return node; + else + return list; +} + +struct dlist *dlist_insert_sorted_with_data( struct dlist *list, DLCMPDF cmp_func, void *data, void *user_data ) +{ + struct dlist *node = NULL; + struct dlist *ptr = list; + int cmp; + + if( !cmp_func ) return list; + + if( !list ) + { + node = __dlist_alloc(); + node->data = data; + return node; + } + + cmp = cmp_func( data, ptr->data, user_data ); + + while( (ptr->next) && (cmp > 0) ) + { + ptr = ptr->next; + cmp = cmp_func( data, ptr->data, user_data ); + } + + node = __dlist_alloc(); + node->data = data; + + if( (!ptr->next) && (cmp > 0) ) + { + ptr->next = node; + node->prev = ptr; + return list; + } + + if( ptr->prev ) + { + ptr->prev->next = node; + node->prev = ptr->prev; + } + node->next = ptr; + ptr->prev = node; + + if( ptr == list ) + return node; + else + return list; +} + +struct dlist *dlist_concat( struct dlist *list1, struct dlist *list2 ) +{ + struct dlist *ptr = NULL; + + if( list2 ) + { + ptr = dlist_last( list1 ); + if( ptr ) + ptr->next = list2; + else + list1 = list2; + + list2->prev = ptr; + } + + return list1; +} + +struct dlist *dlist_insert_list( struct dlist *list1, struct dlist *list2, int position ) +{ + struct dlist *last = NULL; + struct dlist *ptr = NULL; + + if( position < 0 ) { return dlist_concat( list1, list2 ); } + if( position == 0 ) { return dlist_concat( list2, list1 ); } + + ptr = dlist_nth( list1, position ); + if( !ptr ) { return dlist_concat( list1, list2 ); } + + last = dlist_last( list2 ); + if( last ) + { + list2->prev = ptr->prev; + ptr->prev->next = list2; + last->next = ptr; + ptr->prev = last; + } + + return list1; +} + +struct dlist *__dlist_remove_link( struct dlist *list, struct dlist *link ) +{ + if( link == NULL ) return list; + + if( link->prev ) + { + if( link->prev->next == link ) + { + link->prev->next = link->next; + } + else + { + WARNING( "Corrupted double-linked list detected" ); + } + } + if( link->next ) + { + if( link->next->prev == link ) + { + link->next->prev = link->prev; + } + else + { + WARNING( "Corrupted double-linked list detected" ); + } + } + + if( link == list ) list = list->next; + + link->next = NULL; + link->prev = NULL; + + return list; +} + +struct dlist *dlist_remove( struct dlist *list, const void *data ) +{ + struct dlist *ptr = list; + + while( ptr ) + { + if( ptr->data != data ) + { + ptr = ptr->next; + } + else + { + list = __dlist_remove_link( list, ptr ); + free( ptr ); + + break; + } + } + + return list; +} + +struct dlist *dlist_remove_all( struct dlist *list, const void *data ) +{ + struct dlist *ptr = list; + + while( ptr ) + { + if( ptr->data != data ) + { + ptr = ptr->next; + } + else + { + struct dlist *next = ptr->next; + + if( ptr->prev ) + ptr->prev->next = next; + else + list = next; + + if( next ) + next->prev = ptr->prev; + + free( ptr ); + + ptr = next; + } + } + + return list; +} + +struct dlist *dlist_remove_data( struct dlist *list, DLCMPF cmp_func, DLFUNC free_func, const void *data ) +{ + struct dlist *ptr = list; + + if( !cmp_func ) return list; + + while( ptr ) + { + if( cmp_func( ptr->data, data ) != 0 ) + { + ptr = ptr->next; + } + else + { + list = __dlist_remove_link( list, ptr ); + if( free_func ) free_func( ptr->data, (void *)data ); /* free_func() can compare pointers */ + free( ptr ); + + break; + } + } + + return list; +} + +struct dlist *dlist_remove_data_all( struct dlist *list, DLCMPF cmp_func, DLFUNC free_func, const void *data ) +{ + struct dlist *ptr = list; + + if( !cmp_func ) return list; + + while( ptr ) + { + if( cmp_func( ptr->data, data ) != 0 ) + { + ptr = ptr->next; + } + else + { + struct dlist *next = ptr->next; + + if( ptr->prev ) + ptr->prev->next = next; + else + list = next; + + if( next ) + next->prev = ptr->prev; + + if( free_func ) free_func( ptr->data, (void *)data ); /* free_func() can compare pointers */ + free( ptr ); + + ptr = next; + } + } + + return list; +} + + +struct dlist *dlist_copy( struct dlist *list ) +{ + struct dlist *copy = NULL; + + while( list ) + { + copy = dlist_append( copy, list->data ); + list = dlist_next( list ); + } + + return copy; +} + +/* It simply switches the next and prev pointers of each element. */ +struct dlist *dlist_reverse( struct dlist *list ) +{ + struct dlist *last = NULL; + + while( list ) + { + last = list; + list = last->next; + last->next = last->prev; + last->prev = list; + } + + return last; +} + + +static struct dlist *__dlist_sort_merge( struct dlist *l1, struct dlist *l2, DLCMPF cmp_func ) +{ + struct dlist list, *l, *lprev; + int cmp; + + l = &list; + lprev = NULL; + + while( l1 && l2 ) + { + cmp = ((DLCMPF) cmp_func)( l1->data, l2->data ); + + if( cmp <= 0 ) + { + l->next = l1; + l1 = l1->next; + } + else + { + l->next = l2; + l2 = l2->next; + } + l = l->next; + l->prev = lprev; + lprev = l; + } + l->next = l1 ? l1 : l2; + l->next->prev = l; + + return list.next; +} + +static struct dlist *__dlist_sort_merge_with_data( struct dlist *l1, struct dlist *l2, DLCMPDF cmp_func, void *user_data ) +{ + struct dlist list, *l, *lprev; + int cmp; + + l = &list; + lprev = NULL; + + while( l1 && l2 ) + { + cmp = ((DLCMPDF) cmp_func)( l1->data, l2->data, user_data ); + + if( cmp <= 0 ) + { + l->next = l1; + l1 = l1->next; + } + else + { + l->next = l2; + l2 = l2->next; + } + l = l->next; + l->prev = lprev; + lprev = l; + } + l->next = l1 ? l1 : l2; + l->next->prev = l; + + return list.next; +} + + +static struct dlist *__dlist_sort_real( struct dlist *list, DLCMPF cmp_func ) +{ + struct dlist *l1, *l2; + + if( !list ) + return NULL; + if( !list->next ) + return list; + + l1 = list; + l2 = list->next; + + while( (l2 = l2->next) != NULL ) + { + if( (l2 = l2->next) == NULL ) + break; + l1 = l1->next; + } + l2 = l1->next; + l1->next = NULL; + + return __dlist_sort_merge( __dlist_sort_real( list, cmp_func ), + __dlist_sort_real( l2, cmp_func ), + cmp_func ); +} + +static struct dlist *__dlist_sort_real_with_data( struct dlist *list, DLCMPDF cmp_func, void *user_data ) +{ + struct dlist *l1, *l2; + + if( !list ) + return NULL; + if( !list->next ) + return list; + + l1 = list; + l2 = list->next; + + while( (l2 = l2->next) != NULL ) + { + if( (l2 = l2->next) == NULL ) + break; + l1 = l1->next; + } + l2 = l1->next; + l1->next = NULL; + + return __dlist_sort_merge_with_data( __dlist_sort_real_with_data( list, cmp_func, user_data ), + __dlist_sort_real_with_data( l2, cmp_func, user_data ), + cmp_func, + user_data ); +} + + +struct dlist *dlist_sort( struct dlist *list, DLCMPF cmp_func ) +{ + return __dlist_sort_real( list, cmp_func ); +} + +struct dlist *dlist_sort_with_data( struct dlist *list, DLCMPDF cmp_func, void *user_data ) +{ + return __dlist_sort_real_with_data( list, cmp_func, user_data ); +} + + +void dlist_foreach( struct dlist *list, DLFUNC func, void *user_data ) +{ + struct dlist *next = NULL; + + while( list ) + { + next = dlist_next( list ); + if( func ) { func( list->data, user_data ); } + list = next; + } +} + + +void __dlist_free( struct dlist *list ) +{ + struct dlist *next = NULL; + + while( list ) + { + next = dlist_next( list ); + free( list ); list = NULL; + list = next; + } +} + +void dlist_free( struct dlist *list, DLFUNC free_func ) +{ + dlist_foreach( list, free_func, NULL ); + __dlist_free( list ); +} diff --git a/src/dlist.h b/src/dlist.h new file mode 100644 index 0000000..0a84683 --- /dev/null +++ b/src/dlist.h @@ -0,0 +1,83 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +#ifndef _DLIST_H_ +#define _DLIST_H_ + +#ifdef __cplusplus +extern "C" { +#endif + + +struct dlist { + struct dlist *prev; + struct dlist *next; + + void *data; +}; + +typedef void (*DLFUNC) ( void *data, void *user_data ); +typedef int (*DLCMPF) ( const void *a, const void *b ); +typedef int (*DLCMPDF) ( const void *a, const void *b, void *user_data ); + +#define dlist_prev( list ) ( (list)->prev ) +#define dlist_next( list ) ( (list)->next ) + +extern struct dlist *__dlist_alloc( void ); +extern struct dlist *dlist_first( struct dlist *list ); +extern struct dlist *dlist_last( struct dlist *list ); +extern int dlist_length( struct dlist *list ); +extern struct dlist *dlist_nth( struct dlist *list, int n ); +extern void *dlist_nth_data( struct dlist *list, int n ); +extern int dlist_position( struct dlist *list, struct dlist *link ); +extern int dlist_index( struct dlist *list, const void *data ); +extern struct dlist *dlist_find( struct dlist *list, const void *data ); +extern struct dlist *dlist_find_data( struct dlist *list, DLCMPF func, const void *data ); + +extern struct dlist *dlist_append( struct dlist *list, void *data ); +extern struct dlist *dlist_prepend( struct dlist *list, void *data ); +extern struct dlist *dlist_insert( struct dlist *list, void *data, int position ); +extern struct dlist *dlist_insert_sorted( struct dlist *list, DLCMPF cmp_func, void *data ); +extern struct dlist *dlist_insert_sorted_with_data( struct dlist *list, DLCMPDF cmp_func, void *data, void *user_data ); +extern struct dlist *dlist_concat( struct dlist *list1, struct dlist *list2 ); +extern struct dlist *dlist_insert_list( struct dlist *list1, struct dlist *list2, int position ); + +extern struct dlist *__dlist_remove_link( struct dlist *list, struct dlist *link ); +extern struct dlist *dlist_remove( struct dlist *list, const void *data ); +extern struct dlist *dlist_remove_all( struct dlist *list, const void *data ); +extern struct dlist *dlist_remove_data( struct dlist *list, DLCMPF cmp_func, DLFUNC free_func, const void *data ); +extern struct dlist *dlist_remove_data_all( struct dlist *list, DLCMPF cmp_func, DLFUNC free_func, const void *data ); + +extern struct dlist *dlist_copy( struct dlist *list ); +extern struct dlist *dlist_reverse( struct dlist *list ); + +extern struct dlist *dlist_sort( struct dlist *list, DLCMPF cmp_func ); +extern struct dlist *dlist_sort_with_data( struct dlist *list, DLCMPDF cmp_func, void *user_data ); + +extern void dlist_foreach( struct dlist *list, DLFUNC func, void *user_data ); + + +extern void __dlist_free( struct dlist *list ); +extern void dlist_free( struct dlist *list, DLFUNC free_func ); + + +#ifdef __cplusplus +} /* ... extern "C" */ +#endif + +#endif /* _DLIST_H_ */ diff --git a/src/install-package.c b/src/install-package.c new file mode 100644 index 0000000..9b73bf7 --- /dev/null +++ b/src/install-package.c @@ -0,0 +1,2764 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <sys/sysinfo.h> +#include <sys/types.h> +#include <stdint.h> +#include <dirent.h> +#include <sys/stat.h> /* chmod(2) */ +#include <sys/file.h> /* flock(2) */ +#include <fcntl.h> +#include <linux/limits.h> +#include <alloca.h> /* alloca(3) */ +#include <string.h> /* strdup(3) */ +#include <strings.h> /* index(3) */ +#include <libgen.h> /* basename(3) */ +#include <ctype.h> /* tolower(3) */ +#include <errno.h> +#include <time.h> +#include <sys/time.h> +#include <pwd.h> +#include <grp.h> +#include <stdarg.h> +#include <unistd.h> + +#include <math.h> + +#include <sys/wait.h> + +#include <sys/resource.h> + +#include <signal.h> +#if !defined SIGCHLD && defined SIGCLD +# define SIGCHLD SIGCLD +#endif + +#define _GNU_SOURCE +#include <getopt.h> + +#include <config.h> + +#include <msglog.h> +#include <wrapper.h> +#include <system.h> + +#include <cmpvers.h> +#include <dlist.h> + +#if defined( HAVE_DIALOG ) +#include <dialog-ui.h> +#endif + +#define PROGRAM_NAME "install-package" + +#include <defs.h> + + +char *program = PROGRAM_NAME; +char *root = NULL, *pkgs_path = NULL, *rempkgs_path = NULL, + *pkg_fname = NULL, *asc_fname = NULL, *pkglog_fname = NULL, *pkglist_fname = NULL, + *tmpdir = NULL, *curdir = NULL, *log_fname = NULL; + +int ask = 0, rqck = 0, gpgck = 0, ignore_chrefs_errors = 0; +char *description = NULL; + +int exit_status = EXIT_SUCCESS; /* errors counter */ +char *selfdir = NULL; + +static char *pkgname = NULL, + *pkgver = NULL, + *arch = NULL, + *distroname = NULL, + *distrover = NULL, + *group = NULL, + *short_description = NULL, + *url = NULL, + *license = NULL, + *uncompressed_size = NULL, + *compressed_size = NULL, + *total_files = NULL; + +enum _install_mode { + CONSOLE = 0, + INFODIALOG, + MENUDIALOG +} install_mode = CONSOLE; + +enum _priority { + REQUIRED = 0, /* synonims: REQUIRED | required | REQ | req */ + RECOMMENDED, /* synonims: RECOMMENDED | recommended | REC | rec */ + OPTIONAL, /* synonims: OPTIONAL | optional | OPT | opt */ + SKIP /* synonims: SKIP | skip | SKP | skp */ +} priority = REQUIRED; + +enum _procedure +{ + INSTALL = 0, /* 'install' */ + UPDATE /* 'update' */ +} procedure = INSTALL; + +enum _input_type { + IFMT_PKG = 0, + IFMT_LOG, + + IFMT_UNKNOWN +} input_format = IFMT_PKG; + +char uncompress = '\0'; + +static struct dlist *dirs = NULL; +static struct dlist *files = NULL; +static struct dlist *links = NULL; + +static void free_list( struct dlist *list ); + + +#define FREE_PKGINFO_VARIABLES() \ + if( pkgname ) { free( pkgname ); } pkgname = NULL; \ + if( pkgver ) { free( pkgver ); } pkgver = NULL; \ + if( arch ) { free( arch ); } arch = NULL; \ + if( distroname ) { free( distroname ); } distroname = NULL; \ + if( distrover ) { free( distrover ); } distrover = NULL; \ + if( group ) { free( group ); } group = NULL; \ + if( short_description ) { free( short_description ); } short_description = NULL; \ + if( description ) { free( description ); } description = NULL; \ + if( url ) { free( url ); } url = NULL; \ + if( license ) { free( license ); } license = NULL; \ + if( uncompressed_size ) { free( uncompressed_size ); } uncompressed_size = NULL; \ + if( compressed_size ) { free( compressed_size ); } compressed_size = NULL; \ + if( total_files ) { free( total_files ); } total_files = NULL + +void free_resources() +{ + if( root ) { free( root ); root = NULL; } + if( pkgs_path ) { free( pkgs_path ); pkgs_path = NULL; } + if( rempkgs_path ) { free( rempkgs_path ); rempkgs_path = NULL; } + if( pkg_fname ) { free( pkg_fname ); pkg_fname = NULL; } + if( asc_fname ) { free( asc_fname ); asc_fname = NULL; } + if( pkglog_fname ) { free( pkglog_fname ); pkglog_fname = NULL; } + + if( pkglist_fname ) { free( pkglist_fname ); pkglist_fname = NULL; } + + if( dirs ) { free_list( dirs ); dirs = NULL; } + if( files ) { free_list( files ); files = NULL; } + if( links ) { free_list( links ); links = NULL; } + + if( curdir ) { free( curdir ); curdir = NULL; } + if( log_fname ) { free( log_fname ); log_fname = NULL; } + + if( selfdir ) { free( selfdir ); selfdir = NULL; } + + FREE_PKGINFO_VARIABLES(); +} + +void usage() +{ + free_resources(); + + fprintf( stdout, "\n" ); + fprintf( stdout, "Usage: %s [options] <package>\n", program ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Install package.\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Options:\n" ); + fprintf( stdout, " -h,--help Display this information.\n" ); + fprintf( stdout, " -v,--version Display the version of %s utility.\n", program ); + fprintf( stdout, " -a,--always-ask Used with menudialog mode: always ask\n" ); + fprintf( stdout, " if a package should be installed regardless\n" ); + fprintf( stdout, " of what the package priority is. Without\n" ); + fprintf( stdout, " this option, if the priority is equal to\n" ); + fprintf( stdout, " REQUIRED, the package is installed without\n" ); + fprintf( stdout, " asking for confirmation the installation.\n" ); + fprintf( stdout, " -c,--check-requires Check package requires before install.\n" ); +#if defined( HAVE_GPG2 ) + fprintf( stdout, " -g,--gpg-verify Verify GPG2 signature. The signature must be\n" ); + fprintf( stdout, " saved in a file whose name is the same as the\n" ); + fprintf( stdout, " package file name, but with the extension '.asc'\n" ); + fprintf( stdout, " and located in the same directory as the package.\n" ); +#endif + fprintf( stdout, " --ignore-chrefs-errors Ignore change references errors (code: 48).\n" ); +#if defined( HAVE_DIALOG ) + fprintf( stdout, " -i,--info-dialog Show package description during install\n" ); + fprintf( stdout, " process using ncurses dialog.\n" ); + fprintf( stdout, " -m,--menu-dialog Ask for confirmation the inatallation,\n" ); + fprintf( stdout, " unless the priority is REQUIRED.\n" ); +#endif + fprintf( stdout, " -l,--pkglist=<FILENAME> Specify a different package list file\n" ); + fprintf( stdout, " to use for read package priority and type\n" ); + fprintf( stdout, " of install procedure. By default used the\n" ); + fprintf( stdout, " '.pkglist' file found in the directory\n" ); + fprintf( stdout, " where source package is placed.\n" ); + fprintf( stdout, " -p,--priority=<required|recommended|optional|skip>\n" ); + fprintf( stdout, " Provides a priority of package instead of\n" ); + fprintf( stdout, " the priority defined in the .pkglist file.\n" ); + fprintf( stdout, " -r,--root=<DIR> Target rootfs path.\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Parameter:\n" ); + fprintf( stdout, " <package> The PACKAGE tarball.\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Return codes:\n" ); + fprintf( stdout, " ------+-------------------------------------------------------\n" ); + fprintf( stdout, " code | status\n" ); + fprintf( stdout, " ------+-------------------------------------------------------\n" ); + fprintf( stdout, " 31 | package is already installed\n" ); + fprintf( stdout, " 32 | package is already installed but not correct\n" ); + fprintf( stdout, " 33 | previous version is already installed\n" ); + fprintf( stdout, " 34 | previous version is already installed but not correct\n" ); + fprintf( stdout, " 35 | a newer version is already installed\n" ); + fprintf( stdout, " 36 | a newer version is already installed but not correct\n" ); + fprintf( stdout, " ----+----\n" ); + fprintf( stdout, " 41 | installation is aborted due to priority=SKIP\n" ); + fprintf( stdout, " 42 | .pkglist appointed the 'update' procedure instead\n" ); + fprintf( stdout, " 43 | pre-install script returned error status\n" ); + fprintf( stdout, " 44 | uncompress process returned error status\n" ); + fprintf( stdout, " 45 | restore-links script returned error status\n" ); + fprintf( stdout, " 46 | post-install script returned error status\n" ); + fprintf( stdout, " 47 | PKGLOG cannot be stored in the Setup Database\n" ); + fprintf( stdout, " 48 | references cannot be updated in Setup Database\n" ); +#if defined( HAVE_GPG2 ) + fprintf( stdout, " ----+----\n" ); + fprintf( stdout, " 51 | signature verification returned error status\n" ); +#endif + fprintf( stdout, " ------+-------------------------------------------------------\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Upon successful completion zero is returned. Other non-zero return\n" ); + fprintf( stdout, "codes imply incorrect completion of the installation.\n" ); + fprintf( stdout, "\n" ); + + exit( EXIT_FAILURE ); +} + +void to_lowercase( char *s ) +{ + char *p = s; + while( p && *p ) { int c = *p; *p = tolower( c ); ++p; } +} + +void to_uppercase( char *s ) +{ + char *p = s; + while( p && *p ) { int c = *p; *p = toupper( c ); ++p; } +} + +void version() +{ + char *upper = NULL; + + upper = (char *)alloca( strlen( program ) + 1 ); + + strcpy( (char *)upper, (const char *)program ); + to_uppercase( upper ); + + fprintf( stdout, "%s (%s) %s\n", program, upper, PROGRAM_VERSION ); + + fprintf( stdout, "Copyright (C) 2019 Andrey V.Kosteltsev.\n" ); + fprintf( stdout, "This is free software. There is NO warranty; not even\n" ); + fprintf( stdout, "for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" ); + fprintf( stdout, "\n" ); + + free_resources(); + exit( EXIT_SUCCESS ); +} + + +static void remove_trailing_slash( char *dir ) +{ + char *s; + + if( !dir || dir[0] == '\0' ) return; + + s = dir + strlen( dir ) - 1; + while( *s == '/' ) + { + *s = '\0'; --s; + } +} + + +static void bind_asc_extention( char *name ) +{ + char *p = NULL, *q = NULL; + + if( (p = rindex( name, '.' )) && (strlen(p) < 5) ) + { + if( !strncmp( p, ".gz", 3 ) || + !strncmp( p, ".bz2", 4 ) || + !strncmp( p, ".xz", 3 ) ) + { + *p = '\0'; + q = rindex( name, '.' ); + if( q && (strlen(q) < 5) && !strncmp( q, ".tar", 4 ) ) + { + *q = '\0'; + } + } + else if( !strncmp( p, ".tar", 4 ) || + !strncmp( p, ".tbz", 4 ) || + !strncmp( p, ".tgz", 4 ) || + !strncmp( p, ".txz", 4 ) ) + { + *p = '\0'; + } + } + + (void)strcat( name, ".asc" ); +} + +//////////////////////////////////////////////////// +//static char *strmode( enum _install_mode mode ) +//{ +// char *p = NULL; +// +// switch( mode ) +// { +// case CONSOLE: +// p = "CONSOLE"; +// break; +// case INFODIALOG: +// p = "INFODIALOG"; +// break; +// case MENUDIALOG: +// p = "MENUDIALOG"; +// break; +// } +// return p; +//} +//////////////////////////////////////////////////// + +static char *strprio( enum _priority priority, int short_name ) +{ + char *p = NULL; + + switch( priority ) + { + case REQUIRED: + p = ( short_name ) ? "REQ" : "required"; + break; + case RECOMMENDED: + p = ( short_name ) ? "REC" : "recommended"; + break; + case OPTIONAL: + p = ( short_name ) ? "OPT" : "optional"; + break; + case SKIP: + p = ( short_name ) ? "SKP" : "skip"; + break; + } + return p; +} + +static char *strproc( enum _procedure procedure ) +{ + char *p = NULL; + + switch( procedure ) + { + case INSTALL: + p = "install"; + break; + case UPDATE: + p = "update"; + break; + } + return p; +} + + +static int _mkdir_p( const char *dir, const mode_t mode ) +{ + char *buf; + char *p = NULL; + struct stat sb; + + if( !dir ) return -1; + + buf = (char *)alloca( strlen( dir ) + 1 ); + strcpy( buf, dir ); + + remove_trailing_slash( buf ); + + /* check if path exists and is a directory */ + if( stat( buf, &sb ) == 0 ) + { + if( S_ISDIR(sb.st_mode) ) + { + return 0; + } + } + + /* mkdir -p */ + for( p = buf + 1; *p; ++p ) + { + if( *p == '/' ) + { + *p = 0; + /* test path */ + if( stat( buf, &sb ) != 0 ) + { + /* path does not exist - create directory */ + if( mkdir( buf, mode ) < 0 ) + { + return -1; + } + } else if( !S_ISDIR(sb.st_mode) ) + { + /* not a directory */ + return -1; + } + *p = '/'; + } + } + + /* test path */ + if( stat( buf, &sb ) != 0 ) + { + /* path does not exist - create directory */ + if( mkdir( buf, mode ) < 0 ) + { + return -1; + } + } else if( !S_ISDIR(sb.st_mode) ) + { + /* not a directory */ + return -1; + } + + return 0; +} + +static void _rm_tmpdir( const char *dirpath ) +{ + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + if( stat( dirpath, &path_sb ) == -1 ) + { + return; /* stat returns error code; errno is set */ + } + + if( S_ISDIR(path_sb.st_mode) == 0 ) + { + return; /* dirpath is not a directory */ + } + + if( (dir = opendir(dirpath) ) == NULL ) + { + return; /* Cannot open direcroty; errno is set */ + } + + len = strlen( dirpath ); + + while( (entry = readdir( dir )) != NULL) + { + + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( stat( path, &entry_sb ) == 0 ) + { + if( S_ISDIR(entry_sb.st_mode) ) + { + /* recursively remove a nested directory */ + _rm_tmpdir( path ); + } + else + { + /* remove a file object */ + (void)unlink( path ); + } + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + + } + + /* remove the devastated directory and close the object of this directory */ + (void)rmdir( dirpath ); + + closedir( dir ); +} + +static char *_mk_tmpdir( void ) +{ + char *buf = NULL, *p, *tmp = "/tmp"; + size_t len = 0, size = 0; + + (void)umask( S_IWGRP | S_IWOTH ); /* octal 022 */ + + /* Get preferred directory for tmp files */ + if( (p = getenv( "TMP" )) != NULL ) { + tmp = p; + } + else if( (p = getenv( "TEMP" )) != NULL ) { + tmp = p; + } + + size = strlen( tmp ) + strlen( DISTRO_NAME ) + strlen( program ) + 12; + + buf = (char *)malloc( size ); + if( !buf ) return NULL; + + len = snprintf( buf, size, (const char *)"%s/%s/%s-%.7u", tmp, DISTRO_NAME, program, getpid() ); + if( len == 0 || len == size - 1 ) + { + free( buf ); return NULL; + } + + _rm_tmpdir( (const char *)&buf[0] ); + + if( _mkdir_p( buf, S_IRWXU | S_IRWXG | S_IRWXO ) == 0 ) + { + return buf; + } + + free( buf ); return NULL; +} + + +void fatal_error_actions( void ) +{ + logmsg( errlog, MSG_NOTICE, "Free resources on FATAL error..." ); + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); +} + +void sigint( int signum ) +{ + (void)signum; + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); +} + +static void set_signal_handlers() +{ + struct sigaction sa; + sigset_t set; + + memset( &sa, 0, sizeof( sa ) ); + sa.sa_handler = sigint; /* TERM, INT */ + sa.sa_flags = SA_RESTART; + sigemptyset( &set ); + sigaddset( &set, SIGTERM ); + sigaddset( &set, SIGINT ); + sa.sa_mask = set; + sigaction( SIGTERM, &sa, NULL ); + sigaction( SIGINT, &sa, NULL ); + + memset( &sa, 0, sizeof( sa ) ); /* ignore SIGPIPE */ + sa.sa_handler = SIG_IGN; + sa.sa_flags = 0; + sigaction( SIGPIPE, &sa, NULL ); + + /* System V fork+wait does not work if SIGCHLD is ignored */ + signal( SIGCHLD, SIG_DFL ); +} + + +static enum _input_type check_input_file( char *uncompress, const char *fname ) +{ + struct stat st; + size_t pkglog_size = 0; + unsigned char buf[8]; + int rc, fd; + + /* SIGNATURES: https://www.garykessler.net/library/file_sigs.html */ + + if( uncompress ) + { + *uncompress = '\0'; + } + + if( stat( fname, &st ) == -1 ) + { + FATAL_ERROR( "Cannot access %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + + pkglog_size = st.st_size; + + if( (fd = open( fname, O_RDONLY )) == -1 ) + { + FATAL_ERROR( "Cannot open %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + + rc = (int)read( fd, (void *)&buf[0], 7 ); + if( rc != 7 ) + { + close( fd ); return IFMT_UNKNOWN; + } + buf[7] = '\0'; + + /* TEXT */ + if( !strncmp( (const char *)&buf[0], "PACKAGE", 7 ) ) + { + close( fd ); return IFMT_LOG; + } + + /* GZ */ + if( buf[0] == 0x1F && buf[1] == 0x8B && buf[2] == 0x08 ) + { + if( uncompress ) { *uncompress = 'x'; } + close( fd ); return IFMT_PKG; + } + + /* BZ2 */ + if( buf[0] == 0x42 && buf[1] == 0x5A && buf[2] == 0x68 ) + { + if( uncompress ) { *uncompress = 'j'; } + close( fd ); return IFMT_PKG; + } + + /* XZ */ + if( buf[0] == 0xFD && buf[1] == 0x37 && buf[2] == 0x7A && + buf[3] == 0x58 && buf[4] == 0x5A && buf[5] == 0x00 ) + { + if( uncompress ) { *uncompress = 'J'; } + close( fd ); return IFMT_PKG; + } + + if( pkglog_size > 262 ) + { + if( lseek( fd, 257, SEEK_SET ) == -1 ) + { + FATAL_ERROR( "Cannot check signature of %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + rc = (int)read( fd, &buf[0], 5 ); + if( rc != 5 ) + { + FATAL_ERROR( "Cannot read signature of %s file", basename( (char *)fname ) ); + } + /* TAR */ + if( buf[0] == 0x75 && buf[1] == 0x73 && buf[2] == 0x74 && buf[3] == 0x61 && buf[4] == 0x72 ) + { + close( fd ); return IFMT_PKG; + } + } + + close( fd ); return IFMT_UNKNOWN; +} + + +void get_args( int argc, char *argv[] ) +{ +#if defined( HAVE_GPG2 ) +#if defined( HAVE_DIALOG ) + const char* short_options = "hvacgiml:p:r:"; +#else + const char* short_options = "hvacgl:p:r:"; +#endif +#else +#if defined( HAVE_DIALOG ) + const char* short_options = "hvaciml:p:r:"; +#else + const char* short_options = "hvacl:p:r:"; +#endif +#endif + +#define IGNORE_CHREFS_ERRORS 872 + + const struct option long_options[] = + { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'v' }, + { "always-ask", no_argument, NULL, 'a' }, + { "check-requires", no_argument, NULL, 'c' }, +#if defined( HAVE_GPG2 ) + { "gpg-verify", no_argument, NULL, 'g' }, +#endif + { "ignore-chrefs-errors", no_argument, NULL, IGNORE_CHREFS_ERRORS }, +#if defined( HAVE_DIALOG ) + { "info-dialog", no_argument, NULL, 'i' }, + { "menu-dialog", no_argument, NULL, 'm' }, +#endif + { "pkglist", required_argument, NULL, 'l' }, + { "priority", required_argument, NULL, 'p' }, + { "root", required_argument, NULL, 'r' }, + { NULL, 0, NULL, 0 } + }; + + int ret; + int option_index = 0; + + while( (ret = getopt_long( argc, argv, short_options, long_options, &option_index )) != -1 ) + { + switch( ret ) + { + case 'h': + { + usage(); + break; + } + case 'v': + { + version(); + break; + } + case 'a': + { + ask = 1; + break; + } + case 'c': + { + rqck = 1; + break; + } +#if defined( HAVE_GPG2 ) + case 'g': + { + gpgck = 1; + break; + } +#endif + +#if defined( HAVE_DIALOG ) + case 'i': + { + install_mode = INFODIALOG; + break; + } + case 'm': + { + install_mode = MENUDIALOG; + break; + } +#endif + + case IGNORE_CHREFS_ERRORS: + { + ignore_chrefs_errors = 1; + break; + } + + case 'p': + { + if( optarg != NULL ) + { + char *match = NULL; + + if( strlen( (const char *)optarg ) > 2 ) + { + to_lowercase( optarg ); + if( (match = strstr( optarg, "req" )) && match == optarg ) { + priority = REQUIRED; + } + else if( (match = strstr( optarg, "rec" )) && match == optarg ) { + priority = RECOMMENDED; + } + else if( (match = strstr( optarg, "opt" )) && match == optarg ) { + priority = OPTIONAL; + } + else if( (match = strstr( optarg, "sk" )) && match == optarg ) { + priority = SKIP; + } + else { + FATAL_ERROR( "Unknown --priority '%s' value", optarg ); + } + } + else + { + FATAL_ERROR( "Unknown --priority '%s' value", optarg ); + } + } + else + /* option is present but without value */ + usage(); + break; + } + + case 'l': + { + if( optarg != NULL ) + { + pkglist_fname = xstrdup( (const char *)optarg ); + } + else + /* option is present but without value */ + usage(); + break; + } + + case 'r': + { + if( optarg != NULL ) + { + char cwd[PATH_MAX]; + + bzero( (void *)cwd, PATH_MAX ); + if( optarg[0] != '/' && curdir ) + { + /* skip current directory definition './' at start of argument: */ + if( !strncmp( optarg, "./", 2 ) && strncmp( optarg, "..", 2 ) ) + (void)sprintf( cwd, "%s/%s", curdir, optarg + 2 ); + else if( (strlen( optarg ) == 1) && !strncmp( optarg, ".", 1 ) ) + (void)sprintf( cwd, "%s", curdir ); + else + (void)sprintf( cwd, "%s/%s", curdir, optarg ); + root = xstrdup( (const char *)cwd ); + } + else + { + root = xstrdup( (const char *)optarg ); + } + remove_trailing_slash( root ); + } + else + /* option is present but without value */ + usage(); + break; + } + + case '?': default: + { + usage(); + break; + } + } + } + + + if( optind < argc ) + { + struct stat st; + char *buf = NULL; + + bzero( (void *)&st, sizeof( struct stat ) ); + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + /* absolute path to input package: */ + if( argv[optind][0] != '/' && curdir ) + (void)sprintf( buf, "%s/%s", curdir, (const char *)argv[optind] ); + else + (void)strcpy( buf, (const char *)argv[optind] ); + + if( stat( (const char *)&buf[0], &st ) == -1 ) + { + FATAL_ERROR( "Cannot access '%s' file: %s", buf, strerror( errno ) ); + } + + if( S_ISREG(st.st_mode) ) + { + pkg_fname = xstrdup( (const char *)&buf[0] ); + bind_asc_extention( buf ); + asc_fname = xstrdup( (const char *)&buf[0] ); + free( buf ); + } + else + { + FATAL_ERROR( "Input package '%s' is not a regular file", (const char *)argv[optind] ); + } + } + else + { + usage(); + } + + + if( !pkgs_path ) + { + struct stat st; + char *buf = NULL; + int len; + + bzero( (void *)&st, sizeof( struct stat ) ); + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + if( !root ) + { + buf[0] = '/'; buf[1] = '\0'; + root = xstrdup( (const char *)buf ); + } + else + { + len = strlen( root ); + + (void)strcpy( buf, (const char *)root ); + if( buf[ len - 1 ] != '/' ) + { + buf[len] = '/'; buf[len+1] = '\0'; + free( root ); root = xstrdup( (const char *)buf ); + } + } + + if( stat( (const char *)&buf[0], &st ) == -1 ) + { + FATAL_ERROR( "Cannot access '%s' file or directory: %s", buf, strerror( errno ) ); + } + if( !S_ISDIR(st.st_mode) ) + { + FATAL_ERROR( "Defined --root '%s' is not a directory", buf ); + } + + len = strlen( (const char *)buf ); + + (void)strcat( buf, PACKAGES_PATH ); + if( _mkdir_p( buf, S_IRWXU | S_IRWXG | S_IRWXO ) != 0 ) + { + FATAL_ERROR( "Cannot access '/%s' directory", PACKAGES_PATH ); + } + pkgs_path = xstrdup( (const char *)&buf[0] ); + + /********************************************* + Create other directories of Setup Database: + */ + buf[len] = '\0'; + (void)strcat( buf, REMOVED_PKGS_PATH ); + if( _mkdir_p( buf, S_IRWXU | S_IRWXG | S_IRWXO ) != 0 ) + { + FATAL_ERROR( "Cannot access '/%s' directory", REMOVED_PKGS_PATH ); + } + rempkgs_path = xstrdup( (const char *)&buf[0] ); + + buf[len] = '\0'; + (void)strcat( buf, SETUP_PATH ); + if( _mkdir_p( buf, S_IRWXU | S_IRWXG | S_IRWXO ) != 0 ) + { + FATAL_ERROR( "Cannot access '/%s' directory", SETUP_PATH ); + } + + /********************************************* + Allocate memory for Setup LOG File name: + */ + buf[len] = '\0'; + (void)strcat( buf, LOG_PATH ); + (void)strcat( buf, SETUP_LOG_FILE ); + log_fname = xstrdup( (const char *)&buf[0] ); + + free( buf ); + + } /* End if( !pkgs_path ) */ +} + +static void setup_log( char *format, ... ) +{ + FILE *fp = NULL; + + time_t t = time( NULL ); + struct tm tm = *localtime(&t); + + va_list argp; + + if( ! format ) return; + + fp = fopen( (const char *)log_fname, "a" ); + if( !fp ) + { + FATAL_ERROR( "Cannot open /%s%s file", LOG_PATH, SETUP_LOG_FILE ); + } + + fprintf( fp, "[%04d-%02d-%02d %02d:%02d:%02d]: ", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec ); + + va_start( argp, format ); + vfprintf( fp, format, argp ); + fprintf( fp, "\n" ); + + fflush( fp ); + fclose( fp ); +} + +/*********************************************************** + Remove leading spaces and take non-space characters only: + (Especialy for pkginfo lines) + */ +static char *skip_spaces( char *s ) +{ + char *q, *p = (char *)0; + + if( !s || *s == '\0' ) return p; + + p = s; + + while( (*p == ' ' || *p == '\t') && *p != '\0' ) { ++p; } q = p; + while( *q != ' ' && *q != '\t' && *q != '\0' ) { ++q; } *q = '\0'; + + if( *p == '\0' ) return (char *)0; + + return( xstrdup( (const char *)p ) ); +} + + +/******************************* + remove spaces at end of line: + */ +static void skip_eol_spaces( char *s ) +{ + char *p = (char *)0; + + if( !s || *s == '\0' ) return; + + p = s + strlen( s ) - 1; + while( isspace( *p ) ) { *p-- = '\0'; } +} + + +static char *trim( char *s ) +{ + char *p = (char *)0; + + if( !s || *s == '\0' ) return p; + + p = s + strlen( s ) - 1; + while( isspace( *p ) ) { *p-- = '\0'; } + p = s; while( isspace( *p ) ) { ++p; } + + return( p ); +} + + +static char *size_to_string( size_t pkgsize ) +{ + int nd; + double sz = (double)pkgsize / (double)1024; + + char *ret = NULL; + char *tmp = NULL; + + tmp = (char *)malloc( PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + if( sz > (double)1048576 ) + { + sz = sz / (double)1048576; + /* + NOTE: + ---- + Операция округления до одного знака после десятичной точки: sz = round(sz*10.0)/10.0; + здесь не нужна; можно обойтись вычислением количества цифр, выводимых на печать с помощью + формата '%.*g': + + Количество десятичных цифр, необходимое для предстваления целой части числа + 1(одна) + десятичная цифра после десятичной точки. Формат %.*g не будет выводить дробную часть + числа, если после округления, до одного знака после десятичной точки, дробная часть + равна нулю: + */ + nd = (int)ceil(log10(floor(sz) + 1.0)) + 1; + (void)sprintf( (char *)&tmp[0], "%.*gG", nd, sz ); + } + else if( sz > (double)1024 ) + { + sz = sz / (double)1024; + nd = (int)ceil(log10(floor(sz) + 1.0)) + 1; + (void)sprintf( (char *)&tmp[0], "%.*gM", nd, sz ); + } + else + { + nd = (int)ceil(log10(floor(sz) + 1.0)) + 1; + (void)sprintf( (char *)&tmp[0], "%.*gK", nd, sz ); + } + + ret = xstrdup( (const char *)&tmp[0] ); + free( tmp ); + + return ret; +} + +static void read_input_pkginfo( const char *pkginfo_fname ) +{ + char *ln = NULL; + char *line = NULL; + + FILE *pkginfo = NULL; + + if( pkginfo_fname != NULL ) + { + pkginfo = fopen( (const char *)pkginfo_fname, "r" ); + if( !pkginfo ) + { + FATAL_ERROR( "Cannot open %s file", pkginfo_fname ); + } + } + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + while( (ln = fgets( line, PATH_MAX, pkginfo )) ) + { + char *match = NULL; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( (match = strstr( ln, "pkgname" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) pkgname = skip_spaces( p ); + } + if( (match = strstr( ln, "pkgver" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) pkgver = skip_spaces( p ); + } + if( (match = strstr( ln, "arch" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) arch = skip_spaces( p ); + } + if( (match = strstr( ln, "distroname" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) distroname = skip_spaces( p ); + } + if( (match = strstr( ln, "distrover" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) distrover = skip_spaces( p ); + } + + if( (match = strstr( ln, "group" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) group = skip_spaces( p ); + } + + if( (match = strstr( ln, "short_description" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) + { + char *b = index( p, '"'), + *e = rindex( p, '"'); + if( b && e && ( b != e ) ) + { + p = ++b; *e = '\0'; + short_description = xstrdup( (const char *)p ); + } + } + } + if( (match = strstr( ln, "url" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) url = skip_spaces( p ); + } + if( (match = strstr( ln, "license" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) license = skip_spaces( p ); + } + + if( (match = strstr( ln, "uncompressed_size" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) uncompressed_size = skip_spaces( p ); + } + if( (match = strstr( ln, "total_files" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) total_files = skip_spaces( p ); + } + } + + free( line ); + + if( !pkgname || !pkgver || !arch || !distroname || !distrover ) + { + FATAL_ERROR( "Invalid input .PKGINFO file" ); + } + + fclose( pkginfo ); +} + + +static void read_service_files( void ) +{ + struct stat st; + char *fname = pkg_fname; + + enum _input_type type = IFMT_UNKNOWN; + + bzero( (void *)&st, sizeof( struct stat ) ); + + if( stat( (const char *)fname, &st ) == -1 ) + { + FATAL_ERROR( "Cannot access input '%s' file: %s", fname, strerror( errno ) ); + } + + type = check_input_file( &uncompress, fname ); + if( type != IFMT_PKG ) + { + FATAL_ERROR( "Unknown format of input '%s' file", fname ); + } + + if( S_ISREG(st.st_mode) ) + { + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *tmp= NULL, *cmd = NULL; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + (void)sprintf( &tmp[0], "%s", tmpdir ); + if( _mkdir_p( tmp, S_IRWXU | S_IRWXG | S_IRWXO ) != 0 ) + { + FATAL_ERROR( "Cannot get PKGINFO from '%s' file", basename( (char *)fname ) ); + } + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, + "%s/pkginfo -d %s" + " -o pkginfo,description,requires,restore-links,install-script,filelist" + " %s > /dev/null 2>&1", + selfdir, tmp, fname ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot get PKGINFO from %s file", basename( (char *)fname ) ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + if( rc != 0 ) + { + FATAL_ERROR( "Cannot get PKGINFO from '%s' file", basename( (char *)fname ) ); + } + + (void)strcat( tmp, "/.PKGINFO" ); + read_input_pkginfo( (const char *)&tmp[0] ); + *(strstr( tmp, "/.PKGINFO" )) = '\0'; /* :restore tmpdir in tmp[] buffer */ + + compressed_size = size_to_string( st.st_size ); + + /****************** + Get PKGLOG file: + */ + len = snprintf( &cmd[0], PATH_MAX, + "%s/pkglog -m -d %s %s > /dev/null 2>&1", + selfdir, tmp, tmp ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot get PKGLOG from %s file", basename( (char *)fname ) ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + if( rc != 0 ) + { + FATAL_ERROR( "Cannot get PKGLOG from '%s' file", basename( (char *)fname ) ); + } + + if( group ) + (void)sprintf( cmd, "%s/%s/%s-%s-%s-%s-%s", tmp, group, pkgname, pkgver, arch, distroname, distrover ); + else + (void)sprintf( cmd, "%s/%s-%s-%s-%s-%s", tmp, pkgname, pkgver, arch, distroname, distrover ); + + bzero( (void *)&st, sizeof( struct stat ) ); + if( stat( (const char *)cmd, &st ) == -1 ) + { + FATAL_ERROR( "Cannot get PKGLOG from '%s' file: %s", basename( (char *)fname ), strerror( errno ) ); + } + + pkglog_fname = xstrdup( (const char *)cmd ); + + /************************************* + Attempt to read packages list file: + */ + { + if( !pkglist_fname ) + { + /***************************************** + Get source packages path if applicable: + */ + (void)strcpy( cmd, (const char *)fname ); + (void)strcpy( tmp, dirname( cmd ) ); + + if( group && !strcmp( group, basename( tmp ) ) ) + (void)strcpy( cmd, (const char *)dirname( tmp ) ); + else + (void)strcpy( cmd, (const char *)tmp ); + + /***************************************** + Save default packages list file name: + */ + (void)strcat( cmd, "/.pkglist" ); + pkglist_fname = xstrdup( (const char *)cmd ); + } + + /************************** + read .pkglist if exists: + */ + bzero( (void *)&st, sizeof( struct stat ) ); + if( (stat( (const char *)pkglist_fname, &st ) == 0) && S_ISREG(st.st_mode) ) + { + char *ln = NULL; + char *line = NULL; + + FILE *pkglist = NULL; + + pkglist = fopen( (const char *)pkglist_fname, "r" ); + if( !pkglist ) + { + FATAL_ERROR( "Cannot open %s file", pkglist_fname ); + } + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + while( (ln = fgets( line, PATH_MAX, pkglist )) ) + { + char *match = NULL; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( (match = strstr( ln, pkgname )) && match == ln ) + { + char *p = NULL; + char *name = NULL, *vers = NULL, *desc = NULL, *ball = NULL, *proc = NULL, *prio = NULL; + + name = ln; + if( (p = index( (const char *)name, ':' )) ) { *p = '\0'; vers = ++p; name = trim( name ); } else continue; + if( (p = index( (const char *)vers, ':' )) ) { *p = '\0'; desc = ++p; vers = trim( vers ); } else continue; + if( (p = index( (const char *)desc, ':' )) ) { *p = '\0'; ball = ++p; desc = trim( desc ); } else continue; + if( (p = index( (const char *)ball, ':' )) ) { *p = '\0'; proc = ++p; ball = trim( ball ); } else continue; + if( (p = index( (const char *)proc, ':' )) ) { *p = '\0'; prio = ++p; proc = trim( proc ); } else continue; + prio = trim( prio ); + + if( name && vers && desc && ball && proc && prio ) + { + char *grp = index( (const char *)ball, '/' ); + if( grp ) + { + *grp = '\0'; grp = ball; grp = trim( grp ); + if( strcmp( group, grp ) ) continue; + } + + /* read priority: */ + if( strlen( (const char*)prio ) > 2 ) + { + char *m = NULL; + + to_lowercase( prio ); + if( (m = strstr( prio, "req" )) && m == prio ) { + priority = REQUIRED; + } + else if( (m = strstr( prio, "rec" )) && m == prio ) { + priority = RECOMMENDED; + } + else if( (m = strstr( prio, "opt" )) && m == prio ) { + priority = OPTIONAL; + } + else if( (m = strstr( prio, "sk" )) && m == prio ) { + priority = SKIP; + } + else { + priority = REQUIRED; + } + } + else + { + priority = REQUIRED; + } + + /* read procedure: */ + if( strlen( (const char*)proc ) > 5 ) + { + char *m = NULL; + + to_lowercase( proc ); + if( (m = strstr( proc, "install" )) && m == proc ) { + procedure = INSTALL; + } + else if( (m = strstr( proc, "update" )) && m == proc ) { + procedure = UPDATE; + } + else { + procedure = INSTALL; + } + } + else + { + procedure = INSTALL; + } + } + + } /* End if( match ) */ + + } /* End of while( ln = fgets() ) */ + + free( line ); + fclose( pkglist ); + + } /* End of reading .pkglist */ + + } /* End of attemption of reading .pkflist file */ + + free( cmd ); + free( tmp ); + + if( priority == SKIP ) + { + exit_status = 41; + + if( install_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + char *tmp = NULL; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + (void)sprintf( &tmp[0], + "\nInstall procedure is skipped due to specified\nthe '%s' priority.\n", + strprio( priority, 0 ) ); + + info_pkg_box( "Install:", pkgname, pkgver, strprio( priority, 0 ), + (const char *)&tmp[0], 6, 0, 0 ); + + free( tmp ); +#else + fprintf( stdout, + "\nInstall procedure of package '%s-%s' is skipped due to specified '%s' priority.\n\n", + pkgname, pkgver, strprio( priority, 0 ) ); +#endif + } + else + { + fprintf( stdout, + "\nInstall procedure of package '%s-%s' is skipped due to specified '%s' priority.\n\n", + pkgname, pkgver, strprio( priority, 0 ) ); + } + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + + exit( exit_status ); + } + + if( procedure != INSTALL ) + { + exit_status = 42; + + if( install_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + char *tmp = NULL; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + (void)sprintf( &tmp[0], + "\nInstall procedure is skipped because the '%s' procedure\nis specified.\n", + strproc( procedure ) ); + + info_pkg_box( "Install:", pkgname, pkgver, strprio( priority, 0 ), + (const char *)&tmp[0], 6, 0, 0 ); + + free( tmp ); +#else + fprintf( stdout, + "\nInstall procedure of package '%s-%s' is skipped because the '%s' procedure is specified.\n\n", + pkgname, pkgver, strproc( procedure ) ); +#endif + } + else + { + fprintf( stdout, + "\nInstall procedure of package '%s-%s' is skipped because the '%s' procedure is specified.\n\n", + pkgname, pkgver, strproc( procedure ) ); + } + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + + exit( exit_status ); + } + + } + else + { + FATAL_ERROR( "Input %s file is not a regular file", basename( (char *)fname ) ); + } +} + +static void check_package( void ) +{ + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *cmd = NULL; + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, + "%s/check-package --quiet --root=%s %s > /dev/null 2>&1", + selfdir, root, pkglog_fname ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot check whether the package '%s-%s' is already installed", pkgname, pkgver ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + switch( rc ) + { + case 30: + /* Package is not installed. Continue the installation. */ + break; + case 31: + if( install_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Install:", pkgname, pkgver, strprio( priority, 0 ), + "\nPackage is already installed.\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nPackage '%s-%s' is already installed.\n\n", pkgname, pkgver ); +#endif + } + else + { + fprintf( stdout, "\nPackage '%s-%s' is already installed.\n\n", pkgname, pkgver ); + } + break; + case 32: + if( install_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Install:", pkgname, pkgver, strprio( priority, 0 ), + "\nPackage is already installed but not correct.\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nPackage '%s-%s' is already installed but not correct.\n\n", pkgname, pkgver ); +#endif + } + else + { + fprintf( stdout, "\nPackage '%s-%s' is already installed but not correct.\n\n", pkgname, pkgver ); + } + break; + case 33: + if( install_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Install:", pkgname, pkgver, strprio( priority, 0 ), + "\nPrevious version of package is installed.\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nPrevious version of package '%s-%s' is installed.\n\n", pkgname, pkgver ); +#endif + } + else + { + fprintf( stdout, "\nPrevious version of package '%s-%s' is installed.\n\n", pkgname, pkgver ); + } + break; + case 34: + if( install_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Install:", pkgname, pkgver, strprio( priority, 0 ), + "\nPrevious version of package is installed but not correct.\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nPrevious version of package '%s-%s' is installed but not correct.\n\n", pkgname, pkgver ); +#endif + } + else + { + fprintf( stdout, "\nPrevious version of package '%s-%s' is installed but not correct.\n\n", pkgname, pkgver ); + } + break; + case 35: + if( install_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Install:", pkgname, pkgver, strprio( priority, 0 ), + "\nA newer version of package is already installed.\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nA newer version of package '%s-%s' is already installed.\n\n", pkgname, pkgver ); +#endif + } + else + { + fprintf( stdout, "\nA newer version of package '%s-%s' is already installed.\n\n", pkgname, pkgver ); + } + break; + case 36: + if( install_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Install:", pkgname, pkgver, strprio( priority, 0 ), + "\nA newer version of package is installed but not correct.\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nA newer version of package '%s-%s' is installed but not correct.\n\n", pkgname, pkgver ); +#endif + } + else + { + fprintf( stdout, "\nA newer version of package '%s-%s' is installed but not correct.\n\n", pkgname, pkgver ); + } + break; + default: + FATAL_ERROR( "Cannot check whether the package '%s-%s' is already installed", pkgname, pkgver ); + break; + } + + free( cmd ); + + if( rc != 30 ) + { + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + exit( rc ); + } +} + + +static void check_requires( void ) +{ + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *cmd = NULL; + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, + "%s/check-requires --root=%s %s > /dev/null 2>&1", + selfdir, root, pkglog_fname ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot check required packages for '%s-%s' package", pkgname, pkgver ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + + free( cmd ); + + if( rc != 0 ) + { + if( install_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Install:", pkgname, pkgver, strprio( priority, 0 ), + "\nPackage requires other packages to be installed.\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nPackage '%s-%s' requires other packages to be installed.\n\n", pkgname, pkgver ); +#endif + } + else + { + fprintf( stdout, "\nPackage '%s-%s' requires other packages to be installed.\n\n", pkgname, pkgver ); + } + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + exit( rc ); + } +} + +/******************************************************** + Read .FILELIST and .RESTORELINKS functions used for + roolback in case postinstall errors: + */ +static int __cmp_list_items( const void *a, const void *b ) +{ + if( a && b ) + return strcmp( (const char *)a, (const char *)b ); + else if( a ) + return 1; + else + return -1; +} + +static void __free_list( void *data, void *user_data ) +{ + if( data ) { free( data ); } +} + +static void free_list( struct dlist *list ) +{ + if( list ) { dlist_free( list, __free_list ); } +} + +//////////////////////////////////////////////////// +//static void __print_list( void *data, void *user_data ) +//{ +// int *counter = (int *)user_data; +// +// if( counter ) { fprintf( stdout, "item[%.5d]: %s\n", *counter, (char *)data ); ++(*counter); } +// else { fprintf( stdout, "item: %s\n", (char *)data ); } +//} +// +//static void print_list( struct dlist *list ) +//{ +// int cnt = 0; +// if( list ) { dlist_foreach( list, __print_list, (void *)&cnt ); } +//} +//////////////////////////////////////////////////// + +static void read_filelist( void ) +{ + struct stat st; + FILE *fp = NULL; + + char *ln = NULL; + char *line = NULL, *tmp = NULL; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + (void)sprintf( &tmp[0], "%s/.FILELIST", tmpdir ); + bzero( (void *)&st, sizeof( struct stat ) ); + if( (stat( (const char *)&tmp[0], &st ) == -1) ) + { + FATAL_ERROR( "Cannot get .FILELIST from '%s' file", basename( (char *)pkg_fname ) ); + } + + fp = fopen( (const char *)&tmp[0], "r" ); + if( !fp ) + { + FATAL_ERROR( "Cannot open .FILELIST file" ); + } + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)line, PATH_MAX ); + + while( (ln = fgets( line, PATH_MAX, fp )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( *(ln + strlen(ln) - 1) == '/' ) + { + *(ln + strlen(ln) - 1) = '\0'; + (void)sprintf( &tmp[0], "%s%s", (const char *)root, (const char *)ln ); + dirs = dlist_append( dirs, xstrdup( (const char *)&tmp[0] ) ); + } + else + { + (void)sprintf( &tmp[0], "%s%s", (const char *)root, (const char *)ln ); + files = dlist_append( files, xstrdup( (const char *)&tmp[0] ) ); + } + + } /* End of while( file list entry ) */ + + fclose( fp ); + + free( line ); + free( tmp ); +} + +static void read_restorelinks( void ) +{ + struct stat st; + FILE *fp = NULL; + + char *ln = NULL; + char *line = NULL, *tmp = NULL; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + (void)sprintf( &tmp[0], "%s/.RESTORELINKS", tmpdir ); + bzero( (void *)&st, sizeof( struct stat ) ); + if( (stat( (const char *)&tmp[0], &st ) == -1) || (st.st_size < 8) ) + { + free( tmp ); + return; + } + + fp = fopen( (const char *)&tmp[0], "r" ); + if( !fp ) + { + FATAL_ERROR( "Cannot open .RESTORELINKS file" ); + } + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)line, PATH_MAX ); + + while( (ln = fgets( line, PATH_MAX, fp )) ) + { + char *match = NULL; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( (match = strstr( ln, "; rm -rf " )) ) + { + char *q = NULL; + char *p = strstr( ln, "cd" ) + 2; + char *f = strstr( ln, "; rm -rf" ) + 8; + + if( !p || !f ) continue; + + while( (*p == ' ' || *p == '\t') && *p != '\0' ) ++p; + while( (*f == ' ' || *f == '\t') && *f != '\0' ) ++f; + + q = p; while( *q != ' ' && *q != '\t' && *q != ';' && *q != '\0' ) ++q; *q = '\0'; + q = f; while( *q != ' ' && *q != '\t' && *q != ';' && *q != '\0' ) ++q; *q = '\0'; + + if( p && f ) + { + (void)sprintf( &tmp[0], "%s%s/%s", (const char *)root, p, f ); + links = dlist_append( links, xstrdup( (const char *)&tmp[0] ) ); + } + } + } /* End of while( restore links entry ) */ + + fclose( fp ); + + free( line ); + free( tmp ); +} + +/* + End of read .FILELIST and .RESTORELINKS functions. + ********************************************************/ + +/******************************************************** + Rollback functions: + */ +static void __remove_link( void *data, void *user_data ) +{ + const char *fname = (const char *)data; + + if( fname ) + { + (void)unlink( fname ); + } +} + +static void __remove_file( void *data, void *user_data ) +{ + const char *fname = (const char *)data; + + if( fname ) + { + char *p = rindex( fname, '.' ); + /* + Если .new файл остался с тем же именем, это значит что до инсталляции + в системе существовал такой же файл но без расширения .new и при этом + он отличался от нового. В данном случае надо удалять только файл .new. + + Если же файл .new не существует, то надо удалять такой же файл но без + расширения .new . + */ + if( p && !strncmp( (const char *)p, ".new", 4 ) ) + { + struct stat st; + + bzero( (void *)&st, sizeof( struct stat ) ); + if( (stat( fname, &st ) == -1) ) *p = '\0'; + } + + (void)unlink( fname ); + } +} + +static int is_dir_empty( const char *dirpath ) +{ + int ret = 0; + + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + if( stat( dirpath, &path_sb ) == -1 ) return ret; /* stat returns error code; errno is set */ + if( S_ISDIR(path_sb.st_mode) == 0 ) return ret; /* dirpath is not a directory */ + if( (dir = opendir(dirpath) ) == NULL ) return ret; /* Cannot open direcroty; errno is set */ + + ret = 1; + + len = strlen( dirpath ); + while( (entry = readdir( dir )) != NULL) + { + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( stat( path, &entry_sb ) == 0 ) + { + ret = 0; + break; + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + } + closedir( dir ); + + return ret; +} + +static void __remove_dir( void *data, void *user_data ) +{ + const char *dname = (const char *)data; + + if( dname && is_dir_empty( (const char *)dname ) ) + { + (void)rmdir( dname ); + } +} + +static void rollback( void ) +{ + /* Try to change CWD to the ROOT directory: */ + (void)chdir( (const char *)root ); + + if( links ) { dlist_foreach( links, __remove_link, NULL ); } + + if( files ) { dlist_foreach( files, __remove_file, NULL ); } + + if( dirs ) + { + dirs = dlist_sort( dirs, __cmp_list_items ); + dirs = dlist_reverse( dirs ); + dlist_foreach( dirs, __remove_dir, NULL ); + } + + /* Try to remove PKGLOG file */ + { + char *tmp = NULL; + + tmp = (char *)malloc( PATH_MAX ); + if( tmp ) + { + const char *fname = basename( (char *)pkglog_fname ); + + bzero( (void *)tmp, PATH_MAX ); + + if( group ) + (void)sprintf( &tmp[0], "%s/%s/%s", pkgs_path, group, fname ); + else + (void)sprintf( &tmp[0], "%s/%s", pkgs_path, fname ); + + (void)unlink( (const char *)&tmp[0] ); + + if( group ) + { + const char *dir = (const char *)dirname( (char *)&tmp[0] ); + if( is_dir_empty( dir ) ) + { + (void)rmdir( dir ); + } + } + free( tmp ); + } + } + + /* Try to change CWD to the CURRENT directory: */ + (void)chdir( (const char *)curdir ); +} +/* + End of rollback functions. + ********************************************************/ + +static void read_description( void ) +{ + struct stat st; + FILE *fp = NULL; + + char *buf = NULL, *tmp = NULL; + char *lp = NULL; + int n = 0; + + char *ln = NULL; + char *line = NULL; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + (void)sprintf( &tmp[0], "%s/.DESCRIPTION", tmpdir ); + bzero( (void *)&st, sizeof( struct stat ) ); + if( (stat( (const char *)&tmp[0], &st ) == -1) || (st.st_size < 8) ) + { + free( tmp ); + return; + } + + fp = fopen( (const char *)&tmp[0], "r" ); + if( !fp ) + { + FATAL_ERROR( "Cannot open .DESCRIPTION file" ); + } + + (void)sprintf( (char *)&buf[0], "%s:", pkgname ); + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)line, PATH_MAX ); + + lp = (char *)&tmp[0]; + bzero( (void *)tmp, PATH_MAX ); + (void)sprintf( (char *)&tmp[0], "\n" ); + ++lp; + + while( (ln = fgets( line, PATH_MAX, fp )) && n < DESCRIPTION_NUMBER_OF_LINES ) + { + char *match = NULL; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + + if( (match = strstr( (const char *)ln, (const char *)buf )) && match == ln ) /* at start of line only */ + { + int mlen = strlen( match ), plen = strlen( buf ); + int length = ( mlen > plen ) ? (mlen - plen - 1) : 0 ; + + if( length > DESCRIPTION_LENGTH_OF_LINE ) + { + /* WARNING( "Package DESCRIPTION contains lines with length greater than %d characters", DESCRIPTION_LENGTH_OF_LINE ); */ + match[plen + 1 + DESCRIPTION_LENGTH_OF_LINE] = '\0'; /* truncating description line */ + skip_eol_spaces( match ); /* remove spaces at end-of-line */ + } + + match += plen + 1; + if( match[0] != '\0' ) { (void)sprintf( lp, " %s\n", match ); lp += strlen( match ) + 2; } + else { (void)sprintf( lp, "\n" ); ++lp; } + ++n; + } + } /* End of while( ln = fgets() ) */ + + fclose( fp ); + + (void)sprintf( lp, " Uncompressed Size: %s\n", uncompressed_size ); + lp += strlen( uncompressed_size ) + 21; + (void)sprintf( lp, " Compressed Size: %s\n", compressed_size ); + lp += strlen( compressed_size ) + 21; + + description = xstrdup( (const char *)&tmp[0] ); + + free( buf ); + free( line ); + free( tmp ); +} + +static int ask_for_install( void ) +{ + int ret = 0; /* continue installation */ +#if defined( HAVE_DIALOG ) + /****************************************************** + Ask for install dialog shown only in MENUDIALOG mode + when priority != REQUIRED or --always-ask=yes: + */ + if( (install_mode == MENUDIALOG) && (((priority == REQUIRED) && ask) || (priority != REQUIRED)) ) + { + ret = ask_install_box( "Install:", pkgname, pkgver, strprio( priority, 0 ), + description, 18, 0, 0 ); + } + + if( ret ) + { + info_pkg_box( "Install:", pkgname, pkgver, strprio( priority, 0 ), + "\nInstallation terminated by user.\n", 5, 0, 0 ); + } +#endif + return ret; +} + + +static void show_install_progress( void ) +{ + fprintf( stdout, "\033[2J" ); /* clear screen */ + + if( install_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Install:", pkgname, pkgver, strprio( priority, 0 ), + description, 16, 0, 0 ); +#else + fprintf( stdout, "\n Install: %s-%s [%s]...\n", pkgname, pkgver, strprio( priority, 0 )); + /************************************************* + Ruler: 68 characters + 2 spaces left and right: + + | ----handy-ruler----------------------------------------------------- | */ + fprintf( stdout, "|======================================================================|\n" ); + fprintf( stdout, "%s\n", description ); + fprintf( stdout, "|======================================================================|\n\n" ); + fprintf( stdout, "\n\n\n" ); /* 3 lines up for final message */ +#endif + } + else + { + fprintf( stdout, "\n Install: %s-%s [%s]...\n", pkgname, pkgver, strprio( priority, 0 )); + /************************************************* + Ruler: 68 characters + 2 spaces left and right: + + | ----handy-ruler----------------------------------------------------- | */ + fprintf( stdout, "|======================================================================|\n" ); + fprintf( stdout, "%s\n", description ); + fprintf( stdout, "|======================================================================|\n\n" ); + fprintf( stdout, "\n\n\n" ); /* 3 lines up for final message */ + } +} + + +static void pre_install_routine( void ) +{ + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *cmd = NULL; + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, + "cd %s && %s/.INSTALL pre_install %s > /dev/null 2>&1", + root, tmpdir, pkgver ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot run pre-install script for '%s-%s' package", pkgname, pkgver ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + + free( cmd ); + + if( rc != 0 ) + { + exit_status = 43; + + if( install_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Install:", pkgname, pkgver, strprio( priority, 0 ), + "\n\\Z1Pre-install script returned error status.\\Zn\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nPre-install script of '%s-%s' returned error status.\n\n", pkgname, pkgver ); +#endif + } + else + { + fprintf( stdout, "\nPre-install script of '%s-%s' returned error status.\n\n", pkgname, pkgver ); + } + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + exit( exit_status ); + } +} + +static const char *fill_decompressor( char *buffer, char compressor ) +{ + switch( compressor ) + { + case 'J': + (void)sprintf( buffer, "xz --threads=%d -dc", get_nprocs() ); + break; + case 'j': + (void)sprintf( buffer, "bzip2 -dc" ); + break; + case 'z': + (void)sprintf( buffer, "gzip -dc" ); + break; + default: + (void)sprintf( buffer, "cat -" ); + break; + } + return (const char *)buffer; +} + +static void uncompress_package( void ) +{ + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *cmd = NULL; + + char decompressor[64]; + + (void)fill_decompressor( (char *)&decompressor[0], uncompress ); + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, + "cat %s | %s | tar -C %s " + "--exclude='.DESCRIPTION' " + "--exclude='.FILELIST' " + "--exclude='.INSTALL' " + "--exclude='.PKGINFO' " + "--exclude='.REQUIRES' " + "--exclude='.RESTORELINKS' " + "-xf - > /dev/null 2>&1", + pkg_fname, decompressor, root ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot uncompress '%s-%s' package", pkgname, pkgver ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + + free( cmd ); + + if( rc != 0 ) + { + exit_status = 44; + + if( install_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Install:", pkgname, pkgver, strprio( priority, 0 ), + "\n\\Z1Cannot uncompress package.\\Zn\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nCannot uncompress '%s-%s' package.\n\n", pkgname, pkgver ); +#endif + } + else + { + fprintf( stdout, "\nCannot uncompress '%s-%s' package.\n\n", pkgname, pkgver ); + } + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + exit( exit_status ); + } +} + +static void restore_links( void ) +{ + struct stat st; + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *cmd = NULL; + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + (void)sprintf( &cmd[0], "%s/.RESTORELINKS", tmpdir ); + bzero( (void *)&st, sizeof( struct stat ) ); + if( (stat( (const char *)&cmd[0], &st ) == -1) || (st.st_size < 8) ) + { + free( cmd ); + return; + } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, + "cd %s && sh %s/.RESTORELINKS > /dev/null 2>&1", + root, tmpdir ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot restore links for '%s-%s' package", pkgname, pkgver ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + + free( cmd ); + + if( rc != 0 ) + { + rollback(); + + exit_status = 45; + + if( install_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Install:", pkgname, pkgver, strprio( priority, 0 ), + "\n\\Z1Restore-links script returned error status.\\Zn\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nRestore-links script of '%s-%s' returned error status.\n\n", pkgname, pkgver ); +#endif + } + else + { + fprintf( stdout, "\nRestore-links script of '%s-%s' returned error status.\n\n", pkgname, pkgver ); + } + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + exit( exit_status ); + } +} + +static void post_install_routine( void ) +{ + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *cmd = NULL; + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, + "cd %s && %s/.INSTALL post_install %s > /dev/null 2>&1", + root, tmpdir, pkgver ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot run post-install script for '%s-%s' package", pkgname, pkgver ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + + free( cmd ); + + if( rc != 0 ) + { + rollback(); + + exit_status = 46; + + if( install_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Install:", pkgname, pkgver, strprio( priority, 0 ), + "\n\\Z1Post-install script returned error status.\\Zn\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nPost-install script of '%s-%s' returned error status.\n\n", pkgname, pkgver ); +#endif + } + else + { + fprintf( stdout, "\nPost-install script of '%s-%s' returned error status.\n\n", pkgname, pkgver ); + } + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + exit( exit_status ); + } +} + +static void finalize_installation( void ) +{ + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *cmd = NULL, *tmp = NULL; + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + if( group ) + (void)sprintf( &tmp[0], "%s/%s/", pkgs_path, group ); + else + (void)sprintf( &tmp[0], "%s/", pkgs_path ); + + if( _mkdir_p( tmp, S_IRWXU | S_IRWXG | S_IRWXO ) != 0 ) + { + FATAL_ERROR( "Cannot access '/%s' directory", PACKAGES_PATH ); + } + + /**************************************** + Store PKGLOG file into Setup Database: + */ + len = snprintf( &cmd[0], PATH_MAX, + "cp %s %s > /dev/null 2>&1", + pkglog_fname, (const char *)&tmp[0] ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot store '%s' pkglog file", basename( (char *)pkglog_fname ) ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + if( rc != 0 ) + { + rollback(); + + free( cmd ); + free( tmp ); + + exit_status = 47; + + if( install_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Install:", pkgname, pkgver, strprio( priority, 0 ), + "\n\\Z1Cannot store PKGLOG file into Setup Database.\\Zn\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nCannot store '%s' pkglog file into Setup Database.\n\n", basename( (char *)pkglog_fname ) ); +#endif + } + else + { + fprintf( stdout, "\nCannot store '%s' pkglog file into Setup Database.\n\n", basename( (char *)pkglog_fname ) ); + } + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + exit( exit_status ); + } + + /********************************************* + Increment references in the Setup Database: + */ + if( group ) + len = snprintf( &cmd[0], PATH_MAX, + "%s/chrefs --operation=inc --destination=%s %s/%s > /dev/null 2>&1", + selfdir, pkgs_path, group, basename( (char *)pkglog_fname ) ); + else + len = snprintf( &cmd[0], PATH_MAX, + "%s/chrefs --operation=inc --destination=%s %s > /dev/null 2>&1", + selfdir, pkgs_path, basename( (char *)pkglog_fname ) ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot increment '%s-%s' package references", pkgname, pkgver ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + + free( cmd ); + + if( (rc != 0) && !ignore_chrefs_errors ) + { + free( tmp ); + + rollback(); + + exit_status = 48; + + if( install_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Install:", pkgname, pkgver, strprio( priority, 0 ), + "\n\\Z1Cannot increment package references in Setup Database.\\Zn\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nCannot increment '%s-%s' package references in Setup Database.\n\n", pkgname, pkgver ); +#endif + } + else + { + fprintf( stdout, "\nCannot increment '%s-%s' package references in Setup Database.\n\n", pkgname, pkgver ); + } + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + exit( exit_status ); + } + + /************************************************* + Remove backup PKGLOG file from removed-packages + directory if exists: + */ + bzero( (void *)tmp, PATH_MAX ); + { + const char *fname = basename( (char *)pkglog_fname ); + + if( group ) + (void)sprintf( &tmp[0], "%s/%s/%s", rempkgs_path, group, fname ); + else + (void)sprintf( &tmp[0], "%s/%s", rempkgs_path, fname ); + + (void)unlink( (const char *)&tmp[0] ); + + if( group ) + { + const char *dir = (const char *)dirname( (char *)&tmp[0] ); + if( is_dir_empty( dir ) ) + { + (void)rmdir( dir ); + } + } + } + + free( tmp ); +} + +#if defined( HAVE_GPG2 ) +static void verify_gpg_signature( void ) +{ + struct stat st; + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *cmd = NULL; + + bzero( (void *)&st, sizeof( struct stat ) ); + + /****************************************************************** + Do not try to verify signature if '.asc' file is not accessible: + */ + if( stat( (const char *)asc_fname, &st ) == -1 ) return; + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, + "gpg2 --verify %s %s > /dev/null 2>&1", + asc_fname, pkg_fname ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot verify GPG2 signature of '%s-%s' package", pkgname, pkgver ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + + free( cmd ); + + if( rc != 0 ) + { + exit_status = 51; + + if( install_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Install:", pkgname, pkgver, strprio( priority, 0 ), + "\n\\Z1Cannot verify GPG2 signature of the package.\\Zn\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nGPG2 signature verification of '%s-%s' package returned error status.\n\n", pkgname, pkgver ); +#endif + } + else + { + fprintf( stdout, "\nGPG2 signature verification of '%s-%s' package returned error status.\n\n", pkgname, pkgver ); + } + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + exit( exit_status ); + } +} +#endif + + +static void dialogrc( void ) +{ + struct stat st; + char *tmp = NULL; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + /* imagine that the utility is in /sbin directory: */ + (void)sprintf( &tmp[0], "%s/../usr/share/%s/.dialogrc", selfdir, PACKAGE_NAME ); + if( stat( (const char *)&tmp[0], &st ) == -1 ) + { + /* finaly assume that /usr/sbin is a sbindir: */ + (void)sprintf( &tmp[0], "%s/../../usr/share/%s/.dialogrc", selfdir, PACKAGE_NAME ); + } + + setenv( "DIALOGRC", (const char *)&tmp[0], 1 ); + + free( tmp ); +} + +static char *get_curdir( void ) +{ + char *cwd = NULL; + + cwd = (char *)malloc( PATH_MAX ); + if( !cwd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cwd, PATH_MAX ); + + if( getcwd( cwd, (size_t)PATH_MAX ) != NULL ) + { + char *p = NULL; + remove_trailing_slash( cwd ); + p = xstrdup( (const char *)cwd ); + free( cwd ); + return p; + } + else + { + FATAL_ERROR( "Cannot get absolute path to current directory" ); + } + + return (char *)NULL; +} + + +/********************************************* + Get directory where this program is placed: + */ +char *get_selfdir( void ) +{ + char *buf = NULL; + ssize_t len; + + buf = (char *)malloc( PATH_MAX ); + if( !buf ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + bzero( (void *)buf, PATH_MAX ); + len = readlink( "/proc/self/exe", buf, (size_t)PATH_MAX ); + if( len > 0 && len < PATH_MAX ) + { + char *p = xstrdup( (const char *)dirname( buf ) ); + free( buf ); + return p; + } + FATAL_ERROR( "Cannot determine self directory. Please mount /proc filesystem" ); +} + +void set_stack_size( void ) +{ + const rlim_t stack_size = 16 * 1024 * 1024; /* min stack size = 16 MB */ + struct rlimit rl; + int ret; + + ret = getrlimit( RLIMIT_STACK, &rl ); + if( ret == 0 ) + { + if( rl.rlim_cur < stack_size ) + { + rl.rlim_cur = stack_size; + ret = setrlimit( RLIMIT_STACK, &rl ); + if( ret != 0 ) + { + fprintf(stderr, "setrlimit returned result = %d\n", ret); + FATAL_ERROR( "Cannot set stack size" ); + } + } + } +} + + +int main( int argc, char *argv[] ) +{ + gid_t gid; + + set_signal_handlers(); + + gid = getgid(); + setgroups( 1, &gid ); + + fatal_error_hook = fatal_error_actions; + + selfdir = get_selfdir(); + curdir = get_curdir(); + dialogrc(); + + errlog = stderr; + + program = basename( argv[0] ); + get_args( argc, argv ); + + /* set_stack_size(); */ + + tmpdir = _mk_tmpdir(); + if( !tmpdir ) + { + FATAL_ERROR( "Cannot create temporary directory" ); + } + + + /************************************************************ + Getting Service Files, reading pkginfo, preserving pkglog: + */ + read_service_files(); + + /**************************************************** + Checking whether the package is already installed: + */ + check_package(); + + if( rqck ) check_requires(); +#if defined( HAVE_GPG2 ) + if( gpgck ) verify_gpg_signature(); +#endif + + read_filelist(); + read_restorelinks(); + + read_description(); + + if( ask_for_install() ) + { + /* Terminate installation: */ + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + exit( exit_status ); + } + + show_install_progress(); + + /************* + DO INSTALL: + */ + pre_install_routine(); + uncompress_package(); + restore_links(); + post_install_routine(); + finalize_installation(); + + fprintf( stdout, "\033[3A" ); /* move cursor up 3 lines */ + + if( (install_mode != CONSOLE) && (install_mode == MENUDIALOG) ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Install:", pkgname, pkgver, strprio( priority, 0 ), + "\nPackage has been installed.\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nPackage '%s-%s' has been installed.\n\n", pkgname, pkgver ); +#endif + } + else + { + if( (install_mode != INFODIALOG) ) + { + fprintf( stdout, "\nPackage '%s-%s' has been installed.\n\n", pkgname, pkgver ); + } + } + + setup_log( "Package '%s-%s' has been installed", pkgname, pkgver ); + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + + exit( exit_status ); +} diff --git a/src/install-pkglist.c b/src/install-pkglist.c new file mode 100644 index 0000000..7e0cc00 --- /dev/null +++ b/src/install-pkglist.c @@ -0,0 +1,2033 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <sys/sysinfo.h> +#include <sys/types.h> +#include <stdint.h> +#include <dirent.h> +#include <sys/stat.h> /* chmod(2) */ +#include <sys/file.h> /* flock(2) */ +#include <fcntl.h> +#include <linux/limits.h> +#include <alloca.h> /* alloca(3) */ +#include <string.h> /* strdup(3) */ +#include <strings.h> /* index(3) */ +#include <libgen.h> /* basename(3) */ +#include <ctype.h> /* tolower(3) */ +#include <errno.h> +#include <time.h> +#include <sys/time.h> +#include <pwd.h> +#include <grp.h> +#include <stdarg.h> +#include <unistd.h> + +#include <pthread.h> + +#include <math.h> + +#include <sys/wait.h> + +#include <sys/resource.h> + +#include <signal.h> +#if !defined SIGCHLD && defined SIGCLD +# define SIGCHLD SIGCLD +#endif + +#define _GNU_SOURCE +#include <getopt.h> + +#include <msglog.h> +#include <wrapper.h> +#include <system.h> +#include <dlist.h> + +#if defined( HAVE_DIALOG ) +#include <dialog-ui.h> +#endif + +#define PROGRAM_NAME "install-pkglist" + +#include <defs.h> + +#define WAIT_USEC_FOR_CHILD 10000 + +char *program = PROGRAM_NAME; +char *root = NULL, *srcdir = NULL, *pkglist_fname = NULL, + *tmpdir = NULL, *curdir = NULL; + +int rqck = 0, gpgck = 0, progress = 0, parallel = 0, error_pkgs_list = 0, ncpus = 0; + +int exit_status = EXIT_SUCCESS; /* errors counter */ +char *selfdir = NULL; + +int __done = 0, __child = 0, __terminated = 0, __successful = 0, __all = 0; + +enum _install_mode { + CONSOLE = 0, + INFODIALOG, + MENUDIALOG +} install_mode = CONSOLE; + +enum _input_type { + IFMT_PKG = 0, + IFMT_LOG, + + IFMT_UNKNOWN +}; + +/********************************************* + Package structures and declarations: + */ +enum _procedure +{ + INSTALL = 0, /* 'install' */ + UPDATE /* 'update' */ +}; + +enum _priority +{ + REQUIRED = 0, /* synonims: REQUIRED | required | REQ | req */ + RECOMMENDED, /* synonims: RECOMMENDED | recommended | REC | rec */ + OPTIONAL, /* synonims: OPTIONAL | optional | OPT | opt */ + SKIP /* synonims: SKIP | skip | SKP | skp */ +}; + +struct pkg +{ + char *group; + char *name; + char *version; +}; + +struct package +{ + char *name; + char *version; + char *group; + char *description; + + char *tarball; + + enum _procedure procedure; /* install procedure */ + enum _priority priority; /* install user priority */ +}; + +enum _priority install_priority = OPTIONAL; /* by default allow all packages exept 'SKIP' */ + +struct dlist *requires = NULL; /* list of pkg structs */ +struct dlist *packages = NULL; /* list of package structs */ + +static void free_requires( void ); +static void free_packages( void ); + +/* + End of package structures and declarations. + *********************************************/ + +/********************************************* + Return status declarations: + */ +struct pkgrc +{ + pid_t pid; + int status; + char *name; + char *version; + char *group; +}; + +struct dlist *pkgrcl = NULL; /* list of pkgrc structs */ + +static void free_pkgrcl( void ); +static struct pkgrc *find_pkgrc( struct dlist *list, pid_t pid ); +/* + End of return status declarations. + *********************************************/ + + +void free_resources() +{ + if( root ) { free( root ); root = NULL; } + if( srcdir ) { free( srcdir ); srcdir = NULL; } + if( pkglist_fname ) { free( pkglist_fname ); pkglist_fname = NULL; } + + if( requires ) free_requires(); + if( packages ) free_packages(); + if( pkgrcl ) free_pkgrcl(); + + if( curdir ) { free( curdir ); curdir = NULL; } + if( selfdir ) { free( selfdir ); selfdir = NULL; } +} + +void usage() +{ + free_resources(); + + fprintf( stdout, "\n" ); + fprintf( stdout, "Usage: %s [options] <pkglist>\n", program ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Create Packages List in the installation order from set of PKGLOGs\n" ); + fprintf( stdout, "or set of PACKAGEs placed in the source directory. If the source\n" ); + fprintf( stdout, "directory is not defined then directory of <pkglist> will be used\n" ); + fprintf( stdout, "as source PACKAGE directory.\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Options:\n" ); + fprintf( stdout, " -h,--help Display this information.\n" ); + fprintf( stdout, " -v,--version Display the version of %s utility.\n", program ); + + fprintf( stdout, " -c,--check-requires Check the list of requires before install.\n" ); + fprintf( stdout, " -g,--gpg-verify Verify GPG2 signature. The signature must be\n" ); + fprintf( stdout, " saved in a file whose name is the same as the\n" ); + fprintf( stdout, " package file name, but with the extension '.asc'\n" ); + fprintf( stdout, " and located in the same directory as the package.\n" ); +#if defined( HAVE_DIALOG ) + fprintf( stdout, " -i,--info-dialog Show package description during install\n" ); + fprintf( stdout, " process using ncurses dialog.\n" ); + fprintf( stdout, " -m,--menu-dialog Ask for confirmation the inatallation.\n" ); +#endif + fprintf( stdout, " --parallel Parallel installation (dangerous; required the\n" ); + fprintf( stdout, " checking of DB integrity after installation).\n" ); + fprintf( stdout, " --errlist Print the list of not installed packages to the\n" ); + fprintf( stdout, " stderr in following format:\n" ); + fprintf( stdout, " group/name:version:status\n" ); + fprintf( stdout, " This option is applicable only for parallel\n" ); + fprintf( stdout, " installations.\n" ); + fprintf( stdout, " --progress Show progress bar instead of packages information.\n" ); + + fprintf( stdout, " -p,--priority=<required|recommended|optional|all>\n" ); + fprintf( stdout, " Рackage priorities allowed for installation:\n" ); + fprintf( stdout, " - optional | all ) install all packages;\n" ); + fprintf( stdout, " - recommended ) install required and recommended;\n" ); + fprintf( stdout, " - required ) install only required packages.\n" ); + + fprintf( stdout, " -r,--root=<DIR> Target rootfs path.\n" ); + + fprintf( stdout, " -s,--source=<DIR> Packages source directory.\n" ); + + fprintf( stdout, "\n" ); + fprintf( stdout, "Parameter:\n" ); + fprintf( stdout, " <DIR|pkglist> Input PKGLIST file name or a source\n" ); + fprintf( stdout, " directory to find default PKGLIST.\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "If sourse packages directory is defined by option -s,--source then\n" ); + fprintf( stdout, "specified <DIR|pkglist> argumet will be considered relative to the\n" ); + fprintf( stdout, "source directory.\n" ); + fprintf( stdout, "\n" ); + + exit( EXIT_FAILURE ); +} + +void to_lowercase( char *s ) +{ + char *p = s; + while( p && *p ) { int c = *p; *p = tolower( c ); ++p; } +} + +void to_uppercase( char *s ) +{ + char *p = s; + while( p && *p ) { int c = *p; *p = toupper( c ); ++p; } +} + +void version() +{ + char *upper = NULL; + + upper = (char *)alloca( strlen( program ) + 1 ); + + strcpy( (char *)upper, (const char *)program ); + to_uppercase( upper ); + + fprintf( stdout, "%s (%s) %s\n", program, upper, PROGRAM_VERSION ); + + fprintf( stdout, "Copyright (C) 2019 Andrey V.Kosteltsev.\n" ); + fprintf( stdout, "This is free software. There is NO warranty; not even\n" ); + fprintf( stdout, "for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" ); + fprintf( stdout, "\n" ); + + free_resources(); + exit( EXIT_SUCCESS ); +} + + +static void remove_trailing_slash( char *dir ) +{ + char *s; + + if( !dir || dir[0] == '\0' ) return; + + s = dir + strlen( dir ) - 1; + while( *s == '/' ) + { + *s = '\0'; --s; + } +} + + +static int _mkdir_p( const char *dir, const mode_t mode ) +{ + char *buf; + char *p = NULL; + struct stat sb; + + if( !dir ) return -1; + + buf = (char *)alloca( strlen( dir ) + 1 ); + strcpy( buf, dir ); + + remove_trailing_slash( buf ); + + /* check if path exists and is a directory */ + if( stat( buf, &sb ) == 0 ) + { + if( S_ISDIR(sb.st_mode) ) + { + return 0; + } + } + + /* mkdir -p */ + for( p = buf + 1; *p; ++p ) + { + if( *p == '/' ) + { + *p = 0; + /* test path */ + if( stat( buf, &sb ) != 0 ) + { + /* path does not exist - create directory */ + if( mkdir( buf, mode ) < 0 ) + { + return -1; + } + } else if( !S_ISDIR(sb.st_mode) ) + { + /* not a directory */ + return -1; + } + *p = '/'; + } + } + + /* test path */ + if( stat( buf, &sb ) != 0 ) + { + /* path does not exist - create directory */ + if( mkdir( buf, mode ) < 0 ) + { + return -1; + } + } else if( !S_ISDIR(sb.st_mode) ) + { + /* not a directory */ + return -1; + } + + return 0; +} + + +static void _rm_tmpdir( const char *dirpath ) +{ + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + if( stat( dirpath, &path_sb ) == -1 ) + { + return; /* stat returns error code; errno is set */ + } + + if( S_ISDIR(path_sb.st_mode) == 0 ) + { + return; /* dirpath is not a directory */ + } + + if( (dir = opendir(dirpath) ) == NULL ) + { + return; /* Cannot open direcroty; errno is set */ + } + + len = strlen( dirpath ); + + while( (entry = readdir( dir )) != NULL) + { + + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( stat( path, &entry_sb ) == 0 ) + { + if( S_ISDIR(entry_sb.st_mode) ) + { + /* recursively remove a nested directory */ + _rm_tmpdir( path ); + } + else + { + /* remove a file object */ + (void)unlink( path ); + } + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + + } + + /* remove the devastated directory and close the object of this directory */ + (void)rmdir( dirpath ); + + closedir( dir ); +} + + +static char *_mk_tmpdir( void ) +{ + char *buf = NULL, *p, *tmp = "/tmp"; + size_t len = 0, size = 0; + + (void)umask( S_IWGRP | S_IWOTH ); /* octal 022 */ + + /* Get preferred directory for tmp files */ + if( (p = getenv( "TMP" )) != NULL ) { + tmp = p; + } + else if( (p = getenv( "TEMP" )) != NULL ) { + tmp = p; + } + + size = strlen( tmp ) + strlen( DISTRO_NAME ) + strlen( program ) + 12; + + buf = (char *)malloc( size ); + if( !buf ) return NULL; + + len = snprintf( buf, size, (const char *)"%s/%s/%s-%.7u", tmp, DISTRO_NAME, program, getpid() ); + if( len == 0 || len == size - 1 ) + { + free( buf ); return NULL; + } + + _rm_tmpdir( (const char *)&buf[0] ); + + if( _mkdir_p( buf, S_IRWXU | S_IRWXG | S_IRWXO ) == 0 ) + { + return buf; + } + + free( buf ); return NULL; +} + + + +void fatal_error_actions( void ) +{ + logmsg( errlog, MSG_NOTICE, "Free resources on FATAL error..." ); + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); +} + +void sigint( int signum ) +{ + (void)signum; + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); +} + +void sigchld( int signum ) +{ + pid_t pid = 0; + int status; + + (void)signum; + + while( (pid = waitpid( -1, &status, WNOHANG )) > 0 ) + { + struct pkgrc *pkgrc = find_pkgrc( pkgrcl, pid ); + + ++__terminated; /* One of children with 'pid' is terminated */ + + if( WIFEXITED( status ) ) + { + if( (int)WEXITSTATUS( status ) > 0 ) + { + ++exit_status; /* printf( "Child %d returned non zero status: %d\n", pid, (int)WEXITSTATUS (status) ); */ + if( pkgrc ) pkgrc->status = (int)WEXITSTATUS (status); + } + else + { + ++__successful; /* printf( "Child %d terminated with status: %d\n", pid, (int)WEXITSTATUS (status) ); */ + if( pkgrc ) pkgrc->status = (int)WEXITSTATUS (status); + } + } + else if( WIFSIGNALED( status ) ) + { + ++exit_status; /* printf( "Child %d terminated on signal: %d\n", pid, WTERMSIG( status ) ); */ + if( pkgrc ) pkgrc->status = 253; + } + else + { + ++exit_status; /* printf( "Child %d terminated on unknown reason\n", pid ); */ + if( pkgrc ) pkgrc->status = 254; + } + + } + + if( pid == -1 && errno == ECHILD ) + { + /* No child processes: */ + __done = 1; + } + return; +} + + +static void set_signal_handlers() +{ + struct sigaction sa; + sigset_t set; + + memset( &sa, 0, sizeof( sa ) ); + sa.sa_handler = sigint; /* TERM, INT */ + sa.sa_flags = SA_RESTART; + sigemptyset( &set ); + sigaddset( &set, SIGTERM ); + sigaddset( &set, SIGINT ); + sa.sa_mask = set; + sigaction( SIGTERM, &sa, NULL ); + sigaction( SIGINT, &sa, NULL ); + + /* System V fork+wait does not work if SIGCHLD is ignored */ + memset( &sa, 0, sizeof( sa ) ); + sa.sa_handler = sigchld; /* CHLD */ + sa.sa_flags = SA_RESTART; + sigemptyset( &set ); + sigaddset( &set, SIGCHLD ); + sa.sa_mask = set; + sigaction( SIGCHLD, &sa, NULL ); + + memset( &sa, 0, sizeof( sa ) ); /* ignore SIGPIPE */ + sa.sa_handler = SIG_IGN; + sa.sa_flags = 0; + sigaction( SIGPIPE, &sa, NULL ); +} + + +static enum _input_type check_package_file( char *uncompress, const char *fname ) +{ + struct stat st; + size_t pkglog_size = 0; + unsigned char buf[8]; + int rc, fd; + + /* SIGNATURES: https://www.garykessler.net/library/file_sigs.html */ + + if( uncompress ) + { + *uncompress = '\0'; + } + + if( stat( fname, &st ) == -1 ) + { + FATAL_ERROR( "Cannot access %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + + pkglog_size = st.st_size; + + if( (fd = open( fname, O_RDONLY )) == -1 ) + { + FATAL_ERROR( "Cannot open %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + + rc = (int)read( fd, (void *)&buf[0], 7 ); + if( rc != 7 ) + { + close( fd ); return IFMT_UNKNOWN; + } + buf[7] = '\0'; + + /* TEXT */ + if( !strncmp( (const char *)&buf[0], "PACKAGE", 7 ) ) + { + close( fd ); return IFMT_LOG; + } + + /* GZ */ + if( buf[0] == 0x1F && buf[1] == 0x8B && buf[2] == 0x08 ) + { + if( uncompress ) { *uncompress = 'x'; } + close( fd ); return IFMT_PKG; + } + + /* BZ2 */ + if( buf[0] == 0x42 && buf[1] == 0x5A && buf[2] == 0x68 ) + { + if( uncompress ) { *uncompress = 'j'; } + close( fd ); return IFMT_PKG; + } + + /* XZ */ + if( buf[0] == 0xFD && buf[1] == 0x37 && buf[2] == 0x7A && + buf[3] == 0x58 && buf[4] == 0x5A && buf[5] == 0x00 ) + { + if( uncompress ) { *uncompress = 'J'; } + close( fd ); return IFMT_PKG; + } + + if( pkglog_size > 262 ) + { + if( lseek( fd, 257, SEEK_SET ) == -1 ) + { + FATAL_ERROR( "Cannot check signature of %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + rc = (int)read( fd, &buf[0], 5 ); + if( rc != 5 ) + { + FATAL_ERROR( "Cannot read signature of %s file", basename( (char *)fname ) ); + } + /* TAR */ + if( buf[0] == 0x75 && buf[1] == 0x73 && buf[2] == 0x74 && buf[3] == 0x61 && buf[4] == 0x72 ) + { + close( fd ); return IFMT_PKG; + } + } + + close( fd ); return IFMT_UNKNOWN; +} + + +void get_args( int argc, char *argv[] ) +{ +#if defined( HAVE_DIALOG ) + const char* short_options = "hvcgimp:r:s:"; +#else + const char* short_options = "hvcgp:r:s:"; +#endif + +#define PROGRESS 812 +#define PARALLEL 872 +#define _ERRLIST 873 + + const struct option long_options[] = + { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'v' }, + { "check-requires", no_argument, NULL, 'c' }, + { "gpg-verify", no_argument, NULL, 'g' }, +#if defined( HAVE_DIALOG ) + { "info-dialog", no_argument, NULL, 'i' }, + { "menu-dialog", no_argument, NULL, 'm' }, +#endif + { "parallel", no_argument, NULL, PARALLEL }, + { "errlist", no_argument, NULL, _ERRLIST }, + { "progress", no_argument, NULL, PROGRESS }, + { "priority", required_argument, NULL, 'p' }, + { "root", required_argument, NULL, 'r' }, + { "source", required_argument, NULL, 's' }, + { NULL, 0, NULL, 0 } + }; + + int ret; + int option_index = 0; + + while( (ret = getopt_long( argc, argv, short_options, long_options, &option_index )) != -1 ) + { + switch( ret ) + { + case 'h': + { + usage(); + break; + } + case 'v': + { + version(); + break; + } + case 'c': + { + rqck = 1; + break; + } + case 'g': + { + gpgck = 1; + break; + } + +#if defined( HAVE_DIALOG ) + case 'i': + { + install_mode = INFODIALOG; + break; + } + case 'm': + { + install_mode = MENUDIALOG; + break; + } +#endif + + case PARALLEL: + { + parallel = 1; + break; + } + case _ERRLIST: + { + error_pkgs_list = 1; + break; + } + case PROGRESS: + { + progress = 1; + break; + } + + case 'p': + { + if( optarg != NULL ) + { + char *match = NULL; + + if( strlen( (const char *)optarg ) > 2 ) + { + to_lowercase( optarg ); + if( (match = strstr( optarg, "req" )) && match == optarg ) { + install_priority = REQUIRED; + } + else if( (match = strstr( optarg, "rec" )) && match == optarg ) { + install_priority = RECOMMENDED; + } + else if( (match = strstr( optarg, "opt" )) && match == optarg ) { + install_priority = OPTIONAL; + } + else if( (match = strstr( optarg, "all" )) && match == optarg ) { + install_priority = OPTIONAL; + } + else { + FATAL_ERROR( "Unknown --priority '%s' value", optarg ); + } + } + else + { + FATAL_ERROR( "Unknown --priority '%s' value", optarg ); + } + } + else + /* option is present but without value */ + usage(); + break; + } + + case 'r': + { + if( optarg != NULL ) + { + char cwd[PATH_MAX]; + + bzero( (void *)cwd, PATH_MAX ); + if( optarg[0] != '/' && curdir ) + { + /* skip current directory definition './' at start of argument: */ + if( !strncmp( optarg, "./", 2 ) && strncmp( optarg, "..", 2 ) ) + (void)sprintf( cwd, "%s/%s", curdir, optarg + 2 ); + else if( (strlen( optarg ) == 1) && !strncmp( optarg, ".", 1 ) ) + (void)sprintf( cwd, "%s", curdir ); + else + (void)sprintf( cwd, "%s/%s", curdir, optarg ); + root = xstrdup( (const char *)cwd ); + } + else + { + root = xstrdup( (const char *)optarg ); + } + remove_trailing_slash( root ); + } + else + /* option is present but without value */ + usage(); + break; + } + + case 's': + { + if( optarg != NULL ) + { + char cwd[PATH_MAX]; + + bzero( (void *)cwd, PATH_MAX ); + if( optarg[0] != '/' && curdir ) + { + (void)sprintf( cwd, "%s/%s", curdir, optarg ); + srcdir = xstrdup( (const char *)cwd ); + } + else + { + srcdir = xstrdup( (const char *)optarg ); + } + remove_trailing_slash( srcdir ); + } + else + /* option is present but without value */ + usage(); + break; + } + + case '?': default: + { + usage(); + break; + } + } + } + + + /* last command line argument is the intput PKGLIST file */ + if( optind < argc ) + { + struct stat st; + char *buf = NULL; + + bzero( (void *)&st, sizeof( struct stat ) ); + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + (void)strcpy( buf, (const char *)argv[optind++] ); + remove_trailing_slash( (char *)&buf[0] ); + + if( srcdir) + { + char *tmp = xstrdup( (const char *)&buf[0] ); + + /* Ignore already defined srcdir if absolute path is specified: */ + if( buf[0] != '/' ) + { + if( !strncmp( tmp, "./", 2 ) && strncmp( tmp, "..", 2 ) ) + (void)sprintf( buf, "%s/%s", srcdir, tmp + 2 ); + else if( (strlen( tmp ) == 1) && !strncmp( tmp, ".", 1 ) ) + (void)sprintf( buf, "%s", srcdir ); + else + (void)sprintf( buf, "%s/%s", srcdir, tmp ); + } + free( tmp ); + + free( srcdir ); srcdir = xstrdup( (const char *)buf ); + } + else + { + char *tmp = xstrdup( (const char *)&buf[0] ); + + if( buf[0] != '/' && curdir ) + { + if( !strncmp( tmp, "./", 2 ) && strncmp( tmp, "..", 2 ) ) + (void)sprintf( buf, "%s/%s", curdir, tmp + 2 ); + else if( (strlen( tmp ) == 1) && !strncmp( tmp, ".", 1 ) ) + (void)sprintf( buf, "%s", curdir ); + else + (void)sprintf( buf, "%s/%s", curdir, tmp ); + } + free( tmp ); + + srcdir = xstrdup( (const char *)buf ); + } + + stat( (const char *)&buf[0], &st ); /* Do not check return status */ + + if( S_ISDIR(st.st_mode) ) + { + /********************************************************** + Add default '.pkglist' file name to the source dir name: + */ + (void)sprintf( buf, "%s/.pkglist", srcdir ); + pkglist_fname = xstrdup( (const char *)buf ); + } + else + { + if( S_ISREG(st.st_mode) ) + { + pkglist_fname = xstrdup( (const char *)buf ); + free( srcdir ); srcdir = xstrdup( (const char *)dirname( (char *)&buf[0] ) ); + } + else + { + FATAL_ERROR( "Specified '%s' PKGLIST is not a regular file", basename( (char *)&buf[0] ) ); + } + } + + free( buf ); + } + else + { + usage(); + } + + /********************************************* + root is always have the trailing slash '/': + */ + { + struct stat st; + char *buf = NULL; + int len; + + bzero( (void *)&st, sizeof( struct stat ) ); + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + if( !root ) + { + buf[0] = '/'; buf[1] = '\0'; + root = xstrdup( (const char *)buf ); + } + else + { + len = strlen( root ); + + (void)strcpy( buf, (const char *)root ); + if( buf[ len - 1 ] != '/' ) + { + buf[len] = '/'; buf[len+1] = '\0'; + free( root ); root = xstrdup( (const char *)buf ); + } + } + + if( stat( (const char *)&buf[0], &st ) == -1 ) + { + FATAL_ERROR( "Cannot access '%s' file or directory: %s", buf, strerror( errno ) ); + } + if( !S_ISDIR(st.st_mode) ) + { + FATAL_ERROR( "Defined --root '%s' is not a directory", buf ); + } + + free( buf ); + } + /* + End of set root path routine. + *********************************************/ +} + + + + +/********************************************* + Package functions: + */ +static char *strprio( enum _priority priority, int short_name ) +{ + char *p = NULL; + + switch( priority ) + { + case REQUIRED: + p = ( short_name ) ? "REQ" : "REQUIRED"; + break; + case RECOMMENDED: + p = ( short_name ) ? "REC" : "RECOMMENDED"; + break; + case OPTIONAL: + p = ( short_name ) ? "OPT" : "OPTIONAL"; + break; + case SKIP: + p = ( short_name ) ? "SKP" : "SKIP"; + break; + } + return p; +} + +static char *strproc( enum _procedure procedure ) +{ + char *p = NULL; + + switch( procedure ) + { + case INSTALL: + p = "install"; + break; + case UPDATE: + p = "update"; + break; + } + return p; +} + + +static struct pkg *pkg_alloc( void ) +{ + struct pkg *pkg = NULL; + + pkg = (struct pkg *)malloc( sizeof( struct pkg ) ); + if( !pkg ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)pkg, sizeof( struct pkg ) ); + + return pkg; +} + +static void pkg_free( struct pkg *pkg ) +{ + if( pkg ) + { + if( pkg->name ) { free( pkg->name ); pkg->name = NULL; } + if( pkg->version ) { free( pkg->version ); pkg->version = NULL; } + if( pkg->group ) { free( pkg->group ); pkg->group = NULL; } + + free( pkg ); + } +} + +static void __pkg_free_func( void *data, void *user_data ) +{ + struct pkg *pkg = (struct pkg *)data; + if( pkg ) { pkg_free( pkg ); } +} + +static void free_requires( void ) +{ + if( requires ) { dlist_free( requires, __pkg_free_func ); requires = NULL; } +} + +static void add_required( struct pkg *pkg ) +{ + requires = dlist_append( requires, (void *)pkg ); +} + +///////////////////// only if we deside to print requires list +//static void _print_requires( void *data, void *user_data ) +//{ +// struct pkg *pkg = (struct pkg *)data; +// +// if( pkg ) +// { +// if( pkg->group ) +// fprintf( stderr, "%s/%s:%s\n", pkg->group, pkg->name, pkg->version ); +// else +// fprintf( stderr, "%s:%s\n", pkg->name, pkg->version ); +// } +//} +// +//static void print_requires( void ) +//{ +// dlist_foreach( requires, _print_requires, NULL ); +//} +///////////////////// + +static struct package *package_alloc( void ) +{ + struct package *package = NULL; + + package = (struct package *)malloc( sizeof( struct package ) ); + if( !package ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)package, sizeof( struct package ) ); + + return package; +} + +static void package_free( struct package *package ) +{ + if( package ) + { + if( package->name ) { free( package->name ); package->name = NULL; } + if( package->version ) { free( package->version ); package->version = NULL; } + if( package->group ) { free( package->group ); package->group = NULL; } + + if( package->description ) { free( package->description ); package->description = NULL; } + if( package->tarball ) { free( package->tarball ); package->tarball = NULL; } + + free( package ); + } +} + +static void __package_free_func( void *data, void *user_data ) +{ + struct package *package = (struct package *)data; + if( package ) { package_free( package ); } +} + +static void free_packages( void ) +{ + if( packages ) { dlist_free( packages, __package_free_func ); packages = NULL; } +} + +static void add_package( struct package *package ) +{ + packages = dlist_append( packages, (void *)package ); +} + +////////////////////////////// just for testing +//static void _print_packages( void *data, void *user_data ) +//{ +// struct package *package = (struct package *)data; +// +// if( package ) +// { +// if( package->group ) +// fprintf( stderr, "%s/%s:%s:%s:%s:%s:%s\n", package->group, package->name, package->version, strproc( package->procedure ), strprio( package->priority, 0 ), +// package->description, package->tarball ); +// else +// fprintf( stderr, "%s:%s:%s:%s:%s:%s\n", package->name, package->version, strproc( package->procedure ), strprio( package->priority, 0 ), +// package->description, package->tarball ); +// } +//} +// +//static void print_packages( void ) +//{ +// dlist_foreach( packages, _print_packages, NULL ); +//} +////////////////////////////// + +/* + End of package functions. + *********************************************/ + + +/********************************************* + Return status functions: + */ +static struct pkgrc *pkgrc_alloc( void ) +{ + struct pkgrc *pkgrc = NULL; + + pkgrc = (struct pkgrc *)malloc( sizeof( struct pkgrc ) ); + if( !pkgrc ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)pkgrc, sizeof( struct pkgrc ) ); + + return pkgrc; +} + +static void pkgrc_free( struct pkgrc *pkgrc ) +{ + if( pkgrc ) + { + if( pkgrc->name ) { free( pkgrc->name ); pkgrc->name = NULL; } + if( pkgrc->version ) { free( pkgrc->version ); pkgrc->version = NULL; } + if( pkgrc->group ) { free( pkgrc->group ); pkgrc->group = NULL; } + + free( pkgrc ); + } +} + +static void __pkgrc_free_func( void *data, void *user_data ) +{ + struct pkgrc *pkgrc = (struct pkgrc *)data; + if( pkgrc ) { pkgrc_free( pkgrc ); } +} + +static void free_pkgrcl( void ) +{ + if( pkgrcl ) { dlist_free( pkgrcl, __pkgrc_free_func ); pkgrcl = NULL; } +} + +static void add_pkgrc( struct pkgrc *pkgrc ) +{ + pkgrcl = dlist_append( pkgrcl, (void *)pkgrc ); +} + +static struct pkgrc *find_pkgrc( struct dlist *list, pid_t pid ) +{ + if( !list ) return NULL; + + while( list && list->data ) + { + if( ((struct pkgrc *)list->data)->pid == pid ) { return (struct pkgrc *)list->data; } + list = dlist_next( list ); + } + + return NULL; +} + +static void __remove_success_pkgrc( void *data, void *user_data ) +{ + struct pkgrc *pkgrc = (struct pkgrc *)data; + + if( pkgrc && pkgrc->status == 0 ) + { + pkgrcl = dlist_remove( pkgrcl, (const void *)data ); + pkgrc_free( pkgrc ); + } +} + +static void cleanup_pkgrcl( void ) +{ + dlist_foreach( pkgrcl, __remove_success_pkgrc, NULL ); +} + +static void _print_pkgrcl( void *data, void *user_data ) +{ + struct pkgrc *pkgrc = (struct pkgrc *)data; + + if( pkgrc ) + { + if( pkgrc->group ) + fprintf( stderr, "%s/%s:%s:%d\n", pkgrc->group, pkgrc->name, pkgrc->version, pkgrc->status ); + else + fprintf( stderr, "%s:%s:%d\n", pkgrc->name, pkgrc->version, pkgrc->status ); + } +} + +static void return_codes_list( void ) +{ + if( pkgrcl ) + { + dlist_foreach( pkgrcl, _print_pkgrcl, NULL ); + } +} +/* + End of return status functions. + *********************************************/ + + +/******************************* + remove spaces at end of line: + */ +//static void skip_eol_spaces( char *s ) +//{ +// char *p = (char *)0; +// +// if( !s || *s == '\0' ) return; +// +// p = s + strlen( s ) - 1; +// while( isspace( *p ) ) { *p-- = '\0'; } +//} +// +//static char *skip_lead_spaces( char *s ) +//{ +// char *p = (char *)0; +// +// if( !s || *s == '\0' ) return p; +// +// p = s; while( isspace( *p ) ) { ++p; } +// +// return( p ); +//} + +static char *trim( char *s ) +{ + char *p = (char *)0; + + if( !s || *s == '\0' ) return p; + + p = s + strlen( s ) - 1; + while( isspace( *p ) ) { *p-- = '\0'; } + p = s; while( isspace( *p ) ) { ++p; } + + return( p ); +} + + +static void read_pkglist_file( const char *fname ) +{ + char *ln = NULL; + char *line = NULL; + int lnum = 0; + + FILE *fp = NULL; + + if( !fname || (*fname == '\0') ) return; + + fp = fopen( fname, "r" ); + if( !fp ) + { + FATAL_ERROR( "Cannot open '%s' PKGLIST file", basename( (char *)fname ) ); + } + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)line, PATH_MAX ); + + while( (ln = fgets( line, PATH_MAX, fp )) ) + { + char *p = NULL; + char *name = NULL, *vers = NULL, *desc = NULL, *ball = NULL, *proc = NULL, *prio = NULL; + + ++lnum; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + ln = trim( ln ); /* remove leading and trailing spaces */ + + if( *ln == '#' ) + { + if( !strncmp( ln, "# required:", 11 ) ) + { + char *n = NULL, *v = NULL, *g = NULL, *q = NULL; + char *rq = ln + 11; + rq = trim( rq ); + + n = rq; + if( (q = index( (const char *)n, '/' )) ) { *q = '\0'; g = n; n = ++q; g = trim( g ); } + if( (q = index( (const char *)n, '=' )) ) { *q = '\0'; v = ++q; n = trim( n ); } + v = trim( v ); + + if( n && v ) + { + struct pkg *pkg = pkg_alloc(); + + pkg->name = xstrdup( (const char *)n ); + pkg->version = xstrdup( (const char *)v ); + if( g ) + pkg->group = xstrdup( (const char *)g ); + + add_required( pkg ); + } + } + continue; /* skip commented lines */ + } + + name = ln; + if( (p = index( (const char *)name, ':' )) ) { *p = '\0'; vers = ++p; name = trim( name ); } else continue; + if( (p = index( (const char *)vers, ':' )) ) { *p = '\0'; desc = ++p; vers = trim( vers ); } else continue; + if( (p = index( (const char *)desc, ':' )) ) { *p = '\0'; ball = ++p; desc = trim( desc ); } else continue; + if( (p = index( (const char *)ball, ':' )) ) { *p = '\0'; proc = ++p; ball = trim( ball ); } else continue; + if( (p = index( (const char *)proc, ':' )) ) { *p = '\0'; prio = ++p; proc = trim( proc ); } else continue; + prio = trim( prio ); + + if( name && vers && desc && ball && proc && prio ) + { + char *buf = NULL; + struct package *package = NULL; + char *group = index( (const char *)ball, '/' ); + enum _priority priority = OPTIONAL; + + /******************* + Package priority: + */ + if( strlen( (const char*)prio ) > 2 ) + { + char *match = NULL; + + to_lowercase( prio ); + if( (match = strstr( prio, "req" )) && match == prio ) { + priority = REQUIRED; + } + else if( (match = strstr( prio, "rec" )) && match == prio ) { + priority = RECOMMENDED; + } + else if( (match = strstr( prio, "opt" )) && match == prio ) { + priority = OPTIONAL; + } + else if( (match = strstr( prio, "sk" )) && match == prio ) { + priority = SKIP; + } + else { + FATAL_ERROR( "%s: %d: Unknown '%s' priority value", basename( pkglist_fname ), lnum, prio ); + } + } + else + { + FATAL_ERROR( "%s: %d: Unknown '%s' priority value", basename( pkglist_fname ), lnum, prio ); + } + + if( priority > install_priority ) continue; + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + package = package_alloc(); + + package->name = xstrdup( (const char *)name ); + package->version = xstrdup( (const char *)vers ); + package->description = xstrdup( (const char *)desc ); + + (void)sprintf( buf, "%s/%s", (const char *)srcdir, (const char *)ball ); + { + enum _input_type type = IFMT_UNKNOWN; + char uncompress = '\0'; + + type = check_package_file( &uncompress, (const char *)&buf[0] ); + if( type != IFMT_PKG ) + { + FATAL_ERROR( "Unknown format of '%s' package file", (const char *)&buf[0] ); + } + } + package->tarball = xstrdup( (const char *)&buf[0] ); + + free( buf ); + + package->priority = priority; + + /******************** + Install procedure: + */ + if( strlen( (const char*)proc ) > 5 ) + { + char *match = NULL; + + to_lowercase( proc ); + if( (match = strstr( proc, "install" )) && match == proc ) { + package->procedure = INSTALL; + } + else if( (match = strstr( proc, "update" )) && match == proc ) { + package->procedure = UPDATE; + } + else { + FATAL_ERROR( "%s: %d: Unknown '%s' procedure value", basename( pkglist_fname ), lnum, proc ); + } + } + else + { + FATAL_ERROR( "%s: %d: Unknown '%s' procedure value", basename( pkglist_fname ), lnum, proc ); + } + + if( group ) + { + *group = '\0'; + group = ball; + package->group = xstrdup( (const char *)group ); + } + + add_package( package ); + } + + } /* End of while( ln = fgets( line ) ) */ + + free( line ); + fclose( fp ); +} + + +static void check_requires( void ) +{ + if( requires ) + { + exit_status = 1; + + fprintf( stdout, "\nThe input '%s' has the list of %d required packages.\n\n", basename( pkglist_fname ), dlist_length( requires ) ); + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + exit( exit_status ); + } +} + +#if defined( HAVE_DIALOG ) +static DIALOG_LISTITEM *alloc_select_items( void ) +{ + DIALOG_LISTITEM *items = NULL; + int i = 0, num = dlist_length( packages ); + struct dlist *list = packages, *next = NULL; + + items = (DIALOG_LISTITEM *)malloc( (num + 1) * sizeof(DIALOG_LISTITEM) ); + if( !items ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)items, (num + 1) * sizeof(DIALOG_LISTITEM) ); + + while( list ) + { + struct package *package = NULL; + + next = dlist_next( list ); + package = (struct package *)list->data; + if( package ) + { +#define COLUMN_LENGHT 23 /* 22 symbols for name + " [UP] " */ +#define NAME_LENGHT (COLUMN_LENGHT - 7) /* strlen(" [UP] ") + 1; */ +#define UPDATE_SUFFIX " [UP] " +#define INSTALL_SUFFIX " [in] " + + char *name = (char *)malloc( (size_t)COLUMN_LENGHT ); + if( !name ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)name, (size_t)COLUMN_LENGHT ); + + if( strlen( package->name ) > (size_t)NAME_LENGHT ) + { + (void)strncpy( name, (const char *)package->name, NAME_LENGHT - 2 ); + (void)strcat( name, ".." ); + } + else + { + (void)strcpy( name, (const char *)package->name ); + } + + if( package->procedure == UPDATE ) + { + int p = strlen( name ); + while( p < NAME_LENGHT ) { name[p] = ' '; ++p; } + name[p] = '\0'; + (void)strcat( name, UPDATE_SUFFIX ); + } + else + { + int p = strlen( name ); + /* while( p < (COLUMN_LENGHT - 1) ) { name[p] = ' '; ++p; } */ + while( p < NAME_LENGHT ) { name[p] = ' '; ++p; } + name[p] = '\0'; + (void)strcat( name, INSTALL_SUFFIX ); + } + + items[i].name = name; + items[i].text = xstrdup( (const char *)package->description ); + if( package->group ) + items[i].help = xstrdup( (const char *)package->group ); + items[i].state = 1; + } + ++i; + list = next; + } + return items; +} + +static void free_select_items( DIALOG_LISTITEM *items ) +{ + DIALOG_LISTITEM *p = items; + + if( !p ) return; + + while( p->name ) { free( p->name ); free( p->text ); if( p->help ) free( p->help ); ++p; } + + free( items ); +} + +static void remove_unselected_packages( DIALOG_LISTITEM *items ) +{ + DIALOG_LISTITEM *p = items; + struct dlist *rem = NULL, *list = NULL, *next = NULL; + int n = 0; + + if( !p ) return; + + while( p->name ) + { + /* copy packages list item to the local list: */ + if( !p->state ) { list = dlist_append( list, dlist_nth_data( packages, n ) ); } + ++p; ++n; + } + + /**************************************************** + remove items of the local list from packages list: + */ + rem = list; + while( rem ) + { + next = dlist_next( rem ); + packages = dlist_remove( packages, rem->data ); + rem = next; + } + + /*************************** + free unselected packages: + */ + dlist_free( list, __package_free_func ); +} +#endif + + +/********************************************* + Progress functions: + */ +static void show_install_con_progress( const char *title, int percent ) +{ +#define GAUGE_LENGTH 68 + size_t prefix = strlen( title ) + 2; /* title + ' [' */ + size_t suffix = 6; /* '] 100%' */ + size_t length = prefix + GAUGE_LENGTH + suffix; + int i, ctx = GAUGE_LENGTH * percent / 100; + + if( percent < 0 ) percent = 0; + if( percent > 100 ) percent = 100; + + printf( "\033[1A" ); /* move the cursor up 1 line */ + printf( "\033[%dD", (int)length ); /* move cursor to start of line */ + printf( "%s [", title ); + + for( i = 0; i < ctx; ++i ) { fprintf( stdout, "\u2588" ); } + for( ; i < GAUGE_LENGTH; ++i ) { fprintf( stdout, " " ); } + + printf( "] %3d%%\n", percent ); + fflush( stdout ); +} + +static void show_progress( void ) +{ + const char *title = "Install:"; + const char *message = "\n" + "Please wait for install all specified packages:\n" + "\n\n"; + + if( install_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + show_install_dlg_progress( 0 ); +#else + fprintf( stdout, "%s", message ); + show_install_con_progress( title, 0 ); +#endif + } + else + { + fprintf( stdout, "%s", message ); + show_install_con_progress( title, 0 ); + } + +} + +static void update_progress( int percent ) +{ + const char *title = "Install:"; + + if( install_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + show_install_dlg_progress( percent ); +#else + show_install_con_progress( title, percent ); +#endif + } + else + { + show_install_con_progress( title, percent ); + } +} + +static void stop_progress( void ) +{ + const char *title = "Install:"; + + if( install_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + show_install_dlg_progress( 100 ); +#else + show_install_con_progress( title, 100 ); + fprintf( stdout, "\n" ); +#endif + } + else + { + show_install_con_progress( title, 100 ); + fprintf( stdout, "\n" ); + } +} +/* + End of progress functions. + *********************************************/ + + + +/********************************************* + Install functions. + */ +static void install_package( struct package *package ) +{ + int len = 0; + char *cmd = NULL, *opt = NULL, *out = "> /dev/null 2>&1"; + + if( !package ) return; + + opt = (char *)malloc( (size_t)PATH_MAX ); + if( !opt ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)opt, PATH_MAX ); + opt[0] = ' '; + + if( gpgck ) (void)sprintf( opt, "--gpg-verify " ); + if( (install_mode != CONSOLE) && !parallel && !progress ) (void)strcat( opt, "--info-dialog " ); + if( parallel ) (void)strcat( opt, "--ignore-chrefs-errors " ); + if( (install_mode == CONSOLE) && !parallel && !progress ) out = " "; + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, + "%s/%s-package %s --priority=%s --root=%s %s %s", + selfdir, strproc( package->procedure ), opt, + strprio( package->priority, 1 ), + root, package->tarball, out ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot install %s-%s package", package->name, package->version ); + } + if( parallel ) + { + struct pkgrc *pkgrc = pkgrc_alloc(); + + pkgrc->name = xstrdup( (const char *)package->name ); + pkgrc->version = xstrdup( (const char *)package->version ); + if( package->group ) + pkgrc->group = xstrdup( (const char *)package->group ); + pkgrc->pid = sys_exec_command( cmd ); + + add_pkgrc( pkgrc ); + ++__child; + } + else + { + pid_t p = (pid_t) -1; + int rc = 0; + + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + if( rc != 0 && rc != 31 ) + { + FATAL_ERROR( "Cannot install '%s-%s' package", package->name, package->version ); + } + ++__successful; + } + + free( cmd ); + free( opt ); +} + +static void _serial_install_package( void *data, void *user_data ) +{ + struct package *package = (struct package *)data; + int percent; + + if( package ) { install_package( package ); } + + if( progress ) + { + percent = ( __successful < __all ) ? __successful * 100 / __all : 100; + update_progress( percent ); + } +} + +static void serial_install_packages( void ) +{ + if( progress ) + { + show_progress(); + } + + dlist_foreach( packages, _serial_install_package, NULL ); + + if( progress ) + { + stop_progress(); + } +} + + +static void *install_process( void *args ) +{ + struct dlist *list = packages, *next = NULL; + + int nstreams = ncpus * 2; /* two concurents for CPU */ + + while( list ) + { + struct package *package = NULL; + + next = dlist_next( list ); + package = (struct package *)list->data; + if( package ) + { + install_package( package ); + } + list = next; + + /* wait for available CPU: */ + while( (__child - __terminated) > nstreams ) usleep( WAIT_USEC_FOR_CHILD ); + } + + return NULL; +} + +static void parallel_install_packages( void ) +{ + pthread_t install_process_id; + int status; + + /* Start the parallel installation process: */ + status = pthread_create( &install_process_id, NULL, install_process, NULL ); + if( status != 0 ) + { + FATAL_ERROR( "Cannot start parallel installation process" ); + } + (void)pthread_detach( install_process_id ); +} + +/* + End of install functions. + *********************************************/ + + + + +static void dialogrc( void ) +{ + struct stat st; + char *tmp = NULL; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + /* imagine that the utility is in /sbin directory: */ + (void)sprintf( &tmp[0], "%s/../usr/share/%s/.dialogrc", selfdir, PACKAGE_NAME ); + if( stat( (const char *)&tmp[0], &st ) == -1 ) + { + /* finaly assume that /usr/sbin is a sbindir: */ + (void)sprintf( &tmp[0], "%s/../../usr/share/%s/.dialogrc", selfdir, PACKAGE_NAME ); + } + + setenv( "DIALOGRC", (const char *)&tmp[0], 1 ); + + free( tmp ); +} + +static char *get_curdir( void ) +{ + char *cwd = NULL; + + cwd = (char *)malloc( PATH_MAX ); + if( !cwd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cwd, PATH_MAX ); + + if( getcwd( cwd, (size_t)PATH_MAX ) != NULL ) + { + char *p = NULL; + remove_trailing_slash( cwd ); + p = xstrdup( (const char *)cwd ); + free( cwd ); + return p; + } + else + { + FATAL_ERROR( "Cannot get absolute path to current directory" ); + } + + return (char *)NULL; +} + + +/********************************************* + Get directory where this program is placed: + */ +char *get_selfdir( void ) +{ + char *buf = NULL; + ssize_t len; + + buf = (char *)malloc( PATH_MAX ); + if( !buf ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + bzero( (void *)buf, PATH_MAX ); + len = readlink( "/proc/self/exe", buf, (size_t)PATH_MAX ); + if( len > 0 && len < PATH_MAX ) + { + char *p = xstrdup( (const char *)dirname( buf ) ); + free( buf ); + return p; + } + FATAL_ERROR( "Cannot determine self directory. Please mount /proc filesystem" ); +} + +void set_stack_size( void ) +{ + const rlim_t stack_size = 16 * 1024 * 1024; /* min stack size = 16 MB */ + struct rlimit rl; + int ret; + + ret = getrlimit( RLIMIT_STACK, &rl ); + if( ret == 0 ) + { + if( rl.rlim_cur < stack_size ) + { + rl.rlim_cur = stack_size; + ret = setrlimit( RLIMIT_STACK, &rl ); + if( ret != 0 ) + { + fprintf(stderr, "setrlimit returned result = %d\n", ret); + FATAL_ERROR( "Cannot set stack size" ); + } + } + } +} + + +int main( int argc, char *argv[] ) +{ + gid_t gid; + + set_signal_handlers(); + + gid = getgid(); + setgroups( 1, &gid ); + + ncpus = get_nprocs(); + + fatal_error_hook = fatal_error_actions; + + selfdir = get_selfdir(); + curdir = get_curdir(); + dialogrc(); + + errlog = stderr; + + program = basename( argv[0] ); + get_args( argc, argv ); + + /* set_stack_size(); */ + + tmpdir = _mk_tmpdir(); + if( !tmpdir ) + { + FATAL_ERROR( "Cannot create temporary directory" ); + } + + /*********************************************************** + Fill requires and packages lists, check package tarballs, + skip unnesessary packages according to --priority option: + */ + read_pkglist_file( (const char *)pkglist_fname ); + + /* check only the list of requires in the input PKGLIST: */ + if( rqck ) check_requires(); + +#if defined( HAVE_DIALOG ) + if( install_mode == MENUDIALOG ) + { + int status = 0, num = dlist_length( packages ); + DIALOG_LISTITEM *items = alloc_select_items(); + + status = select_packages_box( items, num, 0, 0 ); + if( !status ) + { + remove_unselected_packages( items ); + free_select_items( items ); + } + else + { + /* Abort installation: */ + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + + exit( exit_status ); + } + } +#endif + + + if( parallel ) + { + /************************ + parallel installation: + */ + int percent = 0; + + __all = dlist_length( packages ); + __done = 0; __child = 0; + + __terminated = 0; __successful = 0; + + show_progress(); + + parallel_install_packages(); + + if( __terminated < __all ) + { + while( !__done ) + { + percent = ( __terminated < __all ) ? __terminated * 100 / __all : 100; + + update_progress( percent ); + usleep( WAIT_USEC_FOR_CHILD ); + } + } + + __done = 0; __child = 0; + + stop_progress(); + + if( __successful < __terminated ) { percent = __successful * 100 / __terminated; } + else { percent = 100; } + + __terminated = 0; __successful = 0; + + if( install_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + char *msg = NULL; + + msg = (char *)malloc( (size_t)80 ); + if( !msg ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)msg, 80 ); + + (void)sprintf( msg, "\nSuccessfully installed %d%% of %d specified packages.\n", percent, __all ); + + (void)info_box( " \\Z0INSTALL PACKAGES\\Zn ", msg, 5, 0, 0 ); + + free( msg ); +#else + fprintf( stdout, "\nSuccessfully installed %d%% of %d specified packages.\n\n", percent, __all ); +#endif + } + else + { + fprintf( stdout, "\nSuccessfully installed %d%% of %d specified packages.\n\n", percent, __all ); + } + + cleanup_pkgrcl(); /* remove successfully installed packages from return codes list */ + if( pkgrcl && error_pkgs_list ) + { + return_codes_list(); + } + } + else + { + /********************** + serial installation: + */ + + __successful = 0; + __all = dlist_length( packages ); + + serial_install_packages(); + + if( install_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_box( " \\Z4INSTALL PACKAGES\\Zn ", + "\nAll of specified packages have been installed.\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nAll of specified packages have been installed.\n\n" ); +#endif + } + else + { + fprintf( stdout, "\nAll of specified packages have been installed.\n\n" ); + } + + } + + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + + exit( exit_status ); +} diff --git a/src/jsmin.c b/src/jsmin.c new file mode 100644 index 0000000..f286e7b --- /dev/null +++ b/src/jsmin.c @@ -0,0 +1,358 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <libgen.h> /* basename(3) */ + +#include <msglog.h> + +#include <jsmin.h> + +static FILE *ifile; +static FILE *ofile; + +static const char *input_fname = NULL; + +static void error( const char *fname, char *s ) +{ + if( fname ) + ERROR( "JSMIN: %s: %s", basename( (char *)fname ), s ); + else + ERROR( "JSMIN: %s", s ); +} + +static int is_alpha_or_num( int c ) +{ + return( (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || + (c >= 'A' && c <= 'Z') || c == '_' || c == '$' || c == '\\' || c > 126 ); +} + +static int a; +static int b; +static int lookahead = EOF; +static int x = EOF; +static int y = EOF; + +/* + get - return the next character from stdin. Watch out for lookahead. If + the character is a control character, translate it to a space or + linefeed. + */ +static int get() +{ + int c = lookahead; + lookahead = EOF; + if( c == EOF ) + { + c = getc( ifile ); + } + if( c >= ' ' || c == '\n' || c == EOF ) + { + return c; + } + if( c == '\r' ) + { + return '\n'; + } + return ' '; +} + + +/* + peek - get the next character without getting it. + */ +static int peek() +{ + lookahead = get(); + return lookahead; +} + + +/* + next - get the next character, excluding comments. peek() is used to see + if a '/' is followed by a '/' or '*'. + */ +static int next() +{ + int c = get(); + if ( c == '/' ) + { + switch( peek() ) + { + case '/': + for( ;; ) + { + c = get(); + if( c <= '\n' ) + { + break; + } + } + break; + case '*': + get(); + while( c != ' ' ) + { + switch( get() ) + { + case '*': + if( peek() == '/' ) + { + get(); + c = ' '; + } + break; + case EOF: + error( input_fname, "Unterminated comment" ); + return EOF; + } + } + break; + } + } + y = x; + x = c; + return c; +} + + +/* + action - do something! What you do is determined by the argument: + 1 Output A. Copy B to A. Get the next B. + 2 Copy B to A. Get the next B. (Delete A). + 3 Get the next B. (Delete B). + action treats a string as a single character. Wow! + action recognizes a regular expression if it is preceded by ( or , or =. + */ +static void action( int d ) +{ + switch( d ) + { + case 1: + /* skip first carriage return */ + if( a != '\n' ) putc( a, ofile ); + if( (y == '\n' || y == ' ') && + (a == '+' || a == '-' || a == '*' || a == '/') && + (b == '+' || b == '-' || b == '*' || b == '/') ) + { + putc( y, ofile ); + } + case 2: + a = b; + if( a == '\'' || a == '"' || a == '`' ) + { + for( ;; ) + { + putc( a, ofile ); + a = get(); + if( a == b ) + { + break; + } + if( a == '\\' ) + { + putc( a, ofile ); + a = get(); + } + if( a == EOF ) + { + error( input_fname, "Unterminated string literal" ); + return; + } + } + } + case 3: + b = next(); + if( b == '/' && + ( a == '(' || a == ',' || a == '=' || a == ':' || + a == '[' || a == '!' || a == '&' || a == '|' || + a == '?' || a == '+' || a == '-' || a == '~' || + a == '*' || a == '/' || a == '{' || a == '\n' ) ) + { + putc( a, ofile ); + if( a == '/' || a == '*' ) + { + putc( ' ', ofile ); + } + putc( b, ofile ); + for( ;; ) + { + a = get(); + if( a == '[' ) + { + for( ;; ) + { + putc( a, ofile ); + a = get(); + if( a == ']' ) + { + break; + } + if( a == '\\' ) + { + putc( a, ofile ); + a = get(); + } + if( a == EOF ) + { + error( input_fname, "Unterminated set in Regular Expression literal" ); + return; + } + } + } + else if( a == '/' ) + { + switch( peek() ) + { + case '/': + case '*': + error( input_fname, "Unterminated set in Regular Expression literal" ); + return; + } + break; + } + else if( a =='\\' ) + { + putc( a, ofile ); + a = get(); + } + if( a == EOF ) + { + error( input_fname, "Unterminated Regular Expression literal" ); + return; + } + putc( a, ofile ); + } + b = next(); + } + } +} + + +/* + jsmin - Copy the input to the output, deleting the characters which are + insignificant to JavaScript. Comments will be removed. Tabs will be + replaced with spaces. Carriage returns will be replaced with linefeeds. + Most spaces and linefeeds will be removed. +*/ +static void jsmin() +{ + if( peek() == 0xEF ) { get(); get(); get(); } + a = '\n'; + action( 3 ); + + while( a != EOF ) + { + switch( a ) + { + case ' ': + action(is_alpha_or_num(b) ? 1 : 2); + break; + case '\n': + switch( b ) + { + case '{': case '[': case '(': + case '+': case '-': case '!': + case '~': + action( 1 ); + break; + case ' ': + action( 3 ); + break; + default: + action( is_alpha_or_num(b) ? 1 : 2 ); + } + break; + default: + switch( b ) + { + case ' ': + action( is_alpha_or_num(a) ? 1 : 3 ); + break; + case '\n': + switch( a ) + { + case '}': case ']': case ')': + case '+': case '-': case '"': + case '\'': case '`': + action( 1 ); + break; + default: + action( is_alpha_or_num(a) ? 1 : 3 ); + } + break; + default: + action( 1 ); + break; + } + } + } + /* lats carriage return */ + putc( '\n', ofile ); +} + + +int minimize_json( const char *ifname, const char *ofname ) +{ + int status, ret = -1; + + if( !ifname || !ofname ) return ret; + + status = exit_status; exit_status = 0; + + input_fname = ifname; + + ret = 0; + + ifile = fopen( ifname, "r" ); + if( ifile == NULL ) + { + ERROR( "JSMIN: Can't open '%s' file", ifname ); + exit_status = status + exit_status; + return ret; + } + + ofile = fopen( ofname, "w+" ); + if( ofile == NULL ) + { + ERROR( "JSMIN: Can't open '%s' file", ofname ); + exit_status = status + exit_status; + return ret; + } + + jsmin(); + + fclose( ifile ); ifile = NULL; + fflush( ofile ); fclose( ofile ); ofile = NULL; + + if( exit_status == 0 ) + { + ret = 1; + exit_status = status; + } + else + { + exit_status = status + exit_status; + } + + return ret; +} diff --git a/src/jsmin.h b/src/jsmin.h new file mode 100644 index 0000000..b0a34df --- /dev/null +++ b/src/jsmin.h @@ -0,0 +1,34 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +#ifndef _MINIMIZE_JSON_H_ +#define _MINIMIZE_JSON_H_ + +#ifdef __cplusplus +extern "C" { +#endif + + +extern int minimize_json( const char *ifname, const char *ofname ); + + +#ifdef __cplusplus +} /* ... extern "C" */ +#endif + +#endif /* _MINIMIZE_JSON_H_ */ diff --git a/src/make-package.c b/src/make-package.c new file mode 100644 index 0000000..e68e0e2 --- /dev/null +++ b/src/make-package.c @@ -0,0 +1,1983 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <sys/sysinfo.h> +#include <sys/types.h> +#include <stdint.h> +#include <dirent.h> +#include <sys/stat.h> /* chmod(2) */ +#include <fcntl.h> +#include <linux/limits.h> +#include <alloca.h> /* alloca(3) */ +#include <string.h> /* strdup(3) */ +#include <libgen.h> /* basename(3) */ +#include <ctype.h> /* tolower(3) */ +#include <errno.h> +#include <time.h> +#include <sys/time.h> +#include <pwd.h> +#include <grp.h> +#include <stdarg.h> +#include <unistd.h> + +#include <math.h> + +#include <sys/resource.h> + +#include <signal.h> +#if !defined SIGCHLD && defined SIGCLD +# define SIGCHLD SIGCLD +#endif + +#define _GNU_SOURCE +#include <getopt.h> + +#include <msglog.h> +#include <wrapper.h> +#include <system.h> +#include <dlist.h> + +#define PROGRAM_NAME "make-package" + +#include <defs.h> + + +char *program = PROGRAM_NAME; +char *destination = NULL, *srcdir = NULL, *flavour = NULL, + *tmpdir = NULL; + +FILE *rlinks = NULL; +size_t pkgsize = 0; +int nfiles = 0; + +const char *txz_suffix = ".txz"; +char compress = 'J'; + +#if defined( HAVE_GPG2 ) +char *gnupghome = NULL, *passphrase = NULL, *key_id = NULL; +#endif + +int exit_status = EXIT_SUCCESS; /* errors counter */ +char *selfdir = NULL; + +int mkgroupdir = 0; +int linkadd = 1; + +static char *pkgname = NULL, + *pkgver = NULL, + *arch = NULL, + *distroname = NULL, + *distrover = NULL, + *group = NULL, + *short_description = NULL, + *url = NULL, + *license = NULL, + *uncompressed_size = NULL, + *total_files = NULL; + +struct dlist *filelist = NULL; + +static void create_file_list( void ); +static void free_file_list( void ); + + +#define FREE_PKGINFO_VARIABLES() \ + if( pkgname ) { free( pkgname ); } pkgname = NULL; \ + if( pkgver ) { free( pkgver ); } pkgver = NULL; \ + if( arch ) { free( arch ); } arch = NULL; \ + if( distroname ) { free( distroname ); } distroname = NULL; \ + if( distrover ) { free( distrover ); } distrover = NULL; \ + if( group ) { free( group ); } group = NULL; \ + if( short_description ) { free( short_description ); } short_description = NULL; \ + if( url ) { free( url ); } url = NULL; \ + if( license ) { free( license ); } license = NULL; \ + if( uncompressed_size ) { free( uncompressed_size ); } uncompressed_size = NULL; \ + if( total_files ) { free( total_files ); } total_files = NULL + +void free_resources() +{ + if( srcdir ) { free( srcdir ); srcdir = NULL; } + if( destination ) { free( destination ); destination = NULL; } + if( flavour ) { free( flavour ); flavour = NULL; } + if( filelist ) { free_file_list(); filelist = NULL; } + +#if defined( HAVE_GPG2 ) + if( gnupghome ) { free( gnupghome ); gnupghome = NULL; } + if( passphrase ) { free( passphrase ); passphrase = NULL; } + if( key_id ) { free( key_id ); key_id = NULL; } +#endif + + if( selfdir ) { free( selfdir ); selfdir = NULL; } + + FREE_PKGINFO_VARIABLES(); +} + +void usage() +{ + free_resources(); + + fprintf( stdout, "\n" ); + fprintf( stdout, "Usage: %s [options] <srcpkgdir>\n", program ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Create PACKAGE from SRCPKGDIR where package is installed. The source\n" ); + fprintf( stdout, "directory should content the package service files:\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, " .PKGINFO, .INSTALL, .DESCRIPTION, and .REQUIRES (if applicable)\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "The '.PKGINFO' file is obligatory and should content declarations of\n" ); + fprintf( stdout, "following variables: pkgname, pkgver, arch, distroname, distrover.\n" ); + fprintf( stdout, "Also in the .PKGINFO file can be defined additional and recommended\n" ); + fprintf( stdout, "variables: group, short_description, url, license.\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Options:\n" ); + fprintf( stdout, " -h,--help Display this information.\n" ); + fprintf( stdout, " -v,--version Display the version of %s utility.\n", program ); + fprintf( stdout, " -d,--destination=<DIR> Target directory to save output PACKAGE.\n" ); + fprintf( stdout, " -m,--mkgroupdir Create GROUP subdirectory in the PACKAGE\n" ); + fprintf( stdout, " target directory.\n" ); + fprintf( stdout, " -l,--linkadd={y|n} Create .RESTORELINKS scrypt (default yes).\n" ); + fprintf( stdout, " -f,--flavour=<subdir> The name of additional subdirectory in the\n" ); + fprintf( stdout, " GROUP directory to save target PACKAGE.\n" ); + fprintf( stdout, "\n" ); +#if defined( HAVE_GPG2 ) + fprintf( stdout, "OpenPGP options:\n" ); + fprintf( stdout, " -g,--gnupghome=<DIR> Set the name of the GnuPG home directory\n" ); + fprintf( stdout, " to <DIR>. If this option is not used it\n" ); + fprintf( stdout, " defaults to '~/.gnupg'. This also overrides\n" ); + fprintf( stdout, " the environment variable 'GNUPGHOME'.\n" ); + fprintf( stdout, " -p,--passphrase=<FILE> File with passphrase of private certificate\n" ); + fprintf( stdout, " for signing package. For example:\n" ); + fprintf( stdout, " ~/.gnupg/.passphrase\n" ); + fprintf( stdout, " Passphrase should be placed in the first\n" ); + fprintf( stdout, " line of the file (the new-line symbol at\n" ); + fprintf( stdout, " end of passphrase is allowed). File must\n" ); + fprintf( stdout, " have access mode 600.\n" ); + fprintf( stdout, " -k,--key-id=<USER-ID> Use USER-ID to sign package, for example,\n" ); + fprintf( stdout, " --key-id=0xA5ED710298807270\n" ); + fprintf( stdout, "\n" ); +#endif + fprintf( stdout, "Compression options:\n" ); + fprintf( stdout, " -J,--xz Filter the package archive through xz(1).\n" ); + fprintf( stdout, " -j,--bzip2 Filter the package archive through bzip2(1).\n" ); + fprintf( stdout, " -z,--gzip Filter the package archive through gzip(1).\n" ); + fprintf( stdout, "\n" ); +#if defined( HAVE_GPG2 ) + fprintf( stdout, " If one of arguments: passphrase or key-id is not specified, then\n" ); + fprintf( stdout, " signature will not be created and utility doesn't return any error\n" ); + fprintf( stdout, " code. If error occurs during the creation of a package signature,\n" ); + fprintf( stdout, " the utility returns error code, but continue to create the package\n" ); + fprintf( stdout, " (in this case the signature will not be created).\n" ); + fprintf( stdout, "\n" ); +#endif + fprintf( stdout, "Parameter:\n" ); + fprintf( stdout, " <srcpkgdir> Directory wich contains source package\n" ); + fprintf( stdout, " and package service files.\n" ); + fprintf( stdout, "\n" ); + + exit( EXIT_FAILURE ); +} + +void to_lowercase( char *s ) +{ + char *p = s; + while( p && *p ) { int c = *p; *p = tolower( c ); ++p; } +} + +void to_uppercase( char *s ) +{ + char *p = s; + while( p && *p ) { int c = *p; *p = toupper( c ); ++p; } +} + +void version() +{ + char *upper = NULL; + + upper = (char *)alloca( strlen( program ) + 1 ); + + strcpy( (char *)upper, (const char *)program ); + to_uppercase( upper ); + + fprintf( stdout, "%s (%s) %s\n", program, upper, PROGRAM_VERSION ); + + fprintf( stdout, "Copyright (C) 2019 Andrey V.Kosteltsev.\n" ); + fprintf( stdout, "This is free software. There is NO warranty; not even\n" ); + fprintf( stdout, "for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" ); + fprintf( stdout, "\n" ); + + free_resources(); + exit( EXIT_SUCCESS ); +} + + +static void remove_trailing_slash( char *dir ) +{ + char *s; + + if( !dir || dir[0] == '\0' ) return; + + s = dir + strlen( dir ) - 1; + while( *s == '/' ) + { + *s = '\0'; --s; + } +} + + +static int _mkdir_p( const char *dir, const mode_t mode ) +{ + char *buf; + char *p = NULL; + struct stat sb; + + if( !dir ) return -1; + + buf = (char *)alloca( strlen( dir ) + 1 ); + strcpy( buf, dir ); + + remove_trailing_slash( buf ); + + /* check if path exists and is a directory */ + if( stat( buf, &sb ) == 0 ) + { + if( S_ISDIR(sb.st_mode) ) + { + return 0; + } + } + + /* mkdir -p */ + for( p = buf + 1; *p; ++p ) + { + if( *p == '/' ) + { + *p = 0; + /* test path */ + if( stat( buf, &sb ) != 0 ) + { + /* path does not exist - create directory */ + if( mkdir( buf, mode ) < 0 ) + { + return -1; + } + } else if( !S_ISDIR(sb.st_mode) ) + { + /* not a directory */ + return -1; + } + *p = '/'; + } + } + + /* test path */ + if( stat( buf, &sb ) != 0 ) + { + /* path does not exist - create directory */ + if( mkdir( buf, mode ) < 0 ) + { + return -1; + } + } else if( !S_ISDIR(sb.st_mode) ) + { + /* not a directory */ + return -1; + } + + return 0; +} + +static void _rm_tmpdir( const char *dirpath ) +{ + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + if( stat( dirpath, &path_sb ) == -1 ) + { + return; /* stat returns error code; errno is set */ + } + + if( S_ISDIR(path_sb.st_mode) == 0 ) + { + return; /* dirpath is not a directory */ + } + + if( (dir = opendir(dirpath) ) == NULL ) + { + return; /* Cannot open direcroty; errno is set */ + } + + len = strlen( dirpath ); + + while( (entry = readdir( dir )) != NULL) + { + + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( stat( path, &entry_sb ) == 0 ) + { + if( S_ISDIR(entry_sb.st_mode) ) + { + /* recursively remove a nested directory */ + _rm_tmpdir( path ); + } + else + { + /* remove a file object */ + (void)unlink( path ); + } + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + + } + + /* remove the devastated directory and close the object of this directory */ + (void)rmdir( dirpath ); + + closedir( dir ); +} + +static char *_mk_tmpdir( void ) +{ + char *buf = NULL, *p, *tmp = "/tmp"; + size_t len = 0, size = 0; + + (void)umask( S_IWGRP | S_IWOTH ); /* octal 022 */ + + /* Get preferred directory for tmp files */ + if( (p = getenv( "TMP" )) != NULL ) { + tmp = p; + } + else if( (p = getenv( "TEMP" )) != NULL ) { + tmp = p; + } + + size = strlen( tmp ) + strlen( DISTRO_NAME ) + strlen( program ) + 12; + + buf = (char *)malloc( size ); + if( !buf ) return NULL; + + len = snprintf( buf, size, (const char *)"%s/%s/%s-%.7u", tmp, DISTRO_NAME, program, getpid() ); + if( len == 0 || len == size - 1 ) + { + free( buf ); return NULL; + } + + _rm_tmpdir( (const char *)&buf[0] ); + + if( _mkdir_p( buf, S_IRWXU | S_IRWXG | S_IRWXO ) == 0 ) + { + return buf; + } + + free( buf ); return NULL; +} + + +void fatal_error_actions( void ) +{ + logmsg( errlog, MSG_NOTICE, "Free resources on FATAL error..." ); + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); +} + +void sigint( int signum ) +{ + (void)signum; + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); +} + +static void set_signal_handlers() +{ + struct sigaction sa; + sigset_t set; + + memset( &sa, 0, sizeof( sa ) ); + sa.sa_handler = sigint; /* TERM, INT */ + sa.sa_flags = SA_RESTART; + sigemptyset( &set ); + sigaddset( &set, SIGTERM ); + sigaddset( &set, SIGINT ); + sa.sa_mask = set; + sigaction( SIGTERM, &sa, NULL ); + sigaction( SIGINT, &sa, NULL ); + + memset( &sa, 0, sizeof( sa ) ); /* ignore SIGPIPE */ + sa.sa_handler = SIG_IGN; + sa.sa_flags = 0; + sigaction( SIGPIPE, &sa, NULL ); + + /* System V fork+wait does not work if SIGCHLD is ignored */ + signal( SIGCHLD, SIG_DFL ); +} + + + +void get_args( int argc, char *argv[] ) +{ +#if defined( HAVE_GPG2 ) + const char* short_options = "hvmd:l:f:g:p:k:Jjz"; +#else + const char* short_options = "hvmd:l:f:Jjz"; +#endif + + const struct option long_options[] = + { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'v' }, + { "destination", required_argument, NULL, 'd' }, + { "mkgroupdir", no_argument, NULL, 'm' }, + { "linkadd", required_argument, NULL, 'l' }, + { "flavour", required_argument, NULL, 'f' }, +#if defined( HAVE_GPG2 ) + { "gnupghome", required_argument, NULL, 'g' }, + { "passphrase", required_argument, NULL, 'p' }, + { "key-id", required_argument, NULL, 'k' }, +#endif + { "xz", no_argument, NULL, 'J' }, + { "bzip2", no_argument, NULL, 'j' }, + { "gzip", no_argument, NULL, 'z' }, + { NULL, 0, NULL, 0 } + }; + + int ret; + int option_index = 0; + + while( (ret = getopt_long( argc, argv, short_options, long_options, &option_index )) != -1 ) + { + switch( ret ) + { + case 'h': + { + usage(); + break; + } + case 'v': + { + version(); + break; + } + + case 'd': + { + if( optarg != NULL ) + { + destination = xstrdup( (const char *)optarg ); + remove_trailing_slash( destination ); + } + else + /* option is present but without value */ + usage(); + break; + } + case 'm': + { + mkgroupdir = 1; + break; + } + + case 'J': + { + compress = 'J'; + txz_suffix = ".txz"; + break; + } + case 'j': + { + compress = 'j'; + txz_suffix = ".tbz"; + break; + } + case 'z': + { + compress = 'z'; + txz_suffix = ".tgz"; + break; + } + + case 'l': + { + if( optarg != NULL ) + { + char *buf = NULL; + size_t len = strlen( optarg ) + 1; + + buf = (char *)malloc( len ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, len ); + + (void)strcpy( buf, (const char *)optarg ); + to_lowercase( buf ); + if( !strncmp( (const char *)&buf[0], "n", 1 ) ) + { + linkadd = 0; + } + free( buf ); + } + else + /* option is present but without value */ + usage(); + break; + } + case 'f': + { + if( optarg != NULL ) + { + char *buf = NULL; + size_t len = strlen( optarg ) + 1; + + buf = (char *)malloc( len ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, len ); + + (void)strcpy( buf, (const char *)optarg ); + to_lowercase( buf ); + + flavour = xstrdup( (const char *)&buf[0] ); + free( buf ); + } + else + /* option is present but without value */ + usage(); + break; + } + +#if defined( HAVE_GPG2 ) + case 'g': + { + if( optarg != NULL ) + { + struct stat st; + char *buf = NULL; + char *home = NULL; + + bzero( (void *)&st, sizeof( struct stat ) ); + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + if( *optarg == '~' ) + { + home = getenv( "HOME" ); + if( home ) + { + (void)sprintf( buf, "%s/%s", home, (const char *)((char *)optarg + 2) ); + } + else + { + FATAL_ERROR( "Cannot get HOME directory" ); + } + } + else + { + (void)strcpy( buf, (const char *)optarg ); + } + + if( stat( (const char *)&buf[0], &st ) == -1 ) + { + FATAL_ERROR( "Cannot access '%s' GnuPG home directory: %s", buf, strerror( errno ) ); + } + if( !S_ISDIR(st.st_mode) ) + { + FATAL_ERROR( "The GNUPGHOME '%s' is not a directory", buf ); + } + + remove_trailing_slash( buf ); + gnupghome = xstrdup( (const char *)&buf[0] ); + free( buf ); + } + else + /* option is present but without value */ + usage(); + break; + } + case 'p': + { + if( optarg != NULL ) + { + struct stat st; + char *buf = NULL; + char *home = NULL; + + bzero( (void *)&st, sizeof( struct stat ) ); + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + if( *optarg == '~' ) + { + home = getenv( "HOME" ); + if( home ) + { + (void)sprintf( buf, "%s/%s", home, (const char *)((char *)optarg + 2) ); + } + else + { + FATAL_ERROR( "Cannot get HOME directory" ); + } + } + else + { + (void)strcpy( buf, (const char *)optarg ); + } + + if( stat( (const char *)&buf[0], &st ) == -1 ) + { + FATAL_ERROR( "Cannot access '%s' passphrase source file: %s", basename( buf ), strerror( errno ) ); + } + if( !S_ISREG(st.st_mode) ) + { + FATAL_ERROR( "The passphrase '%s' is not a regular file", basename( buf ) ); + } + + passphrase = xstrdup( (const char *)&buf[0] ); + free( buf ); + } + else + /* option is present but without value */ + usage(); + break; + } + case 'k': + { + if( optarg != NULL ) + { + key_id = xstrdup( (const char *)optarg ); + } + else + /* option is present but without value */ + usage(); + break; + } +#endif + + case '?': default: + { + usage(); + break; + } + } + } + + /* last command line argument is the PACKAGE source directory */ + if( optind < argc ) + { + struct stat st; + char *buf = NULL; + + bzero( (void *)&st, sizeof( struct stat ) ); + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + (void)strcpy( buf, (const char *)argv[optind] ); + remove_trailing_slash( (char *)&buf[0] ); + + if( stat( (const char *)&buf[0], &st ) == -1 ) + { + FATAL_ERROR( "Cannot access '%s' PACKAGE source directory: %s", basename( buf ), strerror( errno ) ); + } + + if( ! S_ISDIR(st.st_mode) ) + { + FATAL_ERROR( "The PACKAGE source '%s' is not a directory", basename( buf ) ); + } + + /* Add .PKGINFO to the input dir name: */ + (void)strcat( buf, "/.PKGINFO" ); + if( stat( (const char *)&buf[0], &st ) == -1 ) { + FATAL_ERROR( "The defined SRCPKGDIR doesn't contain a valid package" ); + } + *(strstr( buf, "/.PKGINFO" )) = '\0'; /* restore tmpdir in tmp[] buffer */ + + /* Add .DESCRIPTION to the input dir name: */ + (void)strcat( buf, "/.DESCRIPTION" ); + if( stat( (const char *)&buf[0], &st ) == -1 ) { + FATAL_ERROR( "The defined SRCPKGDIR doesn't contain package '.DESCRIPTION' file" ); + } + *(strstr( buf, "/.DESCRIPTION" )) = '\0'; /* restore tmpdir in tmp[] buffer */ + + /* Add .INSTALL to the input dir name: */ + (void)strcat( buf, "/.INSTALL" ); + if( stat( (const char *)&buf[0], &st ) == -1 ) { + FATAL_ERROR( "The defined SRCPKGDIR doesn't contain package '.INSTALL' script" ); + } + *(strstr( buf, "/.INSTALL" )) = '\0'; /* restore tmpdir in tmp[] buffer */ + + + srcdir = xstrdup( (const char *)&buf[0] ); + if( srcdir == NULL ) + { + usage(); + } + + free( buf ); + } + else + { + usage(); + } + + if( destination == NULL ) + { + char *buf = NULL; + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + (void)strcpy( buf, (const char *)srcdir ); + remove_trailing_slash( (char *)&buf[0] ); + + destination = xstrdup( (const char *)dirname( (char *)&buf[0] ) ); + + free( buf ); + } +} + + +/* + Especialy for pkginfo lines. + Remove leading spaces and take non-space characters only: + */ +static char *skip_spaces( char *s ) +{ + char *q, *p = (char *)0; + + if( !s || *s == '\0' ) return p; + + p = s; + + while( (*p == ' ' || *p == '\t') && *p != '\0' ) { ++p; } q = p; + while( *q != ' ' && *q != '\t' && *q != '\0' ) { ++q; } *q = '\0'; + + if( *p == '\0' ) return (char *)0; + + return( xstrdup( (const char *)p ) ); +} + +/* + remove spaces at end of line: + */ +static void skip_eol_spaces( char *s ) +{ + char *p = (char *)0; + + if( !s || *s == '\0' ) return; + + p = s + strlen( s ) - 1; + while( isspace( *p ) ) { *p-- = '\0'; } +} + + +static void read_pkginfo( void ) +{ + struct stat st; + char *buf = NULL; + FILE *pkginfo; + + bzero( (void *)&st, sizeof( struct stat ) ); + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + (void)strcpy( buf, (const char *)srcdir ); + (void)strcat( buf, "/.PKGINFO" ); + + if( stat( (const char *)&buf[0], &st ) == -1 ) + { + FATAL_ERROR( "Cannot access .PKGINFO file: %s", strerror( errno ) ); + } + if( !S_ISREG(st.st_mode) ) + { + FATAL_ERROR( "The '%s' is not a regular file", basename( buf ) ); + } + + pkginfo = fopen( (const char *)&buf[0], "r" ); + if( !pkginfo ) + { + FATAL_ERROR( "Cannot open '%s' file", basename( buf ) ); + } + + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + while( (ln = fgets( line, PATH_MAX, pkginfo )) ) + { + char *match = NULL; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( (match = strstr( ln, "pkgname" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) pkgname = skip_spaces( p ); + } + if( (match = strstr( ln, "pkgver" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) pkgver = skip_spaces( p ); + } + + if( (match = strstr( ln, "group" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) group = skip_spaces( p ); + } + + if( (match = strstr( ln, "arch" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) arch = skip_spaces( p ); + } + if( (match = strstr( ln, "distroname" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) distroname = skip_spaces( p ); + } + if( (match = strstr( ln, "distrover" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) distrover = skip_spaces( p ); + } + + if( (match = strstr( ln, "short_description" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) + { + char *b = index( p, '"'), + *e = rindex( p, '"'); + if( b && e && ( b != e ) ) + { + p = ++b; *e = '\0'; + short_description = xstrdup( (const char *)p ); + } + } + } + if( (match = strstr( ln, "url" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) url = skip_spaces( p ); + } + if( (match = strstr( ln, "license" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) license = skip_spaces( p ); + } + } + + free( line ); + + if( !pkgname || !pkgver || !arch || !distroname || !distrover ) + { + FATAL_ERROR( "Invalid input .PKGINFO file" ); + } + + if( !url ) { url = xstrdup( DISTRO_URL ); } + if( !license ) { license = xstrdup( DISTRO_LICENSE ); } + } + + fclose( pkginfo ); + free( buf ); +} + +static void tune_destinations( void ) +{ + if( mkgroupdir && group ) + { + char *buf = NULL; + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + (void)strcpy( buf, (const char *)destination ); + (void)strcat( buf, "/" ); + (void)strcat( buf, (const char *)group ); + if( flavour ) + { + (void)strcat( buf, "/" ); + (void)strcat( buf, (const char *)flavour ); + } + + if( _mkdir_p( buf, S_IRWXU | S_IRWXG | S_IRWXO ) != 0 ) + { + FATAL_ERROR( "Cannot create target directory" ); + } + + free( destination ); + destination = xstrdup( (const char *)&buf[0] ); + free( buf ); + } + + /* here we can allocate memory for output filenames */ +} + +/********************************************* + .RESTORELINKS functions: + */ +static void start_restorelinks_file( void ) +{ + char *tmp = NULL; + + tmp = (char *)malloc( PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + (void)sprintf( (char *)&tmp[0], "%s/%s", tmpdir, ".RESTORELINKS" ); + + rlinks = fopen( (const char *)&tmp[0], "w" ); + if( !rlinks ) + { + FATAL_ERROR( "Cannot create '.RESTORELINKS' file" ); + } + free( tmp ); +} + +static void stop_restorelinks_file( void ) +{ + struct stat sb; + char *tmp = NULL, *cmd = NULL; + int len = 0; + + fflush( rlinks ); fclose( rlinks ); + + tmp = (char *)malloc( PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + (void)sprintf( (char *)&tmp[0], "%s/%s", tmpdir, ".RESTORELINKS" ); + + if( stat( tmp, &sb ) == 0 ) + { + if( S_ISREG(sb.st_mode) && sb.st_size != 0 ) + { + pid_t p = (pid_t) -1; + int rc; + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, "cp %s %s/ > /dev/null 2>&1", tmp, srcdir ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot create '.RESTORELINKS' file" ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + if( rc != 0 ) + { + FATAL_ERROR( "Cannot create '.RESTORELINKS' file" ); + } + + free( cmd ); + } + } + + free( tmp ); +} + + +static void save_link( const char *name_in, const char *name_to, const char *name_is ) +{ + if( rlinks ) + { + fprintf( rlinks, "( cd %s ; rm -rf %s )\n", name_in, name_is ); + fprintf( rlinks, "( cd %s ; ln -sf %s %s )\n", name_in, name_to, name_is ); + } +} + +/* + End of .RESTORELINKS functions. + *********************************************/ + +/********************************************* + File list functions: + */ +static void _print_filelist_entry( void *data, void *user_data ) +{ + const char *path = (const char *)data; + FILE *output = (FILE *)user_data; + + if( !output ) output = stdout; + + fprintf( output, "%s\n", path ); +} + +static void _free_filelist_entry( void *data, void *user_data ) +{ + if( data ) { free( data ); } +} + +static int _compare_fnames( const void *a, const void *b ) +{ + const char *s1 = (const char *)a; + const char *s2 = (const char *)b; + + return strcmp( s1, s2 ); +} + +static void _push_file( const char *name ) +{ + char *fname = (char *)name + strlen( srcdir ) + 1; + filelist = dlist_append( filelist, (void *)xstrdup( (const char *)fname ) ); +} + +static void _push_dir( const char *name ) +{ + char *buf = NULL; + char *dname = (char *)name + strlen( srcdir ) + 1; + + buf = (char *)malloc( strlen( dname ) + 2 ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + (void)sprintf( &buf[0], "%s/", dname ); + + filelist = dlist_append( filelist, (void *)xstrdup( (const char *)&buf[0] ) ); + free( buf ); +} + +static void _list_files( const char *dirpath ) +{ + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + if( stat( dirpath, &path_sb ) == -1 ) + { + FATAL_ERROR( "%s: Cannot stat source directory", dirpath ); + } + + if( S_ISDIR(path_sb.st_mode) == 0 ) + { + FATAL_ERROR( "%s: Source path is not a directory", dirpath ); + } + + if( (dir = opendir(dirpath) ) == NULL ) + { + FATAL_ERROR( "Canot access '%s' directory: %s", dirpath, strerror( errno ) ); + } + + len = strlen( dirpath ); + + while( (entry = readdir( dir )) != NULL) + { + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( lstat( path, &entry_sb ) == 0 ) + { + if( S_ISDIR(entry_sb.st_mode) ) + { + _push_dir( (const char *)path ); + pkgsize += (size_t)entry_sb.st_size; + _list_files( (const char *)path ); + } + else + { + if( S_ISREG(entry_sb.st_mode) ) + { + char *service = basename( path ); + + /* skip service files: */ + if( strcmp( service, ".DESCRIPTION" ) && + strcmp( service, ".FILELIST" ) && + strcmp( service, ".INSTALL" ) && + strcmp( service, ".PKGINFO" ) && + strcmp( service, ".REQUIRES" ) && + strcmp( service, ".RESTORELINKS" ) ) + { + _push_file( (const char *)path ); + pkgsize += (size_t)entry_sb.st_size; + ++nfiles; + } + } + if( S_ISBLK(entry_sb.st_mode) || + S_ISCHR(entry_sb.st_mode) || + S_ISFIFO(entry_sb.st_mode) || + S_ISSOCK(entry_sb.st_mode) ) + { + _push_file( (const char *)path ); + ++nfiles; + } + if( S_ISLNK(entry_sb.st_mode) ) + { + if( linkadd ) + { + char *buf = NULL; + ssize_t len = 0; + + const char *in = (char *)dirpath + strlen( srcdir ) + 1; + const char *is = (char *)path + strlen( dirpath ) + 1; + + buf = (char *)malloc( PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + if( (len = readlink( (const char *)path, buf, PATH_MAX - 1 )) == -1 ) + { + FATAL_ERROR( "%s: Cannot read link: %s", is, strerror( errno ) ); + } + buf[len] = '\0'; + save_link( in, (const char *)&buf[0], is ); + free( buf ); + + /* remove the link: */ + (void)unlink( (const char *)path ); + } + else + { + _push_file( (const char *)path ); + ++nfiles; + } + } + + } /* End if( S_ISDIR(entry_sb.st_mode) ) */ + + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + } + + closedir( dir ); +} + +static void create_file_list( void ) +{ + start_restorelinks_file(); + _list_files( (const char *)srcdir ); + stop_restorelinks_file(); + + if( filelist ) + { + FILE *flist = NULL; + char *tmp = NULL; + + tmp = (char *)malloc( PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + (void)sprintf( (char *)&tmp[0], "%s/%s", srcdir, ".FILELIST" ); + + flist = fopen( (const char *)&tmp[0], "w" ); + if( !flist ) + { + FATAL_ERROR( "Cannot create '.FILELIST' file" ); + } + + /********************************* + save total number of files: + */ + (void)sprintf( (char *)&tmp[0], "%d", nfiles ); + total_files = xstrdup( (const char *)&tmp[0] ); + + /********************************* + save uncompressed package size: + */ + { + int nd; + double sz = (double)pkgsize / (double)1024; + + if( sz > (double)1048576 ) + { + sz = sz / (double)1048576; + /* + NOTE: + ---- + Операция округления до одного знака после десятичной точки: sz = round(sz*10.0)/10.0; + здесь не нужна; можно обойтись вычислением количества цифр, выводимых на печать с помощью + формата '%.*g': + + Количество десятичных цифр, необходимое для предстваления целой части числа + 1(одна) + десятичная цифра после десятичной точки. Формат %.*g не будет выводить дробную часть + числа, если после округления, до одного знака после десятичной точки, дробная часть + равна нулю: + */ + nd = (int)ceil(log10(floor(sz) + 1.0)) + 1; + (void)sprintf( (char *)&tmp[0], "%.*gG", nd, sz ); + } + else if( sz > (double)1024 ) + { + sz = sz / (double)1024; + nd = (int)ceil(log10(floor(sz) + 1.0)) + 1; + (void)sprintf( (char *)&tmp[0], "%.*gM", nd, sz ); + } + else + { + nd = (int)ceil(log10(floor(sz) + 1.0)) + 1; + (void)sprintf( (char *)&tmp[0], "%.*gK", nd, sz ); + } + } + uncompressed_size = xstrdup( (const char *)&tmp[0] ); + + free( tmp ); + + filelist = dlist_sort( filelist, _compare_fnames ); + dlist_foreach( filelist, _print_filelist_entry, flist ); + + fflush( flist ); + fclose( flist ); + } + else + { + FATAL_ERROR( "There are no files in the source package" ); + } +} + +static void free_file_list( void ) +{ + if( filelist ) { dlist_free( filelist, _free_filelist_entry ); filelist = NULL; } +} +/* + End of file list functions. + *********************************************/ + +/********************************************* + Description functions. + */ +static void get_short_description( char *buf, const char *line ) +{ + char *s, *p, *q; + + if( buf ) { buf[0] = '\0'; s = buf; } + if( !line || line[0] == '\0' ) return; + + p = index( line, '(' ); + q = index( line, ')' ); + if( p && q && q > p ) + { + ++p; + while( *p && p < q ) + { + *s = *p; + ++p; ++s; + } + *s = '\0'; + } + else + { + /* + If short description declaration is incorrect at first line + of description; then we take whole first line of description: + */ + p = index( line, ':' ); ++p; + while( (*p == ' ' || *p == '\t') && *p != '\0' ) { ++p; } + strcpy( buf, p ); + } +} + +static char *read_short_description( FILE *fh ) +{ + char *ret = NULL; + char *buf = NULL; + + char *ln = NULL; + char *line = NULL; + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)line, PATH_MAX ); + + /* Get short_description from PACKAGE DESCRIPTION */ + ln = fgets( line, PATH_MAX, fh ); + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + + get_short_description( buf, (const char *)line ); + if( buf[0] != '\0' ) + { + ret = xstrdup( (const char *)buf ); + } + free( buf ); + + return ret; +} + +static void create_description_file( void ) +{ + struct stat sb; + char *buf = NULL, *tmp = NULL; + int n = 0; + + char *ln = NULL; + char *line = NULL; + + FILE *srcdesc = NULL; + FILE *tmpdesc = NULL; + FILE *outdesc = NULL; + + buf = (char *)malloc( PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + tmp = (char *)malloc( PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)line, PATH_MAX ); + + (void)sprintf( (char *)&tmp[0], "%s/%s", srcdir, ".DESCRIPTION" ); + + if( stat( tmp, &sb ) == 0 ) + { + if( S_ISREG(sb.st_mode) && sb.st_size != 0 ) + { + srcdesc = fopen( (const char *)&tmp[0], "r" ); + if( !srcdesc ) + { + FATAL_ERROR( "Cannot read source '.DESCRIPTION' file" ); + } + bzero( (void *)tmp, PATH_MAX ); + + (void)sprintf( (char *)&tmp[0], "%s/%s", tmpdir, ".DESCRIPTION" ); + tmpdesc = fopen( (const char *)&tmp[0], "w+" ); + if( !tmpdesc ) + { + FATAL_ERROR( "Cannot create output '.DESCRIPTION' file" ); + } + bzero( (void *)tmp, PATH_MAX ); + + (void)sprintf( (char *)&tmp[0], "%s/%s-%s-%s-%s-%s.txt", + destination, pkgname, pkgver, arch, distroname, distrover ); + outdesc = fopen( (const char *)&tmp[0], "w" ); + if( !outdesc ) + { + FATAL_ERROR( "Cannot create output '.DESCRIPTION' file" ); + } + bzero( (void *)tmp, PATH_MAX ); + + (void)sprintf( (char *)&buf[0], "%s:", pkgname ); + + fprintf( outdesc, "\n/* begin *\n\n" ); + + while( (ln = fgets( line, PATH_MAX, srcdesc )) && n < DESCRIPTION_NUMBER_OF_LINES ) + { + char *match = NULL; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + + if( (match = strstr( (const char *)ln, (const char *)buf )) && match == ln ) /* at start of line only */ + { + int mlen = strlen( match ), plen = strlen( buf ); + int length = ( mlen > plen ) ? (mlen - plen - 1) : 0 ; + + if( length > DESCRIPTION_LENGTH_OF_LINE ) + { + /* WARNING( "Package DESCRIPTION contains lines with length greater than %d characters", DESCRIPTION_LENGTH_OF_LINE ); */ + match[plen + 1 + DESCRIPTION_LENGTH_OF_LINE] = '\0'; /* truncating description line */ + skip_eol_spaces( match ); /* remove spaces at end-of-line */ + } + + fprintf( tmpdesc, "%s\n", match ); + match += plen + 1; + if( match[0] != '\0' ) { fprintf( outdesc, " %s\n", match ); } + else { fprintf( outdesc, "\n" ); } + ++n; + } + } + + fprintf( outdesc, " * end */\n" ); + + if( !short_description ) + { + /* try to get short description from .DESCRIPTION file */ + fseek( tmpdesc, 0, SEEK_SET ); + short_description = read_short_description( tmpdesc ); + } + + fflush( tmpdesc ); fclose( tmpdesc ); + fflush( outdesc ); fclose( outdesc ); + fclose( srcdesc ); + + /* Copy tmpdesc file to the source package directory: */ + { + char *cmd = NULL; + + bzero( (void *)tmp, PATH_MAX ); + (void)sprintf( (char *)&tmp[0], "%s/%s", tmpdir, ".DESCRIPTION" ); + + if( stat( tmp, &sb ) == 0 ) + { + if( S_ISREG(sb.st_mode) && sb.st_size != 0 ) + { + pid_t p = (pid_t) -1; + int rc, len = 0; + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, "cp %s %s/ > /dev/null 2>&1", tmp, srcdir ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot create output '.DESCRIPTION' file" ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + if( rc != 0 ) + { + FATAL_ERROR( "Cannot create output '.DESCRIPTION' file" ); + } + + free( cmd ); + } + } + } /* End of copy tmpdesc file. */ + } + } + + free( line ); + free( tmp ); + free( buf ); +} +/* + End of description functions. + *********************************************/ + +static void rewrite_pkginfo_file( void ) +{ + FILE *info = NULL; + char *tmp = NULL; + + tmp = (char *)malloc( PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + (void)sprintf( (char *)&tmp[0], "%s/%s", srcdir, ".PKGINFO" ); + + info = fopen( (const char *)&tmp[0], "w" ); + if( !info ) + { + FATAL_ERROR( "Cannot create '.PKGINFO' file" ); + } + + if( pkgname ) + { + (void)sprintf( (char *)&tmp[0], "pkgname=%s\n", pkgname ); + fprintf( info, (const char *)&tmp[0] ); + } + if( pkgver ) + { + (void)sprintf( (char *)&tmp[0], "pkgver=%s\n", pkgver ); + fprintf( info, (const char *)&tmp[0] ); + } + if( arch ) + { + (void)sprintf( (char *)&tmp[0], "arch=%s\n", arch ); + fprintf( info, (const char *)&tmp[0] ); + } + if( distroname ) + { + (void)sprintf( (char *)&tmp[0], "distroname=%s\n", distroname ); + fprintf( info, (const char *)&tmp[0] ); + } + if( distrover ) + { + (void)sprintf( (char *)&tmp[0], "distrover=%s\n", distrover ); + fprintf( info, (const char *)&tmp[0] ); + } + if( group ) + { + (void)sprintf( (char *)&tmp[0], "group=%s\n", group ); + fprintf( info, (const char *)&tmp[0] ); + } + if( short_description ) + { + (void)sprintf( (char *)&tmp[0], "short_description=\"%s\"\n", short_description ); + fprintf( info, (const char *)&tmp[0] ); + } + if( url ) + { + (void)sprintf( (char *)&tmp[0], "url=%s\n", url ); + fprintf( info, (const char *)&tmp[0] ); + } + if( license ) + { + (void)sprintf( (char *)&tmp[0], "license=%s\n", license ); + fprintf( info, (const char *)&tmp[0] ); + } + if( uncompressed_size ) + { + (void)sprintf( (char *)&tmp[0], "uncompressed_size=%s\n", uncompressed_size ); + fprintf( info, (const char *)&tmp[0] ); + } + if( total_files ) + { + (void)sprintf( (char *)&tmp[0], "total_files=%s\n", total_files ); + fprintf( info, (const char *)&tmp[0] ); + } + + free( tmp ); + + fflush( info ); + fclose( info ); +} + +static const char *fill_compressor( char *buffer, char compressor ) +{ + switch( compressor ) + { + default: + case 'J': + (void)sprintf( buffer, "xz -9 --threads=%d -c", get_nprocs() ); + break; + case 'j': + (void)sprintf( buffer, "bzip2 -9 -c" ); + break; + case 'z': + (void)sprintf( buffer, "gzip -9 -c" ); + break; + } + return (const char *)buffer; +} + +static void create_package( void ) +{ + pid_t p = (pid_t) -1; + int rc, len = 0; + + char *tmp = NULL, *cwd = NULL, *dst = NULL, *cmd = NULL; + +#define tar_suffix ".tar" +#define sha_suffix ".sha" +#define asc_suffix ".asc" +#define ACLS "--acls" +#define XATTRS "--xattrs" +#define HASHER "sha256sum -b" + + char compressor[64]; + (void)fill_compressor( (char *)&compressor[0], compress ); + + tmp = (char *)malloc( PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + cwd = (char *)malloc( PATH_MAX ); + if( !cwd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cwd, PATH_MAX ); + + dst = (char *)malloc( PATH_MAX ); + if( !dst ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)dst, PATH_MAX ); + + cmd = (char *)malloc( PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + /* absolute current directory path: */ + if( getcwd( cwd, (size_t)PATH_MAX ) == NULL ) + { + FATAL_ERROR( "%s-%s-%s-%s-%s%s: Cannot create PACKAGE: %s", + pkgname, pkgver, arch, distroname, distrover, txz_suffix, strerror( errno ) ); + } + remove_trailing_slash( cwd ); + + /**************************** + absolute destination path: + */ + (void)sprintf( (char *)&tmp[0], "%s", destination ); + if( tmp[0] != '/' ) + { + (void)sprintf( (char *)&dst[0], "%s/%s/%s", cwd, dirname( (char *)&tmp[0] ), basename( destination ) ); + } + else + { + (void)sprintf( (char *)&dst[0], "%s", destination ); + } + + /***************************************** + change CWD to source package directory: + */ + if( chdir( (const char *)srcdir ) == -1 ) + { + FATAL_ERROR( "Cannot change CWD to the package source directory" ); + } + + /************************************ + Set mode 0755 for .INSTALL script: + */ + (void)sprintf( (char *)&cmd[0], "chmod 0755 ./.INSTALL" ); + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + if( rc != 0 ) + { + FATAL_ERROR( "Cannot make .INSTAL script executable" ); + } + + /********************************** + push package files into tarball: + */ + len = snprintf( (char *)&cmd[0], PATH_MAX, + "find ./ | sed 's,^\\./,,' | " + "sed 's,\\.DESCRIPTION,,' | " + "sed 's,\\.FILELIST,,' | " + "sed 's,\\.INSTALL,,' | " + "sed 's,\\.PKGINFO,,' | " + "sed 's,\\.REQUIRES,,' | " + "sed 's,\\.RESTORELINKS,,' | " + "tar --no-recursion %s %s -T - -cvf %s/%s-%s-%s-%s-%s%s", + ACLS, XATTRS, dst, pkgname, pkgver, arch, distroname, distrover, tar_suffix ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot push package files into tarball" ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + if( rc != 0 ) + { + FATAL_ERROR( "Cannot push package files into tarball" ); + } + + /********************************** + push service files into tarball: + */ + len = snprintf( (char *)&cmd[0], PATH_MAX, + "find ./ -type f \\( -name '.DESCRIPTION' -o " + "-name '.FILELIST' -o " + "-name '.INSTALL' -o " + "-name '.PKGINFO' -o " + "-name '.REQUIRES' -o " + "-name '.RESTORELINKS' \\) | " + "sed 's,^\\./,,' | " + "tar --no-recursion %s %s -T - --append -f %s/%s-%s-%s-%s-%s%s", + ACLS, XATTRS, dst, pkgname, pkgver, arch, distroname, distrover, tar_suffix ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot push service files into tarball" ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + if( rc != 0 ) + { + FATAL_ERROR( "Cannot push service files into tarball" ); + } + + /********************************** + push service files into tarball: + */ + len = snprintf( (char *)&cmd[0], PATH_MAX, + "cat %s/%s-%s-%s-%s-%s%s | %s > %s/%s-%s-%s-%s-%s%s", + dst, pkgname, pkgver, arch, distroname, distrover, tar_suffix, + compressor, dst, pkgname, pkgver, arch, distroname, distrover, txz_suffix ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot compress tarball" ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + if( rc != 0 ) + { + FATAL_ERROR( "Cannot compress tarball" ); + } + + /****************************** + remove uncompressed tarball: + */ + len = snprintf( (char *)&cmd[0], PATH_MAX, + "rm -f %s/%s-%s-%s-%s-%s%s", + dst, pkgname, pkgver, arch, distroname, distrover, tar_suffix ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot remove umcompressed tarball" ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + if( rc != 0 ) + { + FATAL_ERROR( "Cannot remove umcompressed tarball" ); + } + + /*********************************** + NOTE: + To check SHA sum we can make use 'shasum' utility: + $ ( cd destination ; shasum --check filename.sha ) + without explicitly indicated algorithm [-a 256]. + + generate SHA-256 sum of tarball: + */ + len = snprintf( (char *)&cmd[0], PATH_MAX, + "%s %s/%s-%s-%s-%s-%s%s | sed 's,%s/,,' > %s/%s-%s-%s-%s-%s%s", HASHER, + dst, pkgname, pkgver, arch, distroname, distrover, txz_suffix, + dst, dst, pkgname, pkgver, arch, distroname, distrover, sha_suffix ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot generate SHA-256 sum of package tarball" ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + if( rc != 0 ) + { + FATAL_ERROR( "Cannot generate SHA-256 sum of package tarball" ); + } + +#if defined( HAVE_GPG2 ) + /******************************* + generate GPG ascii-signature: + */ + if( !gnupghome ) + { + struct stat st; + char *home = NULL; + + bzero( (void *)&st, sizeof( struct stat ) ); + + home = getenv( "GNUPGHOME" ); + if( home ) + { + (void)sprintf( tmp, "%s", home ); + if( (stat( (const char *)&tmp[0], &st ) == 0) && S_ISDIR(st.st_mode) ) + { + gnupghome = xstrdup( (const char *)&tmp[0] ); + } + } + else + { + home = getenv( "HOME" ); + if( home ) + { + (void)sprintf( tmp, "%s/.gnupg", home ); + if( (stat( (const char *)&tmp[0], &st ) == 0) && S_ISDIR(st.st_mode) ) + { + gnupghome = xstrdup( (const char *)&tmp[0] ); + } + } + } + /* Check GNUPGHOME directory: */ + while( gnupghome ) + { + (void)sprintf( tmp, "%s/%s", gnupghome, "private-keys-v1.d" ); + if( (stat( (const char *)&tmp[0], &st ) == -1) || !S_ISDIR(st.st_mode) ) + { + free( gnupghome ); gnupghome = NULL; + break; + } + (void)sprintf( tmp, "%s/pubring.kbx", gnupghome ); + if( (stat( (const char *)&tmp[0], &st ) == -1) || !S_ISREG(st.st_mode) ) + { + free( gnupghome ); gnupghome = NULL; + break; + } + (void)sprintf( tmp, "%s/trustdb.gpg", gnupghome ); + if( (stat( (const char *)&tmp[0], &st ) == -1) || !S_ISREG(st.st_mode) ) + { + free( gnupghome ); gnupghome = NULL; + break; + } + break; + } + } + if( gnupghome && passphrase && key_id ) + { + (void)sprintf( tmp, "%s/%s", tmpdir, ".gnupg" ); + if( _mkdir_p( tmp, S_IRWXU | S_IRWXG | S_IRWXO ) == -1 ) + { + FATAL_ERROR( "Cannot create temporary GnuPG home directory" ); + } + + len = snprintf( (char *)&cmd[0], PATH_MAX, + "chmod 700 %s ;" + " cp %s/trustdb.gpg %s/ ;" + " cp %s/pubring.kbx %s/ ;" + " cp -r %s/private-keys-v1.d %s/", + tmp, + gnupghome, tmp, + gnupghome, tmp, + gnupghome, tmp ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot prepare temporary GnuPG home for signing" ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + if( rc != 0 ) + { + ERROR( "Cannot prepare temporary GnuPG home for signing" ); + } + + len = snprintf( (char *)&cmd[0], PATH_MAX, + "cat %s | GNUPGHOME=%s gpg2 -u %s --batch --passphrase-fd=0" + " --pinentry-mode=loopback" + " --armor --yes --emit-version" + " --comment %s-%s" + " -o %s/%s-%s-%s-%s-%s%s" + " --detach-sign %s/%s-%s-%s-%s-%s%s 2>/dev/null 1>/dev/null", + passphrase, tmp, key_id, + pkgname, pkgver, + dst, pkgname, pkgver, arch, distroname, distrover, asc_suffix, + dst, pkgname, pkgver, arch, distroname, distrover, txz_suffix ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot generate GPG signature of package tarball" ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + if( rc != 0 ) + { + ERROR( "Cannot generate GPG signature of package tarball" ); + } + } +#endif + + /****************** + change CWD back: + */ + if( chdir( (const char *)&cwd[0] ) == -1 ) + { + FATAL_ERROR( "Cannot change CWD back" ); + } + + fprintf( stdout, "\n%s package %s/%s-%s-%s-%s-%s%s has been created.\n\n", + DISTRO_CAPTION, + destination, pkgname, pkgver, arch, distroname, distrover, txz_suffix ); + + free( cmd ); + free( dst ); + free( cwd ); + free( tmp ); +} + + + +/********************************************* + Get directory where this program is placed: + */ +char *get_selfdir( void ) +{ + char *buf = NULL; + ssize_t len; + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + bzero( (void *)buf, PATH_MAX ); + len = readlink( "/proc/self/exe", buf, (size_t)PATH_MAX ); + if( len > 0 && len < PATH_MAX ) + { + char *p = xstrdup( (const char *)dirname( buf ) ); + free( buf ); + return p; + } + FATAL_ERROR( "Cannot determine self directory. Please mount /proc filesystem" ); +} + +void set_stack_size( void ) +{ + const rlim_t stack_size = 16 * 1024 * 1024; /* min stack size = 16 MB */ + struct rlimit rl; + int ret; + + ret = getrlimit( RLIMIT_STACK, &rl ); + if( ret == 0 ) + { + if( rl.rlim_cur < stack_size ) + { + rl.rlim_cur = stack_size; + ret = setrlimit( RLIMIT_STACK, &rl ); + if( ret != 0 ) + { + fprintf(stderr, "setrlimit returned result = %d\n", ret); + FATAL_ERROR( "Cannot set stack size" ); + } + } + } +} + + +int main( int argc, char *argv[] ) +{ + gid_t gid; + + set_signal_handlers(); + + gid = getgid(); + setgroups( 1, &gid ); + + fatal_error_hook = fatal_error_actions; + + selfdir = get_selfdir(); + + errlog = stderr; + + program = basename( argv[0] ); + get_args( argc, argv ); + + /* set_stack_size(); */ + + tmpdir = _mk_tmpdir(); + if( !tmpdir ) + { + FATAL_ERROR( "Cannot create temporary directory" ); + } + + read_pkginfo(); + tune_destinations(); + create_file_list(); + create_description_file(); + /* + NOTE: + rewrite_pkginfo_file() should be called after create_description_file() + because if there is no short description in the source .PKGINFO file + then we can try to get the short description from the first line of + .DESCRIPTION file (between opening and closing round brackets). + */ + rewrite_pkginfo_file(); + create_package(); + + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + + exit( exit_status ); +} diff --git a/src/make-pkglist.c b/src/make-pkglist.c new file mode 100644 index 0000000..103d540 --- /dev/null +++ b/src/make-pkglist.c @@ -0,0 +1,3155 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <sys/types.h> +#include <stdint.h> +#include <dirent.h> +#include <sys/stat.h> /* chmod(2) */ +#include <sys/file.h> /* flock(2) */ +#include <fcntl.h> +#include <linux/limits.h> +#include <alloca.h> /* alloca(3) */ +#include <string.h> /* strdup(3) */ +#include <strings.h> /* index(3) */ +#include <libgen.h> /* basename(3) */ +#include <ctype.h> /* tolower(3) */ +#include <errno.h> +#include <time.h> +#include <sys/time.h> +#include <pwd.h> +#include <grp.h> +#include <stdarg.h> +#include <unistd.h> + +#include <math.h> + +#include <sys/wait.h> + +#include <sys/resource.h> + +#include <signal.h> +#if !defined SIGCHLD && defined SIGCLD +# define SIGCHLD SIGCLD +#endif + +#define _GNU_SOURCE +#include <getopt.h> + + +#include <msglog.h> +#include <wrapper.h> +#include <system.h> + +#include <dlist.h> +#include <pkglist.h> + +#define PROGRAM_NAME "make-pkglist" + +#include <defs.h> + + +char *program = PROGRAM_NAME; +char *srcdir = NULL, *pkglist_fname = NULL, + *tmpdir = NULL; + +char *srclist_fname = NULL; + +struct srcpkg_fname +{ + char *name; + int line; +}; + +struct dlist *srcpkg_fnames = NULL; + +int exit_status = EXIT_SUCCESS; /* errors counter */ +char *selfdir = NULL; + +int __done = 0, __child = 0; + +enum _output_format { + OFMT_LIST = 0, + OFMT_JSON, + + OFMT_UNKNOWN +} output_format = OFMT_LIST; + +enum _tree_format tree_format = TFMT_BIN; + +enum _input_type { + IFMT_PKG = 0, + IFMT_LOG, + + IFMT_UNKNOWN +} input_format = IFMT_UNKNOWN; + +enum _priority priority = REQUIRED; + +/*************************************************************** + Exclude declarations: + */ +static struct dlist *exclude = NULL; + +static void add_exclude( const char *name ); +static void free_exclude( void ); + +static void read_exclude_list( const char *optarg ); +/* + End of exclude declarstions. + ***************************************************************/ + +/*************************************************************** + Source file names functions: + */ +static struct srcpkg_fname *srcpkg_fname_alloc( const char *name, int line ) +{ + struct srcpkg_fname *fname = NULL; + + fname = (struct srcpkg_fname *)malloc( sizeof( struct srcpkg_fname ) ); + if( !fname ) { FATAL_ERROR( "Cannot allocate memory" ); } + + fname->name = xstrdup( name ); + fname->line = line; + + return fname; +} + +static void srcpkg_fname_free( struct srcpkg_fname *fname ) +{ + if( fname ) + { + if( fname->name ) { free( fname->name ); fname->name = NULL; } + free( fname ); + } +} + +static void __srcpkg_fname_free_func( void *data, void *user_data ) +{ + struct srcpkg_fname *fname = (struct srcpkg_fname *)data; + if( fname ) { srcpkg_fname_free( fname ); } +} + +static void free_srcpkg_fnames( void ) +{ + if( srcpkg_fnames ) { dlist_free( srcpkg_fnames, __srcpkg_fname_free_func ); srcpkg_fnames = NULL; } +} + +static void add_srcpkg_fname( struct srcpkg_fname *fname ) +{ + if( fname ) + srcpkg_fnames = dlist_append( srcpkg_fnames, (void *)fname ); +} +/* + End of source file names functions. + ***************************************************************/ + + +void free_resources() +{ + if( selfdir ) { free( selfdir ); selfdir = NULL; } + if( srcdir ) { free( srcdir ); srcdir = NULL; } + if( srclist_fname ) { free( srclist_fname ); srclist_fname = NULL; } + if( pkglist_fname ) { free( pkglist_fname ); pkglist_fname = NULL; } + + free_exclude(); + + free_tarballs(); + free_packages(); + free_srcpkg_fnames(); + free_srcpkgs(); +} + +void usage() +{ + free_resources(); + + fprintf( stdout, "\n" ); + fprintf( stdout, "Usage: %s [options] <pkglist>\n", program ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Create Packages List in the installation order from set of PKGLOGs\n" ); + fprintf( stdout, "or set of PACKAGEs placed in the source directory. If the source\n" ); + fprintf( stdout, "directory is not defined then directory of <pkglist> will be used\n" ); + fprintf( stdout, "as source PACKAGE directory.\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Options:\n" ); + fprintf( stdout, " -h,--help Display this information.\n" ); + fprintf( stdout, " -v,--version Display the version of %s utility.\n", program ); + fprintf( stdout, " -s,--source=<DIR|pkg|log> Setup Database packages directory or directory\n" ); + fprintf( stdout, " where set of PACKAGEs is placed.\n" ); + fprintf( stdout, " If the source argument is a PACKAGE or PKGLOG\n" ); + fprintf( stdout, " file, then the source directory will be represented\n" ); + fprintf( stdout, " as the base directory of the source file excluding\n" ); + fprintf( stdout, " the group directory if defined.\n" ); + fprintf( stdout, " -F,--files-from=<FILE> Get file names of PACKAGEs or PKGLOGs from FILE.\n" ); + fprintf( stdout, " Each line of the FILE must contain a path to the\n" ); + fprintf( stdout, " PACKAGE or PKGLOG file relative to the directory\n" ); + fprintf( stdout, " specified by option --source.\n" ); + fprintf( stdout, " -e,--exclude=<pname|plist> Ignore package with <pname> or the comma\n" ); + fprintf( stdout, " separated list of package names.\n" ); + fprintf( stdout, " -i,--iformat=<pkg|log> Input format: PACKAGEs or PKGLOGs.\n" ); + fprintf( stdout, " -o,--oformat=<list|json> Output format: LIST or JSON.\n" ); + + fprintf( stdout, " -t,--tformat=<dag|bin> Tree format: DAG or BIN (default BIN).\n" ); + + fprintf( stdout, " -m,--minimize Create .min.json files. Applicable\n" ); + fprintf( stdout, " for JSON output format.\n" ); + + fprintf( stdout, " -p,--prioriy=<PRIORITY> Default install priority: REQ|REC|OPT|SKP.\n" ); + fprintf( stdout, " -w,--hardware=<HARDWARE> Hardware Name used for JSON output format.\n" ); + fprintf( stdout, " -H,--htmlroot=<HTMLROOT> Optional Requires tree HTMLROOT name used\n" ); + fprintf( stdout, " for JSON output format.\n" ); + + fprintf( stdout, "\n" ); + fprintf( stdout, "Parameter:\n" ); + fprintf( stdout, " <pkglist> Output PKGLIST file name or a target\n" ); + fprintf( stdout, " directory to save output PKGLIST.\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "If HTMLROOT is not defined by option --htmlroot then HTMLROOT will be set\n" ); + fprintf( stdout, "as basename of target <pkglist> file in lower case without extension.\n" ); + fprintf( stdout, "\n" ); + + exit( EXIT_FAILURE ); +} + +void to_lowercase( char *s ) +{ + char *p = s; + while( p && *p ) { int c = *p; *p = tolower( c ); ++p; } +} + +void to_uppercase( char *s ) +{ + char *p = s; + while( p && *p ) { int c = *p; *p = toupper( c ); ++p; } +} + +void version() +{ + char *upper = NULL; + + upper = (char *)alloca( strlen( program ) + 1 ); + + strcpy( (char *)upper, (const char *)program ); + to_uppercase( upper ); + + fprintf( stdout, "%s (%s) %s\n", program, upper, PROGRAM_VERSION ); + + fprintf( stdout, "Copyright (C) 2019 Andrey V.Kosteltsev.\n" ); + fprintf( stdout, "This is free software. There is NO warranty; not even\n" ); + fprintf( stdout, "for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" ); + fprintf( stdout, "\n" ); + + free_resources(); + exit( EXIT_SUCCESS ); +} + + +static void remove_trailing_slash( char *dir ) +{ + char *s; + + if( !dir || dir[0] == '\0' ) return; + + s = dir + strlen( dir ) - 1; + while( *s == '/' ) + { + *s = '\0'; --s; + } +} + + +static int _mkdir_p( const char *dir, const mode_t mode ) +{ + char *buf; + char *p = NULL; + struct stat sb; + + if( !dir ) return -1; + + buf = (char *)alloca( strlen( dir ) + 1 ); + strcpy( buf, dir ); + + remove_trailing_slash( buf ); + + /* check if path exists and is a directory */ + if( stat( buf, &sb ) == 0 ) + { + if( S_ISDIR(sb.st_mode) ) + { + return 0; + } + } + + /* mkdir -p */ + for( p = buf + 1; *p; ++p ) + { + if( *p == '/' ) + { + *p = 0; + /* test path */ + if( stat( buf, &sb ) != 0 ) + { + /* path does not exist - create directory */ + if( mkdir( buf, mode ) < 0 ) + { + return -1; + } + } else if( !S_ISDIR(sb.st_mode) ) + { + /* not a directory */ + return -1; + } + *p = '/'; + } + } + + /* test path */ + if( stat( buf, &sb ) != 0 ) + { + /* path does not exist - create directory */ + if( mkdir( buf, mode ) < 0 ) + { + return -1; + } + } else if( !S_ISDIR(sb.st_mode) ) + { + /* not a directory */ + return -1; + } + + return 0; +} + + +static void _rm_tmpdir( const char *dirpath ) +{ + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + if( stat( dirpath, &path_sb ) == -1 ) + { + return; /* stat returns error code; errno is set */ + } + + if( S_ISDIR(path_sb.st_mode) == 0 ) + { + return; /* dirpath is not a directory */ + } + + if( (dir = opendir(dirpath) ) == NULL ) + { + return; /* Cannot open direcroty; errno is set */ + } + + len = strlen( dirpath ); + + while( (entry = readdir( dir )) != NULL) + { + + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( stat( path, &entry_sb ) == 0 ) + { + if( S_ISDIR(entry_sb.st_mode) ) + { + /* recursively remove a nested directory */ + _rm_tmpdir( path ); + } + else + { + /* remove a file object */ + (void)unlink( path ); + } + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + + } + + /* remove the devastated directory and close the object of this directory */ + (void)rmdir( dirpath ); + + closedir( dir ); +} + + +static char *_mk_tmpdir( void ) +{ + char *buf = NULL, *p, *tmp = "/tmp"; + size_t len = 0, size = 0; + + (void)umask( S_IWGRP | S_IWOTH ); /* octal 022 */ + + /* Get preferred directory for tmp files */ + if( (p = getenv( "TMP" )) != NULL ) { + tmp = p; + } + else if( (p = getenv( "TEMP" )) != NULL ) { + tmp = p; + } + + size = strlen( tmp ) + strlen( DISTRO_NAME ) + strlen( program ) + 12; + + buf = (char *)malloc( size ); + if( !buf ) return NULL; + + len = snprintf( buf, size, (const char *)"%s/%s/%s-%.7u", tmp, DISTRO_NAME, program, getpid() ); + if( len == 0 || len == size - 1 ) + { + free( buf ); return NULL; + } + + _rm_tmpdir( (const char *)&buf[0] ); + + if( _mkdir_p( buf, S_IRWXU | S_IRWXG | S_IRWXO ) == 0 ) + { + return buf; + } + + free( buf ); return NULL; +} + + + +void fatal_error_actions( void ) +{ + logmsg( errlog, MSG_NOTICE, "Free resources on FATAL error..." ); + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); +} + +void sigint( int signum ) +{ + (void)signum; + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); +} + +void sigchld( int signum ) +{ + pid_t pid = 0; + int status; + + (void)signum; + + while( (pid = waitpid( -1, &status, WNOHANG )) > 0 ) + { + ; /* One of children with 'pid' is terminated */ + + if( WIFEXITED( status ) ) + { + if( (int) WEXITSTATUS (status) > 0 ) + { + ++exit_status; /* printf( "Child %d returned non zero status: %d\n", pid, (int)WEXITSTATUS (status) ); */ + } + else + { + ; /* printf( "Child %d terminated with status: %d\n", pid, (int)WEXITSTATUS (status) ); */ + } + } + else if( WIFSIGNALED( status ) ) + { + ++exit_status; /* printf( "Child %d terminated on signal: %d\n", pid, WTERMSIG( status ) ); */ + } + else + { + ++exit_status; /* printf( "Child %d terminated on unknown reason\n", pid ); */ + } + + } + + if( pid == -1 && errno == ECHILD ) + { + /* No child processes: */ + __done = 1; + } + return; +} + + +static void set_signal_handlers() +{ + struct sigaction sa; + sigset_t set; + + memset( &sa, 0, sizeof( sa ) ); + sa.sa_handler = sigint; /* TERM, INT */ + sa.sa_flags = SA_RESTART; + sigemptyset( &set ); + sigaddset( &set, SIGTERM ); + sigaddset( &set, SIGINT ); + sa.sa_mask = set; + sigaction( SIGTERM, &sa, NULL ); + sigaction( SIGINT, &sa, NULL ); + + /* System V fork+wait does not work if SIGCHLD is ignored */ + memset( &sa, 0, sizeof( sa ) ); + sa.sa_handler = sigchld; /* CHLD */ + sa.sa_flags = SA_RESTART; + sigemptyset( &set ); + sigaddset( &set, SIGCHLD ); + sa.sa_mask = set; + sigaction( SIGCHLD, &sa, NULL ); + + memset( &sa, 0, sizeof( sa ) ); /* ignore SIGPIPE */ + sa.sa_handler = SIG_IGN; + sa.sa_flags = 0; + sigaction( SIGPIPE, &sa, NULL ); +} + + +static enum _input_type check_input_file( char *uncompress, const char *fname ) +{ + struct stat st; + size_t pkglog_size = 0; + unsigned char buf[8]; + int rc, fd; + + /* SIGNATURES: https://www.garykessler.net/library/file_sigs.html */ + + if( uncompress ) + { + *uncompress = '\0'; + } + + if( stat( fname, &st ) == -1 ) + { + FATAL_ERROR( "Cannot access %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + + pkglog_size = st.st_size; + + if( (fd = open( fname, O_RDONLY )) == -1 ) + { + FATAL_ERROR( "Cannot open %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + + rc = (int)read( fd, (void *)&buf[0], 7 ); + if( rc != 7 ) + { + close( fd ); return IFMT_UNKNOWN; + } + buf[7] = '\0'; + + /* TEXT */ + if( !strncmp( (const char *)&buf[0], "PACKAGE", 7 ) ) + { + close( fd ); return IFMT_LOG; + } + + /* GZ */ + if( buf[0] == 0x1F && buf[1] == 0x8B && buf[2] == 0x08 ) + { + if( uncompress ) { *uncompress = 'x'; } + close( fd ); return IFMT_PKG; + } + + /* BZ2 */ + if( buf[0] == 0x42 && buf[1] == 0x5A && buf[2] == 0x68 ) + { + if( uncompress ) { *uncompress = 'j'; } + close( fd ); return IFMT_PKG; + } + + /* XZ */ + if( buf[0] == 0xFD && buf[1] == 0x37 && buf[2] == 0x7A && + buf[3] == 0x58 && buf[4] == 0x5A && buf[5] == 0x00 ) + { + if( uncompress ) { *uncompress = 'J'; } + close( fd ); return IFMT_PKG; + } + + if( pkglog_size > 262 ) + { + if( lseek( fd, 257, SEEK_SET ) == -1 ) + { + FATAL_ERROR( "Cannot check signature of %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + rc = (int)read( fd, &buf[0], 5 ); + if( rc != 5 ) + { + FATAL_ERROR( "Cannot read signature of %s file", basename( (char *)fname ) ); + } + /* TAR */ + if( buf[0] == 0x75 && buf[1] == 0x73 && buf[2] == 0x74 && buf[3] == 0x61 && buf[4] == 0x72 ) + { + close( fd ); return IFMT_PKG; + } + } + + close( fd ); return IFMT_UNKNOWN; +} + + +void get_args( int argc, char *argv[] ) +{ + const char* short_options = "hvme:s:F:o:i:t:p:w:H:"; + + const struct option long_options[] = + { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'v' }, + { "minimize", no_argument, NULL, 'm' }, + { "exclude", required_argument, NULL, 'e' }, + { "source", required_argument, NULL, 's' }, + { "files-from", required_argument, NULL, 'F' }, + { "oformat", required_argument, NULL, 'o' }, + { "iformat", required_argument, NULL, 'i' }, + { "tformat", required_argument, NULL, 't' }, + { "priority", required_argument, NULL, 'p' }, + { "hardware", required_argument, NULL, 'w' }, + { "htmlroot", required_argument, NULL, 'H' }, + { NULL, 0, NULL, 0 } + }; + + int ret; + int option_index = 0; + + while( (ret = getopt_long( argc, argv, short_options, long_options, &option_index )) != -1 ) + { + switch( ret ) + { + case 'h': + { + usage(); + break; + } + case 'v': + { + version(); + break; + } + case 'm': + { + minimize = 1; + break; + } + + case 'e': + { + if( optarg != NULL ) + { + read_exclude_list( (const char *)optarg ); + } + else + /* option is present but without value */ + usage(); + break; + } + + case 's': + { + if( optarg != NULL ) + { + srcdir = xstrdup( (const char *)optarg ); + remove_trailing_slash( srcdir ); + } + else + /* option is present but without value */ + usage(); + break; + } + case 'F': + { + if( optarg != NULL ) + { + srclist_fname = xstrdup( (const char *)optarg ); + remove_trailing_slash( srclist_fname ); + } + else + /* option is present but without value */ + usage(); + break; + } + case 'o': + { + char *fmt = (char *)alloca( strlen( optarg ) + 1 ); + + strcpy( (char *)fmt, (const char *)optarg ); + to_lowercase( fmt ); + + if( !strcmp( fmt, "list" ) ) { output_format = OFMT_LIST; } + else if( !strcmp( fmt, "json" ) ) { output_format = OFMT_JSON; } + else + { + ERROR( "Invalid output format: %s", fmt ); + usage(); + } + break; + } + case 'i': + { + char *fmt = (char *)alloca( strlen( optarg ) + 1 ); + + strcpy( (char *)fmt, (const char *)optarg ); + to_lowercase( fmt ); + + if( !strcmp( fmt, "pkg" ) ) { input_format = IFMT_PKG; } + else if( !strcmp( fmt, "log" ) ) { input_format = IFMT_LOG; } + else + { + ERROR( "Invalid input format: %s", fmt ); + usage(); + } + break; + } + case 't': + { + char *fmt = (char *)alloca( strlen( optarg ) + 1 ); + + strcpy( (char *)fmt, (const char *)optarg ); + to_lowercase( fmt ); + + if( !strcmp( fmt, "bin" ) ) { tree_format = TFMT_BIN; } + else if( !strcmp( fmt, "dag" ) ) { tree_format = TFMT_DAG; } + else + { + ERROR( "Invalid tree format: %s", fmt ); + usage(); + } + break; + } + case 'p': + { + char *p = (char *)alloca( strlen( optarg ) + 1 ); + + strcpy( (char *)p, (const char *)optarg ); + to_lowercase( p ); + + if( !strcmp( p, "required" ) || !strcmp( p, "req" ) ) { priority = REQUIRED; } + else if( !strcmp( p, "recommended" ) || !strcmp( p, "rec" ) ) { priority = RECOMMENDED; } + else if( !strcmp( p, "optional" ) || !strcmp( p, "opt" ) ) { priority = OPTIONAL; } + else if( !strcmp( p, "skip" ) || !strcmp( p, "skp" ) ) { priority = SKIP; } + else + { + ERROR( "Invalid default install priority: %s", p ); + usage(); + } + break; + } + case 'w': + { + char *hw = (char *)alloca( strlen( optarg ) + 1 ); + + strcpy( (char *)hw, (const char *)optarg ); + to_lowercase( hw ); + + hardware = xstrdup( (const char *)hw ); + break; + } + case 'H': + { + char *root = (char *)alloca( strlen( optarg ) + 1 ); + + strcpy( (char *)root, (const char *)optarg ); + to_lowercase( root ); + + htmlroot = xstrdup( (const char *)root ); + break; + } + + case '?': default: + { + usage(); + break; + } + } + } + + + /* last command line argument is the output PKGLIST file */ + if( optind < argc ) + { + struct stat st; + char *buf = NULL; + size_t hrl = 0; + + bzero( (void *)&st, sizeof( struct stat ) ); + + if( htmlroot ) { hrl = strlen( htmlroot ); } + + buf = (char *)malloc( strlen( (const char *)argv[optind] ) + hrl + 10 ); + if( !buf ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + (void)strcpy( buf, (const char *)argv[optind++] ); + remove_trailing_slash( (char *)&buf[0] ); + + stat( (const char *)&buf[0], &st ); /* Do not check return status */ + + if( S_ISDIR(st.st_mode) ) + { + if( ! srcdir ) + { + srcdir = xstrdup( (const char *)&buf[0] ); + } + /* Add .pkglist or .json to the output dir name: */ + if( htmlroot ) + { + (void)strcat( buf, "/" ); + (void)strcat( buf, htmlroot ); + + if( output_format == OFMT_LIST ) + (void)strcat( buf, ".pkglist" ); + else + (void)strcat( buf, ".json" ); + } + else + { + if( output_format == OFMT_LIST ) + (void)strcat( buf, "/.pkglist" ); + else + (void)strcat( buf, "/.json" ); + } + } + + if( !hardware ) + hardware = xstrdup( "unknown" ); + + /* Check output directory; set srcdir and htmlroot if needed */ + { + char *d, *f, *fname; + size_t len; + + if( !rindex( (const char *)&buf[0], '/' ) ) + { + d = "."; + f = (char *)&buf[0]; + len = strlen( f ) + 3; + } + else + { + f = basename( (char *)&buf[0] ); + d = dirname( (char *)&buf[0] ); /* dirname() cuts the filename from buf[] */ + len = strlen( d ) + strlen( f ) + 2; + } + + fname = (char *)alloca( len ); + (void)sprintf( fname, "%s/%s", d, f ); + + if( stat( (const char *)d, &st ) == -1 ) + { + FATAL_ERROR( "Cannot access output '%s' directory: %s", d, strerror( errno ) ); + } + + pkglist_fname = xstrdup( (const char *)fname ); + if( pkglist_fname == NULL ) { usage(); } + + if( !srcdir ) { srcdir = xstrdup( (const char *)d ); } + + if( !htmlroot ) + { + char *p = NULL; + if( (p = rindex( (const char *)f, '.' )) && p != f ) + { + *p = '\0'; + to_lowercase( f ); + htmlroot = xstrdup( (const char *)f ); + } + else if( *f != '.' ) + { + to_lowercase( f ); + htmlroot = xstrdup( (const char *)f ); + } + else + { + htmlroot = xstrdup( (const char *)hardware ); + } + } + } + + free( buf ); + } + else + { + usage(); + } +} + + +/*************************************************************** + Exclude functions: + */ +static void add_exclude( const char *name ) +{ + exclude = dlist_append( exclude, (void *)xstrdup( name ) ); +} + +static void __free_exclude( void *data, void *user_data ) +{ + if( data ) { free( data ); } +} + +static void free_exclude( void ) +{ + if( exclude ) { dlist_free( exclude, __free_exclude ); exclude = NULL; } +} + +static int __compare_exclude( const void *a, const void *b ) +{ + return strncmp( (const char *)a, (const char *)b, (size_t)strlen((const char *)b) ); +} + +static const char *find_exclude( const char *name ) +{ + struct dlist *node = NULL; + + if( !exclude || !name ) return NULL; + + node = dlist_find_data( exclude, __compare_exclude, (const void *)name ); + if( node ) + { + return (const char *)node->data; + } + + return NULL; +} + +static void read_exclude_list( const char *optarg ) +{ + char *name = NULL, *p = NULL; + + name = p = (char *)optarg; + + if( !p || *p == '\0' ) return; + + while( *p == ',' ) { *p = '\0'; ++p; } + name = p; + + while( p && *p != '\0' ) + { + ++p; + + if( *p == ',' ) + { + while( *p == ',' ) { *p = '\0'; ++p; } + if( name && *name != '\0' ) { add_exclude( (const char *)name ); } + name = p; + } + + if( *p == '\0' ) + { + if( name && *name != '\0' ) { add_exclude( (const char *)name ); } + } + } +} +/* + End of exclude functions. + ***************************************************************/ + + +/*************************************************************** + Extract functions: + */ +static void _extract_pkglog( const char *group, const char *fname ) +{ + enum _input_type type = IFMT_UNKNOWN; + char uncompress = '\0'; + + type = check_input_file( &uncompress, fname ); + + if( type == IFMT_PKG ) + { + int len = 0; + char *tmp= NULL, *cmd = NULL, *tgz = NULL; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + if( group ) { (void)sprintf( &tmp[0], "%s/%s", tmpdir, group ); } + else { (void)sprintf( &tmp[0], "%s", tmpdir ); } + + if( _mkdir_p( tmp, S_IRWXU | S_IRWXG | S_IRWXO ) != 0 ) + { + ERROR( "Cannot save PKGLOG from '%s' file", basename( (char *)fname ) ); + free( tmp ); + return; + } + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, "%s/pkglog -d %s %s > /dev/null 2>&1", selfdir, tmp, fname ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot get PKGLOG from %s file", basename( (char *)fname ) ); + } + (void)sys_exec_command( cmd ); + ++__child; + + tgz = (char *)malloc( (size_t)PATH_MAX ); + if( !tgz ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tgz, PATH_MAX ); + (void)sprintf( &tgz[0], "%s/%s", group, basename( (char *)fname ) ); + add_tarball( (char *)&tgz[0] ); + + free( tmp ); + free( cmd ); + free( tgz ); + } +} + +static void _search_packages( const char *dirpath, const char *grp ) +{ + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + if( stat( dirpath, &path_sb ) == -1 ) + { + FATAL_ERROR( "%s: Cannot stat Setup Database or destination directory", dirpath ); + } + + if( S_ISDIR(path_sb.st_mode) == 0 ) + { + FATAL_ERROR( "%s: Setup Database or destination is not a directory", dirpath ); + } + + if( (dir = opendir(dirpath) ) == NULL ) + { + FATAL_ERROR( "Canot access %s directory: %s", dirpath, strerror( errno ) ); + } + + len = strlen( dirpath ); + + while( (entry = readdir( dir )) != NULL) + { + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( stat( path, &entry_sb ) == 0 ) + { + if( S_ISREG(entry_sb.st_mode) ) + { + _extract_pkglog( grp, (const char *)path ); + } + if( S_ISDIR(entry_sb.st_mode) && grp == NULL ) + { + _search_packages( (const char *)path, (const char *)entry->d_name ); + } + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + } + + closedir( dir ); +} + +/************************************************************** + extract_pkglogs() - returns number of extracted PKGLOGS + or 0 if no packages found in the srcdir. + The exit_status has been set. + */ +int extract_pkglogs( void ) +{ + int ret = 0; + + __done = 0; __child = 0; + + _search_packages( (const char *)srcdir, NULL ); + + if( __child > 0 ) + { + while( !__done ) usleep( 1 ); + ret = __child; + } + + __done = 0; __child = 0; + + return ret; +} +/* + End of Extract functions. + ***************************************************************/ + + +/*************************************************************** + Copy functions: + */ +static void _copy_pkglog( const char *group, const char *fname ) +{ + enum _input_type type = IFMT_UNKNOWN; + char uncompress = '\0'; + + type = check_input_file( &uncompress, fname ); + + if( type == IFMT_LOG ) + { + int len = 0; + char *tmp= NULL, *cmd = NULL; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + if( group ) { (void)sprintf( &tmp[0], "%s/%s", tmpdir, group ); } + else { (void)sprintf( &tmp[0], "%s", tmpdir ); } + + if( _mkdir_p( tmp, S_IRWXU | S_IRWXG | S_IRWXO ) != 0 ) + { + ERROR( "Cannot copy '%s' PKGLOG file", basename( (char *)fname ) ); + free( tmp ); + return; + } + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, "cp %s %s/ > /dev/null 2>&1", fname, tmp ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot copy %s PKGLOG file", basename( (char *)fname ) ); + } + (void)sys_exec_command( cmd ); + ++__child; + + free( tmp ); + free( cmd ); + } +} + +static void _search_pkglogs( const char *dirpath, const char *grp ) +{ + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + if( stat( dirpath, &path_sb ) == -1 ) + { + FATAL_ERROR( "%s: Cannot stat Setup Database or destination directory", dirpath ); + } + + if( S_ISDIR(path_sb.st_mode) == 0 ) + { + FATAL_ERROR( "%s: Setup Database or destination is not a directory", dirpath ); + } + + if( (dir = opendir(dirpath) ) == NULL ) + { + FATAL_ERROR( "Canot access %s directory: %s", dirpath, strerror( errno ) ); + } + + len = strlen( dirpath ); + + while( (entry = readdir( dir )) != NULL) + { + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( stat( path, &entry_sb ) == 0 ) + { + if( S_ISREG(entry_sb.st_mode) ) + { + _copy_pkglog( grp, (const char *)path ); + } + if( S_ISDIR(entry_sb.st_mode) && grp == NULL ) + { + _search_pkglogs( (const char *)path, (const char *)entry->d_name ); + } + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + } + + closedir( dir ); +} + +/*********************************************************** + copy_pkglogs() - returns number of copied PKGLOGS + or 0 if no packages found in the srcdir. + The exit_status has been set. + */ +int copy_pkglogs( void ) +{ + int ret = 0; + + __done = 0; __child = 0; + + _search_pkglogs( (const char *)srcdir, NULL ); + + if( __child > 0 ) + { + while( !__done ) usleep( 1 ); + ret = __child; + } + + __done = 0; __child = 0; + + return ret; +} +/* + Enf of Copy functions. + ***************************************************************/ + + +/*********************************************************** + Remove leading spaces and take non-space characters only: + (Especialy for pkginfo lines) + */ +static char *skip_spaces( char *s ) +{ + char *q, *p = (char *)0; + + if( !s || *s == '\0' ) return p; + + p = s; + + while( (*p == ' ' || *p == '\t') && *p != '\0' ) { ++p; } q = p; + while( *q != ' ' && *q != '\t' && *q != '\0' ) { ++q; } *q = '\0'; + + if( *p == '\0' ) return (char *)0; + + return( xstrdup( (const char *)p ) ); +} + +/******************************* + remove spaces at end of line: + */ +static void skip_eol_spaces( char *s ) +{ + char *p = (char *)0; + + if( !s || *s == '\0' ) return; + + p = s + strlen( s ) - 1; + while( isspace( *p ) ) { *p-- = '\0'; } +} + +static size_t read_usize( char *s ) +{ + size_t size = 0; + size_t mult = 1; + double sz = 0.0; + + char suffix; + char *q, *p = (char *)0; + + if( !s || *s == '\0' ) return size; + + p = s; + + while( (*p == ' ' || *p == '\t') && *p != '\0' ) { ++p; } q = p; + while( *q != ' ' && *q != '\t' && *q != '\0' ) { ++q; } *q = '\0'; + + if( *p == '\0' ) return size; + + --q; + suffix = *q; + switch( suffix ) + { + /* by default size calculates in KiB - 1024 Bytes (du -s -h .) */ + case 'G': + case 'g': + mult = 1024 * 1024; + *q = '\0'; + break; + case 'M': + case 'm': + mult = 1024; + *q = '\0'; + break; + case 'K': + case 'k': + *q = '\0'; + break; + default: + break; + } + + if( sscanf( p, "%lg", &sz ) != 1 ) return size; + + return (size_t)round( sz * (double)mult ); +} + +static int read_total_files( char *s ) +{ + int n = 0; + char *q, *p = (char *)0; + + if( !s || *s == '\0' ) return n; + + p = s; + + while( (*p == ' ' || *p == '\t') && *p != '\0' ) { ++p; } q = p; + while( *q != ' ' && *q != '\t' && *q != '\0' ) { ++q; } *q = '\0'; + + if( *p == '\0' ) return n; + + if( sscanf( p, "%u", &n ) != 1 ) return 0; + + return n; +} + + +static struct pkg *input_package( const char *pkginfo_fname ) +{ + char *ln = NULL; + char *line = NULL; + + FILE *pkginfo = NULL; + + struct pkg *pkg = NULL; + char *pkgname = NULL, *pkgver = NULL, *group = NULL; + + if( pkginfo_fname != NULL ) + { + pkginfo = fopen( (const char *)pkginfo_fname, "r" ); + if( !pkginfo ) + { + FATAL_ERROR( "Cannot open %s file", pkginfo_fname ); + } + } + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + while( (ln = fgets( line, PATH_MAX, pkginfo )) ) + { + char *match = NULL; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( (match = strstr( ln, "pkgname" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) pkgname = skip_spaces( p ); + } + if( (match = strstr( ln, "pkgver" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) pkgver = skip_spaces( p ); + } + if( (match = strstr( ln, "group" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) group = skip_spaces( p ); + } + } + + free( line ); + + if( pkgname && pkgver ) + { + pkg = pkg_alloc(); + if( pkg ) + { + if( group ) + { + pkg->group = group; + } + pkg->name = pkgname; + pkg->version = pkgver; + } + + } + else + { + if( group ) free( group ); + if( pkgname ) free( pkgname ); + if( pkgver ) free( pkgver ); + + FATAL_ERROR( "Invalid input .PKGINFO file" ); + } + + fclose( pkginfo ); + + return( pkg ); +} + + + +static void get_short_description( char *buf, const char *line ) +{ + char *s, *p, *q; + + if( buf ) { buf[0] = '\0'; s = buf; } + if( !line || line[0] == '\0' ) return; + + p = index( line, '(' ); + q = index( line, ')' ); + if( p && q && q > p ) + { + ++p; + while( *p && p < q ) + { + *s = *p; + ++p; ++s; + } + *s = '\0'; + } + else + { + /* + If short description declaration is incorrect at first line + of description; then we take whole first line of description: + */ + p = index( line, ':' ); ++p; + while( (*p == ' ' || *p == '\t') && *p != '\0' ) { ++p; } + strcpy( buf, p ); + } +} + + +static int get_references_section( int *start, int *stop, unsigned int *cnt, FILE *log ) +{ + int ret = -1, found = 0; + + if( !start || !stop || !cnt ) return ret; + + if( log != NULL ) + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + *start = 0; *stop = 0; + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + char *match = NULL; + + if( (match = strstr( ln, "REFERENCE COUNTER:" )) && match == ln ) /* at start of line only */ + { + *start = ret + 1; + ++found; + + /* Get reference counter */ + { + unsigned int count; + int rc; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + rc = sscanf( ln, "REFERENCE COUNTER: %u", &count ); + if( rc == 1 && cnt != NULL ) + { + *cnt = count; + } + } + } + if( (match = strstr( ln, "REQUIRES:" )) && match == ln ) + { + *stop = ret + 1; + ++found; + } + + ++ret; + } + + free( line ); + + ret = ( found == 2 ) ? 0 : 1; /* 0 - success; 1 - not found. */ + + fseek( log, 0, SEEK_SET ); + } + + return( ret ); +} + +static int get_requires_section( int *start, int *stop, FILE *log ) +{ + int ret = -1, found = 0; + + if( !start || !stop ) return ret; + + if( log != NULL ) + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + *start = 0; *stop = 0; + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + char *match = NULL; + + if( (match = strstr( ln, "REQUIRES:" )) && match == ln ) /* at start of line only */ + { + *start = ret + 1; + ++found; + } + if( (match = strstr( ln, "PACKAGE DESCRIPTION:" )) && match == ln ) + { + *stop = ret + 1; + ++found; + } + + ++ret; + } + + free( line ); + + ret = ( found == 2 ) ? 0 : 1; /* 0 - success; 1 - not found. */ + + fseek( log, 0, SEEK_SET ); + } + + return( ret ); +} + +static int get_description_section( int *start, int *stop, FILE *log ) +{ + int ret = -1, found = 0; + + if( !start || !stop ) return ret; + + if( log != NULL ) + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + *start = 0; *stop = 0; + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + char *match = NULL; + + if( (match = strstr( ln, "PACKAGE DESCRIPTION:" )) && match == ln ) /* at start of line only */ + { + *start = ret + 1; + ++found; + } + if( (match = strstr( ln, "RESTORE LINKS:" )) && match == ln ) + { + *stop = ret + 1; + ++found; + } + + ++ret; + } + + free( line ); + + ret = ( found == 2 ) ? 0 : 1; /* 0 - success; 1 - not found. */ + + fseek( log, 0, SEEK_SET ); + } + + return( ret ); +} + +static int get_restore_links_section( int *start, int *stop, FILE *log ) +{ + int ret = -1, found = 0; + + if( !start || !stop ) return ret; + + if( log != NULL ) + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + *start = 0; *stop = 0; + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + char *match = NULL; + + if( (match = strstr( ln, "RESTORE LINKS:" )) && match == ln ) /* at start of line only */ + { + *start = ret + 1; + ++found; + } + if( (match = strstr( ln, "INSTALL SCRIPT:" )) && match == ln ) + { + *stop = ret + 1; + ++found; + } + + ++ret; + } + + free( line ); + + ret = ( found == 2 ) ? 0 : 1; /* 0 - success; 1 - not found. */ + + fseek( log, 0, SEEK_SET ); + } + + return( ret ); +} + + +static int get_install_script_section( int *start, int *stop, FILE *log ) +{ + int ret = -1, found = 0; + + if( !start || !stop ) return ret; + + if( log != NULL ) + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + *start = 0; *stop = 0; + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + char *match = NULL; + + if( (match = strstr( ln, "INSTALL SCRIPT:" )) && match == ln ) /* at start of line only */ + { + *start = ret + 1; + ++found; + } + if( (match = strstr( ln, "FILE LIST:" )) && match == ln ) + { + *stop = ret + 1; + ++found; + } + + ++ret; + } + + free( line ); + + ret = ( found == 2 ) ? 0 : 1; /* 0 - success; 1 - not found. */ + + fseek( log, 0, SEEK_SET ); + } + + return( ret ); +} + + +static int get_file_list_section( int *start, int *stop, FILE *log ) +{ + int ret = -1, found = 0; + + if( !start || !stop ) return ret; + + if( log != NULL ) + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + *start = 0; *stop = 0; + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + char *match = NULL; + + if( (match = strstr( ln, "FILE LIST:" )) && match == ln ) /* at start of line only */ + { + *start = ret + 1; + ++found; + } + + ++ret; + } + + free( line ); + + ret = ( found == 1 ) ? 0 : 1; /* 0 - success; 1 - not found. */ + + fseek( log, 0, SEEK_SET ); + } + + return( ret ); +} + + +int read_pkginfo( FILE *log, struct package *package ) +{ + int ret = -1; + + char *ln = NULL; + char *line = NULL; + + char *pkgname_pattern = "PACKAGE NAME:", + *pkgver_pattern = "PACKAGE VERSION:", + *arch_pattern = "ARCH:", + *distroname_pattern = "DISTRO:", + *distrover_pattern = "DISTRO VERSION:", + *group_pattern = "GROUP:", + *url_pattern = "URL:", + *license_pattern = "LICENSE:", + *uncompressed_size_pattern = "UNCOMPRESSED SIZE:", + *total_files_pattern = "TOTAL FILES:"; + + + if( !log || !package ) return ret; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + char *match = NULL; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( (match = strstr( ln, pkgname_pattern )) && match == ln ) /* at start of line only */ + { + package->pkginfo->name = skip_spaces( ln + strlen( pkgname_pattern ) ); + } + if( (match = strstr( ln, pkgver_pattern )) && match == ln ) + { + package->pkginfo->version = skip_spaces( ln + strlen( pkgver_pattern ) ); + } + if( (match = strstr( ln, arch_pattern )) && match == ln ) + { + package->pkginfo->arch = skip_spaces( ln + strlen( arch_pattern ) ); + } + if( (match = strstr( ln, distroname_pattern )) && match == ln ) + { + package->pkginfo->distro_name = skip_spaces( ln + strlen( distroname_pattern ) ); + } + if( (match = strstr( ln, distrover_pattern )) && match == ln ) + { + package->pkginfo->distro_version = skip_spaces( ln + strlen( distrover_pattern ) ); + } + if( (match = strstr( ln, group_pattern )) && match == ln ) + { + package->pkginfo->group = skip_spaces( ln + strlen( group_pattern ) ); + } + if( (match = strstr( ln, url_pattern )) && match == ln ) + { + package->pkginfo->url = skip_spaces( ln + strlen( url_pattern ) ); + } + if( (match = strstr( ln, license_pattern )) && match == ln ) + { + package->pkginfo->license = skip_spaces( ln + strlen( license_pattern ) ); + } + if( (match = strstr( ln, uncompressed_size_pattern )) && match == ln ) + { + package->pkginfo->uncompressed_size = read_usize( ln + strlen( uncompressed_size_pattern ) ); + } + if( (match = strstr( ln, total_files_pattern )) && match == ln ) + { + package->pkginfo->total_files = read_total_files( ln + strlen( total_files_pattern ) ); + } + + if( (match = strstr( ln, "PACKAGE DESCRIPTION:" )) && match == ln ) + { + char *buf = NULL; + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + /* Get short_description from PACKAGE DESCRIPTION */ + ln = fgets( line, PATH_MAX, log ); + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + + bzero( (void *)buf, PATH_MAX ); + get_short_description( buf, (const char *)line ); + if( buf[0] != '\0' ) + { + package->pkginfo->short_description = xstrdup( (const char *)buf ); + } + free( buf ); + } + + } /* End of while() */ + + free( line ); + + if( package->pkginfo->name == NULL ) ++ret; + if( package->pkginfo->version == NULL ) ++ret; + if( package->pkginfo->arch == NULL ) ++ret; + if( package->pkginfo->distro_name == NULL ) ++ret; + if( package->pkginfo->distro_version == NULL ) ++ret; + /* group can be equal to NULL */ + + fseek( log, 0, SEEK_SET ); + + return( ret ); +} + + +static unsigned int read_references( FILE *log, int start, unsigned int *cnt, struct package *package ) +{ + char *ln = NULL; + char *line = NULL; + char *p = NULL, *group = NULL, *name = NULL, *version = NULL; + int n = 1; + + unsigned int counter, pkgs = 0; + + struct pkg *pkg = NULL; + + if( !log || !cnt || *cnt == 0 || !package ) return pkgs; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + counter = *cnt; + + while( (ln = fgets( line, PATH_MAX, log )) && (n < start) ) ++n; + + n = 0; + while( (ln = fgets( line, PATH_MAX, log )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( strstr( ln, "REQUIRES:" ) ) break; /* if cnt greater than real number of references */ + + if( n < counter ) + { + if( (p = index( (const char *)ln, '=' )) ) + { + *p = '\0'; version = ++p; + if( (p = index( (const char *)ln, '/' )) ) + { + *p = '\0'; name = ++p; group = (char *)&ln[0]; + } + else + { + name = (char *)&ln[0]; group = NULL; + } + + pkg = pkg_alloc(); + + if( group ) pkg->group = xstrdup( (const char *)group ); + pkg->name = xstrdup( (const char *)name ); + pkg->version = xstrdup( (const char *)version ); + + add_reference( package, pkg ); + ++pkgs; + } + ++n; + } + else + break; + } + + free( line ); + + fseek( log, 0, SEEK_SET ); + + *cnt = pkgs; + + return pkgs; +} + + +static unsigned int read_requires( FILE *log, int start, int stop, struct package *package ) +{ + char *ln = NULL; + char *line = NULL; + char *p = NULL, *group = NULL, *name = NULL, *version = NULL; + int n = 1; + + unsigned int pkgs = 0; + + struct pkg *pkg = NULL; + + if( !log || !package ) return pkgs; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + while( (ln = fgets( line, PATH_MAX, log )) && (n < start) ) ++n; + + if( start && start < stop ) + { + ++n; /* skip section header */ + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( strstr( ln, "PACKAGE DESCRIPTION:" ) ) break; /* if (stop - start - 1) greater than real number of requiress */ + + if( (n > start) && (n < stop) ) + { + if( (p = index( (const char *)ln, '=' )) ) + { + *p = '\0'; version = ++p; + if( (p = index( (const char *)ln, '/' )) ) + { + *p = '\0'; name = ++p; group = (char *)&ln[0]; + } + else + { + name = (char *)&ln[0]; group = NULL; + } + + pkg = pkg_alloc(); + + if( group ) pkg->group = xstrdup( (const char *)group ); + pkg->name = xstrdup( (const char *)name ); + pkg->version = xstrdup( (const char *)version ); + + add_required( package, pkg ); + ++pkgs; + } + + } + ++n; + } + + } /* End if( start && start < stop ) */ + + free( line ); + + fseek( log, 0, SEEK_SET ); + + return pkgs; +} + + +static unsigned int read_description( FILE *log, int start, int stop, struct package *package ) +{ + char *ln = NULL; + char *line = NULL; + char *pattern = NULL; + int n = 1; + + char *tmp_fname = NULL; + FILE *tmp = NULL; + + unsigned int lines = 0; + + if( !log || !package ) return lines; + + tmp_fname = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp_fname ) { FATAL_ERROR( "Cannot allocate memory" ); } + + bzero( (void *)tmp_fname, PATH_MAX ); + (void)sprintf( (char *)&tmp_fname[0], "%s/.DESCRIPTION", tmpdir ); + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) { FATAL_ERROR( "Cannot allocate memory" ); } + + pattern = (char *)malloc( (size_t)strlen( package->pkginfo->name ) + 2 ); + if( !pattern ) { FATAL_ERROR( "Cannot allocate memory" ); } + + (void)sprintf( pattern, "%s:", package->pkginfo->name ); + + + while( (ln = fgets( line, PATH_MAX, log )) && (n < start) ) ++n; + + if( start && start < stop ) + { + ++n; /* skip section header */ + + tmp = fopen( (const char *)&tmp_fname[0], "w" ); + if( !tmp ) + { + FATAL_ERROR( "Cannot create temporary %s file", basename( (char *)&tmp_fname[0] ) ); + } + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + char *match = NULL; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( strstr( ln, "RESTORE LINKS:" ) ) break; /* if (stop - start - 1) greater than real number of lines */ + + if( (n > start) && (n < stop) ) + { + /* + skip non-significant spaces at beginning of line + and print lines started with 'pkgname:' + */ + if( (match = strstr( ln, pattern )) && lines < DESCRIPTION_NUMBER_OF_LINES ) + { + int mlen = strlen( match ), plen = strlen( pattern ); + int length = ( mlen > plen ) ? (mlen - plen - 1) : 0 ; + + if( length > DESCRIPTION_LENGTH_OF_LINE ) + { + /* WARNING( "Package DESCRIPTION contains lines with length greater than %d characters", DESCRIPTION_LENGTH_OF_LINE ); */ + match[plen + 1 + DESCRIPTION_LENGTH_OF_LINE] = '\0'; /* truncating description line */ + skip_eol_spaces( match ); /* remove spaces at end-of-line */ + } + fprintf( tmp, "%s\n", match ); + ++lines; + } + + } + ++n; + } + + if( lines < (unsigned int)DESCRIPTION_NUMBER_OF_LINES ) + { + /* WARNING( "Package DESCRIPTION contains less than %d lines", DESCRIPTION_NUMBER_OF_LINES ); */ + while( lines < (unsigned int)DESCRIPTION_NUMBER_OF_LINES ) + { + fprintf( tmp, "%s\n", pattern ); + ++lines; + } + } + + fflush( tmp ); + fclose( tmp ); + + } /* End if( start && start < stop ) */ + + free( pattern ); + free( line ); + + fseek( log, 0, SEEK_SET ); + + /* read temporary saved description */ + { + struct stat sb; + size_t size = 0; + int fd; + + char *desc = NULL; + + if( stat( tmp_fname, &sb ) == -1 ) + { + FATAL_ERROR( "Cannot stat temporary %s file", basename( (char *)&tmp_fname[0] ) ); + } + size = (size_t)sb.st_size; + + if( size ) + { + ssize_t rc = 0; + + desc = (char *)malloc( size + 1 ); + if( !desc ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)desc, size + 1 ); + + if( (fd = open( (const char *)&tmp_fname[0], O_RDONLY )) == -1 ) + { + FATAL_ERROR( "Canot access temporary %s file: %s", basename( (char *)&tmp_fname[0] ), strerror( errno ) ); + } + + rc = read( fd, (void *)desc, size ); + if( rc != (ssize_t)size ) { ERROR( "The %s file is not fully read", basename( (char *)&tmp_fname[0] ) ); } + + package->description = desc; + + close( fd ); + } + + } + + (void)unlink( tmp_fname ); + free( tmp_fname ); + + return lines; +} + + +static unsigned int read_restore_links( FILE *log, int start, int stop, struct package *package ) +{ + char *ln = NULL; + char *line = NULL; + int n = 1; + + char *tmp_fname = NULL; + FILE *tmp = NULL; + + unsigned int lines = 0; + + if( !log || !package ) return lines; + + tmp_fname = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp_fname ) { FATAL_ERROR( "Cannot allocate memory" ); } + + bzero( (void *)tmp_fname, PATH_MAX ); + (void)sprintf( (char *)&tmp_fname[0], "%s/.RESTORELINKS", tmpdir ); + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) { FATAL_ERROR( "Cannot allocate memory" ); } + + while( (ln = fgets( line, PATH_MAX, log )) && (n < start) ) ++n; + + if( start && start < stop ) + { + ++n; /* skip section header */ + + tmp = fopen( (const char *)&tmp_fname[0], "w" ); + if( !tmp ) + { + FATAL_ERROR( "Cannot create temporary %s file", basename( (char *)&tmp_fname[0] ) ); + } + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( strstr( ln, "INSTALL SCRIPT:" ) ) break; /* if (stop - start - 1) greater than real number of lines */ + + if( (n > start) && (n < stop) ) + { + fprintf( tmp, "%s\n", ln ); + ++lines; + } + ++n; + } + + fflush( tmp ); + fclose( tmp ); + + } /* End if( start && start < stop ) */ + + free( line ); + + fseek( log, 0, SEEK_SET ); + + /* read temporary saved description */ + { + struct stat sb; + size_t size = 0; + int fd; + + char *links = NULL; + + if( stat( tmp_fname, &sb ) == -1 ) + { + FATAL_ERROR( "Cannot stat temporary %s file", basename( (char *)&tmp_fname[0] ) ); + } + size = (size_t)sb.st_size; + + if( size ) + { + ssize_t rc = 0; + + links = (char *)malloc( size + 1 ); + if( !links ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)links, size + 1 ); + + if( (fd = open( (const char *)&tmp_fname[0], O_RDONLY )) == -1 ) + { + FATAL_ERROR( "Canot access temporary %s file: %s", basename( (char *)&tmp_fname[0] ), strerror( errno ) ); + } + + rc = read( fd, (void *)links, size ); + if( rc != (ssize_t)size ) { ERROR( "The %s file is not fully read", basename( (char *)&tmp_fname[0] ) ); } + + package->restore_links = links; + + close( fd ); + } + + } + + (void)unlink( tmp_fname ); + free( tmp_fname ); + + return lines; +} + + +static unsigned int read_install_script( FILE *log, int start, int stop, struct package *package ) +{ + char *ln = NULL; + char *line = NULL; + int n = 1; + + char *tmp_fname = NULL; + FILE *tmp = NULL; + + unsigned int lines = 0; + + if( !log || !package ) return lines; + + tmp_fname = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp_fname ) { FATAL_ERROR( "Cannot allocate memory" ); } + + bzero( (void *)tmp_fname, PATH_MAX ); + (void)sprintf( (char *)&tmp_fname[0], "%s/.INSTALL", tmpdir ); + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) { FATAL_ERROR( "Cannot allocate memory" ); } + + while( (ln = fgets( line, PATH_MAX, log )) && (n < start) ) ++n; + + if( start && start < stop ) + { + ++n; /* skip section header */ + + tmp = fopen( (const char *)&tmp_fname[0], "w" ); + if( !tmp ) + { + FATAL_ERROR( "Cannot create temporary %s file", basename( (char *)&tmp_fname[0] ) ); + } + + while( (ln = fgets( line, PATH_MAX, log )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( strstr( ln, "FILE LIST:" ) ) break; /* if (stop - start - 1) greater than real number of lines */ + + if( (n > start) && (n < stop) ) + { + fprintf( tmp, "%s\n", ln ); + ++lines; + } + ++n; + } + + fflush( tmp ); + fclose( tmp ); + + } /* End if( start && start < stop ) */ + + free( line ); + + fseek( log, 0, SEEK_SET ); + + /* read temporary saved description */ + { + struct stat sb; + size_t size = 0; + int fd; + + char *install = NULL; + + if( stat( tmp_fname, &sb ) == -1 ) + { + FATAL_ERROR( "Cannot stat temporary %s file", basename( (char *)&tmp_fname[0] ) ); + } + size = (size_t)sb.st_size; + + if( size ) + { + ssize_t rc = 0; + + install = (char *)malloc( size + 1 ); + if( !install ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)install, size + 1 ); + + if( (fd = open( (const char *)&tmp_fname[0], O_RDONLY )) == -1 ) + { + FATAL_ERROR( "Canot access temporary %s file: %s", basename( (char *)&tmp_fname[0] ), strerror( errno ) ); + } + + rc = read( fd, (void *)install, size ); + if( rc != (ssize_t)size ) { ERROR( "The %s file is not fully read", basename( (char *)&tmp_fname[0] ) ); } + + package->install_script = install; + + close( fd ); + } + + } + + (void)unlink( tmp_fname ); + free( tmp_fname ); + + return lines; +} + + +static unsigned int read_file_list( FILE *log, int start, struct package *package ) +{ + char *ln = NULL; + char *line = NULL; + int n = 1; + + unsigned int files = 0; + + if( !log || !package ) return files; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + while( (ln = fgets( line, PATH_MAX, log )) && (n < start) ) ++n; + + if( start ) + { + while( (ln = fgets( line, PATH_MAX, log )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + add_file( package, (const char *)ln ); + ++files; + } + + } /* End if( start && start < stop ) */ + + free( line ); + + fseek( log, 0, SEEK_SET ); + + return files; +} + + +static void _read_pkglog( const char *group, const char *fname ) +{ + FILE *log = NULL; + char *bname = NULL; + + if( fname != NULL ) + { + log = fopen( (const char *)fname, "r" ); + if( !log ) + { + FATAL_ERROR( "Cannot open %s file", fname ); + } + bname = (char *)fname + strlen( tmpdir ) + 1; + } + + if( log != NULL ) + { + struct package *package = NULL; + int rc, start, stop; + unsigned int counter; + + package = package_alloc(); + + if( read_pkginfo( log, package ) != 0 ) + { + ERROR( "%s: Invalid PKGLOG file", bname ); + package_free( package ); + fclose( log ); + return; + } + + if( hardware ) package->hardware = xstrdup( (const char *)hardware ); + if( tarballs ) /* find tarball and allocate package->tarball */ + { + struct pkginfo *info = package->pkginfo; + const char *tgz = NULL; + char *buf = NULL; + struct stat sb; + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + + if( info->group ) + { + (void)sprintf( buf, "%s/%s-%s-%s-%s-%s", + info->group, info->name, info->version, info->arch, + info->distro_name, info->distro_version ); + } + else + { + (void)sprintf( buf, "%s-%s-%s-%s-%s", + info->name, info->version, info->arch, + info->distro_name, info->distro_version ); + } + tgz = find_tarball( (const char *)&buf[0] ); + if( tgz ) + { + package->tarball = xstrdup( (const char *)tgz ); + + bzero( (void *)&buf[0], PATH_MAX ); + (void)sprintf( buf, "%s/%s", srcdir, tgz ); + if( stat( buf, &sb ) != -1 ) + { + info->compressed_size = (size_t)sb.st_size; + } + } + free( buf ); + } + package->procedure = INSTALL; + package->priority = priority; + + if( package->pkginfo->group && group && strcmp( package->pkginfo->group, group ) != 0 ) + { + char *tgz; + + if( package->tarball ) { tgz = package->tarball; } + else { tgz = basename( (char *)fname ); } + + WARNING( "%s: Should be moved into '%s' subdir", tgz, package->pkginfo->group ); + } + + /****************** + read references: + */ + rc = get_references_section( &start, &stop, &counter, log ); + if( rc != 0 ) + { + ERROR( "%s: PKGLOG doesn't contains REFERENCE COUNTER section", bname ); + package_free( package ); + fclose( log ); + return; + } + if( counter > 0 ) + { + unsigned int pkgs = counter; + + if( read_references( log, start, &counter, package ) != pkgs ) + { + ERROR( "%s: Invalid REFERENCE COUNTER section", bname ); + package_free( package ); + fclose( log ); + return; + } + } + + /****************** + read requires: + */ + rc = get_requires_section( &start, &stop, log ); + if( rc != 0 ) + { + ERROR( "%s: PKGLOG doesn't contains REQUIRES section", bname ); + package_free( package ); + fclose( log ); + return; + } + if( (stop - start) > 1 ) + { + unsigned int pkgs = (unsigned int)(stop - start - 1); /* -1 skips section header */ + + if( read_requires( log, start, stop, package ) != pkgs ) + { + ERROR( "%s: Invalid REQUIRES section", bname ); + package_free( package ); + fclose( log ); + return; + } + } + + /******************* + read description: + */ + rc = get_description_section( &start, &stop, log ); + if( rc != 0 ) + { + ERROR( "%s: PKGLOG doesn't contains PACKAGE DESCRIPTION section", bname ); + package_free( package ); + fclose( log ); + return; + } + if( (stop - start) > 1 ) + { + if( read_description( log, start, stop, package ) != (unsigned int)DESCRIPTION_NUMBER_OF_LINES ) + { + ERROR( "%s: Invalid DESCRIPTION section", bname ); + package_free( package ); + fclose( log ); + return; + } + } + + /********************* + read restore links: + */ + rc = get_restore_links_section( &start, &stop, log ); + if( rc != 0 ) + { + ERROR( "%s: PKGLOG doesn't contains RESTORE LINKS section", bname ); + package_free( package ); + fclose( log ); + return; + } + if( (stop - start) > 1 ) + { + (void)read_restore_links( log, start, stop, package ); + } + + /********************* + read install script: + */ + rc = get_install_script_section( &start, &stop, log ); + if( rc != 0 ) + { + ERROR( "%s: PKGLOG doesn't contains INSTALL SCRIPT section", bname ); + package_free( package ); + fclose( log ); + return; + } + if( (stop - start) > 1 ) + { + (void)read_install_script( log, start, stop, package ); + } + + /***************** + read file_list: + */ + rc = get_file_list_section( &start, &stop, log ); + if( rc != 0 ) + { + ERROR( "%s: PKGLOG doesn't contains FILE LIST section", bname ); + package_free( package ); + fclose( log ); + return; + } + if( start ) + { + unsigned int files = read_file_list( log, start, package ); + if( files == (unsigned int)0 ) + { + /* + Packages that do not contain regular files are ignored. + For example, service package base/init-devices-1.2.3-s9xx-glibc-radix-1.1.txz + */ + if( ! DO_NOT_PRINTOUT_INFO ) + { + INFO( "%s: PKGLOG contains empty FILE LIST section", bname ); + } + package_free( package ); + fclose( log ); + return; + } + package->pkginfo->total_files = (int)files; + } + + /* Skip excluded package: */ + { + const char *name = find_exclude( (const char *)package->pkginfo->name ); + + if( name && !strcmp( name, package->pkginfo->name ) ) + { + package_free( package ); + fclose( log ); + return; + } + } + + add_package( package ); + + ++__child; + fclose( log ); + } +} + +static void _read_pkglogs( const char *dirpath, const char *grp ) +{ + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + if( stat( dirpath, &path_sb ) == -1 ) + { + FATAL_ERROR( "%s: Cannot stat Setup Database or destination directory", dirpath ); + } + + if( S_ISDIR(path_sb.st_mode) == 0 ) + { + FATAL_ERROR( "%s: Setup Database or destination is not a directory", dirpath ); + } + + if( (dir = opendir(dirpath) ) == NULL ) + { + FATAL_ERROR( "Canot access %s directory: %s", dirpath, strerror( errno ) ); + } + + len = strlen( dirpath ); + + while( (entry = readdir( dir )) != NULL) + { + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( stat( path, &entry_sb ) == 0 ) + { + if( S_ISREG(entry_sb.st_mode) ) + { + if( check_input_file( NULL, (const char *)path ) == IFMT_LOG ) + { + _read_pkglog( grp, (const char *)path ); + } + } + if( S_ISDIR(entry_sb.st_mode) && grp == NULL ) + { + _read_pkglogs( (const char *)path, (const char *)entry->d_name ); + } + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + } + + closedir( dir ); +} + +int read_pkglogs( void ) +{ + int ret = 0; + + __child = 0; + + _read_pkglogs( (const char *)tmpdir, NULL ); + + ret = __child; + + __child = 0; + + return ret; +} + + + +/***************************** + Count slashes in file name: + */ +static int count_slashes( char *s ) +{ + int cnt = 0; + char *p = (char *)0; + + if( !s || *s == '\0' ) return cnt; + + p = s; + while( *p ) + { + if( *p == '/' ) { + ++cnt; + } + ++p; + } + + return cnt; +} + +static void read_srcpkg_fnames( const char *flist ) +{ + struct stat st; + FILE *fp = NULL; + + if( !flist ) return; + + bzero( (void *)&st, sizeof( struct stat ) ); + + if( stat( flist, &st ) == -1 ) + { + FATAL_ERROR( "Cannot access input '%s' file or directory: %s", flist, strerror( errno ) ); + } + + if( S_ISREG(st.st_mode) ) + { + char *ln = NULL; + char *line = NULL; + int lnum = 0; + + fp = fopen( flist, "r" ); + if( !fp ) + { + FATAL_ERROR( "Cannot open %s file", flist ); + } + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + while( (ln = fgets( line, PATH_MAX, fp )) ) + { + struct srcpkg_fname *fname = NULL; + + ++lnum; + if( strlen( ln ) == 0 ) continue; + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + /* remove leading spaces: */ + while( (*ln == ' ' || *ln == '\t') && *ln != '\0' ) { ++ln; } + /* skip comments and empty lines: */ + if( *ln == '\0' || *ln == '#' ) continue; + + if( count_slashes( ln ) > 1 ) + { + FATAL_ERROR( "%s:%d: file name contains more than one slash symbols", flist, lnum ); + } + + if( ! isalpha( *ln ) ) + { + FATAL_ERROR( "%s:%d: file path must be relative and start with a letter (as a group name)", flist, lnum ); + } + + fname = srcpkg_fname_alloc( (const char *)ln, lnum ); + add_srcpkg_fname( fname ); + } + + free( line ); + fclose( fp ); + } + else + { + FATAL_ERROR( "Source file names list '%s' is not a regular file" ); + } +} + +static void check_srcfile( void *data, void *user_data ) +{ + struct srcpkg_fname *srcfile = (struct srcpkg_fname *)data; + + if( srcfile ) + { + struct stat st; + char *fname = NULL; + struct pkg *srcpkg = NULL; + + bzero( (void *)&st, sizeof( struct stat ) ); + + fname = (char *)malloc( (size_t)PATH_MAX ); + if( !fname ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)fname, PATH_MAX ); + + (void)sprintf( &fname[0], "%s/%s", srcdir, srcfile->name ); + + if( stat( (const char *)fname, &st ) == -1 ) + { + FATAL_ERROR( "%s:%d: Cannot access input '%s' file or directory: %s", + srcfile->name, srcfile->line, fname, strerror( errno ) ); + } + + if( S_ISREG(st.st_mode) ) + { + enum _input_type format = IFMT_UNKNOWN; + + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *tmp= NULL, *cmd = NULL; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + (void)sprintf( &tmp[0], "%s", tmpdir ); + if( _mkdir_p( tmp, S_IRWXU | S_IRWXG | S_IRWXO ) != 0 ) + { + FATAL_ERROR( "%s:%d: Cannot get PKGINFO from '%s' file", + srcfile->name, srcfile->line, basename( (char *)fname ) ); + } + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, "%s/pkginfo -d %s -o pkginfo %s > /dev/null 2>&1", selfdir, tmp, fname ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "%s:%d: Cannot get PKGINFO from %s file", + srcfile->name, srcfile->line, basename( (char *)fname ) ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + if( rc != 0 ) + { + FATAL_ERROR( "%s:%d: Cannot get PKGINFO from '%s' file", + srcfile->name, srcfile->line, basename( (char *)fname ) ); + } + + (void)strcat( tmp, "/.PKGINFO" ); + + bzero( (void *)&st, sizeof( struct stat ) ); + if( stat( (const char *)tmp, &st ) == -1 ) + { + FATAL_ERROR( "%s:%d: Cannot get PKGINFO from %s file: %s", + srcfile->name, srcfile->line, basename( (char *)fname ), strerror( errno ) ); + } + + srcpkg = input_package( (const char *)&tmp[0] ); + bzero( (void *)tmp, PATH_MAX ); + if( srcpkg ) + { + char *e = NULL; + + if( srcpkg->group ) + { + len = snprintf( &tmp[0], PATH_MAX, "%s/%s", srcpkg->group, basename( (char *)fname ) ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "%s:%d: Cannot get PKGINFO from %s file", + srcfile->name, srcfile->line, basename( (char *)fname ) ); + } + } + else + { + len = snprintf( &tmp[0], PATH_MAX, "%s", basename( (char *)fname ) ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "%s:%d: Cannot get PKGINFO from %s file", + srcfile->name, srcfile->line, basename( (char *)fname ) ); + } + } + + add_srcpkg( srcpkg ); + + remove_trailing_slash( srcdir ); + } + + free( tmp ); + free( cmd ); + } + + free( fname ); + } +} + +static void check_srcdir( void ) +{ + struct stat st; + char *fname = srcdir; + struct pkg *srcpkg = NULL; + + bzero( (void *)&st, sizeof( struct stat ) ); + + if( stat( (const char *)srcdir, &st ) == -1 ) + { + FATAL_ERROR( "Cannot access input '%s' file or directory: %s", srcdir, strerror( errno ) ); + } + + if( S_ISREG(st.st_mode) ) + { + enum _input_type format = IFMT_UNKNOWN; + + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *tmp= NULL, *cmd = NULL; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + (void)sprintf( &tmp[0], "%s", tmpdir ); + if( _mkdir_p( tmp, S_IRWXU | S_IRWXG | S_IRWXO ) != 0 ) + { + FATAL_ERROR( "Cannot get PKGINFO from '%s' file", basename( (char *)fname ) ); + } + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, "%s/pkginfo -d %s -o pkginfo %s > /dev/null 2>&1", selfdir, tmp, fname ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot get PKGINFO from %s file", basename( (char *)fname ) ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + if( rc != 0 ) + { + FATAL_ERROR( "Cannot get PKGINFO from '%s' file", basename( (char *)fname ) ); + } + + (void)strcat( tmp, "/.PKGINFO" ); + + bzero( (void *)&st, sizeof( struct stat ) ); + if( stat( (const char *)tmp, &st ) == -1 ) + { + FATAL_ERROR( "Cannot get PKGINFO from %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + + /* keep or set input file format: */ + format = check_input_file( NULL, fname ); + if( input_format == IFMT_UNKNOWN ) input_format = format; + + srcpkg = input_package( (const char *)&tmp[0] ); + bzero( (void *)tmp, PATH_MAX ); + if( srcpkg ) + { + char *e = NULL; + + if( srcpkg->group ) + { + len = snprintf( &tmp[0], PATH_MAX, "%s/%s", srcpkg->group, basename( (char *)fname ) ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot get PKGINFO from %s file", basename( (char *)fname ) ); + } + } + else + { + len = snprintf( &tmp[0], PATH_MAX, "%s", basename( (char *)fname ) ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot get PKGINFO from %s file", basename( (char *)fname ) ); + } + } + + add_srcpkg( srcpkg ); + + /* remove source file name (with group directory if defined) from srcdir path */ + e = strstr( (const char *)srcdir, (const char *)&tmp[0] ); + if( !e ) { e = strstr( (const char *)srcdir, (const char *)basename( (char *)fname ) ); } + *e = '\0'; + + remove_trailing_slash( srcdir ); + } + + free( tmp ); + free( cmd ); + } + + if( S_ISDIR(st.st_mode) ) + { + if( srclist_fname ) + { + read_srcpkg_fnames( srclist_fname ); + dlist_foreach( srcpkg_fnames, check_srcfile, NULL ); + } + } +} + + +static void _print_extern_requires( void *data, void *user_data ) +{ + struct pkg *pkg = (struct pkg *)data; + + if( pkg ) + { + if( pkg->group ) + fprintf( stderr, "%s/%s:%s:%s\n", pkg->group, pkg->name, pkg->version, strproc( pkg->procedure ) ); + else + fprintf( stderr, "%s:%s:%s\n", pkg->name, pkg->version, strproc( pkg->procedure ) ); + } +} + +static void print_extern_requires( void ) +{ + dlist_foreach( extern_requires, _print_extern_requires, NULL ); +} + + +/********************************************* + Get directory where this program is placed: + */ +char *get_selfdir( void ) +{ + char *buf = NULL; + ssize_t len; + + buf = (char *)malloc( PATH_MAX ); + if( !buf ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + bzero( (void *)buf, PATH_MAX ); + len = readlink( "/proc/self/exe", buf, (size_t)PATH_MAX ); + if( len > 0 && len < PATH_MAX ) + { + char *p = xstrdup( (const char *)dirname( buf ) ); + free( buf ); + return p; + } + FATAL_ERROR( "Cannot determine self directory. Please mount /proc filesystem" ); +} + +void set_stack_size( void ) +{ + const rlim_t stack_size = 16 * 1024 * 1024; /* min stack size = 16 MB */ + struct rlimit rl; + int ret; + + ret = getrlimit( RLIMIT_STACK, &rl ); + if( ret == 0 ) + { + if( rl.rlim_cur < stack_size ) + { + rl.rlim_cur = stack_size; + ret = setrlimit( RLIMIT_STACK, &rl ); + if( ret != 0 ) + { + fprintf(stderr, "setrlimit returned result = %d\n", ret); + FATAL_ERROR( "Cannot set stack size" ); + } + } + } +} + + +int main( int argc, char *argv[] ) +{ + gid_t gid; + + set_signal_handlers(); + + gid = getgid(); + setgroups( 1, &gid ); + + fatal_error_hook = fatal_error_actions; + + selfdir = get_selfdir(); + + errlog = stderr; + + program = basename( argv[0] ); + get_args( argc, argv ); + + /* set_stack_size(); */ + + tmpdir = _mk_tmpdir(); + if( !tmpdir ) + { + FATAL_ERROR( "Cannot create temporary directory" ); + } + + /******************************************************** + Check if the --source argument is a file or directory: + */ + check_srcdir(); + + /* Extract or Copy PKGLOGs into TMPDIR: */ + if( input_format == IFMT_PKG ) + { + int pkgs = extract_pkglogs(); + if( pkgs == 0 ) { FATAL_ERROR( "There are no packages in the '%s' directory", srcdir ); } + if( exit_status > 0 ) { FATAL_ERROR( "Cannot extract PKGLOG from some package" ); } + if( ! DO_NOT_PRINTOUT_INFO ) + { + INFO( "Found %d packages in the '%s' directory", pkgs, srcdir ); + } + } + else + { + int pkgs = copy_pkglogs(); + if( pkgs == 0 ) { FATAL_ERROR( "There are no PKGLOG files in the '%s' directory", srcdir ); } + if( exit_status > 0 ) { FATAL_ERROR( "Cannot copy some PKGLOG file" ); } + if( ! DO_NOT_PRINTOUT_INFO ) + { + INFO( "Found %d PKGLOG files in the '%s' directory", pkgs, srcdir ); + } + } + + /* Read PKGLOGs from TMPDIR and create Double Linked List of PACKAGES: */ + { + int pkgs = read_pkglogs(); + if( pkgs == 0 ) { FATAL_ERROR( "There are no PKGLOG files in the '%s' directory", tmpdir ); } + if( exit_status > 0 ) { FATAL_ERROR( "Cannot read some PKGLOG file" ); } + if( ! DO_NOT_PRINTOUT_INFO ) + { + /* INFO( "Found %d PKGLOG files in the '%s' directory", pkgs, tmpdir ); */ + } + } + + { + int extern_pkgs = create_provides_list( srcpkgs ); + if( output_format == OFMT_LIST ) + { + print_provides_list( (const char *)pkglist_fname ); + if( extern_pkgs ) + { + /* Output machine readable list of requires to stderr: */ + print_extern_requires(); + exit_status += 1; + } + } + if( output_format == OFMT_JSON ) print_provides_tree( (const char *)pkglist_fname, tree_format ); + free_provides_list(); + } + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + + exit( exit_status ); +} diff --git a/src/make-pkglist.h b/src/make-pkglist.h new file mode 100644 index 0000000..ac0d713 --- /dev/null +++ b/src/make-pkglist.h @@ -0,0 +1,36 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +#ifndef _MK_PKGLIST_H_ +#define _MK_PKGLIST_H_ + +#ifdef __cplusplus +extern "C" { +#endif + + +extern char *htmlroot; +extern char *hardware; +extern int minimize; + + +#ifdef __cplusplus +} /* ... extern "C" */ +#endif + +#endif /* _MK_PKGLIST_H_ */ diff --git a/src/msglog.c b/src/msglog.c new file mode 100644 index 0000000..f9b9e03 --- /dev/null +++ b/src/msglog.c @@ -0,0 +1,67 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <unistd.h> +#include <time.h> +#include <sys/time.h> + +#include <msglog.h> + +FILE *errlog; + +void (*fatal_error_hook)( void ); + +void logmsg( FILE *logfile, enum _msg_type type, char *format, ... ) +{ + va_list argp; + + if( ! format ) return; + + switch( type ) + { + case MSG_FATAL: fprintf( logfile, "%s: FATAL: ", program ); break; + case MSG_ERROR: fprintf( logfile, "%s: ERROR: ", program ); break; + case MSG_WARNING: fprintf( logfile, "%s: WARNING: ", program ); break; + case MSG_NOTICE: fprintf( logfile, "%s: NOTE: ", program ); break; + case MSG_INFO: fprintf( logfile, "%s: INFO: ", program ); break; + case MSG_DEBUG: fprintf( logfile, "%s: DEBUG: ", program ); break; + case MSG_LOG: + { + time_t t = time( NULL ); + struct tm tm = *localtime(&t); + + fprintf( logfile, "[%04d-%02d-%02d %02d:%02d:%02d]: %s: ", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec, + program ); + break; + } + default: + fprintf( logfile, "%s: ", program ); + break; + } + va_start( argp, format ); + vfprintf( errlog, format, argp ); + fprintf( errlog, "\n" ); + fflush( errlog ); +} diff --git a/src/msglog.h b/src/msglog.h new file mode 100644 index 0000000..fc256f0 --- /dev/null +++ b/src/msglog.h @@ -0,0 +1,98 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +#ifndef _MSG_LOG_H_ +#define _MSG_LOG_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +extern FILE *errlog; + +extern void (*fatal_error_hook)( void ); + +extern char *program; +extern int exit_status; + +enum _msg_type +{ + MSG_FATAL = 0, + MSG_ERROR, + MSG_WARNING, + MSG_NOTICE, + MSG_INFO, + MSG_DEBUG, + + MSG_LOG +}; + +#define FATAL_ERROR( ... ) \ + do \ + { \ + logmsg( errlog, MSG_FATAL, __VA_ARGS__ ); \ + if( fatal_error_hook) fatal_error_hook(); \ + exit( EXIT_FAILURE ); \ + } while (0) + +#define ERROR( ... ) \ + do \ + { \ + logmsg( errlog, MSG_ERROR, __VA_ARGS__ ); \ + ++exit_status; \ + } while (0) + +#define WARNING( ... ) \ + do \ + { \ + logmsg( errlog, MSG_WARNING, __VA_ARGS__ ); \ + } while (0) + +#define NOTICE( ... ) \ + do \ + { \ + logmsg( errlog, MSG_NOTICE, __VA_ARGS__ ); \ + } while (0) + +#define INFO( ... ) \ + do \ + { \ + logmsg( errlog, MSG_INFO, __VA_ARGS__ ); \ + } while (0) + +#define DEBUG( ... ) \ + do \ + { \ + logmsg( errlog, MSG_DEBUG, __VA_ARGS__ ); \ + } while (0) + +#define LOG( ... ) \ + do \ + { \ + logmsg( errlog, MSG_LOG, __VA_ARGS__ ); \ + } while (0) + + +extern void logmsg( FILE *logfile, enum _msg_type type, char *format, ... ); + + +#ifdef __cplusplus +} /* ... extern "C" */ +#endif + +#endif /* _MSG_LOG_H_ */ diff --git a/src/pkginfo.c b/src/pkginfo.c new file mode 100644 index 0000000..2e93a7c --- /dev/null +++ b/src/pkginfo.c @@ -0,0 +1,1573 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <sys/types.h> +#include <stdint.h> +#include <dirent.h> +#include <sys/stat.h> /* chmod(2) */ +#include <fcntl.h> +#include <linux/limits.h> +#include <alloca.h> /* alloca(3) */ +#include <string.h> /* strdup(3) */ +#include <strings.h> /* index(3) */ +#include <libgen.h> /* basename(3) */ +#include <ctype.h> /* tolower(3) */ +#include <errno.h> +#include <time.h> +#include <sys/time.h> +#include <pwd.h> +#include <grp.h> +#include <stdarg.h> +#include <unistd.h> + +#include <sys/resource.h> + +#include <signal.h> +#if !defined SIGCHLD && defined SIGCLD +# define SIGCHLD SIGCLD +#endif + +#define _GNU_SOURCE +#include <getopt.h> + + +#include <msglog.h> +#include <wrapper.h> +#include <system.h> + +#define PROGRAM_NAME "pkginfo" + +#include <defs.h> + + +char *program = PROGRAM_NAME; +char *destination = NULL, *operation = NULL, *pkglog_fname = NULL; +int exit_status = EXIT_SUCCESS; /* errors counter */ +char *selfdir = NULL; + +char *pkgname = NULL, + *pkgver = NULL, + *arch = NULL, + *distroname = NULL, + *distrover = NULL, + *group = NULL, + *url = NULL, + *license = NULL, + *uncompressed_size = NULL, + *total_files = NULL; + +FILE *pkglog = NULL; +FILE *output = NULL; + + +#define FREE_PKGINFO_VARIABLES() \ + if( pkgname ) { free( pkgname ); } pkgname = NULL; \ + if( pkgver ) { free( pkgver ); } pkgver = NULL; \ + if( arch ) { free( arch ); } arch = NULL; \ + if( distroname ) { free( distroname ); } distroname = NULL; \ + if( distrover ) { free( distrover ); } distrover = NULL; \ + if( group ) { free( group ); } group = NULL; \ + if( url ) { free( url ); } url = NULL; \ + if( license ) { free( license ); } license = NULL; \ + if( uncompressed_size ) { free( uncompressed_size ); } uncompressed_size = NULL; \ + if( total_files ) { free( total_files ); } total_files = NULL + +void free_resources() +{ + if( selfdir ) { free( selfdir ); selfdir = NULL; } + if( destination ) { free( destination ); destination = NULL; } + if( operation ) { free( operation ); operation = NULL; } + if( pkglog_fname ) { free( pkglog_fname ); pkglog_fname = NULL; } + + FREE_PKGINFO_VARIABLES(); +} + +void usage() +{ + free_resources(); + + fprintf( stdout, "\n" ); + fprintf( stdout, "Usage: %s [options] <pkglog|package>\n", program ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Read information from <pkglog> or <package> file and create\n" ); + fprintf( stdout, "requested package's service files in the destination directory.\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Options:\n" ); + fprintf( stdout, " -h,--help Display this information.\n" ); + fprintf( stdout, " -v,--version Display the version of %s utility.\n", program ); + fprintf( stdout, " -d,--destination=<DIR> Target directory to save output files.\n" ); + fprintf( stdout, " -o,--operations=<OP1,..,OPn> Comma separated list of:\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Operations:\n" ); + fprintf( stdout, " ----------------+----------------\n" ); + fprintf( stdout, " operation name | output file\n" ); + fprintf( stdout, " ----------------+----------------\n" ); + fprintf( stdout, " pkginfo | .PKGINFO\n" ); + fprintf( stdout, " references | .REFERENCES\n" ); + fprintf( stdout, " requires | .REQUIRES\n" ); + fprintf( stdout, " description | .DESCRIPTION\n" ); + fprintf( stdout, " restore-links | .RESTORELINKS\n" ); + fprintf( stdout, " install-script | .INSTALL\n" ); + fprintf( stdout, " filelist | .FILELIST\n" ); + fprintf( stdout, " ----------------+----------------\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Parameter:\n" ); + fprintf( stdout, " <pkglog|package> PKGLOG file or package tarball.\n" ); + fprintf( stdout, "\n" ); + + exit( EXIT_FAILURE ); +} + +void to_lowercase( char *s ) +{ + char *p = s; + while( p && *p ) { int c = *p; *p = tolower( c ); ++p; } +} + +void to_uppercase( char *s ) +{ + char *p = s; + while( p && *p ) { int c = *p; *p = toupper( c ); ++p; } +} + +void version() +{ + char *upper = NULL; + + upper = (char *)alloca( strlen( program ) + 1 ); + + strcpy( (char *)upper, (const char *)program ); + to_uppercase( upper ); + + fprintf( stdout, "%s (%s) %s\n", program, upper, PROGRAM_VERSION ); + + fprintf( stdout, "Copyright (C) 2019 Andrey V.Kosteltsev.\n" ); + fprintf( stdout, "This is free software. There is NO warranty; not even\n" ); + fprintf( stdout, "for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" ); + fprintf( stdout, "\n" ); + + free_resources(); + exit( EXIT_SUCCESS ); +} + + +static void remove_trailing_slash( char *dir ) +{ + char *s; + + if( !dir || dir[0] == '\0' ) return; + + s = dir + strlen( dir ) - 1; + while( *s == '/' ) + { + *s = '\0'; --s; + } +} + +void fatal_error_actions( void ) +{ + logmsg( errlog, MSG_NOTICE, "Free resources on FATAL error..." ); + free_resources(); +} + +void sigint( int signum ) +{ + (void)signum; + free_resources(); +} + +static void set_signal_handlers() +{ + struct sigaction sa; + sigset_t set; + + memset( &sa, 0, sizeof( sa ) ); + sa.sa_handler = sigint; /* TERM, INT */ + sa.sa_flags = SA_RESTART; + sigemptyset( &set ); + sigaddset( &set, SIGTERM ); + sigaddset( &set, SIGINT ); + sa.sa_mask = set; + sigaction( SIGTERM, &sa, NULL ); + sigaction( SIGINT, &sa, NULL ); + + memset( &sa, 0, sizeof( sa ) ); /* ignore SIGPIPE */ + sa.sa_handler = SIG_IGN; + sa.sa_flags = 0; + sigaction( SIGPIPE, &sa, NULL ); + + /* System V fork+wait does not work if SIGCHLD is ignored */ + signal( SIGCHLD, SIG_DFL ); +} + +enum _pkglog_type +{ + PKGLOG_TEXT = 0, + PKGLOG_GZ, + PKGLOG_BZ2, + PKGLOG_XZ, + PKGLOG_TAR, + + PKGLOG_UNKNOWN +}; + +static enum _pkglog_type pkglog_type = PKGLOG_UNKNOWN; +static char uncompress[2] = { 0, 0 }; + + +static enum _pkglog_type check_pkglog_file( const char *fname ) +{ + struct stat st; + size_t pkglog_size = 0; + unsigned char buf[8]; + int rc, fd; + + /* SIGNATURES: https://www.garykessler.net/library/file_sigs.html */ + + uncompress[0] = '\0'; + + if( stat( fname, &st ) == -1 ) + { + FATAL_ERROR( "Cannot access %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + + pkglog_size = st.st_size; + + if( (fd = open( fname, O_RDONLY )) == -1 ) + { + FATAL_ERROR( "Cannot open %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + + rc = (int)read( fd, (void *)&buf[0], 7 ); + if( rc != 7 ) + { + FATAL_ERROR( "Unknown type of input file %s", basename( (char *)fname ) ); + } + buf[7] = '\0'; + + /* TEXT */ + if( !strncmp( (const char *)&buf[0], "PACKAGE", 7 ) ) + { + close( fd ); return PKGLOG_TEXT; + } + + /* GZ */ + if( buf[0] == 0x1F && buf[1] == 0x8B && buf[2] == 0x08 ) + { + uncompress[0] = 'x'; + close( fd ); return PKGLOG_GZ; + } + + /* BZ2 */ + if( buf[0] == 0x42 && buf[1] == 0x5A && buf[2] == 0x68 ) + { + uncompress[0] = 'j'; + close( fd ); return PKGLOG_BZ2; + } + + /* XZ */ + if( buf[0] == 0xFD && buf[1] == 0x37 && buf[2] == 0x7A && + buf[3] == 0x58 && buf[4] == 0x5A && buf[5] == 0x00 ) + { + uncompress[0] = 'J'; + close( fd ); return PKGLOG_XZ; + } + + if( pkglog_size > 262 ) + { + if( lseek( fd, 257, SEEK_SET ) == -1 ) + { + FATAL_ERROR( "Cannot check signature of %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + rc = (int)read( fd, &buf[0], 5 ); + if( rc != 5 ) + { + FATAL_ERROR( "Cannot read signature of %s file", basename( (char *)fname ) ); + } + /* TAR */ + if( buf[0] == 0x75 && buf[1] == 0x73 && buf[2] == 0x74 && buf[3] == 0x61 && buf[4] == 0x72 ) + { + close( fd ); return PKGLOG_TAR; + } + } + + close( fd ); return PKGLOG_UNKNOWN; +} + + +void get_args( int argc, char *argv[] ) +{ + const char* short_options = "hvd:o:"; + + const struct option long_options[] = + { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'v' }, + { "destination", required_argument, NULL, 'd' }, + { "operations", required_argument, NULL, 'o' }, + { NULL, 0, NULL, 0 } + }; + + int ret; + int option_index = 0; + + while( (ret = getopt_long( argc, argv, short_options, long_options, &option_index )) != -1 ) + { + switch( ret ) + { + case 'h': + { + usage(); + break; + } + case 'v': + { + version(); + break; + } + + case 'd': + { + if( optarg != NULL ) + { + destination = xstrdup( (const char *)optarg ); + remove_trailing_slash( destination ); + } + else + /* option is present but without value */ + usage(); + break; + } + case 'o': + { + operation = xstrdup( (const char *)optarg ); + to_lowercase( operation ); + break; + } + + case '?': default: + { + usage(); + break; + } + } + } + + if( destination == NULL ) + { + char cwd[PATH_MAX]; + if( getcwd( cwd, sizeof(cwd) ) != NULL ) + destination = xstrdup( (const char *)cwd ); + else + destination = xstrdup( "." ); + } + + if( operation == NULL ) usage(); + + /* last command line argument is the LOGFILE */ + if( optind < argc ) + { + pkglog_fname = xstrdup( (const char *)argv[optind++] ); + if( pkglog_fname == NULL ) + { + usage(); + } + pkglog_type = check_pkglog_file( (const char *)pkglog_fname ); + if( pkglog_type == PKGLOG_UNKNOWN ) + { + ERROR( "%s: Unknown input file format", basename( pkglog_fname ) ); + usage(); + } + } + else + { + usage(); + } +} + + +/* + Especialy for pkginfo lines. + Remove leading spaces and take non-space characters only: + */ +static char *skip_spaces( char *s ) +{ + char *q, *p = (char *)0; + + if( !s || *s == '\0' ) return p; + + p = s; + + while( (*p == ' ' || *p == '\t') && *p != '\0' ) { ++p; } q = p; + while( *q != ' ' && *q != '\t' && *q != '\0' ) { ++q; } *q = '\0'; + + if( *p == '\0' ) return (char *)0; + + return( xstrdup( (const char *)p ) ); +} + +/* + remove spaces at end of line: + */ +static void skip_eol_spaces( char *s ) +{ + char *p = (char *)0; + + if( !s || *s == '\0' ) return; + + p = s + strlen( s ) - 1; + while( isspace( *p ) ) { *p-- = '\0'; } +} + + +int printf_variable( FILE *output, char *log_fname, char *name, char *value, char *pattern, int error ) +{ + int exit_status = 0; /* local errors counter */ + char buf[24]; + char *p; + + bzero( (void *)buf, 24 ); + + if( pattern ) + (void)sprintf( (char *)&buf[0], "%s", pattern ); + + p = (char *)&buf[0]; + p[strlen(buf) - 1] = '\0'; /* skip colon at end of pattern */ + + if( value ) + { + fprintf( output, "%s=%s\n", name, value ); + } + else + { + if( error ) + { + ERROR( "There is no %s declaration in the %s file", p, log_fname ); + } + else + { + if( ! DO_NOT_WARN_ABOUT_OPT_PKGINFO_ITEMS ) + WARNING( "There is no %s declaration in the %s file", p, log_fname ); + } + } + + return( exit_status ); +} + + +static void get_short_description( char *buf, const char *line ) +{ + char *s, *p, *q; + + if( buf ) { buf[0] = '\0'; s = buf; } + if( !line || line[0] == '\0' ) return; + + p = index( line, '(' ); + q = index( line, ')' ); + if( p && q && q > p ) + { + *s = '"'; ++s; /* start " */ + ++p; + while( *p && p < q ) + { + *s = *p; + ++p; ++s; + } + *s++ = '"'; /* stop " */ + *s = '\0'; + } +} + +int write_pkginfo() +{ + int ret = -1; + + if( pkglog_fname != NULL ) + { + pkglog = fopen( (const char *)pkglog_fname, "r" ); + if( !pkglog ) + { + FATAL_ERROR( "Cannot open %s file", pkglog_fname ); + } + } + + if( destination != NULL ) + { + char *output_fname = NULL; + + output_fname = (char *)alloca( strlen( destination ) + 10 ); + strcpy( output_fname, destination ); + strcat( output_fname, "/.PKGINFO" ); + output = fopen( (const char *)output_fname, "w" ); + if( !output ) + { + FATAL_ERROR( "Cannot create %s file", output_fname ); + } + } + + if( (pkglog != NULL) && (output != NULL) ) + { + char *ln = NULL; + char *line = NULL; + char *desc = NULL; + + char *pkgname_pattern = "PACKAGE NAME:", + *pkgver_pattern = "PACKAGE VERSION:", + *arch_pattern = "ARCH:", + *distroname_pattern = "DISTRO:", + *distrover_pattern = "DISTRO VERSION:", + *group_pattern = "GROUP:", + *url_pattern = "URL:", + *license_pattern = "LICENSE:", + *uncompressed_size_pattern = "UNCOMPRESSED SIZE:", + *total_files_pattern = "TOTAL FILES:"; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + + while( (ln = fgets( line, PATH_MAX, pkglog )) ) + { + char *match = NULL; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( (match = strstr( ln, pkgname_pattern )) && match == ln ) /* at start of line only */ + { + pkgname = skip_spaces( ln + strlen( pkgname_pattern ) ); + } + if( (match = strstr( ln, pkgver_pattern )) && match == ln ) + { + pkgver = skip_spaces( ln + strlen( pkgver_pattern ) ); + } + if( (match = strstr( ln, arch_pattern )) && match == ln ) + { + arch = skip_spaces( ln + strlen( arch_pattern ) ); + } + if( (match = strstr( ln, distroname_pattern )) && match == ln ) + { + distroname = skip_spaces( ln + strlen( distroname_pattern ) ); + } + if( (match = strstr( ln, distrover_pattern )) && match == ln ) + { + distrover = skip_spaces( ln + strlen( distrover_pattern ) ); + } + if( (match = strstr( ln, group_pattern )) && match == ln ) + { + group = skip_spaces( ln + strlen( group_pattern ) ); + } + if( (match = strstr( ln, url_pattern )) && match == ln ) + { + url = skip_spaces( ln + strlen( url_pattern ) ); + } + if( (match = strstr( ln, license_pattern )) && match == ln ) + { + license = skip_spaces( ln + strlen( license_pattern ) ); + } + if( (match = strstr( ln, uncompressed_size_pattern )) && match == ln ) + { + uncompressed_size = skip_spaces( ln + strlen( uncompressed_size_pattern ) ); + } + if( (match = strstr( ln, total_files_pattern )) && match == ln ) + { + total_files = skip_spaces( ln + strlen( total_files_pattern ) ); + } + if( (match = strstr( ln, "PACKAGE DESCRIPTION:" )) && match == ln ) + { + char *buf = NULL; + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + /* Get short_description from PACKAGE DESCRIPTION */ + ln = fgets( line, PATH_MAX, pkglog ); + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + + bzero( (void *)buf, PATH_MAX ); + get_short_description( buf, (const char *)line ); + if( buf[0] != '\0' ) + { + desc = xstrdup( (const char *)buf ); + } + free( buf ); + } + } + + free( line ); + + ret += printf_variable( output, pkglog_fname, "pkgname", pkgname, pkgname_pattern, 1 ); + ret += printf_variable( output, pkglog_fname, "pkgver", pkgver, pkgver_pattern, 1 ); + ret += printf_variable( output, pkglog_fname, "arch", arch, arch_pattern, 1 ); + ret += printf_variable( output, pkglog_fname, "distroname", distroname, distroname_pattern, 1 ); + ret += printf_variable( output, pkglog_fname, "distrover", distrover, distrover_pattern, 1 ); + ret += printf_variable( output, pkglog_fname, "group", group, group_pattern, 0 ); + if( desc != NULL ) + { + ret += printf_variable( output, pkglog_fname, "short_description", desc, "SHORT DESCRIPTION:", 0 ); + free( desc ); desc = NULL; + } + ret += printf_variable( output, pkglog_fname, "url", url, url_pattern, 0 ); + ret += printf_variable( output, pkglog_fname, "license", license, license_pattern, 0 ); + ret += printf_variable( output, pkglog_fname, "uncompressed_size", uncompressed_size, uncompressed_size_pattern, 0 ); + ret += printf_variable( output, pkglog_fname, "total_files", total_files, total_files_pattern, 0 ); + + FREE_PKGINFO_VARIABLES(); + + fclose( pkglog ); pkglog = NULL; + fclose( output ); output = NULL; + } + + return( ret ); +} + + +/* + NOTE: + 1. first line has number 1. + 2. sections are ordered according to following list: + */ +int reference_counter = 0; +int requires = 0; +int package_description = 0; +int restore_links = 0; +int install_script = 0; +int file_list = 0; + +int refcount = 0; + + +int get_pkglog_sections() +{ + int ret = -1, found = 0; + + if( pkglog_fname != NULL ) + { + pkglog = fopen( (const char *)pkglog_fname, "r" ); + if( !pkglog ) + { + FATAL_ERROR( "Cannot open %s file", pkglog_fname ); + } + } + + if( pkglog != NULL ) + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + + while( (ln = fgets( line, PATH_MAX, pkglog )) ) + { + char *match = NULL; + + if( (match = strstr( ln, "REFERENCE COUNTER:" )) && match == ln ) /* at start of line only */ + { + reference_counter = ret + 1; + ++found; + } + if( (match = strstr( ln, "REQUIRES:" )) && match == ln ) + { + requires = ret + 1; + ++found; + } + if( (match = strstr( ln, "PACKAGE DESCRIPTION:" )) && match == ln ) + { + package_description = ret + 1; + ++found; + } + if( (match = strstr( ln, "RESTORE LINKS:" )) && match == ln ) + { + restore_links = ret + 1; + ++found; + } + if( (match = strstr( ln, "INSTALL SCRIPT:" )) && match == ln ) + { + install_script = ret + 1; + ++found; + } + if( (match = strstr( ln, "FILE LIST:" )) && match == ln ) + { + file_list = ret + 1; + ++found; + } + + ++ret; + } + + free( line ); + + ret = found; + + fclose( pkglog ); pkglog = NULL; + } + + return( ret ); +} + + +int get_pkglog_line( char *pattern ) +{ + int ret = -1, found = 0; + + if( pkglog_fname != NULL ) + { + pkglog = fopen( (const char *)pkglog_fname, "r" ); + if( !pkglog ) + { + FATAL_ERROR( "Cannot open %s file", pkglog_fname ); + } + } + + if( pkglog != NULL ) + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + while( (ln = fgets( line, PATH_MAX, pkglog )) ) + { + char *match = NULL; + + if( (match = strstr( ln, pattern )) && match == ln ) /* at start of line only */ + { + ++ret; + ++found; + break; + } + ++ret; + } + if( !found ) ret = 0; + + free( line ); + + fclose( pkglog ); pkglog = NULL; + } + return( ret ); +} + + +int get_ref_cnt() +{ + int ret = -1, found = 0; + + if( pkglog_fname != NULL ) + { + pkglog = fopen( (const char *)pkglog_fname, "r" ); + if( !pkglog ) + { + FATAL_ERROR( "Cannot open %s file", pkglog_fname ); + } + } + + if( pkglog != NULL ) + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + + while( (ln = fgets( line, PATH_MAX, pkglog )) ) + { + char *match = NULL; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( (match = strstr( ln, "REFERENCE COUNTER:" )) && match == ln ) /* at start of line only */ + { + char *cnt = skip_spaces( ln + strlen( "REFERENCE COUNTER:" ) ); + ret = atoi( cnt ); + free( cnt ); + ++found; + break; + } + } + if( !found ) ret = -1; + + free( line ); + + fclose( pkglog ); pkglog = NULL; + } + return( ret ); +} + + +int write_references() +{ + int ret = -1; + + if( pkglog_fname != NULL ) + { + pkglog = fopen( (const char *)pkglog_fname, "r" ); + if( !pkglog ) + { + FATAL_ERROR( "Cannot open %s file", pkglog_fname ); + } + } + + if( destination != NULL ) + { + char *output_fname = NULL; + + output_fname = (char *)alloca( strlen( destination ) + 13 ); + strcpy( output_fname, destination ); + strcat( output_fname, "/.REFERENCES" ); + output = fopen( (const char *)output_fname, "w" ); + if( !output ) + { + FATAL_ERROR( "Cannot create %s file", output_fname ); + } + } + + if( (pkglog != NULL) && (output != NULL) ) + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + + if( reference_counter && reference_counter < requires ) + { + int n = 1, lines = 0; + + ++ret; + + while( (ln = fgets( line, PATH_MAX, pkglog )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( (n > reference_counter) && (n < requires) ) + { + fprintf( output, "%s\n", ln ); + ++lines; + } + ++n; + } + + ret = lines; /* number of lines in the LIST */ + } + + free( line ); + + fclose( pkglog ); pkglog = NULL; + fclose( output ); output = NULL; + } + + return( ret ); +} + + +int write_requires() +{ + int ret = -1; + + if( pkglog_fname != NULL ) + { + pkglog = fopen( (const char *)pkglog_fname, "r" ); + if( !pkglog ) + { + FATAL_ERROR( "Cannot open %s file", pkglog_fname ); + } + } + + if( destination != NULL ) + { + char *output_fname = NULL; + + output_fname = (char *)alloca( strlen( destination ) + 11 ); + strcpy( output_fname, destination ); + strcat( output_fname, "/.REQUIRES" ); + output = fopen( (const char *)output_fname, "w" ); + if( !output ) + { + FATAL_ERROR( "Cannot create %s file", output_fname ); + } + } + + if( (pkglog != NULL) && (output != NULL) ) + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + + if( requires && requires < package_description ) + { + int n = 1, lines = 0; + + ++ret; + + while( (ln = fgets( line, PATH_MAX, pkglog )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( (n > requires) && (n < package_description) ) + { + fprintf( output, "%s\n", ln ); + ++lines; + } + ++n; + } + + ret = lines; /* number of lines in the LIST */ + } + + free( line ); + + fclose( pkglog ); pkglog = NULL; + fclose( output ); output = NULL; + } + + return( ret ); +} + + +int write_package_description() +{ + int ret = -1; + + if( pkglog_fname != NULL ) + { + pkglog = fopen( (const char *)pkglog_fname, "r" ); + if( !pkglog ) + { + FATAL_ERROR( "Cannot open %s file", pkglog_fname ); + } + } + + if( destination != NULL ) + { + char *output_fname = NULL; + + output_fname = (char *)alloca( strlen( destination ) + 14 ); + strcpy( output_fname, destination ); + strcat( output_fname, "/.DESCRIPTION" ); + output = fopen( (const char *)output_fname, "w" ); + if( !output ) + { + FATAL_ERROR( "Cannot create %s file", output_fname ); + } + } + + if( (pkglog != NULL) && (output != NULL) ) + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + + if( package_description && package_description < restore_links ) + { + int n = 1, lines = 0; + + ++ret; + + while( (ln = fgets( line, PATH_MAX, pkglog )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( (n > package_description) && (n < restore_links) ) + { + fprintf( output, "%s\n", ln ); + ++lines; + } + ++n; + } + + ret = lines; /* number of lines in the LIST */ + } + + free( line ); + + fclose( pkglog ); pkglog = NULL; + fclose( output ); output = NULL; + } + + return( ret ); +} + + +int write_restore_links() +{ + int ret = -1; + + if( pkglog_fname != NULL ) + { + pkglog = fopen( (const char *)pkglog_fname, "r" ); + if( !pkglog ) + { + FATAL_ERROR( "Cannot open %s file", pkglog_fname ); + } + } + + if( destination != NULL ) + { + char *output_fname = NULL; + + output_fname = (char *)alloca( strlen( destination ) + 15 ); + strcpy( output_fname, destination ); + strcat( output_fname, "/.RESTORELINKS" ); + output = fopen( (const char *)output_fname, "w" ); + if( !output ) + { + FATAL_ERROR( "Cannot create %s file", output_fname ); + } + } + + if( (pkglog != NULL) && (output != NULL) ) + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + + if( restore_links && restore_links < install_script ) + { + int n = 1, lines = 0; + + ++ret; + + while( (ln = fgets( line, PATH_MAX, pkglog )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( (n > restore_links) && (n < install_script) ) + { + fprintf( output, "%s\n", ln ); + ++lines; + } + ++n; + } + + ret = lines; /* number of lines in the LIST */ + } + + free( line ); + + fclose( pkglog ); pkglog = NULL; + fclose( output ); output = NULL; + } + + return( ret ); +} + + +int write_install_script() +{ + int ret = -1; + char *output_fname = NULL; + + if( pkglog_fname != NULL ) + { + pkglog = fopen( (const char *)pkglog_fname, "r" ); + if( !pkglog ) + { + FATAL_ERROR( "Cannot open %s file", pkglog_fname ); + } + } + + if( destination != NULL ) + { + output_fname = (char *)alloca( strlen( destination ) + 10 ); + strcpy( output_fname, destination ); + strcat( output_fname, "/.INSTALL" ); + output = fopen( (const char *)output_fname, "w" ); + if( !output ) + { + FATAL_ERROR( "Cannot create %s file", output_fname ); + } + } + + if( (pkglog != NULL) && (output != NULL) ) + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + + if( install_script && install_script < file_list ) + { + int n = 1, lines = 0; + + ++ret; + + while( (ln = fgets( line, PATH_MAX, pkglog )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( (n > install_script) && (n < file_list) ) + { + fprintf( output, "%s\n", ln ); + ++lines; + } + ++n; + } + + ret = lines; /* number of lines in the LIST */ + } + + free( line ); + + fclose( pkglog ); pkglog = NULL; + fclose( output ); output = NULL; + } + + chmod( (const char *)output_fname, (mode_t)0755 ); + + return( ret ); +} + + +int write_filelist() +{ + int ret = -1; + + if( pkglog_fname != NULL ) + { + pkglog = fopen( (const char *)pkglog_fname, "r" ); + if( !pkglog ) + { + FATAL_ERROR( "Cannot open %s file", pkglog_fname ); + } + } + + if( destination != NULL ) + { + char *output_fname = NULL; + + output_fname = (char *)alloca( strlen( destination ) + 11 ); + strcpy( output_fname, destination ); + strcat( output_fname, "/.FILELIST" ); + output = fopen( (const char *)output_fname, "w" ); + if( !output ) + { + FATAL_ERROR( "Cannot create %s file", output_fname ); + } + } + + if( (pkglog != NULL) && (output != NULL) ) + { + char *ln = NULL; + char *line = NULL; + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + ++ret; + + if( file_list ) + { + int n = 1, lines = 0; + + ++ret; + + while( (ln = fgets( line, PATH_MAX, pkglog )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( n > file_list ) + { + fprintf( output, "%s\n", ln ); + ++lines; + } + ++n; + } + + ret = lines; /* number of lines in the LIST */ + } + + free( line ); + + fclose( pkglog ); pkglog = NULL; + fclose( output ); output = NULL; + } + + return( ret ); +} + + +/********************************************* + Get directory where this program is placed: + */ +char *get_selfdir( void ) +{ + char *buf = NULL; + ssize_t len; + + buf = (char *)malloc( PATH_MAX ); + if( !buf ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + bzero( (void *)buf, PATH_MAX ); + len = readlink( "/proc/self/exe", buf, (size_t)PATH_MAX ); + if( len > 0 && len < PATH_MAX ) + { + char *p = xstrdup( (const char *)dirname( buf ) ); + free( buf ); + return p; + } + FATAL_ERROR( "Cannot determine self directory. Please mount /proc filesystem" ); +} + +void set_stack_size( void ) +{ + const rlim_t stack_size = 16 * 1024 * 1024; /* min stack size = 16 MB */ + struct rlimit rl; + int ret; + + ret = getrlimit( RLIMIT_STACK, &rl ); + if( ret == 0 ) + { + if( rl.rlim_cur < stack_size ) + { + rl.rlim_cur = stack_size; + ret = setrlimit( RLIMIT_STACK, &rl ); + if( ret != 0 ) + { + fprintf(stderr, "setrlimit returned result = %d\n", ret); + FATAL_ERROR( "Cannot set stack size" ); + } + } + } +} + + +int main( int argc, char *argv[] ) +{ + int sections = 0; + gid_t gid; + + set_signal_handlers(); + + gid = getgid(); + setgroups( 1, &gid ); + + fatal_error_hook = fatal_error_actions; + + selfdir = get_selfdir(); + + errlog = stderr; + + program = basename( argv[0] ); + get_args( argc, argv ); + + /* set_stack_size(); */ + + if( pkglog_type == PKGLOG_TEXT ) + { + sections = get_pkglog_sections(); + if( sections < 3 ) + { + FATAL_ERROR( "%s: Wrong PKGLOG file format", basename( pkglog_fname) ); + } + + refcount = get_ref_cnt(); + + if( strstr( operation, "pkginfo" ) ) exit_status += write_pkginfo(); + + if( strstr( operation, "references" ) ) + { + if( !reference_counter ) + { + WARNING( "The REFERENCE COUNTER is not present in %s file", basename( pkglog_fname ) ); + } + else + { + if( write_references() != refcount ) + { + WARNING( "The REFERENCE COUNTER invalid in %s file", basename( pkglog_fname ) ); + } + } + } + + if( strstr( operation, "requires" ) ) + { + if( write_requires() <= 0 ) + { + if( ! DO_NOT_WARN_ABOUT_EMPTY_REQUIRES ) + WARNING( "The REQUIRES is not present in %s file", basename( pkglog_fname ) ); + } + } + + if( strstr( operation, "description" ) ) + { + if( write_package_description() <= 0 ) + { + WARNING( "The PACKAGE DESCRIPTION is not present in %s file", basename( pkglog_fname ) ); + } + } + + if( strstr( operation, "restore-links" ) ) + { + if( write_restore_links() <= 0 ) + { + if( ! DO_NOT_WARN_ABOUT_EMPTY_RESTORE_LINKS ) + WARNING( "The RESTORE LINKS is not present in %s file", basename( pkglog_fname ) ); + } + } + + if( strstr( operation, "install-script" ) ) + { + if( write_install_script() <= 0 ) + { + ERROR( "The INSTALL SCRIPT is not present in %s file", basename( pkglog_fname ) ); + } + } + + if( strstr( operation, "filelist" ) ) + { + if( write_filelist() <= 0 ) + { + ERROR( "The FILE LIST is not present in %s file", basename( pkglog_fname ) ); + } + } + + } + else /* TARBALL: */ + { + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *cmd = NULL, *errmsg = NULL, *wmsg = NULL; + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + + errmsg = (char *)malloc( (size_t)PATH_MAX ); + if( !errmsg ) { FATAL_ERROR( "Cannot allocate memory" ); } + + wmsg = (char *)malloc( (size_t)PATH_MAX ); + if( !wmsg ) { FATAL_ERROR( "Cannot allocate memory" ); } + + + if( strstr( operation, "pkginfo" ) ) /* strongly required */ + { + bzero( (void *)cmd, PATH_MAX ); + bzero( (void *)errmsg, PATH_MAX ); + bzero( (void *)wmsg, PATH_MAX ); + + (void)sprintf( &errmsg[0], "Cannot get .PKGINFO from %s file", basename( pkglog_fname ) ); + + len = snprintf( &cmd[0], PATH_MAX, "tar -C %s -x%sf %s %s > /dev/null 2>&1", destination, uncompress, pkglog_fname, ".PKGINFO" ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( errmsg ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)&wmsg[0], PATH_MAX ); + if( rc != 0 ) + { + /***************************************** + if( rc > 0 ) { return TAR exit status } + else { return EXIT_FAILURE } + */ + if( rc > 0 ) exit_status = rc - 1; /* ERROR() will add one */ + ERROR( errmsg ); + if( fatal_error_hook) fatal_error_hook(); + exit( exit_status ); + } + } + + /* .REFERENCES is not present in package tarball */ + + if( strstr( operation, "requires" ) ) /* optional; may be warning */ + { + bzero( (void *)cmd, PATH_MAX ); + bzero( (void *)errmsg, PATH_MAX ); + bzero( (void *)wmsg, PATH_MAX ); + + (void)sprintf( &errmsg[0], "Cannot get .REQUIRES from %s file", basename( pkglog_fname ) ); + + (void)sprintf( &cmd[0], "tar -C %s -x%sf %s %s > /dev/null 2>&1", destination, uncompress, pkglog_fname, ".REQUIRES" ); + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)&wmsg[0], PATH_MAX ); + if( rc != 0 && DO_NOT_WARN_ABOUT_EMPTY_REQUIRES == 0 ) + { + WARNING( errmsg ); + } + } + + if( strstr( operation, "description" ) ) /* optional; always warning */ + { + bzero( (void *)cmd, PATH_MAX ); + bzero( (void *)wmsg, PATH_MAX ); + + (void)sprintf( &cmd[0], "tar -C %s -x%sf %s %s > /dev/null 2>&1", destination, uncompress, pkglog_fname, ".DESCRIPTION" ); + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)&wmsg[0], PATH_MAX ); + if( rc != 0 ) + { + WARNING( "Cannot get package .DESCRIPTION from %s file", basename( pkglog_fname ) ); + } + } + + if( strstr( operation, "restore-links" ) ) /* optional; may be warning */ + { + bzero( (void *)cmd, PATH_MAX ); + bzero( (void *)errmsg, PATH_MAX ); + bzero( (void *)wmsg, PATH_MAX ); + + (void)sprintf( &errmsg[0], "Cannot get .RESTORELINKS script from %s file", basename( pkglog_fname ) ); + + (void)sprintf( &cmd[0], "tar -C %s -x%sf %s %s > /dev/null 2>&1", destination, uncompress, pkglog_fname, ".RESTORELINKS" ); + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)&wmsg[0], PATH_MAX ); + if( rc != 0 && DO_NOT_WARN_ABOUT_EMPTY_RESTORE_LINKS == 0 ) + { + WARNING( errmsg ); + } + } + + if( strstr( operation, "install-script" ) ) /* strongly required */ + { + bzero( (void *)cmd, PATH_MAX ); + bzero( (void *)errmsg, PATH_MAX ); + bzero( (void *)wmsg, PATH_MAX ); + + (void)sprintf( &errmsg[0], "Cannot get .INSTALL script from %s file", basename( pkglog_fname ) ); + + len = snprintf( &cmd[0], PATH_MAX, "tar -C %s -x%sf %s %s > /dev/null 2>&1", destination, uncompress, pkglog_fname, ".INSTALL" ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( errmsg ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)&wmsg[0], PATH_MAX ); + if( rc != 0 ) + { + /***************************************** + if( rc > 0 ) { return TAR exit status } + else { return EXIT_FAILURE } + */ + if( rc > 0 ) exit_status = rc - 1; /* ERROR() will add one */ + ERROR( errmsg ); + if( fatal_error_hook) fatal_error_hook(); + exit( exit_status ); + } + } + + if( strstr( operation, "filelist" ) ) /* strongly required */ + { + bzero( (void *)cmd, PATH_MAX ); + bzero( (void *)errmsg, PATH_MAX ); + bzero( (void *)wmsg, PATH_MAX ); + + (void)sprintf( &errmsg[0], "Cannot get .FILELIST from %s file", basename( pkglog_fname ) ); + + len = snprintf( &cmd[0], PATH_MAX, "tar -C %s -x%sf %s %s > /dev/null 2>&1", destination, uncompress, pkglog_fname, ".FILELIST" ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( errmsg ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)&wmsg[0], PATH_MAX ); + if( rc != 0 ) + { + /***************************************** + if( rc > 0 ) { return TAR exit status } + else { return EXIT_FAILURE } + */ + if( rc > 0 ) exit_status = rc - 1; /* ERROR() will add one */ + ERROR( errmsg ); + if( fatal_error_hook) fatal_error_hook(); + exit( exit_status ); + } + } + + if( cmd ) free( cmd ); + if( errmsg ) free( errmsg ); + if( wmsg ) free( wmsg ); + } + + + free_resources(); + + exit( exit_status ); +} diff --git a/src/pkglist.c b/src/pkglist.c new file mode 100644 index 0000000..23b080d --- /dev/null +++ b/src/pkglist.c @@ -0,0 +1,2198 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <linux/limits.h> +#include <libgen.h> /* basename(3) */ +#include <unistd.h> +#include <time.h> +#include <math.h> + +#include <msglog.h> +#include <wrapper.h> + +#include <make-pkglist.h> + +#include <cmpvers.h> +#include <dlist.h> +#include <btree.h> +#include <jsmin.h> +#include <pkglist.h> + + +char *htmlroot = NULL; +char *hardware = NULL; +int minimize = 0; + +struct dlist *srcpkgs = NULL; + +struct dlist *packages = NULL; +struct dlist *tarballs = NULL; + +struct dlist *provides = NULL; +struct dlist *extern_requires = NULL; + +static struct dlist *tree = NULL; + +static char *pkgs_fname = NULL, + *tree_fname = NULL, + *html_fname = NULL; + +static char *pkgs_min_fname = NULL, + *tree_min_fname = NULL; + +static const char *tarball_suffix = "txz"; + +/*************************************************************** + tarballs List functions: + ======================= + + NOTE: + ---- + TARBALLS is an optional list created in case when we have + a set of PACKAGES as input of make-pkglist utility. When we + are working with a set of input PKGLOGs the TARBALLS list + is not chreated and pointer to the tarballs == NULL. + */ +void add_tarball( char *tarball ) +{ + tarballs = dlist_append( tarballs, (void *)xstrdup( (const char *)tarball ) ); +} + +static void __free_tarball( void *data, void *user_data ) +{ + if( data ) { free( data ); } +} + +void free_tarballs( void ) +{ + if( tarballs ) { dlist_free( tarballs, __free_tarball ); tarballs = NULL; } +} + +static int __compare_tarballs( const void *a, const void *b ) +{ + return strncmp( (const char *)a, (const char *)b, (size_t)strlen((const char *)b) ); +} + +const char *find_tarball( const char *name ) +{ + struct dlist *node = NULL; + + if( !tarballs || !name ) return NULL; + + node = dlist_find_data( tarballs, __compare_tarballs, (const void *)name ); + if( node ) + { + return (const char *)node->data; + } + + return NULL; +} + +/********************* + Just for debugging: + */ +static void __print_tarball( void *data, void *user_data ) +{ + int *counter = (int *)user_data; + + if( counter ) { fprintf( stdout, "tarball[%.5d]: %s\n", *counter, (char *)data ); ++(*counter); } + else { fprintf( stdout, "tarball: %s\n", (char *)data ); } +} + +void print_tarballs( void ) +{ + int cnt = 0; + if( tarballs ) { dlist_foreach( tarballs, __print_tarball, (void *)&cnt ); } +} +/* + End of tarballs List functions. + ***************************************************************/ + + + +char *strprio( enum _priority priority, int short_name ) +{ + char *p = NULL; + + switch( priority ) + { + case REQUIRED: + p = ( short_name ) ? "REQ" : "REQUIRED"; + break; + case RECOMMENDED: + p = ( short_name ) ? "REC" : "RECOMMENDED"; + break; + case OPTIONAL: + p = ( short_name ) ? "OPT" : "OPTIONAL"; + break; + case SKIP: + p = ( short_name ) ? "SKP" : "SKIP"; + break; + } + return p; +} + +char *strproc( enum _procedure procedure ) +{ + char *p = NULL; + + switch( procedure ) + { + case INSTALL: + p = "install"; + break; + case UPDATE: + p = "update"; + break; + } + return p; +} + + +/*************************************************************** + PACKAGE functions: + */ + +struct pkg *pkg_alloc( void ) +{ + struct pkg *pkg = NULL; + + pkg = (struct pkg *)malloc( sizeof( struct pkg ) ); + if( !pkg ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)pkg, sizeof( struct pkg ) ); + + return pkg; +} + +void pkg_free( struct pkg *pkg ) +{ + if( pkg ) + { + if( pkg->group ) { free( pkg->group ); pkg->group = NULL; } + if( pkg->name ) { free( pkg->name ); pkg->name = NULL; } + if( pkg->version ) { free( pkg->version ); pkg->version = NULL; } + + free( pkg ); + } +} + +static void __pkg_free_func( void *data, void *user_data ) +{ + struct pkg *pkg = (struct pkg *)data; + if( pkg ) { pkg_free( pkg ); } +} + +void free_srcpkgs( void ) +{ + if( srcpkgs ) { dlist_free( srcpkgs, __pkg_free_func ); srcpkgs = NULL; } +} + +void add_srcpkg( struct pkg *pkg ) +{ + srcpkgs = dlist_append( srcpkgs, (void *)pkg ); +} + + +static struct pkginfo *__pkginfo_alloc( void ) +{ + struct pkginfo *pkginfo = NULL; + + pkginfo = (struct pkginfo *)malloc( sizeof( struct pkginfo ) ); + if( !pkginfo ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)pkginfo, sizeof( struct pkginfo ) ); + + return pkginfo; +} + +static void __pkginfo_free( struct pkginfo *pkginfo ) +{ + if( pkginfo ) + { + if( pkginfo->name ) { free( pkginfo->name ); pkginfo->name = NULL; } + if( pkginfo->version ) { free( pkginfo->version ); pkginfo->version = NULL; } + if( pkginfo->arch ) { free( pkginfo->arch ); pkginfo->arch = NULL; } + if( pkginfo->distro_name ) { free( pkginfo->distro_name ); pkginfo->distro_name = NULL; } + if( pkginfo->distro_version ) { free( pkginfo->distro_version ); pkginfo->distro_version = NULL; } + if( pkginfo->group ) { free( pkginfo->group ); pkginfo->group = NULL; } + if( pkginfo->short_description ) { free( pkginfo->short_description ); pkginfo->short_description = NULL; } + if( pkginfo->url ) { free( pkginfo->url ); pkginfo->url = NULL; } + if( pkginfo->license ) { free( pkginfo->license ); pkginfo->license = NULL; } + + free( pkginfo ); + } +} + + +static struct references *__references_alloc( void ) +{ + struct references *references = NULL; + + references = (struct references *)malloc( sizeof( struct references ) ); + if( !references ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)references, sizeof( struct references ) ); + + return references; +} + +static void __references_free( struct references *references ) +{ + if( references ) + { + if( references->list ) { dlist_free( references->list, __pkg_free_func ); references->list = NULL; } + free( references ); + } +} + + +static struct requires *__requires_alloc( void ) +{ + struct requires *requires = NULL; + + requires = (struct requires *)malloc( sizeof( struct requires ) ); + if( !requires ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)requires, sizeof( struct requires ) ); + + return requires; +} + +static void __requires_free( struct requires *requires ) +{ + if( requires ) + { + if( requires->list ) { dlist_free( requires->list, __pkg_free_func ); requires->list = NULL; } + free( requires ); + } +} + + +static struct files *__files_alloc( void ) +{ + struct files *files = NULL; + + files = (struct files *)malloc( sizeof( struct files ) ); + if( !files ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)files, sizeof( struct files ) ); + + return files; +} + +static void __files_free_func( void *data, void *user_data ) +{ + if( data ) { free( data ); } +} + +static void __files_free( struct files *files ) +{ + if( files ) + { + if( files->list ) { dlist_free( files->list, __files_free_func ); files->list = NULL; } + free( files ); + } +} + + +struct package *package_alloc( void ) +{ + struct package *package = NULL; + struct pkginfo *pkginfo = __pkginfo_alloc(); + struct references *references = __references_alloc(); + struct requires *requires = __requires_alloc(); + struct files *files = __files_alloc(); + + package = (struct package *)malloc( sizeof( struct package ) ); + if( !package ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)package, sizeof( struct package ) ); + + package->pkginfo = pkginfo; + package->references = references; + package->requires = requires; + package->files = files; + + return package; +} + +void package_free( struct package *package ) +{ + if( package ) + { + if( package->pkginfo ) { __pkginfo_free( package->pkginfo ); package->pkginfo = NULL; } + if( package->references ) { __references_free( package->references ); package->references = NULL; } + if( package->requires ) { __requires_free( package->requires ); package->requires = NULL; } + if( package->files ) { __files_free( package->files ); package->files = NULL; } + + if( package->description ) { free( package->description ); package->description = NULL; } + if( package->restore_links ) { free( package->restore_links ); package->restore_links = NULL; } + if( package->install_script ) { free( package->install_script ); package->install_script = NULL; } + if( package->hardware ) { free( package->hardware ); package->hardware = NULL; } + if( package->tarball ) { free( package->tarball ); package->tarball = NULL; } + + free( package ); + } +} + +static void __package_free_func( void *data, void *user_data ) +{ + struct package *package = (struct package *)data; + if( package ) { package_free( package ); } +} + +void free_packages( void ) +{ + if( packages ) { dlist_free( packages, __package_free_func ); packages = NULL; } +} + + +static int __compare_packages( const void *a, const void *b ) +{ + int ret = -1; + + struct package *pkg1 = (struct package *)a; + struct package *pkg2 = (struct package *)b; + + if( pkg1->pkginfo->group && pkg2->pkginfo->group ) + { + ret = strcmp( pkg1->pkginfo->group, pkg2->pkginfo->group ); + } + else if( !pkg1->pkginfo->group && !pkg2->pkginfo->group ) + { + ret = 0; + } + else if( pkg1->pkginfo->group ) + { + ret = 1; + } + + if( ! ret ) + { + return strcmp( pkg1->pkginfo->name, pkg2->pkginfo->name ); + } + return ret; +} + +static int __compare_packages_with_version( const void *a, const void *b ) +{ + int ret = -1; + + struct package *pkg1 = (struct package *)a; + struct package *pkg2 = (struct package *)b; + + if( pkg1->pkginfo->group && pkg2->pkginfo->group ) + { + ret = strcmp( pkg1->pkginfo->group, pkg2->pkginfo->group ); + } + else if( !pkg1->pkginfo->group && !pkg2->pkginfo->group ) + { + ret = 0; + } + else if( pkg1->pkginfo->group ) + { + ret = 1; + } + + if( ! ret ) + { + ret = strcmp( pkg1->pkginfo->name, pkg2->pkginfo->name ); + if( ! ret ) + { + return cmp_version( (const char *)pkg1->pkginfo->version, (const char *)pkg2->pkginfo->version ); + } + } + return ret; +} + + +void add_package( struct package *package ) +{ + packages = dlist_append( packages, (void *)package ); +} + +void add_reference( struct package *package, struct pkg *pkg ) +{ + if( package && package->references && pkg ) + { + package->references->list = dlist_append( package->references->list, (void *)pkg ); + package->references->size = dlist_length( package->references->list ); + } +} + +void add_required( struct package *package, struct pkg *pkg ) +{ + if( package && package->requires && pkg ) + { + package->requires->list = dlist_append( package->requires->list, (void *)pkg ); + package->requires->size = dlist_length( package->requires->list ); + } +} + +void add_file( struct package *package, const char *fname ) +{ + if( package && package->files && fname ) + { + package->files->list = dlist_append( package->files->list, (void *)xstrdup( (const char *)fname ) ); + package->files->size = dlist_length( package->files->list ); + } +} + +/********************* + Just for debugging: + */ +static void __print_reference( void *data, void *user_data ) +{ + struct pkg *pkg = (struct pkg *)data; + + if( pkg ) + { + if( pkg->group ) { fprintf( stdout, "reference: %s/%s=%s\n", pkg->group, pkg->name, pkg->version ); } + else { fprintf( stdout, "reference: %s=%s\n", pkg->name, pkg->version ); } + } +} + +void package_print_references( struct package *package ) +{ + if( !package ) return; + + if( package->references->list ) + { + dlist_foreach( package->references->list, __print_reference, NULL ); + } +} + +static void __print_required( void *data, void *user_data ) +{ + struct pkg *pkg = (struct pkg *)data; + + if( pkg ) + { + if( pkg->group ) { fprintf( stdout, "required: %s/%s=%s\n", pkg->group, pkg->name, pkg->version ); } + else { fprintf( stdout, "required: %s=%s\n", pkg->name, pkg->version ); } + } +} + +void package_print_requires( struct package *package ) +{ + if( !package ) return; + + if( package->requires->list ) + { + dlist_foreach( package->requires->list, __print_required, NULL ); + } +} + +static void __print_file( void *data, void *user_data ) +{ + int *counter = (int *)user_data; + + if( counter ) { fprintf( stdout, "file[%.5d]: %s\n", *counter, (char *)data ); ++(*counter); } + else { fprintf( stdout, "file: %s\n", (char *)data ); } +} + +void package_print_files( struct package *package ) +{ + int cnt = 0; + + if( !package ) return; + + if( package->files->list ) + { + dlist_foreach( package->files->list, __print_file, (void *)&cnt ); + } +} + +/* + End of PACKAGES functions. + ***************************************************************/ + +/*************************************************************** + Extern REQUIRES list functions: + */ + +static int __compare_required( const void *a, const void *b ) +{ + int ret = -1; + + struct pkg *pkg1 = (struct pkg *)a; + struct pkg *pkg2 = (struct pkg *)b; + + if( pkg1->group && pkg2->group ) + { + ret = strcmp( pkg1->group, pkg2->group ); + } + else if( !pkg1->group && !pkg2->group ) + { + ret = 0; + } + else if( pkg1->group ) + { + ret = 1; + } + + if( ! ret ) + { + return strcmp( pkg1->name, pkg2->name ); + } + return ret; +} + +static int __compare_required_with_version( const void *a, const void *b ) +{ + int ret = -1; + + struct pkg *pkg1 = (struct pkg *)a; + struct pkg *pkg2 = (struct pkg *)b; + + if( pkg1->group && pkg2->group ) + { + ret = strcmp( pkg1->group, pkg2->group ); + } + else if( !pkg1->group && !pkg2->group ) + { + ret = 0; + } + else if( pkg1->group ) + { + ret = 1; + } + + if( ! ret ) + { + ret = strcmp( pkg1->name, pkg2->name ); + if( ! ret ) + { + return cmp_version( (const char *)pkg1->version, (const char *)pkg2->version ); + } + } + return ret; +} + +static void __add_unique_required( void *data, void *user_data ) +{ + struct pkg *pkg = (struct pkg *)data; + + if( pkg ) + { + struct dlist *found = dlist_find_data( extern_requires, __compare_required, (const void *)data ); + + if( found ) + { + if( cmp_version( (const char *)((struct pkg *)found->data)->version, (const char *)pkg->version ) ) + { + char *s = ((struct pkg *)found->data)->version; + ((struct pkg *)found->data)->version = + xstrdup( (const char *)max_version( (const char *)((struct pkg *)found->data)->version, + (const char *)pkg->version ) ); + free( s ); + } + } + else + { + struct pkg *req = pkg_alloc(); + if( req ) + { + if( pkg->group ) + { + req->group = xstrdup( (const char *)pkg->group ); + } + req->name = xstrdup( (const char *)pkg->name ); + req->version = xstrdup( (const char *)pkg->version ); + + extern_requires = dlist_append( extern_requires, (void *)req ); + } + } + } +} + +static void __fill_extern_requires( void *data, void *user_data ) +{ + struct package *package = (struct package *)data; + + if( package ) + { + struct pkg *provide = pkg_alloc(); + + if( provide ) + { + if( package->pkginfo->group ) + { + provide->group = xstrdup( (const char *)package->pkginfo->group ); + } + provide->name = xstrdup( (const char *)package->pkginfo->name ); + provide->version = xstrdup( (const char *)package->pkginfo->version ); + + provides = dlist_append( provides, (void *)provide ); + } + + if( package->requires->list ) + { + dlist_foreach( package->requires->list, __add_unique_required, NULL ); + } + } +} + +static void __clean_extern_requires( void *data, void *user_data ) +{ + if( data ) + { + extern_requires = dlist_remove_data( extern_requires, __compare_required_with_version, __pkg_free_func, (const void *)data ); + } +} + +static int __compare_provided_old_package( const void *a, const void *b ) +{ + int ret = -1; + + struct package *pkg1 = (struct package *)a; + struct pkg *pkg2 = (struct pkg *)b; + + if( pkg1->pkginfo->group && pkg2->group ) + { + ret = strcmp( pkg1->pkginfo->group, pkg2->group ); + } + else if( !pkg1->pkginfo->group && !pkg2->group ) + { + ret = 0; + } + else if( pkg1->pkginfo->group ) + { + ret = 1; + } + + if( ! ret ) + { + ret = strcmp( pkg1->pkginfo->name, pkg2->name ); + if( ! ret ) + { + pkg2->procedure = UPDATE; /* mark as too old */ + return ret; + } + } + return ret; +} + +static void __remove_old_package( void *data, void *user_data ) +{ + packages = dlist_remove_data( packages, __compare_provided_old_package, __package_free_func, (const void *)data ); +} + +static void remove_old_packages( void ) +{ + dlist_foreach( extern_requires, __remove_old_package, NULL ); +} +/* + End of Extern REQUIRES list functions. + ***************************************************************/ + + +/*************************************************************** + Check REQUIRES functions: + */ +static int __compare_provided( const void *a, const void *b ) +{ + int ret = -1; + + struct package *pkg1 = (struct package *)a; + struct pkg *pkg2 = (struct pkg *)b; + + if( pkg1->pkginfo->group && pkg2->group ) + { + ret = strcmp( pkg1->pkginfo->group, pkg2->group ); + } + else if( !pkg1->pkginfo->group && !pkg2->group ) + { + ret = 0; + } + else if( pkg1->pkginfo->group ) + { + ret = 1; + } + + if( ! ret ) + { + return strcmp( pkg1->pkginfo->name, pkg2->name ); + } + return ret; +} + +static int __compare_packages_by_name( const void *a, const void *b ) +{ + int ret = -1; + + struct package *pkg1 = (struct package *)a; + struct package *pkg2 = (struct package *)b; + + if( !strcmp( pkg1->pkginfo->name, pkg2->pkginfo->name ) ) + { + if( pkg1->pkginfo->group && pkg2->pkginfo->group ) + { + ret = strcmp( pkg1->pkginfo->group, pkg2->pkginfo->group ); + } + else if( !pkg1->pkginfo->group && !pkg2->pkginfo->group ) + { + ret = 0; + } + else if( pkg1->pkginfo->group ) + { + ret = 1; + } + + /* returns equal only if groups are not equal */ + if( ret ) return 0; + } + + return ret; +} + +static int check_dependencies( struct package *package ) +{ + struct dlist *list = NULL, *next = NULL, *update = NULL; + int depended = -1; + + if( !package ) return depended; + depended = 0; + + if( !(list = package->requires->list) ) return depended; + + while( list ) + { + next = dlist_next( list ); + { + int has_extern_dependencies = 0, already_provided = 0; + + struct pkg *pkg = (struct pkg *)list->data; + struct dlist *found = dlist_find_data( extern_requires, __compare_required, (const void *)pkg ); + + if( found ) + { + if( cmp_version( (const char *)((struct pkg *)found->data)->version, (const char *)pkg->version ) >= 0 ) + { + /* required package is found in the extern_requires list */ + has_extern_dependencies += 1; + } + } + + found = dlist_find_data( provides, __compare_provided, (const void *)pkg ); + if( found ) + { + if( cmp_version( (const char *)((struct package *)found->data)->pkginfo->version, (const char *)pkg->version ) >= 0 ) + { + /* required package is found in the extern_requires list */ + already_provided += 1; + } + } + + if( !already_provided && !has_extern_dependencies ) depended += 1; + } + list = next; + } + + /* Check if the package with the same name already exists in the provides list */ + update = dlist_find_data( provides, __compare_packages_by_name, (const void *)package ); + if( update ) + { + /* Set install procedure to UPDATE: */ + package->procedure = UPDATE; + } + + return depended; +} +/* + End of Check REQUIRES functions. + ***************************************************************/ + +static void __fill_provides_list( void *data, void *user_data ) +{ + struct package *package = (struct package *)data; + + if( package ) + { + if( !check_dependencies( package ) ) + { + /* move independed package to the provides list */ + packages = dlist_remove( packages, (const void *)data ); + provides = dlist_append( provides, (void *)package ); + } + } +} + + +static void __print_extern_package( void *data, void *user_data ) +{ + FILE *output = (FILE *)user_data; + struct pkg *pkg = (struct pkg *)data; + + if( pkg ) + { + if( pkg->group ) { fprintf( output, "# required: %s/%s=%s\n", pkg->group, pkg->name, pkg->version ); } + else { fprintf( output, "# required: %s=%s\n", pkg->name, pkg->version ); } + } +} + +static void __print_provided_package( void *data, void *user_data ) +{ + FILE *output = (FILE *)user_data; + struct package *package = (struct package *)data; + + if( package ) + { + fprintf( output, "%s:", package->pkginfo->name ); + fprintf( output, "%s:", package->pkginfo->version ); + fprintf( output, "%s:", package->pkginfo->short_description ); + if( package->tarball ) + { + fprintf( output, "%s:", package->tarball ); + } + else + { + if( package->pkginfo->group ) fprintf( output, "%s/", package->pkginfo->group ); + + fprintf( output, "%s-", package->pkginfo->name ); + fprintf( output, "%s-", package->pkginfo->version ); + fprintf( output, "%s-", package->pkginfo->arch ); + fprintf( output, "%s-", package->pkginfo->distro_name ); + fprintf( output, "%s.", package->pkginfo->distro_version ); + fprintf( output, "%s:", tarball_suffix ); /* default is '.txz' */ + } + fprintf( output, "%s:", strproc( package->procedure ) ); + fprintf( output, "%s\n", strprio( package->priority, 0 ) ); + } +} + + +static void __reduce_packages_list( struct pkg *pkg ) +{ + struct package *package = NULL; + struct dlist *found = NULL; + + if( !pkg ) return; + + found = dlist_find_data( packages, __compare_provided, (const void *)pkg ); + if( found && found->data ) + { + struct dlist *list = NULL, *next = NULL; + + package = (struct package *)found->data; + + packages = dlist_remove( packages, (const void *)package ); + provides = dlist_append( provides, (void *)package ); + + if( !(list = package->requires->list) ) return; + + while( list ) + { + next = dlist_next( list ); + { + __reduce_packages_list( (struct pkg *)list->data ); + } + list = next; + } + } +} + +static void __reduce_packages_list_single( void *data, void *user_data ) +{ + struct pkg *pkg = (struct pkg *)data; + + if( pkg ) { __reduce_packages_list( pkg ); } +} + + +static void reduce_packages_list( struct dlist *srcpkgs ) +{ + if( ! srcpkgs ) return; + + dlist_foreach( srcpkgs, __reduce_packages_list_single, NULL ); + + dlist_free( packages, __package_free_func ); + if( dlist_length( provides ) != 0 ) + { + packages = provides; + provides = NULL; + } +} + +int create_provides_list( struct dlist *srcpkgs ) +{ + int ret = 0; + + if( !packages ) return ret; + + if( srcpkgs && dlist_length( srcpkgs ) > 0 ) + { + /****************************************************************** + Reduce packages list to the list of requires of source packages: + */ + reduce_packages_list( srcpkgs ); + } + + /* Fill two lists: provides and extern_requires: */ + dlist_foreach( packages, __fill_extern_requires, NULL ); + + /* Remove packages from extern_requires list which present in the provides list: */ + dlist_foreach( provides, __clean_extern_requires, NULL ); + + /* Now we don't need previous contents of provides list: */ + dlist_free( provides, __pkg_free_func ); + provides = NULL; + + /* Remove old packages if required new version of them */ + remove_old_packages(); + + /* move packages into provides list in order of installation: */ + while( dlist_length( packages ) != 0 ) + { + dlist_foreach( packages, __fill_provides_list, NULL ); + } + + return dlist_length( extern_requires ); +} + +void free_provides_list( void ) +{ + if( htmlroot ) { free( htmlroot ); htmlroot = NULL; } + if( hardware ) { free( hardware ); hardware = NULL; } + + dlist_free( extern_requires, __pkg_free_func ); + dlist_free( provides, __package_free_func ); +} + +void print_provides_list( const char *plist_fname ) +{ + FILE *plist = NULL; + + if( !plist_fname || !provides ) return; + + plist = fopen( plist_fname, "w" ); + if( !plist ) + { + FATAL_ERROR( "Cannot create output %s file", basename( (char *)plist_fname ) ); + } + + fprintf( plist, "#\n" ); + fprintf( plist, "# file format:\n" ); + fprintf( plist, "# ===========\n" ); + fprintf( plist, "#\n" ); + fprintf( plist, "# Each line contains six fields separated by colon symbol ':' like following.\n" ); + fprintf( plist, "#\n" ); + fprintf( plist, "# pkgname:version:description:tarball:procedure:priority\n" ); + fprintf( plist, "#\n" ); + fprintf( plist, "# where:\n" ); + fprintf( plist, "#\n" ); + fprintf( plist, "# pkgname - should be the same as the value of pkgname in the '.DESCRIPTION' file;\n" ); + fprintf( plist, "# version - package version for showing in check list dialog box if this file is\n" ); + fprintf( plist, "# used to complete common check dialog for installing group of packages;\n" ); + fprintf( plist, "# description - short description for showing in check list dialog box if this file is\n" ); + fprintf( plist, "# used to complete common check dialog for installing group of packages;\n" ); + fprintf( plist, "# tarball - should end in '.txz';\n" ); + fprintf( plist, "# procedure - installation procedure {install | update}:\n" ); + fprintf( plist, "# * 'install' - if package requires normal installation,\n" ); + fprintf( plist, "# * 'update' - if already installed package should be updated by this\n" ); + fprintf( plist, "# package archive;\n" ); + fprintf( plist, "# priority - { REQUIRED|RECOMMENDED|OPTIONAL|SKIP }\n" ); + fprintf( plist, "# synonims:\n" ); + fprintf( plist, "# { REQUIRED | required | REQ | req }\n" ); + fprintf( plist, "# { RECOMMENDED | recommended | REC | rec }\n" ); + fprintf( plist, "# { OPTIONAL | optional | OPT | opt }\n" ); + fprintf( plist, "# { SKIP | skip | SKP | skp }\n" ); + fprintf( plist, "#\n" ); + + if( extern_requires ) + { + dlist_foreach( extern_requires, __print_extern_package, plist ); + fprintf( plist, "#\n" ); + } + dlist_foreach( provides, __print_provided_package, plist ); + + fflush( plist ); + fclose( plist ); +} + + +/*************************************************************** + Requires TREE functions: + */ + +struct _ctx +{ + FILE *output; + int index, size, depth; +}; + +/************************** + HTML Template Variables: + */ +static char *root = NULL; +static char *bug_url = NULL; + +static int svg_width = 2; +static int svg_height = 2; + +static char *json_pkgs_file = NULL; +static char *json_tree_file = NULL; + +static char *copying = "Radix cross Linux"; + +#define max(a,b) ({ typeof (a) _a = (a); typeof (b) _b = (b); _a > _b ? _a : _b; }) + +/* + формирование имен файлов для вывода REQUIRES tree: + + json_fname | last argument of make-pkglist | last argument type + -------------------------+-------------------------------+-------------------- + './a.txt' | a.txt | regular file + './a.json' | a.json | regular file + './.json' | .json | regular file + './khadas-vim.json' | . | directory + './tmp/khadas-vim.json' | tmp | directory + -------------------------+-------------------------------+-------------------- + + - если есть основное базовое имя файла и расширение, то расширение + заменяем на: '.pkgs.json', '.tree.json', '.tree.html'; + + - если есть основное базовое имя файла без расширения, то добавляем + расширение: '.pkgs.json', '.tree.json', '.tree.html'; + + - если основное базовое имя файла начинается с точки, то расширение + заменяем на: 'pkgs.json', 'tree.json', 'tree.html'. +*/ +static void allocate_fnames( const char *json_fname ) +{ + char *p, *e, *f = NULL; + char *buf = NULL; + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + (void)sprintf( &buf[0], "%s", json_fname ); + p = rindex( (const char *)&buf[0], '/' ); + if( p ) + { + if( p != &buf[0] ) f = ++p; + else f = &buf[0]; + } + e = rindex( (const char *)f, '.' ); + if( e ) + { + if( e != f ) + { + (void)sprintf( e, ".pkgs.json" ); pkgs_fname = xstrdup( (const char *)&buf[0] ); + (void)sprintf( e, ".tree.json" ); tree_fname = xstrdup( (const char *)&buf[0] ); + (void)sprintf( e, ".tree.html" ); html_fname = xstrdup( (const char *)&buf[0] ); + + (void)sprintf( e, ".pkgs.min.json" ); pkgs_min_fname = xstrdup( (const char *)&buf[0] ); + (void)sprintf( e, ".tree.min.json" ); tree_min_fname = xstrdup( (const char *)&buf[0] ); + } + else + { + (void)sprintf( e, "pkgs.json" ); pkgs_fname = xstrdup( (const char *)&buf[0] ); + (void)sprintf( e, "tree.json" ); tree_fname = xstrdup( (const char *)&buf[0] ); + (void)sprintf( e, "tree.html" ); html_fname = xstrdup( (const char *)&buf[0] ); + + (void)sprintf( e, "pkgs.min.json" ); pkgs_min_fname = xstrdup( (const char *)&buf[0] ); + (void)sprintf( e, "tree.min.json" ); tree_min_fname = xstrdup( (const char *)&buf[0] ); + } + } + else + { + e = f + strlen( f ); + + (void)sprintf( e, ".pkgs.json" ); pkgs_fname = xstrdup( (const char *)&buf[0] ); + (void)sprintf( e, ".tree.json" ); tree_fname = xstrdup( (const char *)&buf[0] ); + (void)sprintf( e, ".tree.html" ); html_fname = xstrdup( (const char *)&buf[0] ); + + (void)sprintf( e, ".pkgs.min.json" ); pkgs_min_fname = xstrdup( (const char *)&buf[0] ); + (void)sprintf( e, ".tree.min.json" ); tree_min_fname = xstrdup( (const char *)&buf[0] ); + } + + if( minimize ) + { + json_pkgs_file = xstrdup( (const char *)basename( pkgs_min_fname ) ); + json_tree_file = xstrdup( (const char *)basename( tree_min_fname ) ); + } + else + { + json_pkgs_file = xstrdup( (const char *)basename( pkgs_fname ) ); + json_tree_file = xstrdup( (const char *)basename( tree_fname ) ); + } + + free( buf ); +} + + +/******************************************************************* + find_pkg(): + ---------- + Returns package found in packages list coresponded to pkg. + */ +static struct package *find_pkg( struct dlist *list, struct pkg *pkg ) +{ + struct package *package = NULL; + struct dlist *found = NULL; + + if( !pkg ) return package; + + found = dlist_find_data( list, __compare_provided, (const void *)pkg ); + if( found ) + { + return (struct package *)found->data; + } + + return package; +} + +/******************************************************************* + find_package(): + -------------- + Returns package found in packages list coresponded to package. + */ +static struct package *find_package( struct dlist *list, struct package *pkg ) +{ + struct package *package = NULL; + struct dlist *found = NULL; + + if( !pkg ) return package; + + found = dlist_find_data( list, __compare_packages, (const void *)pkg ); + if( found ) + { + return (struct package *)found->data; + } + + return package; +} + +static void __print_package_data( FILE *output, struct package *package ) +{ + if( !output || !package ) return; + + /* "id": "net:bind-9.10.1", */ + if( package->pkginfo->group ) { + fprintf( output, " \"id\": \"%s:%s-%s\",\n", package->pkginfo->group, + package->pkginfo->name, + package->pkginfo->version ); + } else { + fprintf( output, " \"id\": \"%s-%s\",\n", package->pkginfo->name, + package->pkginfo->version ); + } + /* "name": "bind", */ + fprintf( output, " \"name\": \"%s\",\n", package->pkginfo->name ); + /* "version": "9.10.1", */ + fprintf( output, " \"version\": \"%s\",\n", package->pkginfo->version ); + /* "group": "net", */ + if( package->pkginfo->group ) { + fprintf( output, " \"group\": \"%s\",\n", package->pkginfo->group ); + } else { + fprintf( output, " \"group\": \"\",\n" ); + } + /* "arch": "omap543x-eglibc", */ + fprintf( output, " \"arch\": \"%s\",\n", package->pkginfo->arch ); + /* "hardware": "omap5uevm", */ + fprintf( output, " \"hardware\": \"%s\",\n", hardware ); + /* "license": "custom", */ + fprintf( output, " \"license\": \"%s\",\n", package->pkginfo->license ); + /* "description": "bind 9.10.1 (DNS server and utilities)", */ + fprintf( output, " \"description\": \"%s %s (%s)\",\n", package->pkginfo->name, + package->pkginfo->version, + package->pkginfo->short_description ); + /* "uncompressed_size": "17M", */ + fprintf( output, " \"uncompressed_size\": \"" ); + if( package->pkginfo->uncompressed_size > 1048576 ) { + fprintf( output, "%ldG\",\n", package->pkginfo->uncompressed_size / 1048576 ); + } else if( package->pkginfo->uncompressed_size > 1024 ) { + fprintf( output, "%ldM\",\n", package->pkginfo->uncompressed_size / 1024 ); + } else { + fprintf( output, "%ldK\",\n", package->pkginfo->uncompressed_size ); + } + /* "total_files": "421" */ + fprintf( output, " \"total_files\": \"%d\"\n", package->pkginfo->total_files ); +} + +static void __print_pkgs_node( void *data, void *user_data ) +{ + struct package *package = (struct package *)data; + struct _ctx *ctx = (struct _ctx *)user_data; + + if( !package || !ctx ) return; + + if( ctx->index != 0 ) + { + fprintf( ctx->output, " },\n {\n" ); + } + __print_package_data( ctx->output, package ); + ++ctx->index; +} + +static void print_pkgs_json( FILE *output, struct dlist *list ) +{ + struct _ctx ctx; + + if( !output ) return; + + bzero( (void *)&ctx, sizeof(struct _ctx) ); + + ctx.output = output; + ctx.index = 0; + + fprintf( output, "[{\n" ); + + dlist_foreach( list, __print_pkgs_node, (void *)&ctx ); + + fprintf( output, " }]\n" ); +} + +static void __remove_required_package( void *data, void *user_data ) +{ + struct package *package = NULL; + struct pkg *pkg = (struct pkg *)data; + + if( pkg ) + { + package = find_pkg( tree, pkg ); + if( package ) + { + /******************************************* + if package reqired for some other package + we have to remove it from tree list: + */ + tree = dlist_remove_data( tree, __compare_packages, NULL, (const void *)package ); + } + } +} + +static void __remove_required_packages( void *data, void *user_data ) +{ + struct package *package = (struct package *)data; + struct dlist *list = NULL; + + if( !package ) return; + + if( !(list = package->requires->list) ) return; + + dlist_foreach( list, __remove_required_package, NULL ); +} + +static void remove_required_packages( struct dlist *list ) +{ + dlist_foreach( list, __remove_required_packages, NULL ); +} + + +static void __check_pkg_requires( void *data, void *user_data ) +{ + struct pkg *pkg = (struct pkg *)data; + int *counter = (int *)user_data; + + if( pkg ) + { + struct package *package = find_pkg( provides, pkg ); + if( package ) { ++(*counter); } + } +} + +static int check_pkg_requires( struct dlist *list ) +{ + int cnt = 0; + dlist_foreach( list, __check_pkg_requires, (void *)&cnt ); + return cnt; +} + + +/*************************************************************** + Sort Requires Tree functions: + ---------------------------- + + NOTE: + Requires sorted in reverse installation order according to + provides list. + */ + +static int __install_pkg_index( struct dlist *list, struct pkg *pkg ) +{ + int index = -1; + + if( !pkg ) return index; + + if( list ) + { + struct package *package = find_pkg( list, pkg ); + + index = dlist_index( list, package ); + } + + return index; +} + +static int __install_package_index( struct dlist *list, struct package *package ) +{ + int index = -1; + + if( !package ) return index; + + if( list ) + index = dlist_index( list, package ); + + return index; +} + +static int __compare_pkg_order( const void *a, const void *b ) +{ + int ret = 0; + int ia = -1, ib = -1; + + ia = __install_pkg_index( provides, (struct pkg *)a ); + ib = __install_pkg_index( provides, (struct pkg *)b ); + + if( ia < ib ) ret = -1; + if( ia > ib ) ret = 1; + + return ret; +} + +static int __compare_package_order( const void *a, const void *b ) +{ + int ret = 0; + int ia = -1, ib = -1; + + ia = __install_package_index( provides, (struct package *)a ); + ib = __install_package_index( provides, (struct package *)b ); + + if( ia < ib ) ret = -1; + if( ia > ib ) ret = 1; + + return ret; +} + +static void __sort_requires( void *data, void *user_data ) +{ + struct package *package = (struct package *)data; + struct dlist *reqs = NULL; + + if( !package ) return; + + if( (reqs = package->requires->list) && check_pkg_requires( reqs ) > 0 ) + { + package->requires->list = reqs = dlist_sort( reqs, __compare_pkg_order ); + } +} + +static struct dlist *sort_requires_tree( struct dlist *list ) +{ + int lenght = 0; + + if( !list ) return list; + + lenght = dlist_length( list ); + + if( lenght > 1 ) + list = dlist_sort( list, __compare_package_order ); + + return list; +} + +static void sort_requires( struct dlist *list ) +{ + int lenght = 0; + + if( !list ) return; + + dlist_foreach( list, __sort_requires, NULL ); +} +/* + End of Sort Requires Tree functions: + ***************************************************************/ + +/*************************************************************** + Binary Tree functions: + --------------------- + */ +static struct dlist *pkgs = NULL; +static struct btree *btree = NULL; + +static struct pkg *duplicate_pkg( struct package *package ) +{ + struct pkg *pkg = NULL; + + if( !package ) return pkg; + + pkg = pkg_alloc(); + pkg->name = xstrdup( (const char *)package->pkginfo->name ); + pkg->group = xstrdup( (const char *)package->pkginfo->group ); + pkg->version = xstrdup( (const char *)package->pkginfo->version ); + pkg->procedure = package->procedure; + + return pkg; +} + +static void __fill_pkgs_list( void *data, void *user_data ) +{ + struct package *package = (struct package *)data; + struct pkg *pkg = NULL; + + if( !package ) return; + + pkg = duplicate_pkg( package ); + pkgs = dlist_append( pkgs, (void *)pkg ); +} + +/******************************************* + create_pkgs_list(): + ------------------ + Creates pkgs list from provides list. + */ +static void create_pkgs_list( struct dlist *list ) +{ + if( !list ) return; + + dlist_foreach( list, __fill_pkgs_list, NULL ); +} + +static void free_pkgs_list() +{ + if( pkgs ) + { + dlist_free( pkgs, __pkg_free_func ); + pkgs = NULL; + } +} + +static int __compare_pkg( const void *a, const void *b ) +{ + int ret = -1; + + struct pkg *pkg1 = (struct pkg *)a; + struct pkg *pkg2 = (struct pkg *)b; + + if( pkg1->group && pkg2->group ) + { + ret = strcmp( pkg1->group, pkg2->group ); + } + else if( !pkg1->group && !pkg2->group ) + { + ret = 0; + } + else if( pkg1->group ) + { + ret = 1; + } + + if( ! ret ) + { + return strcmp( pkg1->name, pkg2->name ); + } + return ret; +} + +static struct pkg *__find_pkg( struct dlist *list, struct pkg *package ) +{ + struct pkg *pkg = NULL; + struct dlist *found = NULL; + + if( !package ) return pkg; + + found = dlist_find_data( list, __compare_pkg, (const void *)package ); + if( found ) + { + return (struct pkg *)found->data; + } + + return pkg; +} + +static int __compare_pkg_with_package( const void *a, const void *b ) +{ + int ret = -1; + + struct pkg *pkg1 = (struct pkg *)a; + struct package *pkg2 = (struct package *)b; + + if( pkg1->group && pkg2->pkginfo->group ) + { + ret = strcmp( pkg1->group, pkg2->pkginfo->group ); + } + else if( !pkg1->group && !pkg2->pkginfo->group ) + { + ret = 0; + } + else if( pkg2->pkginfo->group ) + { + ret = 1; + } + + if( ! ret ) + { + return strcmp( pkg1->name, pkg2->pkginfo->name ); + } + return ret; +} + +static struct pkg *__find_pkg_by_package( struct dlist *list, struct package *package ) +{ + struct pkg *pkg = NULL; + struct dlist *found = NULL; + + if( !package ) return pkg; + + found = dlist_find_data( list, __compare_pkg_with_package, (const void *)package ); + if( found ) + { + return (struct pkg *)found->data; + } + + return pkg; +} + +static struct pkg *__find_pkg_by_pkg( struct dlist *list, struct pkg *package ) +{ + struct pkg *pkg = NULL; + struct dlist *found = NULL; + + if( !package ) return pkg; + + found = dlist_find_data( list, __compare_pkg, (const void *)package ); + if( found ) + { + return (struct pkg *)found->data; + } + + return pkg; +} + +static void __btree_add_requires( struct btree *tree ); + +static struct btree *__btree_add_left( void *data, void *user_data ) +{ + struct btree *tree = (struct btree *)user_data; + struct pkg *left = (struct pkg *)data; + struct pkg *pkg = NULL; + struct btree *node = NULL; + + if( !tree || !left ) return node; + + pkg = __find_pkg_by_pkg( pkgs, left ); + + node = btree_insert_left( tree, __btree_alloc( (void *)pkg ) ); + __btree_add_requires( node ); + + return node; +} + +static void __btree_add_right( void *data, void *user_data ) +{ + struct btree *tree = *((struct btree **)user_data); + struct pkg *right = (struct pkg *)data; + struct pkg *pkg = NULL; + struct btree *node = NULL; + + if( !tree || !right ) return; + + pkg = __find_pkg_by_pkg( pkgs, right ); + + node = btree_insert_right( tree, __btree_alloc( (void *)pkg ) ); + __btree_add_requires( node ); + + *((struct btree **)user_data) = node; +} + +static void __btree_add_requires( struct btree *tree ) +{ + struct package *package = NULL; + + if( !tree ) return; + + package = find_pkg( provides, (struct pkg *)tree->data ); + if( package ) + { + struct dlist *list = NULL; + + if( (list = package->requires->list) ) + { + struct pkg *pkg = NULL; + struct btree *node = NULL; + + pkg = __find_pkg_by_pkg( pkgs, (struct pkg *)list->data ); + + node = __btree_add_left( (void *)pkg, (void *)tree ); + + if( dlist_length( list ) > 1 ) + { + dlist_foreach( list->next, __btree_add_right, (void *)&node ); + } + } + } +} + +static void __fill_btree( void *data, void *user_data ) +{ + struct btree *tree = *((struct btree **)user_data); + struct package *package = (struct package *)data; + struct pkg *pkg = NULL; + struct btree *node = NULL; + + if( !tree || !package ) return; + + pkg = __find_pkg_by_package( pkgs, package ); + + node = btree_insert_right( tree, __btree_alloc( (void *)pkg ) ); + __btree_add_requires( node ); + + *((struct btree **)user_data) = node; +} + +/******************************************************************* + __print_btree_pkg(): + ------------------- + Print out package "group/name-version". Used for debuging only. + */ +static void __print_btree_pkg( void *data, void *user_data ) +{ + struct pkg *pkg = (struct pkg *)data; + + if( !pkg ) return; + + fprintf( stdout, "%s/%s-%s\n", pkg->group, pkg->name, pkg->version ); +} + +/***************************************************************** + __print_btree_node(): + -------------------- + Print out package in JSON format. Used by btree_print_json(). + */ +static void __print_btree_node( void *data, void *user_data ) +{ + struct pkg *pkg = (struct pkg *)data; + struct _bctx *ctx = (struct _bctx *)user_data; + + char *p, buf[PATH_MAX*2]; + int depth = 0, max_depth = PATH_MAX + PATH_MAX / 2; + + if( !pkg || !ctx ) return; + + buf[0] = ' '; + buf[1] = '\0'; + + p = (char *)&buf[1]; + depth = ctx->indent; + + if( depth < 1 ) depth = 0; + if( depth > max_depth ) depth = max_depth; + + while( depth ) + { + (void)sprintf( p, " " ); --depth; ++p; *p = '\0'; + } + + if( pkg->group ) + (void)sprintf( p, "\"name\": \"%s:%s-%s\"", pkg->group, + pkg->name, + pkg->version ); + else + (void)sprintf( p, "\"name\": \"%s-%s\"", pkg->name, + pkg->version ); + + fprintf( ctx->output, (char *)&buf[0] ); +} + +static int __width_factor( int width ) +{ + double w, x = (double)width; + + if( width < 2 ) return 48; + + if( width > 1 && width < 150 ) + { + w = floor( (38.399 - 7.4262 * log( x )) + 8.0 ); + + return (int)w; + } + else + { + return 8; + } +} + +/******************************************************************* + __print_btree_header(): + ---------------------- + Print out Binary Tree Header. + */ +static void __print_btree_header( FILE *fp, struct btree *btree, struct dlist *tree ) +{ + struct package *package = NULL; + + if( !fp || !btree || !tree ) return; + + package = find_pkg( provides, (struct pkg *)btree->data ); + + if( package ) + { + char *buf = NULL; + + if( dlist_length( tree ) > 1 ) + { + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + (void)sprintf( &buf[0], "%s", htmlroot ); + root = xstrdup( (const char *)&buf[0] ); + (void)sprintf( &buf[0], "%s", package->pkginfo->url ); + bug_url = xstrdup( (const char *)&buf[0] ); + free( buf ); + } + else + { + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + if( package->pkginfo->group ) + (void)sprintf( &buf[0], "%s/%s-%s", package->pkginfo->group, + package->pkginfo->name, + package->pkginfo->version ); + else + (void)sprintf( &buf[0], "%s-%s", package->pkginfo->name, + package->pkginfo->version ); + + root = xstrdup( (const char *)&buf[0] ); + (void)sprintf( &buf[0], "%s", package->pkginfo->url ); + bug_url = xstrdup( (const char *)&buf[0] ); + free( buf ); + } + + fprintf( fp, " \"distro\": [ \"%s\", \"%s\", \"%s\" ],\n", + package->pkginfo->distro_name, + package->pkginfo->distro_version, + package->pkginfo->url ); + } +} + + +/*************************************** + create_btree(): + -------------- + Creates btree from tree list (DAG). + */ +static void create_btree( struct dlist *dag, struct dlist *install ) +{ + struct pkg *pkg = NULL; + struct package *package = NULL; + struct dlist *list = NULL; + struct btree *node = NULL; + + if( !dag || !install ) return; + + /* first package: */ + package = (struct package *)dag->data; + pkg = __find_pkg_by_package( pkgs, package ); + + node = btree = __btree_alloc( (void *)pkg ); + + __btree_add_requires( node ); + + if( dlist_length( dag ) > 1 ) + dlist_foreach( dag->next, __fill_btree, (void *)&node ); + + btree_reduce( btree, __compare_pkg, NULL ); +} +/* + End of Binary Tree functions: + ***************************************************************/ + + +/*************************************************************** + Print json format of DAG functions: + */ +static void __print_pkg_tree( struct _ctx *ctx, struct dlist *list ) +{ + struct dlist *next = NULL; + + if( !ctx || !list ) return; + + ctx->depth += 2; + svg_width = max( svg_width, ctx->depth ); + + while( list ) + { + next = dlist_next( list ); + { + struct pkg *pkg = (struct pkg *)list->data; + struct package *package = find_pkg( provides, pkg ); + + if( package ) + { + char *p, *buf = NULL; + int depth = 0; + + struct dlist *reqs = NULL; + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + buf[0] = ' '; + buf[1] = '\0'; + + p = (char *)&buf[1]; + depth = ctx->depth; + + while( depth ) { (void)sprintf( p, " " ); --depth; ++p; *p = '\0'; } + + (void)sprintf( p - 1, "{\n" ); + fprintf( ctx->output, (char *)&buf[0] ); + *(p - 1) = ' '; *p = '\0'; + + if( pkg->group ) + (void)sprintf( p, "\"name\": \"%s:%s-%s\"", pkg->group, + pkg->name, + pkg->version ); + else + (void)sprintf( p, "\"name\": \"%s-%s\"", pkg->name, + pkg->version ); + + fprintf( ctx->output, (char *)&buf[0] ); + + if( (reqs = package->requires->list) && check_pkg_requires( reqs ) > 0 ) + { + fprintf( ctx->output, ",\n" ); + + (void)sprintf( p, "\"children\": [\n" ); + fprintf( ctx->output, (char *)&buf[0] ); + + __print_pkg_tree( ctx, reqs ); + + (void)sprintf( p, "]\n" ); + fprintf( ctx->output, (char *)&buf[0] ); + } + else + { + fprintf( ctx->output, "\n" ); + } + + (void)sprintf( p - 1, "}" ); + fprintf( ctx->output, (char *)&buf[0] ); + *(p - 1) = ' '; *p = '\0'; + + if( next ) { fprintf( ctx->output, ",\n" ); } + else { fprintf( ctx->output, "\n" ); } + + free( buf ); + } /* End if( package ) */ + } + list = next; + } /* End of while( list ) */ + + ctx->depth -= 2; +} + +static void __print_package_node( struct _ctx *ctx, struct package *package ) +{ + char *p, *buf = NULL; + int depth = 0; + + struct dlist *list = NULL; + + if( !package || !ctx ) return; + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + buf[0] = ' '; + buf[1] = '\0'; + + p = (char *)&buf[1]; + depth = ctx->depth; + + while( depth ) { (void)sprintf( p, " " ); --depth; ++p; *p = '\0'; } + + (void)sprintf( p - 1, "{\n" ); + fprintf( ctx->output, (char *)&buf[0] ); + *(p - 1) = ' '; *p = '\0'; + + if( package->pkginfo->group ) + (void)sprintf( p, "\"name\": \"%s:%s-%s\"", package->pkginfo->group, + package->pkginfo->name, + package->pkginfo->version ); + else + (void)sprintf( p, "\"name\": \"%s-%s\"", package->pkginfo->name, + package->pkginfo->version ); + + fprintf( ctx->output, (char *)&buf[0] ); + + if( (list = package->requires->list) && check_pkg_requires( list ) > 0 ) + { + fprintf( ctx->output, ",\n" ); + + (void)sprintf( p, "\"children\": [\n" ); + fprintf( ctx->output, (char *)&buf[0] ); + + __print_pkg_tree( ctx, list ); + + (void)sprintf( p, "]\n" ); + fprintf( ctx->output, (char *)&buf[0] ); + } + else + { + fprintf( ctx->output, "\n" ); + } + + (void)sprintf( p - 1, "}" ); + fprintf( ctx->output, (char *)&buf[0] ); + *(p - 1) = ' '; *p = '\0'; + + free( buf ); +} + +static void __print_tree_node( void *data, void *user_data ) +{ + struct package *package = (struct package *)data; + struct _ctx *ctx = (struct _ctx *)user_data; + + if( !package || !ctx ) return; + + if( ctx->size > 1 ) + { + if( ctx->index == 0 ) + { + char *buf = NULL; + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + (void)sprintf( &buf[0], "%s", htmlroot ); + root = xstrdup( (const char *)&buf[0] ); + (void)sprintf( &buf[0], "%s", package->pkginfo->url ); + bug_url = xstrdup( (const char *)&buf[0] ); + free( buf ); + + fprintf( ctx->output, " \"distro\": [ \"%s\", \"%s\", \"%s\" ],\n", + package->pkginfo->distro_name, + package->pkginfo->distro_version, + package->pkginfo->url ); + fprintf( ctx->output, " \"name\": \"%s\",\n", htmlroot ); + fprintf( ctx->output, " \"children\": [\n" ); + } + + + __print_package_node( ctx, package ); + svg_height += 2; + + + if( ctx->index < ctx->size - 1 ) fprintf( ctx->output, "," ); + else fprintf( ctx->output, "\n ]" ); + + fprintf( ctx->output, "\n" ); + } + else + { + struct dlist *reqs = NULL; + char *buf = NULL; + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + if( package->pkginfo->group ) + (void)sprintf( &buf[0], "%s/%s-%s", package->pkginfo->group, + package->pkginfo->name, + package->pkginfo->version ); + else + (void)sprintf( &buf[0], "%s-%s", package->pkginfo->name, + package->pkginfo->version ); + + root = xstrdup( (const char *)&buf[0] ); + (void)sprintf( &buf[0], "%s", package->pkginfo->url ); + bug_url = xstrdup( (const char *)&buf[0] ); + free( buf ); + + fprintf( ctx->output, " \"distro\": [ \"%s\", \"%s\", \"%s\" ],\n", + package->pkginfo->distro_name, + package->pkginfo->distro_version, + package->pkginfo->url ); + if( package->pkginfo->group ) + fprintf( ctx->output, " \"name\": \"%s:%s-%s\"", package->pkginfo->group, + package->pkginfo->name, + package->pkginfo->version ); + else + fprintf( ctx->output, " \"name\": \"%s-%s\"", package->pkginfo->name, + package->pkginfo->version ); + + + svg_height += 2; + ctx->depth -=2; + + if( (reqs = package->requires->list) && check_pkg_requires( reqs ) > 0 ) + { + fprintf( ctx->output, ",\n" ); + + fprintf( ctx->output, " \"children\": [\n" ); + + __print_pkg_tree( ctx, reqs ); + + fprintf( ctx->output, " ]\n" ); + } + + } + + ++ctx->index; +} + +static void print_tree_json( FILE *output, struct dlist *list ) +{ + struct _ctx ctx; + + if( !output || !list ) return; + + bzero( (void *)&ctx, sizeof(struct _ctx) ); + + ctx.output = output; + ctx.index = 0; + ctx.size = dlist_length( list ); + ctx.depth = 2; + + fprintf( output, "{\n" ); + dlist_foreach( list, __print_tree_node, (void *)&ctx ); + fprintf( output, "}\n" ); + + svg_height += svg_width / 2; + + svg_width = (svg_width + 4) * 160; + svg_height = (svg_height + 4) * 24; +} +/* + End of print json format of DAG functions. + ***************************************************************/ + +#include <pkglist.html.c> + +void print_provides_tree( const char *json_fname, enum _tree_format tree_format ) +{ + FILE *pkgs_fp = NULL, *tree_fp = NULL, *html_fp = NULL; + + allocate_fnames( json_fname ); + + pkgs_fp = fopen( (const char *)pkgs_fname, "w" ); + if( !pkgs_fp ) { FATAL_ERROR( "Cannot create %s file", basename( pkgs_fname ) ); } + tree_fp = fopen( (const char *)tree_fname, "w" ); + if( !tree_fp ) { FATAL_ERROR( "Cannot create %s file", basename( tree_fname ) ); } + html_fp = fopen( (const char *)html_fname, "w" ); + if( !html_fp ) { FATAL_ERROR( "Cannot create %s file", basename( html_fname ) ); } + + tree = dlist_copy( provides ); + + /***************************************************** + print out the array of all packages in JSON format: + */ + print_pkgs_json( pkgs_fp, provides ); + fflush( pkgs_fp ); fclose( pkgs_fp ); + + provides = dlist_reverse( provides ); + sort_requires( provides ); /* sort requires in reverse installation order */ + + /******************************************************** + Sort the REQUIRES TREE in reverse installation order. + */ + tree = sort_requires_tree( tree ); + + /******************************************************** + remove unneded packages from tree list to leave the + last installation layer of packages presented in DAG: + */ + remove_required_packages( provides ); + + + if( tree_format == TFMT_BIN ) + { + int width = 0, height = 0; + + /******************************************************************** + print out the REQUIRES TREE in JSON format as reduced binary tree: + */ + create_pkgs_list( provides ); + create_btree( tree, provides ); + + width = btree_width( btree ); + height = btree_height( btree ); + + svg_width = (height + 4) * 240; + svg_height = (width + 4) * __width_factor( width ); + + fprintf( tree_fp, "{\n" ); + __print_btree_header( tree_fp, btree, tree ); + btree_print_json( tree_fp, btree, __print_btree_node ); + fprintf( tree_fp, "}\n" ); + + __btree_free( btree ); + free_pkgs_list(); + } + else + { + /**************************************************** + print out the REQUIRES TREE in JSON format as DAG: + */ + print_tree_json( tree_fp, tree ); + } + + fflush( tree_fp ); fclose( tree_fp ); + + if( minimize ) + { + if( minimize_json( (const char *)pkgs_fname, (const char *)pkgs_min_fname ) < 1 ) + { + (void)unlink( (const char *)pkgs_min_fname ); + } + if( minimize_json( (const char *)tree_fname, (const char *)tree_min_fname ) < 1 ) + { + (void)unlink( (const char *)tree_min_fname ); + } + } + + + /*********************************************** + print out the HTML to view REQIIRES TREE: + */ + print_tree_html( html_fp ); + fflush( html_fp ); fclose( html_fp ); + + + /***************** + free resources: + */ + if( root ) { free( root ); root = NULL; } + if( bug_url ) { free( bug_url ); bug_url = NULL; } + + if( pkgs_fname ) { free( pkgs_fname ); pkgs_fname = NULL; } + if( tree_fname ) { free( tree_fname ); tree_fname = NULL; } + if( html_fname ) { free( html_fname ); html_fname = NULL; } + + if( pkgs_min_fname ) { free( pkgs_min_fname ); pkgs_min_fname = NULL; } + if( tree_min_fname ) { free( tree_min_fname ); tree_min_fname = NULL; } + + if( json_pkgs_file ) { free( json_pkgs_file ); json_pkgs_file = NULL; } + if( json_tree_file ) { free( json_tree_file ); json_tree_file = NULL; } + + __dlist_free( tree ); /* do not free node data */ +} +/* + End of Requires TREE functions. + ***************************************************************/ diff --git a/src/pkglist.h b/src/pkglist.h new file mode 100644 index 0000000..2688a70 --- /dev/null +++ b/src/pkglist.h @@ -0,0 +1,169 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +#ifndef _PKG_LIST_H_ +#define _PKG_LIST_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <dlist.h> + + +enum _tree_format { + TFMT_BIN = 0, + TFMT_DAG, + + TFMT_UNKNOWN +}; + +enum _procedure +{ + INSTALL = 0, /* 'install' */ + UPDATE /* 'update' */ +}; + +enum _priority +{ + REQUIRED = 0, /* synonims: REQUIRED | required | REQ | req */ + RECOMMENDED, /* synonims: RECOMMENDED | recommended | REC | rec */ + OPTIONAL, /* synonims: OPTIONAL | optional | OPT | opt */ + SKIP /* synonims: SKIP | skip | SKP | skp */ +}; + + +struct pkginfo +{ + char *name; + char *version; + char *arch; + char *distro_name; + char *distro_version; + char *group; + char *short_description; + char *url; + char *license; + size_t uncompressed_size; /* size in 1024-byte blocks */ + size_t compressed_size; /* size in bytes */ + int total_files; +}; + +struct pkg +{ + char *group; + char *name; + char *version; + + enum _procedure procedure; /* install procedure */ +}; + +struct references +{ + int size; + struct dlist *list; /* list of pkg structs */ +}; + +struct requires +{ + int size; + struct dlist *list; /* list of pkg structs */ +}; + +struct files +{ + int size; + struct dlist *list; /* list of strings */ +}; + + +struct package +{ + struct pkginfo *pkginfo; + + char *hardware; /* optional parameter for JSON */ + + char *tarball; + enum _procedure procedure; /* install procedure */ + enum _priority priority; /* install user priority */ + + struct references *references; + struct requires *requires; + + char *description; + + char *restore_links; + char *install_script; + + struct files *files; +}; + + +extern char *htmlroot; +extern char *hardware; +extern int minimize; + +extern char *strprio( enum _priority priority, int short_name ); +extern char *strproc( enum _procedure procedure ); + +extern struct dlist *tarballs; + +extern void add_tarball( char *tarball ); /* append the tarballs list */ +extern void free_tarballs( void ); +extern const char *find_tarball( const char *name ); +extern void print_tarballs( void ); + + +extern struct dlist *srcpkgs; + +extern struct pkg *pkg_alloc( void ); +extern void pkg_free( struct pkg *pkg ); + +extern void add_srcpkg( struct pkg *pkg ); +extern void free_srcpkgs( void ); + +extern struct dlist *packages; + +extern struct package *package_alloc( void ); +extern void package_free( struct package *package ); + +extern void add_reference( struct package *package, struct pkg *pkg ); +extern void add_required( struct package *package, struct pkg *pkg ); +extern void add_file( struct package *package, const char *fname ); +extern void package_print_references( struct package *package ); +extern void package_print_requires( struct package *package ); +extern void package_print_files( struct package *package ); + +extern void add_package( struct package *package ); /* append the packages list */ +extern void free_packages( void ); + + +extern struct dlist *provides; +extern struct dlist *extern_requires; + +extern int create_provides_list( struct dlist *srcpkgs ); +extern void print_provides_list( const char *plist_fname ); +extern void print_provides_tree( const char *json_fname, enum _tree_format tree_format ); +extern void free_provides_list( void ); + + +#ifdef __cplusplus +} /* ... extern "C" */ +#endif + +#endif /* _PKG_LIST_H_ */ diff --git a/src/pkglist.html.c b/src/pkglist.html.c new file mode 100644 index 0000000..f4fc8b8 --- /dev/null +++ b/src/pkglist.html.c @@ -0,0 +1,1011 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +static void print_tree_html( FILE *output ) +{ + time_t t = time( NULL ); + struct tm tm = *localtime(&t); + + if( !output ) return; + + fprintf( output, "<!DOCTYPE html>\n" ); + fprintf( output, "<html>\n" ); + fprintf( output, " <head>\n" ); + fprintf( output, " <meta charset=\"utf-8\">\n" ); + fprintf( output, " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n" ); + fprintf( output, " <meta name=\"owner\" content=\"Andrey V.Kosteltsev\">\n" ); + fprintf( output, " <meta name=\"author\" content=\"Andrey V.Kosteltsev\">\n" ); + fprintf( output, " <meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n" ); + fprintf( output, " <meta http-equiv=\"Content-script-type\" content=\"text/javascript\">\n" ); + fprintf( output, " <meta http-equiv=\"Content-Style-Type\" content=\"text/css\">\n" ); + fprintf( output, "\n" ); + fprintf( output, " <link href=\"data:image/x-icon;base64," ); + fprintf( output, "AAABAAMAMDAAAAEAIACoJQAANgAAACAgAAABACAAqBAAAN4lAAAQEAAAAQAgAGgEAACGNgAAKAAA" ); + fprintf( output, "ADAAAABgAAAAAQAgAAAAAAAAJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACsrKysrKyuP" ); + fprintf( output, "Kysr2SsrK/grKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr+CsrK9krKyuPKysrKwAAAAAAAAAAAAAA" ); + fprintf( output, "AAAAAAArKysDKysrWSsrK9krKyv+Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv+Kysr2SsrK1krKysDAAAAAAAAAAArKytZKysr7isrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK+4rKytZAAAAACsrKywrKyvYKysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "KyvYKysrLCsrK48rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/KysrjysrK9grKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr2CsrK/crKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Ly8v/zIzM/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr9ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ykpKf8oKCj/KCgo/ykpKf8rKyv/cnh4/1NWVv8mJib/KCgo/ykpKf8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kioq/ygo" ); + fprintf( output, "KP8oKCj/KSgo/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/LS0t/05QUf9YW1v/WFtc/0VHSP9UV1f/" ); + fprintf( output, "ho2O/0hKSv9YW1z/V1tb/1FUVP8vLy//Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8qKir/PT8//1daW/9XW1v/VFdX/zMzM/8rKyr/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8pKCj/W15f" ); + fprintf( output, "/9Lf4f/g7e//2ebo/3h+f/+LkpP/RUdH/1NWV//N2dv/4e7w/9nm6P9xdnf/KSkp/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/yoqKv8zNDT/p7Cy/+Hu8P/h7/H/p7Cx/zQ0NP8q" ); + fprintf( output, "Kir/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/yoqKv85Ojr/t8HD/+r4+v/q+Pr/pa6v/3uBgv9qb2//KSkp/ysrK/98goP/5PHz" ); + fprintf( output, "/+r4+v/P3N3/UVRU/ykpKf8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ykpKf94fn//" ); + fprintf( output, "5PLz/+n4+v/U4eP/U1dX/ykpKf8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/+GjI3/5vT2/+n3+f/M2Nr/cHV2/4aN" ); + fprintf( output, "jv8yMjL/Kioq/yoqKv8xMjL/n6ip/+n3+f/p+Pr/tL7A/zo7O/8qKir/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/KSkp/0xOT//O2tz/6ff5/+b09v+BiIn/Kioq/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/KSgo/1ZZWf/W" ); + fprintf( output, "4uT/6ff5/+Px8/+BiIj/iZCR/0pNTf8pKSn/Kysr/ysrK/8pKSn/QUND/7/Ky//q+Pr/5/X3/5GZ" ); + fprintf( output, "mv8tLi7/Kysr/ysrK/8rKyv/Kysr/ysrK/8qKir/MjIy/6avsP/p9/n/6fj6/7K8vv83ODj/Kioq" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8qKir/Njc3/7G7vf/p+Pr/6fj6/6mytP94fn//bnN0/ykpKf8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/KSgo/1teX//W4+T/6ff5/9/s7v9scXL/KSkp/ysrK/8rKyv/Kysr/ysrK/8pKSn/dHl6/+Pw" ); + fprintf( output, "8v/p9/n/1uPl/1ZaWv8pKCj/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8qKir/f4aG/+Xz9f/p9/n/z9vd/3B1dv+Ij5D/" ); + fprintf( output, "NDQ0/yoqKv8rKyv/Kysr/ysrK/8rKyv/Kysr/yoqKv98goP/4/Hz/+n4+v/M2Nr/TVBQ/ykpKf8r" ); + fprintf( output, "Kyv/Kysr/ykpKf9JS0z/zNjZ/+n3+f/m9Pb/hoyN/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ykpKf9RVFT/0t/h" ); + fprintf( output, "/+n3+f/k8vT/hIuM/4iPkP9OUVH/KSkp/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/yoqKv8xMjL/" ); + fprintf( output, "n6ip/+n3+f/p+Pr/sLq7/zg5Of8qKir/Kioq/zExMf+iq6z/6ff5/+n4+v+2wML/OTo6/yoqKv8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kioq/zQ0Nf+stbb/6fj6/+n4+v+tt7j/dnt8/3F3d/8qKir/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8pKSn/QUND/7/KzP/q+Pr/5/T2/4yUlf8tLS3/KSgo/3B1dv/h7/H/" ); + fprintf( output, "6ff5/9jl5/9aXV7/KSgo/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/KSkp/3l/gP/k8vT/6ff5/9Lf4P9wdXb/ipGS/0JE" ); + fprintf( output, "RP8qKir/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/KSgo/1tfX//W4+X/6fj6" ); + fprintf( output, "/93q7P9mamv/REZH/8nV1v/p9/n/5/X3/4qRkv8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8pKSn/TE9P/8/b3f/p" ); + fprintf( output, "9/n/5fP1/4eOj/+Ei4z/b3R1/7G7vP9obW7/Kioq/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/yoqKv98goP/5PHz/+b09v9+hYX/nKSm/+n3+f/p+Pr/usTG/zs8PP8qKir/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/yoqKv8zNDT/qLGy/+v6/P/r+fv/sbu9/3N4ef97gYL/n6ip/+v5+//V4eP/XmJj/ykpKf8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/yoqKv8yMjL/oaqr/6q0tf92fH3/3+3v/+n3" ); + fprintf( output, "+f/a5+n/XWFi/ykoKP8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ykpKf9dYWL/w87Q/8nV1/++ycv/b3R1/4qRkv99hIX/" ); + fprintf( output, "4O3v/+j2+P/q+Pr/ws3P/0BCQv8qKSn/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8q" ); + fprintf( output, "KSn/PD09/1tfX//E0NL/6ff5/+j2+P+OlZb/LCws/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/80NTX/PD09" ); + fprintf( output, "/zs8PP88PT3/hIuM/2BkZP+zvb7/6vn7/+f19//n9ff/6Pb4/4mQkf8qKir/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/LS0t/5mhov/p9/n/6fj6/7vGyP86Ozv/Jycn/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8qKir/Kioq/ygnJ/9iZmf/e4GC/ysrK/9iZmf/2OTm/+j2+P/n9ff/6ff5" ); + fprintf( output, "/8jU1f8+QED/Kioq/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8pKCj/aGxt/9/s7v/p9/n/" ); + fprintf( output, "2+jq/32Dg/9yd3j/VVhZ/ywsLP8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kikp/0BCQv+OlZb/Ozw8/yoq" ); + fprintf( output, "Kv8qKir/j5eY/+j2+P/n9ff/5/X3/+Pw8v9kaGn/KCgo/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ykpKf9BQ0P/w87Q/+n3+v/n9ff/oqqs/7vGyP/p9/n/0t7g/1JUVf8pKSn/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/LS0t/4GHiP9bX1//KSgo/ysrK/8pKCj/UVRV/9rn6f/o9vj/5/X3/+n3+f+GjY7/KCgo/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/y0uLv+Wnp//6Pb4/+n4+v++ycv/XmFi/9fk5v/q+Pr/5/X3" ); + fprintf( output, "/2lub/8nJyf/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8pKCj/XmJj/3+Fhv8sLS3/Kysr/ysrK/8qKir/Ojs7/8fS1P/p" ); + fprintf( output, "9/n/5/X3/+r4+v+ZoqP/Kioq/ysrK/8rKyv/Kysr/ysrK/8rKyv/KSgo/2Roaf/d6uz/6ff5/93r" ); + fprintf( output, "7f9kaWn/Ly8v/5ObnP/T3+H/s72//4qRkv9eYmP/KSkp/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/yoqKv89Pz//jpWW/z4/QP8qKir/" ); + fprintf( output, "Kysr/ysrK/8qKir/Nzg4/8LOz//p9/n/5/X3/+r4+/+dpab/Kysr/ysrK/8rKyv/Kysr/ysrK/8q" ); + fprintf( output, "KSn/P0BA/8DLzP/p+Pr/6Pb4/5aen/8uLi7/Kioq/y8wMP9NUFD/o6yt/+Px8//I09X/SEpK/ykp" ); + fprintf( output, "Kf8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ywsLP9+hIX/X2Nk/ykoKP8rKyv/Kysr/ysrK/8pKSn/REZG/9He3//o9vn/5/X3/+r4+v+Ql5n/" ); + fprintf( output, "KSkp/ysrK/8rKyv/Kysr/ysrK/8tLS3/kZma/+j2+P/p9/n/w8/Q/0FDQ/8pKSn/Kysr/yoqKv8y" ); + fprintf( output, "MzP/qLKz/+r4+v/p9/n/qrO0/zU2Nv8qKir/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/KSgo/1pdXv+CiIn/Li4u/ysrK/8rKyv/Kysr/ysrK/8oKCj/cHV2" ); + fprintf( output, "/+Ty9P/n9ff/5/X3/+f19/9zeXn/KCgo/ysrK/8rKyv/Kysr/ykoKP9gZGX/2+nr/+n3+f/f7O7/" ); + fprintf( output, "aW1u/ykoKP8rKyv/Kysr/ysrK/8pKSn/R0lK/8bS1P/p+Pr/5vP1/4eOj/8sLCz/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8qKir/Ojs8/46Vlv9BQ0P/Kikp/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ykpKf8+QED/vsjK/+n3+f/n9ff/6Pb4/9fj5f9NT0//KSkp/ysrK/8rKyv/Kioq" ); + fprintf( output, "/zw+Pv+8x8n/6fj6/+n3+f+ao6T/Li8v/ysrK/8rKyv/Kysr/ysrK/8rKyv/KSkp/2Roaf/b6Or/" ); + fprintf( output, "6ff5/9vo6v9kaWn/KSkp/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/eoCB/2Roaf8pKSn/Kysr/yoqKv8pKSn/KCgo/z9AQP+mr7H/5/X4/+f19//n9ff/6vj6/6ew" ); + fprintf( output, "sf8vLzD/Kysr/ysrK/8rKyv/LCws/42Vlv/n9ff/6ff5/8bS1P9ERkb/KSkp/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ywsLP+Hjo//5vP1/+n4+v/H09T/SEpK/ykpKf8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ykpKf9WWVn/hYyM/y8vL/8qKir/Li4u/zk6Ov9GSEn/cnh5/7/Ky//o" ); + fprintf( output, "9vj/5/X3/+f19//o9vj/2ufp/1peXv8pKCj/Kysr/ysrK/8pKSn/XWFi/9rn6f/p9/r/4O7w/2xx" ); + fprintf( output, "cv8pKSn/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/yoqKv81Njb/qrS1/+n3+f/p9/n/qrO0" ); + fprintf( output, "/zU2Nv8qKir/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kioq/zg5Of+NlJX/RUdH/ykpKf8pKSn/" ); + fprintf( output, "TVBQ/77Jy//U4OL/5fP1/+n3+f/n9ff/5/X3/+j2+P/m9Pb/iZCR/ywsLP8rKyv/Kysr/yoqKv84" ); + fprintf( output, "OTn/r7m6/9/t7//f7O7/nKSm/zAwMP8rKir/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8pKSn/SUtL/8jT1f/p+Pr/5vP1/4eOj/8sLCz/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kioq" ); + fprintf( output, "/3Z8ff9obW7/KSkp/ysrK/8oKCj/VVhZ/+Dt7//p9/n/5/X3/+f19//n9ff/6Pb4/+b09v+aoqT/" ); + fprintf( output, "NDU1/yoqKv8rKyv/Kysr/yoqKv80NDX/UlVV/1RXV/9TVlf/OTo6/yoqKv8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/KSkp/2Zqa//c6ev/6ff5/9vo6v9kaWn/KSkp/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8pKSn/UVRV/4eOj/8xMTH/Kyoq/ysrK/8oKCj/VVhY/97r7f/o9vj/5/X3" ); + fprintf( output, "/+j2+P/q+Pr/2ufp/4mQkf80NTX/Kioq/ysrK/8rKyv/Kysr/ysrK/8qKir/KSko/ykoKP8pKCj/" ); + fprintf( output, "Kioq/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ywsLP+J" ); + fprintf( output, "kJH/5vT2/+n4+v/H09T/SEpK/ykpKf8rKyv/Kysr/yoqKv83ODj/jJSV/0hLS/8pKSn/Kysr/ysr" ); + fprintf( output, "K/8oKCj/VVlZ/+Hu8P/q+fv/5/X3/9fk5f+nsLL/Wl5e/ywsLP8qKir/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/yoqKv82Nzf/rLa3/+v6/P/s+/3/rbe4/zg5Of8qKir/Kysr/ykpKf9J" ); + fprintf( output, "TEz/XmJj/yoqKv8rKyv/Kysr/ysrK/8pKSn/REZG/5Wcnv+QmJn/dHl6/01PUP8vMDD/KSgo/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8pKSn/SEpL/5Wdnv+bpKX/" ); + fprintf( output, "mKCh/09SUv8pKSn/Kysr/ysrK/8rKyv/Kioq/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/yoqKv8p" ); + fprintf( output, "KSn/KCgo/ykpKf8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kioq/yoqKv8qKir/Kioq/ywsLP8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/krKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr+SsrK9wrKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr3CsrK5crKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/KysrlysrKzQrKyve" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyveKysrNAAAAAArKytmKysr9CsrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/QrKytmAAAAAAAAAAArKysFKysraCsrK+QrKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr5CsrK2grKysFAAAAAAAA" ); + fprintf( output, "AAAAAAAAKysrAisrKzorKyulKysr6ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK+sr" ); + fprintf( output, "KyulKysrOisrKwIAAAAAAAAAAPAAAAAADwAA4AAAAAAHAADAAAAAAAMAAIAAAAAAAQAAAAAAAAAA" ); + fprintf( output, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" ); + fprintf( output, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" ); + fprintf( output, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" ); + fprintf( output, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" ); + fprintf( output, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" ); + fprintf( output, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAEAAMAAAAAAAwAA4AAAAAAHAADwAAAA" ); + fprintf( output, "AA8AACgAAAAgAAAAQAAAAAEAIAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKysrEisrK30r" ); + fprintf( output, "KyvcKysr/CsrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv8Kysr3CsrK30rKysSAAAA" ); + fprintf( output, "ACsrKxIrKyueKysr+ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr+ysrK54rKysSKysrfisrK/orKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr+isrK34rKyvbKysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kioq/yoqKv8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr2ysrK/srKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8pKSn/KSkp/ykpKf87PDz/Nzg4/ygoKP8pKSn/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8qKir/KSkp/ykpKf8rKyv/Kysr/ysrK/8rKyv7" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Li4u/0lMTP9QU1P/QUJD/21ycv9T" ); + fprintf( output, "Vlb/UFNT/0JERP8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kioq/zY3N/9PUlL/TE5P/zAw" ); + fprintf( output, "MP8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ygoKP9hZWb/1ODi" ); + fprintf( output, "/8jU1f+Ahof/U1ZW/2NoaP/U4eP/xM/R/0pNTf8pKSn/Kysr/ysrK/8rKyv/Kysr/ysrK/8uLi7/" ); + fprintf( output, "lJyd/9/s7v+fp6n/MTIy/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8q" ); + fprintf( output, "Kir/PT4+/73Iyv/o9vj/nKSl/2xxcf8uLi7/Li4u/5OanP/q+Pr/rLa3/zY3N/8qKir/Kysr/ysr" ); + fprintf( output, "K/8rKyv/KSgo/2Vqav/f7e//09/h/1BTU/8pKSn/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ywsLP+NlZb/6vj6/7rFxv96gIH/PkBA/yoqKv8qKir/Ojs7/7S+wP/o9/n/" ); + fprintf( output, "iI+Q/ywsLP8rKyv/Kysr/ykpKf8/QUH/wczO/+f19/99g4T/Kioq/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8oKCj/XGBg/9vo6v/X5Ob/h46P/1daW/8pKSn/Kysr/ysr" ); + fprintf( output, "K/8pKSn/UFNT/9Dc3v/d6uz/ZGhp/ykoKP8rKyv/LS0t/5ObnP/r+fv/r7i6/zU2Nv8qKir/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kioq/zk7O/+4w8T/6Pb4/52mp/9tcnL/" ); + fprintf( output, "LzAw/ysrK/8rKyv/Kysr/ysrK/8pKSn/b3R1/+Lv8f/H09X/R0lK/ycmJv9hZmb/3uvt/9Xh4/9T" ); + fprintf( output, "Vlb/KSgo/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/h46P/+n3" ); + fprintf( output, "+f+9yMn/fIKD/0pMTP8pKCj/Kysr/ysrK/8rKyv/Kysr/ysrK/8uLi7/kpqb/+r4+v+nsLH/RkhI" ); + fprintf( output, "/73Iyv/o9vj/gYeI/yoqKv8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "KSkp/1hcXP/Z5uj/2ufp/4aNjv+DiYr/q7W2/05QUf8pKSn/Kysr/ysrK/8rKyv/Kysr/yoqKv86" ); + fprintf( output, "Ozv/tsDC/7nExf+bo6T/6fj6/7K8vv83ODj/Kioq/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8xMTH/mKCh/87a3P+ZoaL/fIKD/7bBwv/s+vz/tsDB/zo7O/8qKir/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ykpKf9JS0z/g4mK/9rn6f/W4+X/VVhZ/ykoKP8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/y0uLv88PT3/P0FB/21yc/9obG3/09/h/+n3+f/n" ); + fprintf( output, "9ff/eoCB/ykpKf8rKyv/Kysr/ysrK/8rKyv/Kioq/zo7O/+5xMX/6Pb4/4yUlf80NTX/Kioq/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ygoKP9RVFX/X2Nj" ); + fprintf( output, "/yoqKv+Ei4z/5/T2/+r4+v+2wML/MjMz/ysqKv8rKyv/Kysr/ysrK/8rKyv/ipGT/+n3+f/Czc//" ); + fprintf( output, "sbu9/7K8vf9CQ0T/KSkp/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8q" ); + fprintf( output, "Kir/Ojs7/290df8zMzT/KCgo/0dKSv/U4eL/6vj6/9Hd3/9DRUX/KSkp/ysrK/8rKyv/KSgo/1pe" ); + fprintf( output, "Xv/a5+n/2OXm/290df/K1tj/3Onr/2JmZ/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ywsLP9obG3/SEpL/ykpKf8qKir/Ojw8/8jU1f/q+Pr/1+Tm/0pMTf8pKSn/" ); + fprintf( output, "Kysr/yoqKv85Ojr/t8HD/+r4+v+JkJH/LCws/1ZZWv+bo6T/xM/R/2BkZf8pKCj/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8pKSn/T1JT/2JmZ/8qKir/Kysr/ykpKf9JS0z/1eLk/+r4" ); + fprintf( output, "+v/Q3N7/QkRE/yopKf8rKyv/Kysr/4aNjv/p9/n/usTG/zo8PP8qKir/Jycn/3B2dv/l8/X/xM/R" ); + fprintf( output, "/0RGR/8pKSn/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kioq/zc4Of9wdXX/NTU2/yoqKv8qKir/" ); + fprintf( output, "Kioq/4mQkf/n9ff/6vj6/7O9v/8yMjL/Kysr/ykoKP9XWlr/1+Tm/9vo6v9dYWH/KCgo/ysrK/8r" ); + fprintf( output, "Kir/MDAw/5mhov/q+Pr/pa6v/zM0NP8qKir/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Zmpr/0tO" ); + fprintf( output, "Tv8pKSn/Kysr/zY3N/97gYL/2+jq/+j2+P/m8/X/dnt8/ykoKP8qKir/ODk5/7S+wP/q+fv/jpWW" ); + fprintf( output, "/ywsLP8rKyv/Kysr/ysrK/8pKSn/Pj8//7vFx//n9ff/gYiJ/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "KSkp/0xPT/9laWr/Kioq/z9BQf+Wnp//vsnK/+Px8v/o9vj/6vj6/7C6u/83ODj/Kioq/yoqKv93" ); + fprintf( output, "fX3/2ufp/7fBw/89Pj7/Kioq/ysrK/8rKyv/Kysr/ysrK/8pKCj/Vlla/9Th4//a5+n/YGNk/yko" ); + fprintf( output, "KP8rKyv/Kysr/yoqKv81Njb/cHV1/zc4OP8oKCj/TlFR/93q7P/q+fv/6ff5/+n3+f+4wsT/R0lK" ); + fprintf( output, "/ykpKf8rKyv/LCws/0RFRv9OUVH/PkBA/yoqKv8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8qKir/" ); + fprintf( output, "d3x9/+Ty9P/Ez9H/RUZH/ykpKf8rKyv/Kysr/2JnZ/9OUVH/KSkp/ykpKf9OUVH/2+jq/+Ty9P/O" ); + fprintf( output, "2tz/jpaX/z5AQP8pKSn/Kysr/ysrK/8rKyv/Kikp/ykpKf8qKir/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/yoqKv8wMDD/m6Ok/+r4+v+mr7H/NTU1/yoqKv8wMDD/SkxN/y0tLf8rKyv/Kioq" ); + fprintf( output, "/zo7O/9yd3j/ZWpq/0NFRf8rKyv/Kioq/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/yopKf89Pj7/dXp7/3Z7fP89Pj7/Kioq/ysrK/8q" ); + fprintf( output, "Kir/Kysr/ysrK/8rKyv/Kioq/ygoKP8oKCj/KSkp/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/yoqKv8oKCj/KCgo" ); + fprintf( output, "/yoqKv8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv8Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/CsrK94rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyveKysrhSsr" ); + fprintf( output, "K/wrKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/CsrK4UrKysWKysrqCsrK/0rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/0rKyuoKysrFgAAAAArKysXKysriysrK+grKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyvoKysriysrKxcAAAAA4AAAB4AAAAGAAAABAAAAAAAAAAAA" ); + fprintf( output, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" ); + fprintf( output, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAcAAAAMoAAAAEAAA" ); + fprintf( output, "ACAAAAABACAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAKysrTCsrK9QrKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyvUKysrTCsrK9QrKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/KSkp/yoqKv8qKir/Kioq/ysrK/8rKyv/Kysr/ysqKv8pKSn/Kysr/ysrK9QrKyv9Kysr/ysr" ); + fprintf( output, "K/8rKyv/Li4u/0NFRf9GSUn/SkxM/zY3N/8qKir/Kysr/yoqKv8yMjL/REVG/y4uLv8rKyv9Kysr" ); + fprintf( output, "/ysrK/8rKyv/KSgo/2lubv+0v8D/Wl5e/3l+f/+epqf/MzQ0/yoqKv8qKir/g4qL/5aeoP8vLzD/" ); + fprintf( output, "Kysr/ysrK/8rKyv/KSkp/0FDQ/+7xsf/iI+Q/zIzM/81Njb/pa6w/4GIif8oKCj/Vlla/73Iyf9O" ); + fprintf( output, "UVH/KSkp/ysrK/8rKyv/Kysr/y4uLv+Wnp//sLq7/0pNTf8pKSn/KSkp/0hKSv+1wMH/Z2xt/6u1" ); + fprintf( output, "tv97gYL/KSkp/ysrK/8rKyv/Kysr/ykpKf9UV1j/sry+/5ykpf+ZoqP/NTY2/yoqKv8pKSn/XWFh" ); + fprintf( output, "/7G7vP+kra//MzQ0/yoqKv8rKyv/Kysr/ysrK/8rKyv/Njc3/1hbW/+Bh4j/4O7w/2ltbv8oKCj/" ); + fprintf( output, "KSgo/1FUVf/G0dP/j5eY/zM0NP8qKir/Kysr/ysrK/8rKyv/Kioq/0BBQf9CRET/QUND/9Tg4v+N" ); + fprintf( output, "lZb/KCgn/zQ1Nf+qs7X/i5KT/5mio/9zeHn/Kysr/ysrK/8rKyv/Kioq/zM0NP9NUFD/KSkp/1Za" ); + fprintf( output, "Wv/c6ev/gIaH/ycnJ/9+hIX/rLW3/zM0NP9FR0f/tsDC/11hYf8pKCj/Kysr/ywsLP9KTE3/QUND" ); + fprintf( output, "/3B1df/Ez9H/z9vd/0hLS/9FR0f/sLq8/1RXV/8pKCj/KSkp/2htbf+0v8D/QkRE/ykpKf8/QUH/" ); + fprintf( output, "REZG/0VHSP/N2dv/ws7P/2FlZf8qKir/NTY2/0FDQ/8sLCz/Kysr/ysrK/8sLCz/ipGS/5igof8y" ); + fprintf( output, "MjL/NTY2/y4uLv8yMzP/UFNU/zo7O/8pKSn/Kysr/yoqKv8qKin/Kysr/ysrK/8rKyv/Kioq/zQ1" ); + fprintf( output, "Nf9SVVb/MzM0/yoqKv4rKyv/Kysr/ykpKf8qKir/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8qKir/KSkp/ysrK/4rKyvXKysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyvXKysrUSsrK9orKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyvaKysrUYABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" ); + fprintf( output, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIABAAA=" ); + fprintf( output, "\" rel=\"icon\" type=\"image/x-icon\" />\n" ); + fprintf( output, "\n" ); + fprintf( output, " <title>%s – Requires Tree</title>\n", root ); + fprintf( output, "\n" ); + fprintf( output, " <style>\n" ); + fprintf( output, " @import url(https://fonts.googleapis.com/css?family=Roboto:400,700italic,700,500italic,500,400italic&subset=cyrillic-ext,latin);\n" ); + fprintf( output, " @import url(https://fonts.googleapis.com/css?family=Cousine:400,400italic,700,700italic&subset=cyrillic-ext,latin);\n" ); + fprintf( output, " </style>\n" ); + fprintf( output, "\n" ); + fprintf( output, " <style>\n" ); + fprintf( output, " body, html {\n" ); + fprintf( output, " margin: 0 0 0 0;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " #front_wrapper {\n" ); + fprintf( output, " margin: 0 auto;\n" ); + fprintf( output, " height: 100vh;\n" ); + fprintf( output, " position: relative;\n" ); + fprintf( output, " overflow: auto;\n" ); + fprintf( output, " background-color: #ececec;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " #spinner {\n" ); + fprintf( output, " margin: 0 auto;\n" ); + fprintf( output, " min-height: 256px;\n" ); + fprintf( output, " text-align: center;\n" ); + fprintf( output, " display: flex;\n" ); + fprintf( output, " align-items: center;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " #tree_view {\n" ); + fprintf( output, " margin: 0 auto;\n" ); + fprintf( output, " min-height: 256px;\n" ); + fprintf( output, " width: 2720px;\n" ); + fprintf( output, " border: 0px solid #e7e7e7;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " .header-wrapper {\n" ); + fprintf( output, " height: 160px;\n" ); + fprintf( output, " width: 100%%;\n" ); + fprintf( output, " margin: 0 auto;\n" ); + fprintf( output, " position: relative;\n" ); + fprintf( output, " background: transparent;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " .content-wrapper {\n" ); + fprintf( output, " background-color: #ffffff;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " .footer-wrapper {\n" ); + fprintf( output, " background: #ececec;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " .content {\n" ); + fprintf( output, " width: 1018px;\n" ); + fprintf( output, " min-height: 256px;\n" ); + fprintf( output, " padding: 18px 3px 12px 3px;\n" ); + fprintf( output, " margin: 0 auto;\n" ); + fprintf( output, " background-color: #fdfdfd;\n" ); + fprintf( output, " position: relative;\n" ); + fprintf( output, " overflow: hidden;\n" ); + fprintf( output, " align: center;\n" ); + fprintf( output, " border: 1px solid #e7e7e7;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " .footer {\n" ); + fprintf( output, " width: 1022px;\n" ); + fprintf( output, " height: 48px;\n" ); + fprintf( output, " margin: 0 auto;\n" ); + fprintf( output, "\n" ); + fprintf( output, " -moz-border-radius-topleft: 0px;\n" ); + fprintf( output, " -moz-border-radius-topright: 0px;\n" ); + fprintf( output, " -moz-border-radius-bottomright: 4px;\n" ); + fprintf( output, " -moz-border-radius-bottomleft: 4px;\n" ); + fprintf( output, "\n" ); + fprintf( output, " -webkit-border-top-left-radius: 0px;\n" ); + fprintf( output, " -webkit-border-top-right-radius: 0px;\n" ); + fprintf( output, " -webkit-border-bottom-left-radius: 4px;\n" ); + fprintf( output, " -webkit-border-bottom-right-radius: 4px;\n" ); + fprintf( output, "\n" ); + fprintf( output, " border-top-left-radius: 0px;\n" ); + fprintf( output, " border-top-right-radius: 0px;\n" ); + fprintf( output, " border-bottom-left-radius: 4px;\n" ); + fprintf( output, " border-bottom-right-radius: 4px;\n" ); + fprintf( output, "\n" ); + fprintf( output, " border: 1px solid #545454;\n" ); + fprintf( output, " background-color: #4c4c4c;\n" ); + fprintf( output, " background: linear-gradient(288deg, rgb(84, 84, 84), rgb(76, 76, 76));\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " .footer-top {\n" ); + fprintf( output, " margin: 2px auto 1px auto;\n" ); + fprintf( output, " color: #ffffff;\n" ); + fprintf( output, " text-align: center;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " .footer-bottom {\n" ); + fprintf( output, " margin: 0 8px 0 8px;\n" ); + fprintf( output, " min-height: 20px;\n" ); + fprintf( output, " color: #ffffff;\n" ); + fprintf( output, " font-size: 10px;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " .logo {\n" ); + fprintf( output, " width: 1024px;\n" ); + fprintf( output, " height: 80px;\n" ); + fprintf( output, " margin: 0 auto;\n" ); + fprintf( output, " background-color: transparent;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " .navigator {\n" ); + fprintf( output, " width: 1024px;\n" ); + fprintf( output, " height: 79px;\n" ); + fprintf( output, " margin: 0 auto;\n" ); + fprintf( output, " padding: 1px 0 0;\n" ); + fprintf( output, "\n" ); + fprintf( output, " -moz-border-radius-topleft: 4px;\n" ); + fprintf( output, " -moz-border-radius-topright: 4px;\n" ); + fprintf( output, " -moz-border-radius-bottomright: 0px;\n" ); + fprintf( output, " -moz-border-radius-bottomleft: 0px;\n" ); + fprintf( output, "\n" ); + fprintf( output, " -webkit-border-top-left-radius: 4px;\n" ); + fprintf( output, " -webkit-border-top-right-radius: 4px;\n" ); + fprintf( output, " -webkit-border-bottom-left-radius: 0px;\n" ); + fprintf( output, " -webkit-border-bottom-right-radius: 0px;\n" ); + fprintf( output, "\n" ); + fprintf( output, " border-top-left-radius: 4px;\n" ); + fprintf( output, " border-top-right-radius: 4px;\n" ); + fprintf( output, " border-bottom-left-radius: 0px;\n" ); + fprintf( output, " border-bottom-right-radius: 0px;\n" ); + fprintf( output, "\n" ); + fprintf( output, " border: 1px solid #545454;\n" ); + fprintf( output, " background-color: #4c4c4c;\n" ); + fprintf( output, " background: linear-gradient(288deg, rgb(84, 84, 84), rgb(76, 76, 76));\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " .copyright {\n" ); + fprintf( output, " color: #f0f0ea;\n" ); + fprintf( output, " text-decoration: none;\n" ); + fprintf( output, " font-family: 'Roboto', helvetica, arial, sans-serif;\n" ); + fprintf( output, " font-weight: bold;\n" ); + fprintf( output, " font-style: normal;\n" ); + fprintf( output, " font-size: 12px;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " .copyright:hover {\n" ); + fprintf( output, " text-decoration: underline;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, "\n" ); + fprintf( output, " .date-title {\n" ); + fprintf( output, " height: 16px;\n" ); + fprintf( output, " font: 12px 'Roboto', sans-serif;\n" ); + fprintf( output, " font-weight: bold;\n" ); + fprintf( output, " padding-top: 6px;\n" ); + fprintf( output, " margin-bottom: -10px;\n" ); + fprintf( output, " padding-left: 16px;\n" ); + fprintf( output, " color: #c0c0c0;\n" ); + fprintf( output, " }\n" ); + fprintf( output, " .time-title {\n" ); + fprintf( output, " color: #82946f;\n" ); + fprintf( output, " }\n" ); + fprintf( output, " .hardware-title {\n" ); + fprintf( output, " height: 20px;\n" ); + fprintf( output, " float: right;\n" ); + fprintf( output, " text-align: right;\n" ); + fprintf( output, " padding-right: 16px;\n" ); + fprintf( output, " width: 512px; font: 14px 'Roboto', sans-serif;\n" ); + fprintf( output, " font-weight: bold;\n" ); + fprintf( output, " color: #f0f0ea;\n" ); + fprintf( output, " }\n" ); + fprintf( output, " .hw-title {\n" ); + fprintf( output, " font: 10px 'Roboto', sans-serif;\n" ); + fprintf( output, " font-weight: bold;\n" ); + fprintf( output, " color: #cadaba;\n" ); + fprintf( output, " }\n" ); + fprintf( output, " .tree-title {\n" ); + fprintf( output, " height: 42px;\n" ); + fprintf( output, " padding-left: 16px;\n" ); + fprintf( output, " font: 28px 'Roboto', sans-serif;\n" ); + fprintf( output, " font-weight: bold;\n" ); + fprintf( output, " color: #f0f0ea;\n" ); + fprintf( output, " }\n" ); + fprintf( output, " .tree-hw-title {\n" ); + fprintf( output, " color: #cadaba;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* SVG spinner icon animation */\n" ); + fprintf( output, " .spinner {\n" ); + fprintf( output, " -webkit-animation: rotate 2s linear infinite;\n" ); + fprintf( output, " animation: rotate 2s linear infinite;\n" ); + fprintf( output, " z-index: 2;\n" ); + fprintf( output, " position: relative;\n" ); + fprintf( output, " top: 50%%;\n" ); + fprintf( output, " left: 50%%;\n" ); + fprintf( output, " margin: -25px 0 0 -25px;\n" ); + fprintf( output, " width: 50px;\n" ); + fprintf( output, " height: 50px;\n" ); + fprintf( output, " }\n" ); + fprintf( output, " .spinner-text {\n" ); + fprintf( output, " z-index: 2;\n" ); + fprintf( output, " position: absolute;\n" ); + fprintf( output, " top: 0;\n" ); + fprintf( output, " left: 0;\n" ); + fprintf( output, " margin: 36px;\n" ); + fprintf( output, " font: 28px 'Roboto', sans-serif;\n" ); + fprintf( output, " color: #c0c0c0;\n" ); + fprintf( output, " }\n" ); + fprintf( output, " .spinner .path {\n" ); + fprintf( output, " stroke: #cccccc;\n" ); + fprintf( output, " stroke-linecap: round;\n" ); + fprintf( output, " -webkit-animation: dash 1.5s ease-in-out infinite;\n" ); + fprintf( output, " animation: dash 1.5s ease-in-out infinite;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " @-webkit-keyframes rotate {\n" ); + fprintf( output, " 100%% { -webkit-transform: rotate(360deg); transform: rotate(360deg); }\n" ); + fprintf( output, " }\n" ); + fprintf( output, " @keyframes rotate {\n" ); + fprintf( output, " 100%% { -webkit-transform: rotate(360deg); transform: rotate(360deg); }\n" ); + fprintf( output, " }\n" ); + fprintf( output, " @-webkit-keyframes dash {\n" ); + fprintf( output, " 0%% { stroke-dasharray: 1, 150; stroke-dashoffset: 0; }\n" ); + fprintf( output, " 50%% { stroke-dasharray: 90, 150; stroke-dashoffset: -35; }\n" ); + fprintf( output, " 100%% { stroke-dasharray: 90, 150; stroke-dashoffset: -124; }\n" ); + fprintf( output, " }\n" ); + fprintf( output, " @keyframes dash {\n" ); + fprintf( output, " 0%% { stroke-dasharray: 1, 150; stroke-dashoffset: 0; }\n" ); + fprintf( output, " 50%% { stroke-dasharray: 90, 150; stroke-dashoffset: -35; }\n" ); + fprintf( output, " 100%% { stroke-dasharray: 90, 150; stroke-dashoffset: -124; }\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, "\n" ); + fprintf( output, " .node {\n" ); + fprintf( output, " cursor: pointer;\n" ); + fprintf( output, " }\n" ); + fprintf( output, " .node text {\n" ); + fprintf( output, " font: 14px 'Cousine', monospace;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " .tree-tooltip {\n" ); + fprintf( output, " position: absolute;\n" ); + fprintf( output, " text-align: left;\n" ); + fprintf( output, " padding: 16px 16px 8px;\n" ); + fprintf( output, " background-color: #fafafa;\n" ); + fprintf( output, " border: 1px solid #71ad93;\n" ); + fprintf( output, " border-radius: 8px;\n" ); + fprintf( output, " pointer-events: none;\n" ); + fprintf( output, " color: #343434;\n" ); + fprintf( output, " -webkit-box-shadow: 0 0 5px #aaa;\n" ); + fprintf( output, " box-shadow: 0 0 5px #aaa;\n" ); + fprintf( output, " }\n" ); + fprintf( output, " .tooltip-header {\n" ); + fprintf( output, " font: 14px Roboto, sans-serif;\n" ); + fprintf( output, " font-weight: bold;\n" ); + fprintf( output, " color: DarkRed;\n" ); + fprintf( output, "\n" ); + fprintf( output, " white-space: nowrap;\n" ); + fprintf( output, " text-align: left;\n" ); + fprintf( output, " }\n" ); + fprintf( output, " .tooltip-header-not-packaged {\n" ); + fprintf( output, " font: 11px Cousine,monospace;\n" ); + fprintf( output, " font-weight: bold;\n" ); + fprintf( output, " color: DarkRed;\n" ); + fprintf( output, "\n" ); + fprintf( output, " white-space: nowrap;\n" ); + fprintf( output, "\n" ); + fprintf( output, " padding-left: 8px;\n" ); + fprintf( output, " padding-right: 8px;\n" ); + fprintf( output, " padding-bottom: 8px;\n" ); + fprintf( output, " text-align: left;\n" ); + fprintf( output, " }\n" ); + fprintf( output, " .tooltip-description {\n" ); + fprintf( output, " font: 14px Roboto, sans-serif;\n" ); + fprintf( output, " font-style: italic;\n" ); + fprintf( output, " font-weight: bold;\n" ); + fprintf( output, " color: #343434;\n" ); + fprintf( output, "\n" ); + fprintf( output, " white-space: nowrap;\n" ); + fprintf( output, " text-align: left;\n" ); + fprintf( output, " padding-left: 1.5em;\n" ); + fprintf( output, " padding-top: .5em;\n" ); + fprintf( output, " font-style: italic;\n" ); + fprintf( output, " }\n" ); + fprintf( output, " .tooltip-content {\n" ); + fprintf( output, " font: 11px 'Cousine', monospace;\n" ); + fprintf( output, " font-weight: bold;\n" ); + fprintf( output, "\n" ); + fprintf( output, " white-space: pre;\n" ); + fprintf( output, " margin: 12px 0 8px;\n" ); + fprintf( output, " }\n" ); + fprintf( output, " .flavour {\n" ); + fprintf( output, " color: DarkBlue;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, "\n" ); + fprintf( output, " @media (min-width: 1200px) {\n" ); + fprintf( output, " .navigator { width: 1140px; }\n" ); + fprintf( output, " .logo { width: 1140px; }\n" ); + fprintf( output, " .footer { width: 1140px; }\n" ); + fprintf( output, " .content { width: 1134px; }\n" ); + fprintf( output, " }\n" ); + fprintf( output, " @media (min-width: 992px) and (max-width: 1199px) {\n" ); + fprintf( output, " .navigator { width: 960px; }\n" ); + fprintf( output, " .logo { width: 960px; }\n" ); + fprintf( output, " .footer { width: 960px; }\n" ); + fprintf( output, " .content { width: 954px; }\n" ); + fprintf( output, " }\n" ); + fprintf( output, " @media (min-width: 768px) and (max-width: 991px) {\n" ); + fprintf( output, " .navigator { width: 720px; }\n" ); + fprintf( output, " .logo { width: 720px; }\n" ); + fprintf( output, " .footer { width: 720px; }\n" ); + fprintf( output, " .content { width: 714px; }\n" ); + fprintf( output, " }\n" ); + fprintf( output, " @media (min-width: 576px) and (max-width: 767px) {\n" ); + fprintf( output, " .navigator { width: 540px; }\n" ); + fprintf( output, " .logo { width: 540px; }\n" ); + fprintf( output, " .footer { width: 540px; }\n" ); + fprintf( output, " .content { width: 534px; }\n" ); + fprintf( output, "\n" ); + fprintf( output, " .node text { font-size: 12px; }\n" ); + fprintf( output, "\n" ); + fprintf( output, " .tooltip-header { font-size: 12px; }\n" ); + fprintf( output, " .tooltip-description { font-size: 12px; }\n" ); + fprintf( output, " .tooltip-content { font-size: 10px; }\n" ); + fprintf( output, " }\n" ); + fprintf( output, " @media (max-width: 575px) {\n" ); + fprintf( output, " .navigator { width: 480px; }\n" ); + fprintf( output, " .logo { width: 480px; }\n" ); + fprintf( output, " .footer { width: 480px; }\n" ); + fprintf( output, " .content { width: 474px; }\n" ); + fprintf( output, "\n" ); + fprintf( output, " .node text { font-size: 12px; }\n" ); + fprintf( output, "\n" ); + fprintf( output, " .tooltip-header { font-size: 12px; }\n" ); + fprintf( output, " .tooltip-description { font-size: 12px; }\n" ); + fprintf( output, " .tooltip-content { font-size: 10px; }\n" ); + fprintf( output, " }\n" ); + fprintf( output, " </style>\n" ); + fprintf( output, "\n" ); + fprintf( output, " <script src=\"https://code.jquery.com/jquery-3.4.1.min.js\"></script>\n" ); + fprintf( output, " <script src=\"https://code.jquery.com/ui/1.12.1/jquery-ui.min.js\"></script>\n" ); + fprintf( output, " <script src=\"https://d3js.org/d3.v5.min.js\"></script>\n" ); + fprintf( output, " <script>\n" ); + fprintf( output, " !function(o){function t(o,t){if(!(o.originalEvent.touches.length>1)){o.preventDefault();var e=o.originalEvent.changedTouches[0],n=document.createEvent(\"MouseEvents\");n.initMouseEvent(t,!0,!0,window,1,e.screenX,e.screenY,e.clientX,e.clientY,!1,!1,!1,!1,0,null),o.target.dispatchEvent(n)}}if(o.support.touch=\"ontouchend\"in document,o.support.touch){var e,n,u=o.ui.mouse.prototype,c=u._mouseInit,i=u._mouseDestroy;u._touchStart=function(o){var u=this;!n&&u._mouseCapture(o.originalEvent.changedTouches[0])&&(n=!0,u._touchMoved=!1,e=o,t(o,\"mouseover\"),t(o,\"mousemove\"),t(o,\"mousedown\"))},u._touchMove=function(o){if(n){var u=e.originalEvent.touches[0].screenX,c=e.originalEvent.touches[0].screenY,i=o.originalEvent.touches[0].screenX,r=o.originalEvent.touches[0].screenY;if(u===i&&c===r)return void(this._touchMoved=!1);this._touchMoved=!0,t(o,\"mousemove\")}},u._touchEnd=function(o){n&&(t(o,\"mouseup\"),t(o,\"mouseout\"),this._touchMoved||t(o,\"click\"),n=!1)},u._mouseInit=function(){var t=this;t.element.bind({touchstart:o.proxy(t,\"_touchStart\"),touchmove:o.proxy(t,\"_touchMove\"),touchend:o.proxy(t,\"_touchEnd\")}),c.call(t)},u._mouseDestroy=function(){var t=this;t.element.unbind({touchstart:o.proxy(t,\"_touchStart\"),touchmove:o.proxy(t,\"_touchMove\"),touchend:o.proxy(t,\"_touchEnd\")}),i.call(t)}}}(jQuery);\n" ); + fprintf( output, " $(function() {\n" ); + fprintf( output, " $( \"#tree_view\" ).draggable();\n" ); + fprintf( output, " });\n" ); + fprintf( output, " </script>\n" ); + fprintf( output, " <script>\n" ); + fprintf( output, " function load_json( url, callback ) {\n" ); + fprintf( output, " var xobj = new XMLHttpRequest();\n" ); + fprintf( output, " xobj.overrideMimeType(\"application/json\");\n" ); + fprintf( output, " xobj.open('GET', url, true);\n" ); + fprintf( output, " xobj.onreadystatechange = function () {\n" ); + fprintf( output, " if (xobj.readyState == 4 && xobj.status == \"200\") {\n" ); + fprintf( output, " callback(xobj.responseText);\n" ); + fprintf( output, " }\n" ); + fprintf( output, " };\n" ); + fprintf( output, " xobj.send(null);\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " var pkgs;\n" ); + fprintf( output, "\n" ); + fprintf( output, " $(document).ready(function() {\n" ); + fprintf( output, " load_json( '%s', function(response) {\n", json_pkgs_file ); + fprintf( output, " pkgs = JSON.parse(response);\n" ); + fprintf( output, " });\n" ); + fprintf( output, "\n" ); + fprintf( output, " $('#tree_view')\n" ); + fprintf( output, " .mousedown(function() { $(this).css( 'cursor', 'grab' ); })\n" ); + fprintf( output, " .mouseup( function() { $(this).css( 'cursor', 'auto' ); });\n" ); + fprintf( output, " });\n" ); + fprintf( output, " </script>\n" ); + fprintf( output, " </head>\n" ); + fprintf( output, " <body>\n" ); + fprintf( output, " <div id=\"front_wrapper\">\n" ); + fprintf( output, " <div class=\"header-wrapper\">\n" ); + fprintf( output, " <div class=\"logo\"></div>\n" ); + fprintf( output, " <div class=\"navigator\">\n" ); + fprintf( output, " <div style=\"height: 36px;\">\n" ); + fprintf( output, " <div class=\"date-title\">%04d-%02d-%02d <span class=\"time-title\">%02d:%02d:%02d</span></div>\n", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec ); + fprintf( output, " <div class=\"hardware-title\">\n" ); + fprintf( output, " <span class=\"hw-title\">HARDWARE:</span> %s\n", hardware ); + fprintf( output, " </div>\n" ); + fprintf( output, " </div>\n" ); + fprintf( output, " <div class=\"tree-title\">\n" ); + fprintf( output, " <span class=\"tree-hw-title\">%s</span> – Requires Tree\n", root ); + fprintf( output, " </div>\n" ); + fprintf( output, " </div> <!-- \"navigator\" -->\n" ); + fprintf( output, " </div> <!-- \"header_wrapper\" -->\n" ); + fprintf( output, "\n" ); + fprintf( output, " <div class=\"content-wrapper\">\n" ); + fprintf( output, " <div class=\"content\">\n" ); + fprintf( output, " <div id=\"spinner\">\n" ); + fprintf( output, " <svg class=\"spinner\" viewBox=\"0 0 50 50\"><circle class=\"path\" cx=\"25\" cy=\"25\" r=\"20\" fill=\"none\" stroke-width=\"5\"></circle></svg>\n" ); + fprintf( output, " <div class=\"spinner-text\">Loading ...</div>\n" ); + fprintf( output, " </div>\n" ); + fprintf( output, " <div id=\"tree_view\" class=\"ui-widget-content\">\n" ); + fprintf( output, " </div>\n" ); + fprintf( output, " </div> <!-- \"content\" -->\n" ); + fprintf( output, " </div> <!-- \"content_wrapper\" -->\n" ); + fprintf( output, "\n" ); + fprintf( output, " <div class=\"footer-wrapper\">\n" ); + fprintf( output, " <div class=\"footer\">\n" ); + fprintf( output, " <div class=\"footer-top\">\n" ); + fprintf( output, " <a class=\"copyright\" target=\"_blank\" href=\"%s\">© %s</a>\n", bug_url, copying ); + fprintf( output, " </div>\n" ); + fprintf( output, " <div class=\"footer-bottom\">\n" ); + fprintf( output, " </div>\n" ); + fprintf( output, " </div> <!-- \"footer\" -->\n" ); + fprintf( output, " </div> <!-- \"footer_wrapper\" -->\n" ); + fprintf( output, " </div> <!-- \"front_wrapper\" -->\n" ); + fprintf( output, "\n" ); + fprintf( output, " <script>\n" ); + fprintf( output, " var margin = {top: 20, right: 120, bottom: 20, left: 220},\n" ); + fprintf( output, " width = %d - margin.right - margin.left,\n", svg_width ); + fprintf( output, " height = %d - margin.top - margin.bottom;\n", svg_height ); + fprintf( output, "\n" ); + fprintf( output, " var i = 0,\n" ); + fprintf( output, " duration = 750,\n" ); + fprintf( output, " root = 0;\n" ); + fprintf( output, "\n" ); + fprintf( output, " var treemap = d3.tree()\n" ); + fprintf( output, " .size([height, width]);\n" ); + fprintf( output, "\n" ); + fprintf( output, " var svg = d3.select(document.getElementById( 'tree_view' )).append(\"svg\")\n" ); + fprintf( output, " .attr(\"width\", width + margin.right + margin.left)\n" ); + fprintf( output, " .attr(\"height\", height + margin.top + margin.bottom)\n" ); + fprintf( output, " .append(\"g\")\n" ); + fprintf( output, " .attr(\"transform\", \"translate(\" + margin.left + \",\" + margin.top + \")\");\n" ); + fprintf( output, "\n" ); + fprintf( output, " var div = d3.select(document.getElementById( 'front_wrapper' )).append(\"div\")\n" ); + fprintf( output, " .attr(\"class\", \"tree-tooltip\")\n" ); + fprintf( output, " .style(\"display\", \"none\")\n" ); + fprintf( output, " .style(\"opacity\", 0);\n" ); + fprintf( output, "\n" ); + fprintf( output, "\n" ); + fprintf( output, " load_json( '%s', function(response) {\n", json_tree_file ); + fprintf( output, " var treeData = JSON.parse(response);\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* Assigns parent, children, height, depth: */\n" ); + fprintf( output, " root = d3.hierarchy(treeData, function(d) { return d.children; });\n" ); + fprintf( output, "\n" ); + fprintf( output, " root.x0 = height / 2;\n" ); + fprintf( output, " root.y0 = 0;\n" ); + fprintf( output, "\n" ); + fprintf( output, " function collapse(d) {\n" ); + fprintf( output, " if( d.children ) {\n" ); + fprintf( output, " d._children = d.children;\n" ); + fprintf( output, " d._children.forEach(collapse);\n" ); + fprintf( output, " d.children = null;\n" ); + fprintf( output, " }\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " document.getElementById('spinner').remove();\n" ); + fprintf( output, " root.children.forEach(collapse);\n" ); + fprintf( output, " update(root);\n" ); + fprintf( output, " });\n" ); + fprintf( output, "\n" ); + fprintf( output, "\n" ); + fprintf( output, " function update(source) {\n" ); + fprintf( output, "\n" ); + fprintf( output, " var tree = treemap( root );\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* Compute the new tree layout. */\n" ); + fprintf( output, " var nodes = tree.descendants(),\n" ); + fprintf( output, " links = tree.descendants().slice(1);\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* Normalize for fixed-depth. */\n" ); + fprintf( output, " nodes.forEach(function(d) { d.y = d.depth * 220; });\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* Update the nodes . . . */\n" ); + fprintf( output, " var node = svg.selectAll(\"g.node\")\n" ); + fprintf( output, " .data(nodes, function(d) { return d.id || (d.id = ++i); });\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* Enter any new nodes at the parent's previous position. */\n" ); + fprintf( output, " var nodeEnter = node.enter().append(\"g\")\n" ); + fprintf( output, " .attr(\"class\", \"node\")\n" ); + fprintf( output, " .attr(\"transform\", function(d) { return \"translate(\" + source.y0 + \",\" + source.x0 + \")\"; })\n" ); + fprintf( output, " .on(\"click\", click)\n" ); + fprintf( output, " .on(\"mouseover\", function(d) {\n" ); + fprintf( output, " div.transition()\n" ); + fprintf( output, " .duration(200)\n" ); + fprintf( output, " .style(\"opacity\", .92);\n" ); + fprintf( output, " {\n" ); + fprintf( output, " var content = '<div class=\"tooltip-header-not-packaged\">' + 'void' + '</div>';\n" ); + fprintf( output, "\n" ); + fprintf( output, " if( d.name === \"void\" ) {\n" ); + fprintf( output, " /* draw div.tree-tooltip to get actual size */\n" ); + fprintf( output, " div.html( content )\n" ); + fprintf( output, " .style(\"left\", (d3.event.pageX + document.getElementById( 'front_wrapper' ).scrollLeft + 12) + \"px\")\n" ); + fprintf( output, " .style(\"top\", (d3.event.pageY + document.getElementById( 'front_wrapper' ).scrollTop + 12) + \"px\");\n" ); + fprintf( output, " }\n" ); + fprintf( output, " else\n" ); + fprintf( output, " {\n" ); + fprintf( output, " /* find package in the pkgs array: */\n" ); + fprintf( output, " var pkg = pkgs.find(obj => { return obj.id === d.data.name; });\n" ); + fprintf( output, "\n" ); + fprintf( output, " if( pkg === undefined )\n" ); + fprintf( output, " {\n" ); + fprintf( output, " content = '<div class=\"tooltip-header-not-packaged\">' + 'not packaged collection' + '</div>';\n" ); + fprintf( output, " /* draw div.tree-tooltip to get actual size */\n" ); + fprintf( output, " div.html( content )\n" ); + fprintf( output, " .style(\"left\", (d3.event.pageX + document.getElementById( 'front_wrapper' ).scrollLeft + 12) + \"px\")\n" ); + fprintf( output, " .style(\"top\", (d3.event.pageY + document.getElementById( 'front_wrapper' ).scrollTop + 12) + \"px\");\n" ); + fprintf( output, " }\n" ); + fprintf( output, " else\n" ); + fprintf( output, " {\n" ); + fprintf( output, " content = '<div class=\"tooltip-header\">' + pkg.name + '</div>' +\n" ); + fprintf( output, " '<div class=\"tooltip-description\">' + pkg.description + '</div>' +\n" ); + fprintf( output, " '<div class=\"tooltip-content\">' +\n" ); + fprintf( output, " ' group: ' + pkg.group + '\\n' +\n" ); + fprintf( output, " ' architecture: ' + pkg.arch + '\\n' +\n" ); + fprintf( output, " ' hardware: ' + pkg.hardware + '\\n';\n" ); + fprintf( output, " if( pkg.flavour !== undefined )\n" ); + fprintf( output, " {\n" ); + fprintf( output, " content += ' <span class=\"flavour\">edition</span>: ' + pkg.flavour + '\\n';\n" ); + fprintf( output, " }\n" ); + fprintf( output, " content += ' license: ' + pkg.license + '\\n' +\n" ); + fprintf( output, " ' bug report url: ' + root.data.distro[2] + '\\n' +\n" ); + fprintf( output, " ' distribution: ' + root.data.distro[0] + '-' + root.data.distro[1] + '\\n' +\n" ); + fprintf( output, " ' package tarball: ' + pkg.name + '-' + pkg.version + '-' + pkg.arch + '-' + root.data.distro[0] + '-' + root.data.distro[1] + '.'+ '%s' + '\\n' +\n", tarball_suffix ); + fprintf( output, " ' uncompressed size: ' + pkg.uncompressed_size + '\\n' +\n" ); + fprintf( output, " ' number of files: ' + pkg.total_files + '\\n' +\n" ); + fprintf( output, " '</div>' +\n" ); + fprintf( output, " '</div>' +\n" ); + fprintf( output, " '</div>';\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* draw div.tree-tooltip to get actual size */\n" ); + fprintf( output, " div.html( content )\n" ); + fprintf( output, " .style(\"left\", (d3.event.pageX + document.getElementById( 'front_wrapper' ).scrollLeft + 12) + \"px\")\n" ); + fprintf( output, " .style(\"top\", (d3.event.pageY + document.getElementById( 'front_wrapper' ).scrollTop + 12) + \"px\");\n" ); + fprintf( output, " }\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* draw div.tree-tooltip at actual position */\n" ); + fprintf( output, "\n" ); + fprintf( output, " var cW = $( window ).width();\n" ); + fprintf( output, " var cH = $( window ).height();\n" ); + fprintf( output, " var cX = d3.event.pageX;\n" ); + fprintf( output, " var cY = d3.event.pageY;\n" ); + fprintf( output, " var tW = $('div.tree-tooltip').width();\n" ); + fprintf( output, " var tH = $('div.tree-tooltip').height();\n" ); + fprintf( output, " var oX;\n" ); + fprintf( output, " var oY;\n" ); + fprintf( output, " var dX = ( cW - cX ) - ( tW + 12 );\n" ); + fprintf( output, " var dY = ( cH - cY ) - ( tH + 12 );\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* shift left to according to width=16 of browser vertical scroll bar */\n" ); + fprintf( output, " if( dX <= 24 ) { dX = 24 - dX; }\n" ); + fprintf( output, " else { dX = 0; }\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* shift top to according to width=16 of browser horizontal scroll bar */\n" ); + fprintf( output, " if( dY <= 24 ) { dY = 24 - dY; }\n" ); + fprintf( output, " else { dY = 0; }\n" ); + fprintf( output, "\n" ); + fprintf( output, " if( ( cW - cX ) < ( tW + 12 ) ) { oX = - 12 - tW; } else { oX = 12 - dX; }\n" ); + fprintf( output, " if( ( cH - cY ) < ( tH + 12 ) ) { oY = - 12 - tH; } else { oY = 12 - dY; }\n" ); + fprintf( output, "\n" ); + fprintf( output, " if( (( cW - cX ) < ( tW + 12 )) && (cX < ( tW + 12 )) )\n" ); + fprintf( output, " {\n" ); + fprintf( output, " /* in this case we have to center tooltip */\n" ); + fprintf( output, " oX = - (tW + 12) / 2 + (cW/2 - cX);\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " div.html( content )\n" ); + fprintf( output, " .style(\"left\", (d3.event.pageX + document.getElementById( 'front_wrapper' ).scrollLeft + oX) + \"px\")\n" ); + fprintf( output, " .style(\"top\", (d3.event.pageY + document.getElementById( 'front_wrapper' ).scrollTop + oY) + \"px\")\n" ); + fprintf( output, " .style(\"display\",\"block\");\n" ); + fprintf( output, " }\n" ); + fprintf( output, " })\n" ); + fprintf( output, " .on(\"mouseout\", function(d) {\n" ); + fprintf( output, " div.transition()\n" ); + fprintf( output, " .duration(500)\n" ); + fprintf( output, " .style(\"opacity\", 0);\n" ); + fprintf( output, " });\n" ); + fprintf( output, "\n" ); + fprintf( output, " nodeEnter.append(\"circle\")\n" ); + fprintf( output, " .attr('class', 'node')\n" ); + fprintf( output, " /* Additional attributes (see the 'style' section) */\n" ); + fprintf( output, " .attr(\"stroke\", \"#5d5d5d\")\n" ); + fprintf( output, " .attr(\"stroke-width\", \"1.0\")\n" ); + fprintf( output, " /* End of additional attributes */\n" ); + fprintf( output, " .attr(\"r\", 1e-6)\n" ); + fprintf( output, " .style(\"fill\", function(d) { return d._children ? \"#abd8d4\" : \"#fff\"; });\n" ); + fprintf( output, "\n" ); + fprintf( output, " nodeEnter.append(\"text\")\n" ); + fprintf( output, " .attr(\"x\", function(d) { return d.children || d._children ? -10 : 10; })\n" ); + fprintf( output, " .attr(\"dy\", \"-.35em\")\n" ); + fprintf( output, " .attr(\"text-anchor\", function(d) { return d.children || d._children ? \"end\" : \"start\"; })\n" ); + fprintf( output, " .text(function(d) { return (d.data.name.indexOf(\":\",0) > 0 ) ? d.data.name.substr(d.data.name.indexOf(\":\",0) + 1) : d.data.name; })\n" ); + fprintf( output, " .style(\"fill-opacity\", 1);\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* Update */\n" ); + fprintf( output, " var nodeUpdate = nodeEnter.merge(node);\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* Transition nodes to their new position. */\n" ); + fprintf( output, " nodeUpdate.transition()\n" ); + fprintf( output, " .duration(duration)\n" ); + fprintf( output, " .attr(\"transform\", function(d) { return \"translate(\" + d.y + \",\" + d.x + \")\"; });\n" ); + fprintf( output, "\n" ); + fprintf( output, " nodeUpdate.select(\"circle.node\")\n" ); + fprintf( output, " .attr(\"r\", 4.5)\n" ); + fprintf( output, " .style(\"fill\", function(d) {\n" ); + fprintf( output, " if( d._children )\n" ); + fprintf( output, " {\n" ); + fprintf( output, " return \"#abd8d4\";\n" ); + fprintf( output, " }\n" ); + fprintf( output, " else\n" ); + fprintf( output, " {\n" ); + fprintf( output, " if( d.children == undefined )\n" ); + fprintf( output, " {\n" ); + fprintf( output, " if( d.name == \"void\" )\n" ); + fprintf( output, " {\n" ); + fprintf( output, " return \"#c9c9c9\";\n" ); + fprintf( output, " }\n" ); + fprintf( output, " else\n" ); + fprintf( output, " {\n" ); + fprintf( output, " return \"#fff\";\n" ); + fprintf( output, " }\n" ); + fprintf( output, " }\n" ); + fprintf( output, " else\n" ); + fprintf( output, " {\n" ); + fprintf( output, " return \"#d2ebd8\";\n" ); + fprintf( output, " }\n" ); + fprintf( output, " }\n" ); + fprintf( output, " })\n" ); + fprintf( output, " .attr('cursor', 'pointer');\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* Transition exiting nodes to the parent's new position. */\n" ); + fprintf( output, " var nodeExit = node.exit().transition()\n" ); + fprintf( output, " .duration(duration)\n" ); + fprintf( output, " .attr(\"transform\", function(d) { return \"translate(\" + source.y + \",\" + source.x + \")\"; })\n" ); + fprintf( output, " .remove();\n" ); + fprintf( output, "\n" ); + fprintf( output, " nodeExit.select(\"circle\")\n" ); + fprintf( output, " .attr(\"r\", 1e-6);\n" ); + fprintf( output, "\n" ); + fprintf( output, " nodeExit.select(\"text\")\n" ); + fprintf( output, " .style(\"fill-opacity\", 1e-6);\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* Update the links . . . */\n" ); + fprintf( output, " var link = svg.selectAll('path.link')\n" ); + fprintf( output, " .data(links, function(d) { return d.id; });\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* Enter any new links at the parent's previous position. */\n" ); + fprintf( output, " var linkEnter = link.enter().insert('path', 'g')\n" ); + fprintf( output, " .attr(\"class\", \"link\")\n" ); + fprintf( output, " .attr(\"d\", function(d) {\n" ); + fprintf( output, " var o = {x: source.x0, y: source.y0};\n" ); + fprintf( output, " return diagonal(o, o);\n" ); + fprintf( output, " });\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* Update */\n" ); + fprintf( output, " var linkUpdate = linkEnter.merge(link);\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* Transition links to their new position. */\n" ); + fprintf( output, " linkUpdate.transition()\n" ); + fprintf( output, " .duration(duration)\n" ); + fprintf( output, " /* Additional attributes (see the 'style' section) */\n" ); + fprintf( output, " .style(\"fill\", \"none\")\n" ); + fprintf( output, " .attr(\"stroke\", \"DarkGray\")\n" ); + fprintf( output, " .attr(\"stroke-width\", \"1.5\")\n" ); + fprintf( output, " /* End of additional attributes */\n" ); + fprintf( output, " .attr(\"d\", function(d){ return diagonal(d, d.parent) });\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* Transition exiting nodes to the parent's new position. */\n" ); + fprintf( output, " var linkExit = link.exit().transition()\n" ); + fprintf( output, " .duration(duration)\n" ); + fprintf( output, " .attr(\"d\", function(d) {\n" ); + fprintf( output, " var o = {x: source.x, y: source.y};\n" ); + fprintf( output, " return diagonal(o, o);\n" ); + fprintf( output, " })\n" ); + fprintf( output, " .remove();\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* Stash the old positions for transition. */\n" ); + fprintf( output, " nodes.forEach(function(d) {\n" ); + fprintf( output, " d.x0 = d.x;\n" ); + fprintf( output, " d.y0 = d.y;\n" ); + fprintf( output, " });\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* Creates a curved (diagonal) path from parent to the child nodes. */\n" ); + fprintf( output, " function diagonal(s, d) {\n" ); + fprintf( output, " path = `M ${s.y} ${s.x}\n" ); + fprintf( output, " C ${(s.y + d.y) / 2} ${s.x},\n" ); + fprintf( output, " ${(s.y + d.y) / 2} ${d.x},\n" ); + fprintf( output, " ${d.y} ${d.x}`;\n" ); + fprintf( output, " return path;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* Toggle children on click. */\n" ); + fprintf( output, " function click(d) {\n" ); + fprintf( output, " if (d.children) {\n" ); + fprintf( output, " d._children = d.children;\n" ); + fprintf( output, " d.children = null;\n" ); + fprintf( output, " } else {\n" ); + fprintf( output, " d.children = d._children;\n" ); + fprintf( output, " d._children = null;\n" ); + fprintf( output, " }\n" ); + fprintf( output, " update(d);\n" ); + fprintf( output, " }\n" ); + fprintf( output, " }\n" ); + fprintf( output, " </script>\n" ); + fprintf( output, " </body>\n" ); + fprintf( output, "</html>\n" ); +} diff --git a/src/pkglist.html.v3.c b/src/pkglist.html.v3.c new file mode 100644 index 0000000..32dc9f9 --- /dev/null +++ b/src/pkglist.html.v3.c @@ -0,0 +1,992 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +static void print_tree_html( FILE *output ) +{ + time_t t = time( NULL ); + struct tm tm = *localtime(&t); + + if( !output ) return; + + fprintf( output, "<!DOCTYPE html>\n" ); + fprintf( output, "<html>\n" ); + fprintf( output, " <head>\n" ); + fprintf( output, " <meta charset=\"utf-8\">\n" ); + fprintf( output, " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n" ); + fprintf( output, " <meta name=\"owner\" content=\"Andrey V.Kosteltsev\">\n" ); + fprintf( output, " <meta name=\"author\" content=\"Andrey V.Kosteltsev\">\n" ); + fprintf( output, " <meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n" ); + fprintf( output, " <meta http-equiv=\"Content-script-type\" content=\"text/javascript\">\n" ); + fprintf( output, " <meta http-equiv=\"Content-Style-Type\" content=\"text/css\">\n" ); + fprintf( output, "\n" ); + fprintf( output, " <link href=\"data:image/x-icon;base64," ); + fprintf( output, "AAABAAMAMDAAAAEAIACoJQAANgAAACAgAAABACAAqBAAAN4lAAAQEAAAAQAgAGgEAACGNgAAKAAA" ); + fprintf( output, "ADAAAABgAAAAAQAgAAAAAAAAJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACsrKysrKyuP" ); + fprintf( output, "Kysr2SsrK/grKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr+CsrK9krKyuPKysrKwAAAAAAAAAAAAAA" ); + fprintf( output, "AAAAAAArKysDKysrWSsrK9krKyv+Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv+Kysr2SsrK1krKysDAAAAAAAAAAArKytZKysr7isrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK+4rKytZAAAAACsrKywrKyvYKysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "KyvYKysrLCsrK48rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/KysrjysrK9grKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr2CsrK/crKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Ly8v/zIzM/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr9ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ykpKf8oKCj/KCgo/ykpKf8rKyv/cnh4/1NWVv8mJib/KCgo/ykpKf8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kioq/ygo" ); + fprintf( output, "KP8oKCj/KSgo/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/LS0t/05QUf9YW1v/WFtc/0VHSP9UV1f/" ); + fprintf( output, "ho2O/0hKSv9YW1z/V1tb/1FUVP8vLy//Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8qKir/PT8//1daW/9XW1v/VFdX/zMzM/8rKyr/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8pKCj/W15f" ); + fprintf( output, "/9Lf4f/g7e//2ebo/3h+f/+LkpP/RUdH/1NWV//N2dv/4e7w/9nm6P9xdnf/KSkp/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/yoqKv8zNDT/p7Cy/+Hu8P/h7/H/p7Cx/zQ0NP8q" ); + fprintf( output, "Kir/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/yoqKv85Ojr/t8HD/+r4+v/q+Pr/pa6v/3uBgv9qb2//KSkp/ysrK/98goP/5PHz" ); + fprintf( output, "/+r4+v/P3N3/UVRU/ykpKf8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ykpKf94fn//" ); + fprintf( output, "5PLz/+n4+v/U4eP/U1dX/ykpKf8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/+GjI3/5vT2/+n3+f/M2Nr/cHV2/4aN" ); + fprintf( output, "jv8yMjL/Kioq/yoqKv8xMjL/n6ip/+n3+f/p+Pr/tL7A/zo7O/8qKir/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/KSkp/0xOT//O2tz/6ff5/+b09v+BiIn/Kioq/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/KSgo/1ZZWf/W" ); + fprintf( output, "4uT/6ff5/+Px8/+BiIj/iZCR/0pNTf8pKSn/Kysr/ysrK/8pKSn/QUND/7/Ky//q+Pr/5/X3/5GZ" ); + fprintf( output, "mv8tLi7/Kysr/ysrK/8rKyv/Kysr/ysrK/8qKir/MjIy/6avsP/p9/n/6fj6/7K8vv83ODj/Kioq" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8qKir/Njc3/7G7vf/p+Pr/6fj6/6mytP94fn//bnN0/ykpKf8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/KSgo/1teX//W4+T/6ff5/9/s7v9scXL/KSkp/ysrK/8rKyv/Kysr/ysrK/8pKSn/dHl6/+Pw" ); + fprintf( output, "8v/p9/n/1uPl/1ZaWv8pKCj/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8qKir/f4aG/+Xz9f/p9/n/z9vd/3B1dv+Ij5D/" ); + fprintf( output, "NDQ0/yoqKv8rKyv/Kysr/ysrK/8rKyv/Kysr/yoqKv98goP/4/Hz/+n4+v/M2Nr/TVBQ/ykpKf8r" ); + fprintf( output, "Kyv/Kysr/ykpKf9JS0z/zNjZ/+n3+f/m9Pb/hoyN/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ykpKf9RVFT/0t/h" ); + fprintf( output, "/+n3+f/k8vT/hIuM/4iPkP9OUVH/KSkp/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/yoqKv8xMjL/" ); + fprintf( output, "n6ip/+n3+f/p+Pr/sLq7/zg5Of8qKir/Kioq/zExMf+iq6z/6ff5/+n4+v+2wML/OTo6/yoqKv8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kioq/zQ0Nf+stbb/6fj6/+n4+v+tt7j/dnt8/3F3d/8qKir/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8pKSn/QUND/7/KzP/q+Pr/5/T2/4yUlf8tLS3/KSgo/3B1dv/h7/H/" ); + fprintf( output, "6ff5/9jl5/9aXV7/KSgo/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/KSkp/3l/gP/k8vT/6ff5/9Lf4P9wdXb/ipGS/0JE" ); + fprintf( output, "RP8qKir/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/KSgo/1tfX//W4+X/6fj6" ); + fprintf( output, "/93q7P9mamv/REZH/8nV1v/p9/n/5/X3/4qRkv8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8pKSn/TE9P/8/b3f/p" ); + fprintf( output, "9/n/5fP1/4eOj/+Ei4z/b3R1/7G7vP9obW7/Kioq/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/yoqKv98goP/5PHz/+b09v9+hYX/nKSm/+n3+f/p+Pr/usTG/zs8PP8qKir/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/yoqKv8zNDT/qLGy/+v6/P/r+fv/sbu9/3N4ef97gYL/n6ip/+v5+//V4eP/XmJj/ykpKf8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/yoqKv8yMjL/oaqr/6q0tf92fH3/3+3v/+n3" ); + fprintf( output, "+f/a5+n/XWFi/ykoKP8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ykpKf9dYWL/w87Q/8nV1/++ycv/b3R1/4qRkv99hIX/" ); + fprintf( output, "4O3v/+j2+P/q+Pr/ws3P/0BCQv8qKSn/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8q" ); + fprintf( output, "KSn/PD09/1tfX//E0NL/6ff5/+j2+P+OlZb/LCws/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/80NTX/PD09" ); + fprintf( output, "/zs8PP88PT3/hIuM/2BkZP+zvb7/6vn7/+f19//n9ff/6Pb4/4mQkf8qKir/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/LS0t/5mhov/p9/n/6fj6/7vGyP86Ozv/Jycn/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8qKir/Kioq/ygnJ/9iZmf/e4GC/ysrK/9iZmf/2OTm/+j2+P/n9ff/6ff5" ); + fprintf( output, "/8jU1f8+QED/Kioq/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8pKCj/aGxt/9/s7v/p9/n/" ); + fprintf( output, "2+jq/32Dg/9yd3j/VVhZ/ywsLP8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kikp/0BCQv+OlZb/Ozw8/yoq" ); + fprintf( output, "Kv8qKir/j5eY/+j2+P/n9ff/5/X3/+Pw8v9kaGn/KCgo/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ykpKf9BQ0P/w87Q/+n3+v/n9ff/oqqs/7vGyP/p9/n/0t7g/1JUVf8pKSn/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/LS0t/4GHiP9bX1//KSgo/ysrK/8pKCj/UVRV/9rn6f/o9vj/5/X3/+n3+f+GjY7/KCgo/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/y0uLv+Wnp//6Pb4/+n4+v++ycv/XmFi/9fk5v/q+Pr/5/X3" ); + fprintf( output, "/2lub/8nJyf/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8pKCj/XmJj/3+Fhv8sLS3/Kysr/ysrK/8qKir/Ojs7/8fS1P/p" ); + fprintf( output, "9/n/5/X3/+r4+v+ZoqP/Kioq/ysrK/8rKyv/Kysr/ysrK/8rKyv/KSgo/2Roaf/d6uz/6ff5/93r" ); + fprintf( output, "7f9kaWn/Ly8v/5ObnP/T3+H/s72//4qRkv9eYmP/KSkp/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/yoqKv89Pz//jpWW/z4/QP8qKir/" ); + fprintf( output, "Kysr/ysrK/8qKir/Nzg4/8LOz//p9/n/5/X3/+r4+/+dpab/Kysr/ysrK/8rKyv/Kysr/ysrK/8q" ); + fprintf( output, "KSn/P0BA/8DLzP/p+Pr/6Pb4/5aen/8uLi7/Kioq/y8wMP9NUFD/o6yt/+Px8//I09X/SEpK/ykp" ); + fprintf( output, "Kf8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ywsLP9+hIX/X2Nk/ykoKP8rKyv/Kysr/ysrK/8pKSn/REZG/9He3//o9vn/5/X3/+r4+v+Ql5n/" ); + fprintf( output, "KSkp/ysrK/8rKyv/Kysr/ysrK/8tLS3/kZma/+j2+P/p9/n/w8/Q/0FDQ/8pKSn/Kysr/yoqKv8y" ); + fprintf( output, "MzP/qLKz/+r4+v/p9/n/qrO0/zU2Nv8qKir/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/KSgo/1pdXv+CiIn/Li4u/ysrK/8rKyv/Kysr/ysrK/8oKCj/cHV2" ); + fprintf( output, "/+Ty9P/n9ff/5/X3/+f19/9zeXn/KCgo/ysrK/8rKyv/Kysr/ykoKP9gZGX/2+nr/+n3+f/f7O7/" ); + fprintf( output, "aW1u/ykoKP8rKyv/Kysr/ysrK/8pKSn/R0lK/8bS1P/p+Pr/5vP1/4eOj/8sLCz/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8qKir/Ojs8/46Vlv9BQ0P/Kikp/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ykpKf8+QED/vsjK/+n3+f/n9ff/6Pb4/9fj5f9NT0//KSkp/ysrK/8rKyv/Kioq" ); + fprintf( output, "/zw+Pv+8x8n/6fj6/+n3+f+ao6T/Li8v/ysrK/8rKyv/Kysr/ysrK/8rKyv/KSkp/2Roaf/b6Or/" ); + fprintf( output, "6ff5/9vo6v9kaWn/KSkp/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/eoCB/2Roaf8pKSn/Kysr/yoqKv8pKSn/KCgo/z9AQP+mr7H/5/X4/+f19//n9ff/6vj6/6ew" ); + fprintf( output, "sf8vLzD/Kysr/ysrK/8rKyv/LCws/42Vlv/n9ff/6ff5/8bS1P9ERkb/KSkp/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ywsLP+Hjo//5vP1/+n4+v/H09T/SEpK/ykpKf8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ykpKf9WWVn/hYyM/y8vL/8qKir/Li4u/zk6Ov9GSEn/cnh5/7/Ky//o" ); + fprintf( output, "9vj/5/X3/+f19//o9vj/2ufp/1peXv8pKCj/Kysr/ysrK/8pKSn/XWFi/9rn6f/p9/r/4O7w/2xx" ); + fprintf( output, "cv8pKSn/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/yoqKv81Njb/qrS1/+n3+f/p9/n/qrO0" ); + fprintf( output, "/zU2Nv8qKir/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kioq/zg5Of+NlJX/RUdH/ykpKf8pKSn/" ); + fprintf( output, "TVBQ/77Jy//U4OL/5fP1/+n3+f/n9ff/5/X3/+j2+P/m9Pb/iZCR/ywsLP8rKyv/Kysr/yoqKv84" ); + fprintf( output, "OTn/r7m6/9/t7//f7O7/nKSm/zAwMP8rKir/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8pKSn/SUtL/8jT1f/p+Pr/5vP1/4eOj/8sLCz/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kioq" ); + fprintf( output, "/3Z8ff9obW7/KSkp/ysrK/8oKCj/VVhZ/+Dt7//p9/n/5/X3/+f19//n9ff/6Pb4/+b09v+aoqT/" ); + fprintf( output, "NDU1/yoqKv8rKyv/Kysr/yoqKv80NDX/UlVV/1RXV/9TVlf/OTo6/yoqKv8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/KSkp/2Zqa//c6ev/6ff5/9vo6v9kaWn/KSkp/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8pKSn/UVRV/4eOj/8xMTH/Kyoq/ysrK/8oKCj/VVhY/97r7f/o9vj/5/X3" ); + fprintf( output, "/+j2+P/q+Pr/2ufp/4mQkf80NTX/Kioq/ysrK/8rKyv/Kysr/ysrK/8qKir/KSko/ykoKP8pKCj/" ); + fprintf( output, "Kioq/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ywsLP+J" ); + fprintf( output, "kJH/5vT2/+n4+v/H09T/SEpK/ykpKf8rKyv/Kysr/yoqKv83ODj/jJSV/0hLS/8pKSn/Kysr/ysr" ); + fprintf( output, "K/8oKCj/VVlZ/+Hu8P/q+fv/5/X3/9fk5f+nsLL/Wl5e/ywsLP8qKir/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/yoqKv82Nzf/rLa3/+v6/P/s+/3/rbe4/zg5Of8qKir/Kysr/ykpKf9J" ); + fprintf( output, "TEz/XmJj/yoqKv8rKyv/Kysr/ysrK/8pKSn/REZG/5Wcnv+QmJn/dHl6/01PUP8vMDD/KSgo/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8pKSn/SEpL/5Wdnv+bpKX/" ); + fprintf( output, "mKCh/09SUv8pKSn/Kysr/ysrK/8rKyv/Kioq/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/yoqKv8p" ); + fprintf( output, "KSn/KCgo/ykpKf8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kioq/yoqKv8qKir/Kioq/ywsLP8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/krKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr+SsrK9wrKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr3CsrK5crKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/KysrlysrKzQrKyve" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyveKysrNAAAAAArKytmKysr9CsrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/QrKytmAAAAAAAAAAArKysFKysraCsrK+QrKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr5CsrK2grKysFAAAAAAAA" ); + fprintf( output, "AAAAAAAAKysrAisrKzorKyulKysr6ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK+sr" ); + fprintf( output, "KyulKysrOisrKwIAAAAAAAAAAPAAAAAADwAA4AAAAAAHAADAAAAAAAMAAIAAAAAAAQAAAAAAAAAA" ); + fprintf( output, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" ); + fprintf( output, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" ); + fprintf( output, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" ); + fprintf( output, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" ); + fprintf( output, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" ); + fprintf( output, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAEAAMAAAAAAAwAA4AAAAAAHAADwAAAA" ); + fprintf( output, "AA8AACgAAAAgAAAAQAAAAAEAIAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKysrEisrK30r" ); + fprintf( output, "KyvcKysr/CsrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv8Kysr3CsrK30rKysSAAAA" ); + fprintf( output, "ACsrKxIrKyueKysr+ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr+ysrK54rKysSKysrfisrK/orKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr+isrK34rKyvbKysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kioq/yoqKv8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr2ysrK/srKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8pKSn/KSkp/ykpKf87PDz/Nzg4/ygoKP8pKSn/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8qKir/KSkp/ykpKf8rKyv/Kysr/ysrK/8rKyv7" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Li4u/0lMTP9QU1P/QUJD/21ycv9T" ); + fprintf( output, "Vlb/UFNT/0JERP8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kioq/zY3N/9PUlL/TE5P/zAw" ); + fprintf( output, "MP8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ygoKP9hZWb/1ODi" ); + fprintf( output, "/8jU1f+Ahof/U1ZW/2NoaP/U4eP/xM/R/0pNTf8pKSn/Kysr/ysrK/8rKyv/Kysr/ysrK/8uLi7/" ); + fprintf( output, "lJyd/9/s7v+fp6n/MTIy/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8q" ); + fprintf( output, "Kir/PT4+/73Iyv/o9vj/nKSl/2xxcf8uLi7/Li4u/5OanP/q+Pr/rLa3/zY3N/8qKir/Kysr/ysr" ); + fprintf( output, "K/8rKyv/KSgo/2Vqav/f7e//09/h/1BTU/8pKSn/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ywsLP+NlZb/6vj6/7rFxv96gIH/PkBA/yoqKv8qKir/Ojs7/7S+wP/o9/n/" ); + fprintf( output, "iI+Q/ywsLP8rKyv/Kysr/ykpKf8/QUH/wczO/+f19/99g4T/Kioq/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8oKCj/XGBg/9vo6v/X5Ob/h46P/1daW/8pKSn/Kysr/ysr" ); + fprintf( output, "K/8pKSn/UFNT/9Dc3v/d6uz/ZGhp/ykoKP8rKyv/LS0t/5ObnP/r+fv/r7i6/zU2Nv8qKir/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kioq/zk7O/+4w8T/6Pb4/52mp/9tcnL/" ); + fprintf( output, "LzAw/ysrK/8rKyv/Kysr/ysrK/8pKSn/b3R1/+Lv8f/H09X/R0lK/ycmJv9hZmb/3uvt/9Xh4/9T" ); + fprintf( output, "Vlb/KSgo/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/h46P/+n3" ); + fprintf( output, "+f+9yMn/fIKD/0pMTP8pKCj/Kysr/ysrK/8rKyv/Kysr/ysrK/8uLi7/kpqb/+r4+v+nsLH/RkhI" ); + fprintf( output, "/73Iyv/o9vj/gYeI/yoqKv8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "KSkp/1hcXP/Z5uj/2ufp/4aNjv+DiYr/q7W2/05QUf8pKSn/Kysr/ysrK/8rKyv/Kysr/yoqKv86" ); + fprintf( output, "Ozv/tsDC/7nExf+bo6T/6fj6/7K8vv83ODj/Kioq/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8xMTH/mKCh/87a3P+ZoaL/fIKD/7bBwv/s+vz/tsDB/zo7O/8qKir/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ykpKf9JS0z/g4mK/9rn6f/W4+X/VVhZ/ykoKP8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/y0uLv88PT3/P0FB/21yc/9obG3/09/h/+n3+f/n" ); + fprintf( output, "9ff/eoCB/ykpKf8rKyv/Kysr/ysrK/8rKyv/Kioq/zo7O/+5xMX/6Pb4/4yUlf80NTX/Kioq/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ygoKP9RVFX/X2Nj" ); + fprintf( output, "/yoqKv+Ei4z/5/T2/+r4+v+2wML/MjMz/ysqKv8rKyv/Kysr/ysrK/8rKyv/ipGT/+n3+f/Czc//" ); + fprintf( output, "sbu9/7K8vf9CQ0T/KSkp/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8q" ); + fprintf( output, "Kir/Ojs7/290df8zMzT/KCgo/0dKSv/U4eL/6vj6/9Hd3/9DRUX/KSkp/ysrK/8rKyv/KSgo/1pe" ); + fprintf( output, "Xv/a5+n/2OXm/290df/K1tj/3Onr/2JmZ/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ywsLP9obG3/SEpL/ykpKf8qKir/Ojw8/8jU1f/q+Pr/1+Tm/0pMTf8pKSn/" ); + fprintf( output, "Kysr/yoqKv85Ojr/t8HD/+r4+v+JkJH/LCws/1ZZWv+bo6T/xM/R/2BkZf8pKCj/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8pKSn/T1JT/2JmZ/8qKir/Kysr/ykpKf9JS0z/1eLk/+r4" ); + fprintf( output, "+v/Q3N7/QkRE/yopKf8rKyv/Kysr/4aNjv/p9/n/usTG/zo8PP8qKir/Jycn/3B2dv/l8/X/xM/R" ); + fprintf( output, "/0RGR/8pKSn/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kioq/zc4Of9wdXX/NTU2/yoqKv8qKir/" ); + fprintf( output, "Kioq/4mQkf/n9ff/6vj6/7O9v/8yMjL/Kysr/ykoKP9XWlr/1+Tm/9vo6v9dYWH/KCgo/ysrK/8r" ); + fprintf( output, "Kir/MDAw/5mhov/q+Pr/pa6v/zM0NP8qKir/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Zmpr/0tO" ); + fprintf( output, "Tv8pKSn/Kysr/zY3N/97gYL/2+jq/+j2+P/m8/X/dnt8/ykoKP8qKir/ODk5/7S+wP/q+fv/jpWW" ); + fprintf( output, "/ywsLP8rKyv/Kysr/ysrK/8pKSn/Pj8//7vFx//n9ff/gYiJ/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "KSkp/0xPT/9laWr/Kioq/z9BQf+Wnp//vsnK/+Px8v/o9vj/6vj6/7C6u/83ODj/Kioq/yoqKv93" ); + fprintf( output, "fX3/2ufp/7fBw/89Pj7/Kioq/ysrK/8rKyv/Kysr/ysrK/8pKCj/Vlla/9Th4//a5+n/YGNk/yko" ); + fprintf( output, "KP8rKyv/Kysr/yoqKv81Njb/cHV1/zc4OP8oKCj/TlFR/93q7P/q+fv/6ff5/+n3+f+4wsT/R0lK" ); + fprintf( output, "/ykpKf8rKyv/LCws/0RFRv9OUVH/PkBA/yoqKv8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8qKir/" ); + fprintf( output, "d3x9/+Ty9P/Ez9H/RUZH/ykpKf8rKyv/Kysr/2JnZ/9OUVH/KSkp/ykpKf9OUVH/2+jq/+Ty9P/O" ); + fprintf( output, "2tz/jpaX/z5AQP8pKSn/Kysr/ysrK/8rKyv/Kikp/ykpKf8qKir/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/yoqKv8wMDD/m6Ok/+r4+v+mr7H/NTU1/yoqKv8wMDD/SkxN/y0tLf8rKyv/Kioq" ); + fprintf( output, "/zo7O/9yd3j/ZWpq/0NFRf8rKyv/Kioq/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/yopKf89Pj7/dXp7/3Z7fP89Pj7/Kioq/ysrK/8q" ); + fprintf( output, "Kir/Kysr/ysrK/8rKyv/Kioq/ygoKP8oKCj/KSkp/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/yoqKv8oKCj/KCgo" ); + fprintf( output, "/yoqKv8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv8Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/CsrK94rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyveKysrhSsr" ); + fprintf( output, "K/wrKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/CsrK4UrKysWKysrqCsrK/0rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysr" ); + fprintf( output, "K/8rKyv/Kysr/ysrK/0rKyuoKysrFgAAAAArKysXKysriysrK+grKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyvoKysriysrKxcAAAAA4AAAB4AAAAGAAAABAAAAAAAAAAAA" ); + fprintf( output, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" ); + fprintf( output, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAcAAAAMoAAAAEAAA" ); + fprintf( output, "ACAAAAABACAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAKysrTCsrK9QrKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyvUKysrTCsrK9QrKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/KSkp/yoqKv8qKir/Kioq/ysrK/8rKyv/Kysr/ysqKv8pKSn/Kysr/ysrK9QrKyv9Kysr/ysr" ); + fprintf( output, "K/8rKyv/Li4u/0NFRf9GSUn/SkxM/zY3N/8qKir/Kysr/yoqKv8yMjL/REVG/y4uLv8rKyv9Kysr" ); + fprintf( output, "/ysrK/8rKyv/KSgo/2lubv+0v8D/Wl5e/3l+f/+epqf/MzQ0/yoqKv8qKir/g4qL/5aeoP8vLzD/" ); + fprintf( output, "Kysr/ysrK/8rKyv/KSkp/0FDQ/+7xsf/iI+Q/zIzM/81Njb/pa6w/4GIif8oKCj/Vlla/73Iyf9O" ); + fprintf( output, "UVH/KSkp/ysrK/8rKyv/Kysr/y4uLv+Wnp//sLq7/0pNTf8pKSn/KSkp/0hKSv+1wMH/Z2xt/6u1" ); + fprintf( output, "tv97gYL/KSkp/ysrK/8rKyv/Kysr/ykpKf9UV1j/sry+/5ykpf+ZoqP/NTY2/yoqKv8pKSn/XWFh" ); + fprintf( output, "/7G7vP+kra//MzQ0/yoqKv8rKyv/Kysr/ysrK/8rKyv/Njc3/1hbW/+Bh4j/4O7w/2ltbv8oKCj/" ); + fprintf( output, "KSgo/1FUVf/G0dP/j5eY/zM0NP8qKir/Kysr/ysrK/8rKyv/Kioq/0BBQf9CRET/QUND/9Tg4v+N" ); + fprintf( output, "lZb/KCgn/zQ1Nf+qs7X/i5KT/5mio/9zeHn/Kysr/ysrK/8rKyv/Kioq/zM0NP9NUFD/KSkp/1Za" ); + fprintf( output, "Wv/c6ev/gIaH/ycnJ/9+hIX/rLW3/zM0NP9FR0f/tsDC/11hYf8pKCj/Kysr/ywsLP9KTE3/QUND" ); + fprintf( output, "/3B1df/Ez9H/z9vd/0hLS/9FR0f/sLq8/1RXV/8pKCj/KSkp/2htbf+0v8D/QkRE/ykpKf8/QUH/" ); + fprintf( output, "REZG/0VHSP/N2dv/ws7P/2FlZf8qKir/NTY2/0FDQ/8sLCz/Kysr/ysrK/8sLCz/ipGS/5igof8y" ); + fprintf( output, "MjL/NTY2/y4uLv8yMzP/UFNU/zo7O/8pKSn/Kysr/yoqKv8qKin/Kysr/ysrK/8rKyv/Kioq/zQ1" ); + fprintf( output, "Nf9SVVb/MzM0/yoqKv4rKyv/Kysr/ykpKf8qKir/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr" ); + fprintf( output, "/ysrK/8qKir/KSkp/ysrK/4rKyvXKysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyv/" ); + fprintf( output, "Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyvXKysrUSsrK9orKyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8r" ); + fprintf( output, "Kyv/Kysr/ysrK/8rKyv/Kysr/ysrK/8rKyvaKysrUYABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" ); + fprintf( output, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIABAAA=" ); + fprintf( output, "\" rel=\"icon\" type=\"image/x-icon\" />\n" ); + fprintf( output, "\n" ); + fprintf( output, " <title>%s – Requires Tree</title>\n", hardware ); + fprintf( output, "\n" ); + fprintf( output, " <style>\n" ); + fprintf( output, " @import url(https://fonts.googleapis.com/css?family=Roboto:400,700italic,700,500italic,500,400italic&subset=cyrillic-ext,latin);\n" ); + fprintf( output, " @import url(https://fonts.googleapis.com/css?family=Cousine:400,400italic,700,700italic&subset=cyrillic-ext,latin);\n" ); + fprintf( output, " </style>\n" ); + fprintf( output, "\n" ); + fprintf( output, " <style>\n" ); + fprintf( output, " body, html {\n" ); + fprintf( output, " margin: 0 0 0 0;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " #front_wrapper {\n" ); + fprintf( output, " margin: 0 auto;\n" ); + fprintf( output, " height: 100vh;\n" ); + fprintf( output, " position: relative;\n" ); + fprintf( output, " overflow: auto;\n" ); + fprintf( output, " background-color: #ececec;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " #spinner {\n" ); + fprintf( output, " margin: 0 auto;\n" ); + fprintf( output, " min-height: 256px;\n" ); + fprintf( output, " text-align: center;\n" ); + fprintf( output, " display: flex;\n" ); + fprintf( output, " align-items: center;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " #tree_view {\n" ); + fprintf( output, " margin: 0 auto;\n" ); + fprintf( output, " min-height: 256px;\n" ); + fprintf( output, " width: 2720px;\n" ); + fprintf( output, " border: 0px solid #e7e7e7;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " .header-wrapper {\n" ); + fprintf( output, " height: 160px;\n" ); + fprintf( output, " width: 100%%;\n" ); + fprintf( output, " margin: 0 auto;\n" ); + fprintf( output, " position: relative;\n" ); + fprintf( output, " background: transparent;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " .content-wrapper {\n" ); + fprintf( output, " background-color: #ffffff;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " .footer-wrapper {\n" ); + fprintf( output, " background: #ececec;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " .content {\n" ); + fprintf( output, " width: 1018px;\n" ); + fprintf( output, " min-height: 256px;\n" ); + fprintf( output, " padding: 18px 3px 12px 3px;\n" ); + fprintf( output, " margin: 0 auto;\n" ); + fprintf( output, " background-color: #fdfdfd;\n" ); + fprintf( output, " position: relative;\n" ); + fprintf( output, " overflow: hidden;\n" ); + fprintf( output, " align: center;\n" ); + fprintf( output, " border: 1px solid #e7e7e7;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " .footer {\n" ); + fprintf( output, " width: 1022px;\n" ); + fprintf( output, " height: 48px;\n" ); + fprintf( output, " margin: 0 auto;\n" ); + fprintf( output, "\n" ); + fprintf( output, " -moz-border-radius-topleft: 0px;\n" ); + fprintf( output, " -moz-border-radius-topright: 0px;\n" ); + fprintf( output, " -moz-border-radius-bottomright: 4px;\n" ); + fprintf( output, " -moz-border-radius-bottomleft: 4px;\n" ); + fprintf( output, "\n" ); + fprintf( output, " -webkit-border-top-left-radius: 0px;\n" ); + fprintf( output, " -webkit-border-top-right-radius: 0px;\n" ); + fprintf( output, " -webkit-border-bottom-left-radius: 4px;\n" ); + fprintf( output, " -webkit-border-bottom-right-radius: 4px;\n" ); + fprintf( output, "\n" ); + fprintf( output, " border-top-left-radius: 0px;\n" ); + fprintf( output, " border-top-right-radius: 0px;\n" ); + fprintf( output, " border-bottom-left-radius: 4px;\n" ); + fprintf( output, " border-bottom-right-radius: 4px;\n" ); + fprintf( output, "\n" ); + fprintf( output, " border: 1px solid #545454;\n" ); + fprintf( output, " background-color: #4c4c4c;\n" ); + fprintf( output, " background: linear-gradient(288deg, rgb(84, 84, 84), rgb(76, 76, 76));\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " .footer-top {\n" ); + fprintf( output, " margin: 2px auto 1px auto;\n" ); + fprintf( output, " color: #ffffff;\n" ); + fprintf( output, " text-align: center;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " .footer-bottom {\n" ); + fprintf( output, " margin: 0 8px 0 8px;\n" ); + fprintf( output, " min-height: 20px;\n" ); + fprintf( output, " color: #ffffff;\n" ); + fprintf( output, " font-size: 10px;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " .logo {\n" ); + fprintf( output, " width: 1024px;\n" ); + fprintf( output, " height: 80px;\n" ); + fprintf( output, " margin: 0 auto;\n" ); + fprintf( output, " background-color: transparent;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " .navigator {\n" ); + fprintf( output, " width: 1024px;\n" ); + fprintf( output, " height: 79px;\n" ); + fprintf( output, " margin: 0 auto;\n" ); + fprintf( output, " padding: 1px 0 0;\n" ); + fprintf( output, "\n" ); + fprintf( output, " -moz-border-radius-topleft: 4px;\n" ); + fprintf( output, " -moz-border-radius-topright: 4px;\n" ); + fprintf( output, " -moz-border-radius-bottomright: 0px;\n" ); + fprintf( output, " -moz-border-radius-bottomleft: 0px;\n" ); + fprintf( output, "\n" ); + fprintf( output, " -webkit-border-top-left-radius: 4px;\n" ); + fprintf( output, " -webkit-border-top-right-radius: 4px;\n" ); + fprintf( output, " -webkit-border-bottom-left-radius: 0px;\n" ); + fprintf( output, " -webkit-border-bottom-right-radius: 0px;\n" ); + fprintf( output, "\n" ); + fprintf( output, " border-top-left-radius: 4px;\n" ); + fprintf( output, " border-top-right-radius: 4px;\n" ); + fprintf( output, " border-bottom-left-radius: 0px;\n" ); + fprintf( output, " border-bottom-right-radius: 0px;\n" ); + fprintf( output, "\n" ); + fprintf( output, " border: 1px solid #545454;\n" ); + fprintf( output, " background-color: #4c4c4c;\n" ); + fprintf( output, " background: linear-gradient(288deg, rgb(84, 84, 84), rgb(76, 76, 76));\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " .copyright {\n" ); + fprintf( output, " color: #f0f0ea;\n" ); + fprintf( output, " text-decoration: none;\n" ); + fprintf( output, " font-family: 'Roboto', helvetica, arial, sans-serif;\n" ); + fprintf( output, " font-weight: bold;\n" ); + fprintf( output, " font-style: normal;\n" ); + fprintf( output, " font-size: 12px;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " .copyright:hover {\n" ); + fprintf( output, " text-decoration: underline;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, "\n" ); + fprintf( output, " .date-title {\n" ); + fprintf( output, " height: 16px;\n" ); + fprintf( output, " font: 12px 'Roboto', sans-serif;\n" ); + fprintf( output, " font-weight: bold;\n" ); + fprintf( output, " padding-top: 6px;\n" ); + fprintf( output, " margin-bottom: -10px;\n" ); + fprintf( output, " padding-left: 16px;\n" ); + fprintf( output, " color: #c0c0c0;\n" ); + fprintf( output, " }\n" ); + fprintf( output, " .time-title {\n" ); + fprintf( output, " color: #82946f;\n" ); + fprintf( output, " }\n" ); + fprintf( output, " .hardware-title {\n" ); + fprintf( output, " height: 20px;\n" ); + fprintf( output, " float: right;\n" ); + fprintf( output, " text-align: right;\n" ); + fprintf( output, " padding-right: 16px;\n" ); + fprintf( output, " width: 512px; font: 14px 'Roboto', sans-serif;\n" ); + fprintf( output, " font-weight: bold;\n" ); + fprintf( output, " color: #f0f0ea;\n" ); + fprintf( output, " }\n" ); + fprintf( output, " .hw-title {\n" ); + fprintf( output, " font: 10px 'Roboto', sans-serif;\n" ); + fprintf( output, " font-weight: bold;\n" ); + fprintf( output, " color: #cadaba;\n" ); + fprintf( output, " }\n" ); + fprintf( output, " .tree-title {\n" ); + fprintf( output, " height: 42px;\n" ); + fprintf( output, " padding-left: 16px;\n" ); + fprintf( output, " font: 28px 'Roboto', sans-serif;\n" ); + fprintf( output, " font-weight: bold;\n" ); + fprintf( output, " color: #f0f0ea;\n" ); + fprintf( output, " }\n" ); + fprintf( output, " .tree-hw-title {\n" ); + fprintf( output, " color: #cadaba;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* SVG spinner icon animation */\n" ); + fprintf( output, " .spinner {\n" ); + fprintf( output, " -webkit-animation: rotate 2s linear infinite;\n" ); + fprintf( output, " animation: rotate 2s linear infinite;\n" ); + fprintf( output, " z-index: 2;\n" ); + fprintf( output, " position: relative;\n" ); + fprintf( output, " top: 50%%;\n" ); + fprintf( output, " left: 50%%;\n" ); + fprintf( output, " margin: -25px 0 0 -25px;\n" ); + fprintf( output, " width: 50px;\n" ); + fprintf( output, " height: 50px;\n" ); + fprintf( output, " }\n" ); + fprintf( output, " .spinner-text {\n" ); + fprintf( output, " z-index: 2;\n" ); + fprintf( output, " position: absolute;\n" ); + fprintf( output, " top: 0;\n" ); + fprintf( output, " left: 0;\n" ); + fprintf( output, " margin: 36px;\n" ); + fprintf( output, " font: 28px 'Roboto', sans-serif;\n" ); + fprintf( output, " color: #c0c0c0;\n" ); + fprintf( output, " }\n" ); + fprintf( output, " .spinner .path {\n" ); + fprintf( output, " stroke: #cccccc;\n" ); + fprintf( output, " stroke-linecap: round;\n" ); + fprintf( output, " -webkit-animation: dash 1.5s ease-in-out infinite;\n" ); + fprintf( output, " animation: dash 1.5s ease-in-out infinite;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " @-webkit-keyframes rotate {\n" ); + fprintf( output, " 100%% { -webkit-transform: rotate(360deg); transform: rotate(360deg); }\n" ); + fprintf( output, " }\n" ); + fprintf( output, " @keyframes rotate {\n" ); + fprintf( output, " 100%% { -webkit-transform: rotate(360deg); transform: rotate(360deg); }\n" ); + fprintf( output, " }\n" ); + fprintf( output, " @-webkit-keyframes dash {\n" ); + fprintf( output, " 0%% { stroke-dasharray: 1, 150; stroke-dashoffset: 0; }\n" ); + fprintf( output, " 50%% { stroke-dasharray: 90, 150; stroke-dashoffset: -35; }\n" ); + fprintf( output, " 100%% { stroke-dasharray: 90, 150; stroke-dashoffset: -124; }\n" ); + fprintf( output, " }\n" ); + fprintf( output, " @keyframes dash {\n" ); + fprintf( output, " 0%% { stroke-dasharray: 1, 150; stroke-dashoffset: 0; }\n" ); + fprintf( output, " 50%% { stroke-dasharray: 90, 150; stroke-dashoffset: -35; }\n" ); + fprintf( output, " 100%% { stroke-dasharray: 90, 150; stroke-dashoffset: -124; }\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, "\n" ); + fprintf( output, " .node {\n" ); + fprintf( output, " cursor: pointer;\n" ); + fprintf( output, " }\n" ); + fprintf( output, " .node text {\n" ); + fprintf( output, " font: 14px 'Cousine', monospace;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " .tree-tooltip {\n" ); + fprintf( output, " position: absolute;\n" ); + fprintf( output, " text-align: left;\n" ); + fprintf( output, " padding: 16px 16px 8px;\n" ); + fprintf( output, " background-color: #fafafa;\n" ); + fprintf( output, " border: 1px solid #71ad93;\n" ); + fprintf( output, " border-radius: 8px;\n" ); + fprintf( output, " pointer-events: none;\n" ); + fprintf( output, " color: #343434;\n" ); + fprintf( output, " -webkit-box-shadow: 0 0 5px #aaa;\n" ); + fprintf( output, " box-shadow: 0 0 5px #aaa;\n" ); + fprintf( output, " }\n" ); + fprintf( output, " .tooltip-header {\n" ); + fprintf( output, " font: 14px Roboto, sans-serif;\n" ); + fprintf( output, " font-weight: bold;\n" ); + fprintf( output, " color: DarkRed;\n" ); + fprintf( output, "\n" ); + fprintf( output, " white-space: nowrap;\n" ); + fprintf( output, " text-align: left;\n" ); + fprintf( output, " }\n" ); + fprintf( output, " .tooltip-header-not-packaged {\n" ); + fprintf( output, " font: 11px Cousine,monospace;\n" ); + fprintf( output, " font-weight: bold;\n" ); + fprintf( output, " color: DarkRed;\n" ); + fprintf( output, "\n" ); + fprintf( output, " white-space: nowrap;\n" ); + fprintf( output, "\n" ); + fprintf( output, " padding-left: 8px;\n" ); + fprintf( output, " padding-right: 8px;\n" ); + fprintf( output, " padding-bottom: 8px;\n" ); + fprintf( output, " text-align: left;\n" ); + fprintf( output, " }\n" ); + fprintf( output, " .tooltip-description {\n" ); + fprintf( output, " font: 14px Roboto, sans-serif;\n" ); + fprintf( output, " font-style: italic;\n" ); + fprintf( output, " font-weight: bold;\n" ); + fprintf( output, " color: #343434;\n" ); + fprintf( output, "\n" ); + fprintf( output, " white-space: nowrap;\n" ); + fprintf( output, " text-align: left;\n" ); + fprintf( output, " padding-left: 1.5em;\n" ); + fprintf( output, " padding-top: .5em;\n" ); + fprintf( output, " font-style: italic;\n" ); + fprintf( output, " }\n" ); + fprintf( output, " .tooltip-content {\n" ); + fprintf( output, " font: 11px 'Cousine', monospace;\n" ); + fprintf( output, " font-weight: bold;\n" ); + fprintf( output, "\n" ); + fprintf( output, " white-space: pre;\n" ); + fprintf( output, " margin: 12px 0 8px;\n" ); + fprintf( output, " }\n" ); + fprintf( output, " .flavour {\n" ); + fprintf( output, " color: DarkBlue;\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, "\n" ); + fprintf( output, " @media (min-width: 1200px) {\n" ); + fprintf( output, " .navigator { width: 1140px; }\n" ); + fprintf( output, " .logo { width: 1140px; }\n" ); + fprintf( output, " .footer { width: 1140px; }\n" ); + fprintf( output, " .content { width: 1134px; }\n" ); + fprintf( output, " }\n" ); + fprintf( output, " @media (min-width: 992px) and (max-width: 1199px) {\n" ); + fprintf( output, " .navigator { width: 960px; }\n" ); + fprintf( output, " .logo { width: 960px; }\n" ); + fprintf( output, " .footer { width: 960px; }\n" ); + fprintf( output, " .content { width: 954px; }\n" ); + fprintf( output, " }\n" ); + fprintf( output, " @media (min-width: 768px) and (max-width: 991px) {\n" ); + fprintf( output, " .navigator { width: 720px; }\n" ); + fprintf( output, " .logo { width: 720px; }\n" ); + fprintf( output, " .footer { width: 720px; }\n" ); + fprintf( output, " .content { width: 714px; }\n" ); + fprintf( output, " }\n" ); + fprintf( output, " @media (min-width: 576px) and (max-width: 767px) {\n" ); + fprintf( output, " .navigator { width: 540px; }\n" ); + fprintf( output, " .logo { width: 540px; }\n" ); + fprintf( output, " .footer { width: 540px; }\n" ); + fprintf( output, " .content { width: 534px; }\n" ); + fprintf( output, "\n" ); + fprintf( output, " .node text { font-size: 12px; }\n" ); + fprintf( output, "\n" ); + fprintf( output, " .tooltip-header { font-size: 12px; }\n" ); + fprintf( output, " .tooltip-description { font-size: 12px; }\n" ); + fprintf( output, " .tooltip-content { font-size: 10px; }\n" ); + fprintf( output, " }\n" ); + fprintf( output, " @media (max-width: 575px) {\n" ); + fprintf( output, " .navigator { width: 480px; }\n" ); + fprintf( output, " .logo { width: 480px; }\n" ); + fprintf( output, " .footer { width: 480px; }\n" ); + fprintf( output, " .content { width: 474px; }\n" ); + fprintf( output, "\n" ); + fprintf( output, " .node text { font-size: 12px; }\n" ); + fprintf( output, "\n" ); + fprintf( output, " .tooltip-header { font-size: 12px; }\n" ); + fprintf( output, " .tooltip-description { font-size: 12px; }\n" ); + fprintf( output, " .tooltip-content { font-size: 10px; }\n" ); + fprintf( output, " }\n" ); + fprintf( output, " </style>\n" ); + fprintf( output, "\n" ); + fprintf( output, " <script src=\"https://code.jquery.com/jquery-3.4.1.min.js\"></script>\n" ); + fprintf( output, " <script src=\"https://code.jquery.com/ui/1.12.1/jquery-ui.min.js\"></script>\n" ); + fprintf( output, " <script src=\"https://d3js.org/d3.v3.min.js\"></script>\n" ); + fprintf( output, " <script>\n" ); + fprintf( output, " !function(o){function t(o,t){if(!(o.originalEvent.touches.length>1)){o.preventDefault();var e=o.originalEvent.changedTouches[0],n=document.createEvent(\"MouseEvents\");n.initMouseEvent(t,!0,!0,window,1,e.screenX,e.screenY,e.clientX,e.clientY,!1,!1,!1,!1,0,null),o.target.dispatchEvent(n)}}if(o.support.touch=\"ontouchend\"in document,o.support.touch){var e,n,u=o.ui.mouse.prototype,c=u._mouseInit,i=u._mouseDestroy;u._touchStart=function(o){var u=this;!n&&u._mouseCapture(o.originalEvent.changedTouches[0])&&(n=!0,u._touchMoved=!1,e=o,t(o,\"mouseover\"),t(o,\"mousemove\"),t(o,\"mousedown\"))},u._touchMove=function(o){if(n){var u=e.originalEvent.touches[0].screenX,c=e.originalEvent.touches[0].screenY,i=o.originalEvent.touches[0].screenX,r=o.originalEvent.touches[0].screenY;if(u===i&&c===r)return void(this._touchMoved=!1);this._touchMoved=!0,t(o,\"mousemove\")}},u._touchEnd=function(o){n&&(t(o,\"mouseup\"),t(o,\"mouseout\"),this._touchMoved||t(o,\"click\"),n=!1)},u._mouseInit=function(){var t=this;t.element.bind({touchstart:o.proxy(t,\"_touchStart\"),touchmove:o.proxy(t,\"_touchMove\"),touchend:o.proxy(t,\"_touchEnd\")}),c.call(t)},u._mouseDestroy=function(){var t=this;t.element.unbind({touchstart:o.proxy(t,\"_touchStart\"),touchmove:o.proxy(t,\"_touchMove\"),touchend:o.proxy(t,\"_touchEnd\")}),i.call(t)}}}(jQuery);\n" ); + fprintf( output, " $(function() {\n" ); + fprintf( output, " $( \"#tree_view\" ).draggable();\n" ); + fprintf( output, " });\n" ); + fprintf( output, " </script>\n" ); + fprintf( output, " <script>\n" ); + fprintf( output, " function loadJSON( callback ) {\n" ); + fprintf( output, " var xobj = new XMLHttpRequest();\n" ); + fprintf( output, " xobj.overrideMimeType(\"application/json\");\n" ); + fprintf( output, " xobj.open('GET', '%s', true);\n", json_pkgs_file ); + fprintf( output, " xobj.onreadystatechange = function () {\n" ); + fprintf( output, " if (xobj.readyState == 4 && xobj.status == \"200\") {\n" ); + fprintf( output, " callback(xobj.responseText);\n" ); + fprintf( output, " }\n" ); + fprintf( output, " };\n" ); + fprintf( output, " xobj.send(null);\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " var pkgs;\n" ); + fprintf( output, "\n" ); + fprintf( output, " $(document).ready(function() {\n" ); + fprintf( output, " loadJSON(function(response) {\n" ); + fprintf( output, " pkgs = JSON.parse(response);\n" ); + fprintf( output, " });\n" ); + fprintf( output, "\n" ); + fprintf( output, " $('#tree_view')\n" ); + fprintf( output, " .mousedown(function() { $(this).css( 'cursor', 'grab' ); })\n" ); + fprintf( output, " .mouseup( function() { $(this).css( 'cursor', 'auto' ); });\n" ); + fprintf( output, " });\n" ); + fprintf( output, " </script>\n" ); + fprintf( output, " </head>\n" ); + fprintf( output, " <body>\n" ); + fprintf( output, " <div id=\"front_wrapper\">\n" ); + fprintf( output, " <div class=\"header-wrapper\">\n" ); + fprintf( output, " <div class=\"logo\"></div>\n" ); + fprintf( output, " <div class=\"navigator\">\n" ); + fprintf( output, " <div style=\"height: 36px;\">\n" ); + fprintf( output, " <div class=\"date-title\">%04d-%02d-%02d <span class=\"time-title\">%02d:%02d:%02d</span></div>\n", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec ); + fprintf( output, " <div class=\"hardware-title\">\n" ); + fprintf( output, " <span class=\"hw-title\">HARDWARE:</span> %s\n", hardware ); + fprintf( output, " </div>\n" ); + fprintf( output, " </div>\n" ); + fprintf( output, " <div class=\"tree-title\">\n" ); + fprintf( output, " <span class=\"tree-hw-title\">%s</span> – Requires Tree\n", root ); + fprintf( output, " </div>\n" ); + fprintf( output, " </div> <!-- \"navigator\" -->\n" ); + fprintf( output, " </div> <!-- \"header_wrapper\" -->\n" ); + fprintf( output, "\n" ); + fprintf( output, " <div class=\"content-wrapper\">\n" ); + fprintf( output, " <div class=\"content\">\n" ); + fprintf( output, " <div id=\"spinner\">\n" ); + fprintf( output, " <svg class=\"spinner\" viewBox=\"0 0 50 50\"><circle class=\"path\" cx=\"25\" cy=\"25\" r=\"20\" fill=\"none\" stroke-width=\"5\"></circle></svg>\n" ); + fprintf( output, " <div class=\"spinner-text\">Loading ...</div>\n" ); + fprintf( output, " </div>\n" ); + fprintf( output, " <div id=\"tree_view\" class=\"ui-widget-content\">\n" ); + fprintf( output, " </div>\n" ); + fprintf( output, " </div> <!-- \"content\" -->\n" ); + fprintf( output, " </div> <!-- \"content_wrapper\" -->\n" ); + fprintf( output, "\n" ); + fprintf( output, " <div class=\"footer-wrapper\">\n" ); + fprintf( output, " <div class=\"footer\">\n" ); + fprintf( output, " <div class=\"footer-top\">\n" ); + fprintf( output, " <a class=\"copyright\" target=\"_blank\" href=\"%s\">© %s</a>\n", bug_url, copying ); + fprintf( output, " </div>\n" ); + fprintf( output, " <div class=\"footer-bottom\">\n" ); + fprintf( output, " </div>\n" ); + fprintf( output, " </div> <!-- \"footer\" -->\n" ); + fprintf( output, " </div> <!-- \"footer_wrapper\" -->\n" ); + fprintf( output, " </div> <!-- \"front_wrapper\" -->\n" ); + fprintf( output, "\n" ); + fprintf( output, " <script>\n" ); + fprintf( output, " var margin = {top: 20, right: 120, bottom: 20, left: 220},\n" ); + fprintf( output, " width = %d - margin.right - margin.left,\n", svg_width ); + fprintf( output, " height = %d - margin.top - margin.bottom;\n", svg_height ); + fprintf( output, "\n" ); + fprintf( output, " var i = 0,\n" ); + fprintf( output, " duration = 750,\n" ); + fprintf( output, " root = 0;\n" ); + fprintf( output, "\n" ); + fprintf( output, " var tree = d3.layout.tree()\n" ); + fprintf( output, " .size([height, width]);\n" ); + fprintf( output, "\n" ); + fprintf( output, " var diagonal = d3.svg.diagonal()\n" ); + fprintf( output, " .projection(function(d) { return [d.y, d.x]; });\n" ); + fprintf( output, "\n" ); + fprintf( output, " var svg = d3.select(document.getElementById( 'tree_view' )).append(\"svg\")\n" ); + fprintf( output, " .attr(\"width\", width + margin.right + margin.left)\n" ); + fprintf( output, " .attr(\"height\", height + margin.top + margin.bottom)\n" ); + fprintf( output, " .append(\"g\")\n" ); + fprintf( output, " .attr(\"transform\", \"translate(\" + margin.left + \",\" + margin.top + \")\");\n" ); + fprintf( output, "\n" ); + fprintf( output, " var div = d3.select(document.getElementById( 'front_wrapper' )).append(\"div\")\n" ); + fprintf( output, " .attr(\"class\", \"tree-tooltip\")\n" ); + fprintf( output, " .style(\"display\", \"none\")\n" ); + fprintf( output, " .style(\"opacity\", 0);\n" ); + fprintf( output, "\n" ); + fprintf( output, " d3.json(\"%s\", function(error, requires) {\n", json_tree_file ); + fprintf( output, " root = requires;\n" ); + fprintf( output, " root.x0 = height / 2;\n" ); + fprintf( output, " root.y0 = 0;\n" ); + fprintf( output, "\n" ); + fprintf( output, " function collapse(d) {\n" ); + fprintf( output, " if( d.children ) {\n" ); + fprintf( output, " d._children = d.children;\n" ); + fprintf( output, " d._children.forEach(collapse);\n" ); + fprintf( output, " d.children = null;\n" ); + fprintf( output, " }\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " document.getElementById('spinner').remove();\n" ); + fprintf( output, " root.children.forEach(collapse);\n" ); + fprintf( output, " update(root);\n" ); + fprintf( output, " });\n" ); + fprintf( output, "\n" ); + fprintf( output, " function update(source) {\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* Compute the new tree layout. */\n" ); + fprintf( output, " var nodes = tree.nodes(root).reverse(),\n" ); + fprintf( output, " links = tree.links(nodes);\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* Normalize for fixed-depth. */\n" ); + fprintf( output, " nodes.forEach(function(d) { d.y = d.depth * 220; });\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* Update the nodes . . . */\n" ); + fprintf( output, " var node = svg.selectAll(\"g.node\")\n" ); + fprintf( output, " .data(nodes, function(d) { return d.id || (d.id = ++i); });\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* Enter any new nodes at the parent's previous position. */\n" ); + fprintf( output, " var nodeEnter = node.enter().append(\"g\")\n" ); + fprintf( output, " .attr(\"class\", \"node\")\n" ); + fprintf( output, " .attr(\"transform\", function(d) { return \"translate(\" + source.y0 + \",\" + source.x0 + \")\"; })\n" ); + fprintf( output, " .on(\"click\", click)\n" ); + fprintf( output, " .on(\"mouseover\", function(d) {\n" ); + fprintf( output, " div.transition()\n" ); + fprintf( output, " .duration(200)\n" ); + fprintf( output, " .style(\"opacity\", .92);\n" ); + fprintf( output, " {\n" ); + fprintf( output, " var content = '<div class=\"tooltip-header-not-packaged\">' + 'void' + '</div>';\n" ); + fprintf( output, "\n" ); + fprintf( output, " if( d.name === \"void\" ) {\n" ); + fprintf( output, " /* draw div.tree-tooltip to get actual size */\n" ); + fprintf( output, " div.html( content )\n" ); + fprintf( output, " .style(\"left\", (d3.event.pageX + document.getElementById( 'front_wrapper' ).scrollLeft + 12) + \"px\")\n" ); + fprintf( output, " .style(\"top\", (d3.event.pageY + document.getElementById( 'front_wrapper' ).scrollTop + 12) + \"px\");\n" ); + fprintf( output, " }\n" ); + fprintf( output, " else\n" ); + fprintf( output, " {\n" ); + fprintf( output, " /* find package in the pkgs array: */\n" ); + fprintf( output, " var pkg = pkgs.find(obj => { return obj.id === d.name; });\n" ); + fprintf( output, "\n" ); + fprintf( output, " if( pkg === undefined )\n" ); + fprintf( output, " {\n" ); + fprintf( output, " content = '<div class=\"tooltip-header-not-packaged\">' + 'not packaged collection' + '</div>';\n" ); + fprintf( output, " /* draw div.tree-tooltip to get actual size */\n" ); + fprintf( output, " div.html( content )\n" ); + fprintf( output, " .style(\"left\", (d3.event.pageX + document.getElementById( 'front_wrapper' ).scrollLeft + 12) + \"px\")\n" ); + fprintf( output, " .style(\"top\", (d3.event.pageY + document.getElementById( 'front_wrapper' ).scrollTop + 12) + \"px\");\n" ); + fprintf( output, " }\n" ); + fprintf( output, " else\n" ); + fprintf( output, " {\n" ); + fprintf( output, " content = '<div class=\"tooltip-header\">' + pkg.name + '</div>' +\n" ); + fprintf( output, " '<div class=\"tooltip-description\">' + pkg.description + '</div>' +\n" ); + fprintf( output, " '<div class=\"tooltip-content\">' +\n" ); + fprintf( output, " ' group: ' + pkg.group + '\\n' +\n" ); + fprintf( output, " ' architecture: ' + pkg.arch + '\\n' +\n" ); + fprintf( output, " ' hardware: ' + pkg.hardware + '\\n';\n" ); + fprintf( output, " if( pkg.flavour !== undefined )\n" ); + fprintf( output, " {\n" ); + fprintf( output, " content += ' <span class=\"flavour\">edition</span>: ' + pkg.flavour + '\\n';\n" ); + fprintf( output, " }\n" ); + fprintf( output, " content += ' license: ' + pkg.license + '\\n' +\n" ); + fprintf( output, " ' bug report url: ' + root.distro[2] + '\\n' +\n" ); + fprintf( output, " ' distribution: ' + root.distro[0] + '-' + root.distro[1] + '\\n' +\n" ); + fprintf( output, " ' package tarball: ' + pkg.name + '-' + pkg.version + '-' + pkg.arch + '-' + root.distro[0] + '-' + root.distro[1] + '.'+ '%s' + '\\n' +\n", tarball_suffix ); + fprintf( output, " ' uncompressed size: ' + pkg.uncompressed_size + '\\n' +\n" ); + fprintf( output, " ' number of files: ' + pkg.total_files + '\\n' +\n" ); + fprintf( output, " '</div>' +\n" ); + fprintf( output, " '</div>' +\n" ); + fprintf( output, " '</div>';\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* draw div.tree-tooltip to get actual size */\n" ); + fprintf( output, " div.html( content )\n" ); + fprintf( output, " .style(\"left\", (d3.event.pageX + document.getElementById( 'front_wrapper' ).scrollLeft + 12) + \"px\")\n" ); + fprintf( output, " .style(\"top\", (d3.event.pageY + document.getElementById( 'front_wrapper' ).scrollTop + 12) + \"px\");\n" ); + fprintf( output, " }\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* draw div.tree-tooltip at actual position */\n" ); + fprintf( output, "\n" ); + fprintf( output, " var cW = $( window ).width();\n" ); + fprintf( output, " var cH = $( window ).height();\n" ); + fprintf( output, " var cX = d3.event.pageX;\n" ); + fprintf( output, " var cY = d3.event.pageY;\n" ); + fprintf( output, " var tW = $('div.tree-tooltip').width();\n" ); + fprintf( output, " var tH = $('div.tree-tooltip').height();\n" ); + fprintf( output, " var oX;\n" ); + fprintf( output, " var oY;\n" ); + fprintf( output, " var dX = ( cW - cX ) - ( tW + 12 );\n" ); + fprintf( output, " var dY = ( cH - cY ) - ( tH + 12 );\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* shift left to according to width=16 of browser vertical scroll bar */\n" ); + fprintf( output, " if( dX <= 24 ) { dX = 24 - dX; }\n" ); + fprintf( output, " else { dX = 0; }\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* shift top to according to width=16 of browser horizontal scroll bar */\n" ); + fprintf( output, " if( dY <= 24 ) { dY = 24 - dY; }\n" ); + fprintf( output, " else { dY = 0; }\n" ); + fprintf( output, "\n" ); + fprintf( output, " if( ( cW - cX ) < ( tW + 12 ) ) { oX = - 12 - tW; } else { oX = 12 - dX; }\n" ); + fprintf( output, " if( ( cH - cY ) < ( tH + 12 ) ) { oY = - 12 - tH; } else { oY = 12 - dY; }\n" ); + fprintf( output, "\n" ); + fprintf( output, " if( (( cW - cX ) < ( tW + 12 )) && (cX < ( tW + 12 )) )\n" ); + fprintf( output, " {\n" ); + fprintf( output, " /* in this case we have to center tooltip */\n" ); + fprintf( output, " oX = - (tW + 12) / 2 + (cW/2 - cX);\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " div.html( content )\n" ); + fprintf( output, " .style(\"left\", (d3.event.pageX + document.getElementById( 'front_wrapper' ).scrollLeft + oX) + \"px\")\n" ); + fprintf( output, " .style(\"top\", (d3.event.pageY + document.getElementById( 'front_wrapper' ).scrollTop + oY) + \"px\")\n" ); + fprintf( output, " .style(\"display\",\"block\");\n" ); + fprintf( output, " }\n" ); + fprintf( output, " })\n" ); + fprintf( output, " .on(\"mouseout\", function(d) {\n" ); + fprintf( output, " div.transition()\n" ); + fprintf( output, " .duration(500)\n" ); + fprintf( output, " .style(\"opacity\", 0);\n" ); + fprintf( output, " });\n" ); + fprintf( output, "\n" ); + fprintf( output, " nodeEnter.append(\"circle\")\n" ); + fprintf( output, " /* Additional attributes (see the 'style' section) */\n" ); + fprintf( output, " .attr(\"stroke\", \"#5d5d5d\")\n" ); + fprintf( output, " .attr(\"stroke-width\", \"1.0\")\n" ); + fprintf( output, " /* End of additional attributes */\n" ); + fprintf( output, " .attr(\"r\", 1e-6)\n" ); + fprintf( output, " .style(\"fill\", function(d) { return d._children ? \"#abd8d4\" : \"#fff\"; });\n" ); + fprintf( output, "\n" ); + fprintf( output, " nodeEnter.append(\"text\")\n" ); + fprintf( output, " .attr(\"x\", function(d) { return d.children || d._children ? -10 : 10; })\n" ); + fprintf( output, " .attr(\"dy\", \"-.35em\")\n" ); + fprintf( output, " .attr(\"text-anchor\", function(d) { return d.children || d._children ? \"end\" : \"start\"; })\n" ); + fprintf( output, " .text(function(d) { return (d.name.indexOf(\":\",0) > 0 ) ? d.name.substr(d.name.indexOf(\":\",0) + 1) : d.name; })\n" ); + fprintf( output, " .style(\"fill-opacity\", 1e-6);\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* Transition nodes to their new position. */\n" ); + fprintf( output, " var nodeUpdate = node.transition()\n" ); + fprintf( output, " .duration(duration)\n" ); + fprintf( output, " .attr(\"transform\", function(d) { return \"translate(\" + d.y + \",\" + d.x + \")\"; });\n" ); + fprintf( output, "\n" ); + fprintf( output, " nodeUpdate.select(\"circle\")\n" ); + fprintf( output, " .attr(\"r\", 4.5)\n" ); + fprintf( output, " .style(\"fill\", function(d) {\n" ); + fprintf( output, " if( d._children )\n" ); + fprintf( output, " {\n" ); + fprintf( output, " return \"#abd8d4\";\n" ); + fprintf( output, " }\n" ); + fprintf( output, " else\n" ); + fprintf( output, " {\n" ); + fprintf( output, " if( d.children == undefined )\n" ); + fprintf( output, " {\n" ); + fprintf( output, " if( d.name == \"void\" )\n" ); + fprintf( output, " {\n" ); + fprintf( output, " return \"#c9c9c9\";\n" ); + fprintf( output, " }\n" ); + fprintf( output, " else\n" ); + fprintf( output, " {\n" ); + fprintf( output, " return \"#fff\";\n" ); + fprintf( output, " }\n" ); + fprintf( output, " }\n" ); + fprintf( output, " else\n" ); + fprintf( output, " {\n" ); + fprintf( output, " return \"#d2ebd8\";\n" ); + fprintf( output, " }\n" ); + fprintf( output, " }\n" ); + fprintf( output, " });\n" ); + fprintf( output, "\n" ); + fprintf( output, " nodeUpdate.select(\"text\")\n" ); + fprintf( output, " .style(\"fill-opacity\", 1);\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* Transition exiting nodes to the parent's new position. */\n" ); + fprintf( output, " var nodeExit = node.exit().transition()\n" ); + fprintf( output, " .duration(duration)\n" ); + fprintf( output, " .attr(\"transform\", function(d) { return \"translate(\" + source.y + \",\" + source.x + \")\"; })\n" ); + fprintf( output, " .remove();\n" ); + fprintf( output, "\n" ); + fprintf( output, " nodeExit.select(\"circle\")\n" ); + fprintf( output, " .attr(\"r\", 1e-6);\n" ); + fprintf( output, "\n" ); + fprintf( output, " nodeExit.select(\"text\")\n" ); + fprintf( output, " .style(\"fill-opacity\", 1e-6);\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* Update the links . . . */\n" ); + fprintf( output, " var link = svg.selectAll(\"path.link\")\n" ); + fprintf( output, " .data(links, function(d) { return d.target.id; });\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* Enter any new links at the parent's previous position. */\n" ); + fprintf( output, " link.enter().insert('path', 'g')\n" ); + fprintf( output, " .attr(\"class\", \"link\")\n" ); + fprintf( output, " .attr(\"d\", function(d) {\n" ); + fprintf( output, " var o = {x: source.x0, y: source.y0};\n" ); + fprintf( output, " return diagonal({source: o, target: o});\n" ); + fprintf( output, " });\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* Transition links to their new position. */\n" ); + fprintf( output, " link.transition()\n" ); + fprintf( output, " .duration(duration)\n" ); + fprintf( output, " /* Additional attributes (see the 'style' section) */\n" ); + fprintf( output, " .style(\"fill\", \"none\")\n" ); + fprintf( output, " .attr(\"stroke\", \"DarkGray\")\n" ); + fprintf( output, " .attr(\"stroke-width\", \"1.5\")\n" ); + fprintf( output, " /* End of additional attributes */\n" ); + fprintf( output, " .attr(\"d\", diagonal);\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* Transition exiting nodes to the parent's new position. */\n" ); + fprintf( output, " link.exit().transition()\n" ); + fprintf( output, " .duration(duration)\n" ); + fprintf( output, " .attr(\"d\", function(d) {\n" ); + fprintf( output, " var o = {x: source.x, y: source.y};\n" ); + fprintf( output, " return diagonal({source: o, target: o});\n" ); + fprintf( output, " })\n" ); + fprintf( output, " .remove();\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* Stash the old positions for transition. */\n" ); + fprintf( output, " nodes.forEach(function(d) {\n" ); + fprintf( output, " d.x0 = d.x;\n" ); + fprintf( output, " d.y0 = d.y;\n" ); + fprintf( output, " });\n" ); + fprintf( output, " }\n" ); + fprintf( output, "\n" ); + fprintf( output, " /* Toggle children on click. */\n" ); + fprintf( output, " function click(d) {\n" ); + fprintf( output, " if (d.children) {\n" ); + fprintf( output, " d._children = d.children;\n" ); + fprintf( output, " d.children = null;\n" ); + fprintf( output, " } else {\n" ); + fprintf( output, " d.children = d._children;\n" ); + fprintf( output, " d._children = null;\n" ); + fprintf( output, " }\n" ); + fprintf( output, " update(d);\n" ); + fprintf( output, " }\n" ); + fprintf( output, " </script>\n" ); + fprintf( output, " </body>\n" ); + fprintf( output, "</html>\n" ); +} diff --git a/src/pkglog.c b/src/pkglog.c new file mode 100644 index 0000000..12ab381 --- /dev/null +++ b/src/pkglog.c @@ -0,0 +1,1316 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <sys/types.h> +#include <stdint.h> +#include <dirent.h> +#include <sys/stat.h> /* chmod(2) */ +#include <fcntl.h> +#include <linux/limits.h> +#include <alloca.h> /* alloca(3) */ +#include <string.h> /* strdup(3) */ +#include <libgen.h> /* basename(3) */ +#include <ctype.h> /* tolower(3) */ +#include <errno.h> +#include <time.h> +#include <sys/time.h> +#include <pwd.h> +#include <grp.h> +#include <stdarg.h> +#include <unistd.h> + +#include <sys/resource.h> + +#include <signal.h> +#if !defined SIGCHLD && defined SIGCLD +# define SIGCHLD SIGCLD +#endif + +#define _GNU_SOURCE +#include <getopt.h> + +#include <msglog.h> +#include <wrapper.h> +#include <system.h> + +#define PROGRAM_NAME "pkglog" + +#include <defs.h> + + +char *program = PROGRAM_NAME; +char *destination = NULL, *srcdir = NULL, *pkginfo_fname = NULL, *output_fname = NULL; +int exit_status = EXIT_SUCCESS; /* errors counter */ +char *selfdir = NULL; + +int mkgroupdir = 0; + +int rm_srcdir_at_exit = 0; + +char *pkgname = NULL, + *pkgver = NULL, + *arch = NULL, + *distroname = NULL, + *distrover = NULL, + *group = NULL, + *url = NULL, + *license = NULL, + *uncompressed_size = NULL, + *total_files = NULL; + +FILE *pkginfo = NULL; +FILE *output = NULL; + + +#define FREE_PKGLOG_VARIABLES() \ + if( pkgname ) { free( pkgname ); } pkgname = NULL; \ + if( pkgver ) { free( pkgver ); } pkgver = NULL; \ + if( arch ) { free( arch ); } arch = NULL; \ + if( distroname ) { free( distroname ); } distroname = NULL; \ + if( distrover ) { free( distrover ); } distrover = NULL; \ + if( group ) { free( group ); } group = NULL; \ + if( url ) { free( url ); } url = NULL; \ + if( license ) { free( license ); } license = NULL; \ + if( uncompressed_size ) { free( uncompressed_size ); } uncompressed_size = NULL; \ + if( total_files ) { free( total_files ); } total_files = NULL + +void free_resources() +{ + if( selfdir ) { free( selfdir ); selfdir = NULL; } + if( srcdir ) { free( srcdir ); srcdir = NULL; } + if( destination ) { free( destination ); destination = NULL; } + if( pkginfo_fname ) { free( pkginfo_fname ); pkginfo_fname = NULL; } + if( output_fname ) + { + if( output ) { (void)fflush( output ); fclose( output ); output = NULL; } + free( output_fname ); output_fname = NULL; + } + + FREE_PKGLOG_VARIABLES(); +} + +void usage() +{ + free_resources(); + + fprintf( stdout, "\n" ); + fprintf( stdout, "Usage: %s [options] <dir|pkginfo|package>\n", program ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Read information from package's service files and create PKGLOG file\n" ); + fprintf( stdout, "in the destination directory. <pkginfo> is a full name of '.PKGINFO'\n" ); + fprintf( stdout, "service file of package. Rest of package's serfice files should be\n" ); + fprintf( stdout, "present in the directory of '.PKGINFO'. If last argument is <dir>\n" ); + fprintf( stdout, "then pkglog utility will try to read '.PKGINFO' and rest of service\n" ); + fprintf( stdout, "files from directory given by <dir> argument.\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Options:\n" ); + fprintf( stdout, " -h,--help Display this information.\n" ); + fprintf( stdout, " -v,--version Display the version of %s utility.\n", program ); + fprintf( stdout, " -d,--destination=<DIR> Target directory to save output PKGLOG.\n" ); + fprintf( stdout, " -m,--mkgroupdir Create group subdirectory in the PKGLOG\n" ); + fprintf( stdout, " target directory.\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Parameter:\n" ); + fprintf( stdout, " <dir|pkginfo|package> Directory wich contains the package's\n" ); + fprintf( stdout, " service files or path to .PKGINFO file\n" ); + fprintf( stdout, " or package tarball.\n" ); + fprintf( stdout, "\n" ); + + exit( EXIT_FAILURE ); +} + +void to_lowercase( char *s ) +{ + char *p = s; + while( p && *p ) { int c = *p; *p = tolower( c ); ++p; } +} + +void to_uppercase( char *s ) +{ + char *p = s; + while( p && *p ) { int c = *p; *p = toupper( c ); ++p; } +} + +void version() +{ + char *upper = NULL; + + upper = (char *)alloca( strlen( program ) + 1 ); + + strcpy( (char *)upper, (const char *)program ); + to_uppercase( upper ); + + fprintf( stdout, "%s (%s) %s\n", program, upper, PROGRAM_VERSION ); + + fprintf( stdout, "Copyright (C) 2019 Andrey V.Kosteltsev.\n" ); + fprintf( stdout, "This is free software. There is NO warranty; not even\n" ); + fprintf( stdout, "for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" ); + fprintf( stdout, "\n" ); + + free_resources(); + exit( EXIT_SUCCESS ); +} + + +static void remove_trailing_slash( char *dir ) +{ + char *s; + + if( !dir || dir[0] == '\0' ) return; + + s = dir + strlen( dir ) - 1; + while( *s == '/' ) + { + *s = '\0'; --s; + } +} + + +static int _mkdir_p( const char *dir, const mode_t mode ) +{ + char *buf; + char *p = NULL; + struct stat sb; + + if( !dir ) return -1; + + buf = (char *)alloca( strlen( dir ) + 1 ); + strcpy( buf, dir ); + + remove_trailing_slash( buf ); + + /* check if path exists and is a directory */ + if( stat( buf, &sb ) == 0 ) + { + if( S_ISDIR(sb.st_mode) ) + { + return 0; + } + } + + /* mkdir -p */ + for( p = buf + 1; *p; ++p ) + { + if( *p == '/' ) + { + *p = 0; + /* test path */ + if( stat( buf, &sb ) != 0 ) + { + /* path does not exist - create directory */ + if( mkdir( buf, mode ) < 0 ) + { + return -1; + } + } else if( !S_ISDIR(sb.st_mode) ) + { + /* not a directory */ + return -1; + } + *p = '/'; + } + } + + /* test path */ + if( stat( buf, &sb ) != 0 ) + { + /* path does not exist - create directory */ + if( mkdir( buf, mode ) < 0 ) + { + return -1; + } + } else if( !S_ISDIR(sb.st_mode) ) + { + /* not a directory */ + return -1; + } + + return 0; +} + + +static void _rm_tmpdir( const char *dirpath ) +{ + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + if( stat( dirpath, &path_sb ) == -1 ) + { + return; /* stat returns error code; errno is set */ + } + + if( S_ISDIR(path_sb.st_mode) == 0 ) + { + return; /* dirpath is not a directory */ + } + + if( (dir = opendir(dirpath) ) == NULL ) + { + return; /* Cannot open direcroty; errno is set */ + } + + len = strlen( dirpath ); + + while( (entry = readdir( dir )) != NULL) + { + + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( stat( path, &entry_sb ) == 0 ) + { + if( S_ISDIR(entry_sb.st_mode) ) + { + /* recursively remove a nested directory */ + _rm_tmpdir( path ); + } + else + { + /* remove a file object */ + (void)unlink( path ); + } + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + + } + + /* remove the devastated directory and close the object of this directory */ + (void)rmdir( dirpath ); + + closedir( dir ); +} + + +static char *_mk_tmpdir( void ) +{ + char *buf = NULL, *p, *tmp = "/tmp"; + size_t len = 0, size = 0; + + (void)umask( S_IWGRP | S_IWOTH ); /* octal 022 */ + + /* Get preferred directory for tmp files */ + if( (p = getenv( "TMP" )) != NULL ) { + tmp = p; + } + else if( (p = getenv( "TEMP" )) != NULL ) { + tmp = p; + } + + size = strlen( tmp ) + strlen( DISTRO_NAME ) + strlen( program ) + 12; + + buf = (char *)malloc( size ); + if( !buf ) return NULL; + + len = snprintf( buf, size, (const char *)"%s/%s/%s-%.7u", tmp, DISTRO_NAME, program, getpid() ); + if( len == 0 || len == size - 1 ) + { + free( buf ); return NULL; + } + + _rm_tmpdir( (const char *)&buf[0] ); + + if( _mkdir_p( buf, S_IRWXU | S_IRWXG | S_IRWXO ) == 0 ) + { + return buf; + } + + free( buf ); return NULL; +} + + +void fatal_error_actions( void ) +{ + logmsg( errlog, MSG_NOTICE, "Free resources on FATAL error..." ); + if( rm_srcdir_at_exit ) _rm_tmpdir( (const char *)srcdir ); + if( output_fname ) + { + if( output ) { (void)fflush( output ); fclose( output ); output = NULL; } + (void)unlink( output_fname ); + free( output_fname ); output_fname = NULL; + } + free_resources(); +} + +void sigint( int signum ) +{ + (void)signum; + + if( rm_srcdir_at_exit ) _rm_tmpdir( (const char *)srcdir ); + if( output_fname ) + { + if( output ) { (void)fflush( output ); fclose( output ); output = NULL; } + (void)unlink( output_fname ); + free( output_fname ); output_fname = NULL; + } + free_resources(); +} + +static void set_signal_handlers() +{ + struct sigaction sa; + sigset_t set; + + memset( &sa, 0, sizeof( sa ) ); + sa.sa_handler = sigint; /* TERM, INT */ + sa.sa_flags = SA_RESTART; + sigemptyset( &set ); + sigaddset( &set, SIGTERM ); + sigaddset( &set, SIGINT ); + sa.sa_mask = set; + sigaction( SIGTERM, &sa, NULL ); + sigaction( SIGINT, &sa, NULL ); + + memset( &sa, 0, sizeof( sa ) ); /* ignore SIGPIPE */ + sa.sa_handler = SIG_IGN; + sa.sa_flags = 0; + sigaction( SIGPIPE, &sa, NULL ); + + /* System V fork+wait does not work if SIGCHLD is ignored */ + signal( SIGCHLD, SIG_DFL ); +} + +enum _pkginfo_type +{ + PKGINFO_TEXT = 0, + PKGINFO_GZ, + PKGINFO_BZ2, + PKGINFO_XZ, + PKGINFO_TAR, + + PKGINFO_UNKNOWN +}; + +static enum _pkginfo_type pkginfo_type = PKGINFO_UNKNOWN; +static char uncompress[2] = { 0, 0 }; + + +static enum _pkginfo_type check_pkginfo_file( const char *fname ) +{ + struct stat st; + size_t pkginfo_size = 0; + unsigned char buf[8]; + int rc, fd; + + /* SIGNATURES: https://www.garykessler.net/library/file_sigs.html */ + + uncompress[0] = '\0'; + + if( stat( fname, &st ) == -1 ) + { + FATAL_ERROR( "Cannot access %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + + pkginfo_size = st.st_size; + + if( (fd = open( fname, O_RDONLY )) == -1 ) + { + FATAL_ERROR( "Cannot open %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + + rc = (int)read( fd, (void *)&buf[0], 7 ); + if( rc != 7 ) + { + FATAL_ERROR( "Unknown type of input file %s", basename( (char *)fname ) ); + } + buf[7] = '\0'; + + /* TEXT */ + if( !strncmp( (const char *)&buf[0], "pkgname", 7 ) ) + { + close( fd ); return PKGINFO_TEXT; + } + + /* GZ */ + if( buf[0] == 0x1F && buf[1] == 0x8B && buf[2] == 0x08 ) + { + uncompress[0] = 'x'; + close( fd ); return PKGINFO_GZ; + } + + /* BZ2 */ + if( buf[0] == 0x42 && buf[1] == 0x5A && buf[2] == 0x68 ) + { + uncompress[0] = 'j'; + close( fd ); return PKGINFO_BZ2; + } + + /* XZ */ + if( buf[0] == 0xFD && buf[1] == 0x37 && buf[2] == 0x7A && + buf[3] == 0x58 && buf[4] == 0x5A && buf[5] == 0x00 ) + { + uncompress[0] = 'J'; + close( fd ); return PKGINFO_XZ; + } + + if( pkginfo_size > 262 ) + { + if( lseek( fd, 257, SEEK_SET ) == -1 ) + { + FATAL_ERROR( "Cannot check signature of %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + rc = (int)read( fd, &buf[0], 5 ); + if( rc != 5 ) + { + FATAL_ERROR( "Cannot read signature of %s file", basename( (char *)fname ) ); + } + /* TAR */ + if( buf[0] == 0x75 && buf[1] == 0x73 && buf[2] == 0x74 && buf[3] == 0x61 && buf[4] == 0x72 ) + { + close( fd ); return PKGINFO_TAR; + } + } + + close( fd ); return PKGINFO_UNKNOWN; +} + + +void get_args( int argc, char *argv[] ) +{ + const char* short_options = "hvmd:"; + + const struct option long_options[] = + { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'v' }, + { "destination", required_argument, NULL, 'd' }, + { "mkgroupdir", no_argument, NULL, 'm' }, + { NULL, 0, NULL, 0 } + }; + + int ret; + int option_index = 0; + + while( (ret = getopt_long( argc, argv, short_options, long_options, &option_index )) != -1 ) + { + switch( ret ) + { + case 'h': + { + usage(); + break; + } + case 'v': + { + version(); + break; + } + + case 'd': + { + if( optarg != NULL ) + { + destination = xstrdup( (const char *)optarg ); + remove_trailing_slash( destination ); + } + else + /* option is present but without value */ + usage(); + break; + } + case 'm': + { + mkgroupdir = 1; + break; + } + + case '?': default: + { + usage(); + break; + } + } + } + + if( destination == NULL ) + { + char cwd[PATH_MAX]; + if( getcwd( cwd, sizeof(cwd) ) != NULL ) + destination = xstrdup( (const char *)cwd ); + else + destination = xstrdup( "." ); + } + + /* last command line argument is the PKGLOG file */ + if( optind < argc ) + { + struct stat st; + char *buf = NULL; + + buf = (char *)malloc( strlen( (const char *)argv[optind] ) + 10 ); + if( !buf ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + (void)strcpy( buf, (const char *)argv[optind++] ); + remove_trailing_slash( (char *)&buf[0] ); + + if( stat( (const char *)&buf[0], &st ) == -1 ) + { + FATAL_ERROR( "Cannot access '%s' file: %s", basename( buf ), strerror( errno ) ); + } + + if( S_ISDIR(st.st_mode) ) + { + /* Add .PKGINFO to the input dir name: */ + (void)strcat( buf, "/.PKGINFO" ); + } + + pkginfo_fname = xstrdup( (const char *)&buf[0] ); + if( pkginfo_fname == NULL ) + { + usage(); + } + + free( buf ); + + pkginfo_type = check_pkginfo_file( (const char *)pkginfo_fname ); + if( pkginfo_type == PKGINFO_UNKNOWN ) + { + ERROR( "%s: Unknown input file format", basename( pkginfo_fname ) ); + usage(); + } + } + else + { + usage(); + } +} + + +/* + Especialy for pkginfo lines. + Remove leading spaces and take non-space characters only: + */ +static char *skip_spaces( char *s ) +{ + char *q, *p = (char *)0; + + if( !s || *s == '\0' ) return p; + + p = s; + + while( (*p == ' ' || *p == '\t') && *p != '\0' ) { ++p; } q = p; + while( *q != ' ' && *q != '\t' && *q != '\0' ) { ++q; } *q = '\0'; + + if( *p == '\0' ) return (char *)0; + + return( xstrdup( (const char *)p ) ); +} + +/* + remove spaces at end of line: + */ +static void skip_eol_spaces( char *s ) +{ + char *p = (char *)0; + + if( !s || *s == '\0' ) return; + + p = s + strlen( s ) - 1; + while( isspace( *p ) ) { *p-- = '\0'; } +} + + +void write_pkginfo( void ) +{ + char *ln = NULL; + char *line = NULL; + + if( pkginfo_fname != NULL ) + { + pkginfo = fopen( (const char *)pkginfo_fname, "r" ); + if( !pkginfo ) + { + FATAL_ERROR( "Cannot open %s file", pkginfo_fname ); + } + } + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + while( (ln = fgets( line, PATH_MAX, pkginfo )) ) + { + char *match = NULL; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( (match = strstr( ln, "pkgname" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) pkgname = skip_spaces( p ); + } + if( (match = strstr( ln, "pkgver" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) pkgver = skip_spaces( p ); + } + if( (match = strstr( ln, "arch" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) arch = skip_spaces( p ); + } + if( (match = strstr( ln, "distroname" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) distroname = skip_spaces( p ); + } + if( (match = strstr( ln, "distrover" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) distrover = skip_spaces( p ); + } + + + if( (match = strstr( ln, "group" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) group = skip_spaces( p ); + } + /* variable short_description="..." is not stored in the PKGLOG file */ + if( (match = strstr( ln, "url" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) url = skip_spaces( p ); + } + if( (match = strstr( ln, "license" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) license = skip_spaces( p ); + } + if( (match = strstr( ln, "uncompressed_size" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) uncompressed_size = skip_spaces( p ); + } + if( (match = strstr( ln, "total_files" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) total_files = skip_spaces( p ); + } + } + + free( line ); + + if( pkgname && pkgver && arch && distroname && distrover ) + { + int len; + char *buf = NULL; + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + if( mkgroupdir && group ) + { + len = snprintf( buf, PATH_MAX, "%s/%s", + destination, group ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot create output file" ); + } + + if( _mkdir_p( buf, S_IRWXU | S_IRWXG | S_IRWXO ) != 0 ) + { + FATAL_ERROR( "Cannot create output directory" ); + } + + len = snprintf( buf, PATH_MAX, "%s/%s/%s-%s-%s-%s-%s", + destination, group, pkgname, pkgver, arch, distroname, distrover ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot create output file" ); + } + } + else + { + len = snprintf( buf, PATH_MAX, "%s/%s-%s-%s-%s-%s", + destination, pkgname, pkgver, arch, distroname, distrover ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot create output file" ); + } + } + output_fname = xstrdup( (const char *)&buf[0] ); + if( output_fname ) + { + output = fopen( (const char *)output_fname, "w" ); + if( !output ) + { + FATAL_ERROR( "Cannot create %s file", output_fname ); + } + } + + free( buf ); + + fprintf( output, "PACKAGE NAME: %s\n", pkgname ); + fprintf( output, "PACKAGE VERSION: %s\n", pkgver ); + fprintf( output, "ARCH: %s\n", arch ); + fprintf( output, "DISTRO: %s\n", distroname ); + fprintf( output, "DISTRO VERSION: %s\n", distrover ); + } + else + { + FATAL_ERROR( "Invalid input .PKGINFO file" ); + } + + if( group ) + fprintf( output, "GROUP: %s\n", group ); + if( url ) + fprintf( output, "URL: %s\n", url ); + if( license ) + fprintf( output, "LICENSE: %s\n", license ); + if( uncompressed_size ) + fprintf( output, "UNCOMPRESSED SIZE: %s\n", uncompressed_size ); + if( total_files ) + fprintf( output, "TOTAL FILES: %s\n", total_files ); + + /* reference counter of not installed package is always zero */ + fprintf( output, "REFERENCE COUNTER: 0\n" ); +} + + +void write_requires( void ) +{ + struct stat sb; + char *buf = NULL; + + if( output == NULL && output_fname == NULL ) + { + FATAL_ERROR( "Unable to access output file" ); + } + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + fprintf( output, "REQUIRES:\n" ); + + bzero( (void *)buf, PATH_MAX ); + (void)sprintf( (char *)&buf[0], "%s/.REQUIRES", srcdir ); + + /* check if path exists and is a regular file */ + if( stat( (const char *)&buf[0], &sb ) == 0 && S_ISREG(sb.st_mode) ) + { + char *ln = NULL; + char *line = NULL; + + FILE *input; + + input = fopen( (const char *)&buf[0], "r" ); + if( !input ) + { + FATAL_ERROR( "Unable to access %s file", (char *)&buf[0] ); + } + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + /* cat .REQUIRES >> PKGLOG */ + while( (ln = fgets( line, PATH_MAX, input )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + /* print non-empty lines */ + if( *ln ) + { + fprintf( output, "%s\n", ln ); + } + } + free( line ); + } + free( buf ); +} + + +void write_description( void ) +{ + struct stat sb; + char *buf = NULL; + + if( output == NULL && output_fname == NULL ) + { + FATAL_ERROR( "Unable to access output file" ); + } + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + fprintf( output, "PACKAGE DESCRIPTION:\n" ); + + bzero( (void *)buf, PATH_MAX ); + (void)sprintf( (char *)&buf[0], "%s/.DESCRIPTION", srcdir ); + + /* check if path exists and is a regular file */ + if( stat( (const char *)&buf[0], &sb ) == 0 && S_ISREG(sb.st_mode) ) + { + char *ln = NULL; + char *line = NULL; + char *pattern = NULL; + int n = 0; + + FILE *input; + + input = fopen( (const char *)&buf[0], "r" ); + if( !input ) + { + FATAL_ERROR( "Unable to access %s file", (char *)&buf[0] ); + } + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) { FATAL_ERROR( "Cannot allocate memory" ); } + + pattern = (char *)malloc( (size_t)strlen( pkgname ) + 2 ); + if( !pattern ) { FATAL_ERROR( "Cannot allocate memory" ); } + + (void)sprintf( pattern, "%s:", pkgname ); + + /* cat .DESCRIPTION >> PKGLOG */ + while( (ln = fgets( line, PATH_MAX, input )) ) + { + char *match = NULL; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + /* + skip non-significant spaces at beginning of line + and print lines started with 'pkgname:' + */ + if( (match = strstr( ln, pattern )) && n < DESCRIPTION_NUMBER_OF_LINES ) + { + int mlen = strlen( match ), plen = strlen( pattern ); + int length = ( mlen > plen ) ? (mlen - plen - 1) : 0 ; + + if( length > DESCRIPTION_LENGTH_OF_LINE ) + { + /* WARNING( "Package DESCRIPTION contains lines with length greater than %d characters", DESCRIPTION_LENGTH_OF_LINE ); */ + match[plen + 1 + DESCRIPTION_LENGTH_OF_LINE] = '\0'; /* truncating description line */ + skip_eol_spaces( match ); /* remove spaces at end-of-line */ + } + fprintf( output, "%s\n", match ); + ++n; + } + } + + if( n < DESCRIPTION_NUMBER_OF_LINES ) + { + /* WARNING( "Package DESCRIPTION contains less than %d lines", DESCRIPTION_NUMBER_OF_LINES ); */ + while( n < DESCRIPTION_NUMBER_OF_LINES ) + { + fprintf( output, "%s\n", pattern ); + ++n; + } + } + + free( pattern ); + free( line ); + } + + free( buf ); +} + + +void write_restore_links( void ) +{ + struct stat sb; + char *buf = NULL; + + if( output == NULL && output_fname == NULL ) + { + FATAL_ERROR( "Unable to access output file" ); + } + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + fprintf( output, "RESTORE LINKS:\n" ); + + bzero( (void *)buf, PATH_MAX ); + (void)sprintf( (char *)&buf[0], "%s/.RESTORELINKS", srcdir ); + + /* check if path exists and is a regular file */ + if( stat( (const char *)&buf[0], &sb ) == 0 && S_ISREG(sb.st_mode) ) + { + char *ln = NULL; + char *line = NULL; + + FILE *input; + + input = fopen( (const char *)&buf[0], "r" ); + if( !input ) + { + FATAL_ERROR( "Unable to access %s file", (char *)&buf[0] ); + } + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + /* cat .REQUIRES >> PKGLOG */ + while( (ln = fgets( line, PATH_MAX, input )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + /* print non-empty lines */ + if( *ln ) + { + fprintf( output, "%s\n", ln ); + } + } + + free( line ); + } + + free( buf ); +} + + +void write_install_script( void ) +{ + struct stat sb; + char *buf = NULL; + + if( output == NULL && output_fname == NULL ) + { + FATAL_ERROR( "Unable to access output file" ); + } + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + fprintf( output, "INSTALL SCRIPT:\n" ); + + bzero( (void *)buf, PATH_MAX ); + (void)sprintf( (char *)&buf[0], "%s/.INSTALL", srcdir ); + + /* check if path exists and is a regular file */ + if( stat( (const char *)&buf[0], &sb ) == 0 && S_ISREG(sb.st_mode) ) + { + char *ln = NULL; + char *line = NULL; + + FILE *input; + + input = fopen( (const char *)&buf[0], "r" ); + if( !input ) + { + FATAL_ERROR( "Unable to access %s file", (char *)&buf[0] ); + } + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + /* cat .REQUIRES >> PKGLOG */ + while( (ln = fgets( line, PATH_MAX, input )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + /* print all lines */ + fprintf( output, "%s\n", ln ); + } + free( line ); + } + else + { + FATAL_ERROR( "Package doesn't contains the INSTALL script" ); + } + + free( buf ); +} + + +void write_file_list( void ) +{ + struct stat sb; + char *buf = NULL; + + if( output == NULL && output_fname == NULL ) + { + FATAL_ERROR( "Unable to access output file" ); + } + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + fprintf( output, "FILE LIST:\n" ); + + bzero( (void *)buf, PATH_MAX ); + (void)sprintf( (char *)&buf[0], "%s/.FILELIST", srcdir ); + + /* check if path exists and is a regular file */ + if( stat( (const char *)&buf[0], &sb ) == 0 && S_ISREG(sb.st_mode) ) + { + char *ln; + char *line = NULL; + + FILE *input; + + input = fopen( (const char *)&buf[0], "r" ); + if( !input ) + { + FATAL_ERROR( "Unable to access %s file", (char *)&buf[0] ); + } + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + /* cat .REQUIRES >> PKGLOG */ + while( (ln = fgets( line, PATH_MAX, input )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + /* print non-empty lines */ + if( *ln ) + { + fprintf( output, "%s\n", ln ); + } + } + + free( line ); + } + else + { + FATAL_ERROR( "Package doesn't contains the FILE list" ); + } + + free( buf ); +} + + +/********************************************* + Get directory where this program is placed: + */ +char *get_selfdir( void ) +{ + char *buf = NULL; + ssize_t len; + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + bzero( (void *)buf, PATH_MAX ); + len = readlink( "/proc/self/exe", buf, (size_t)PATH_MAX ); + if( len > 0 && len < PATH_MAX ) + { + char *p = xstrdup( (const char *)dirname( buf ) ); + free( buf ); + return p; + } + FATAL_ERROR( "Cannot determine self directory. Please mount /proc filesystem" ); +} + +void set_stack_size( void ) +{ + const rlim_t stack_size = 16 * 1024 * 1024; /* min stack size = 16 MB */ + struct rlimit rl; + int ret; + + ret = getrlimit( RLIMIT_STACK, &rl ); + if( ret == 0 ) + { + if( rl.rlim_cur < stack_size ) + { + rl.rlim_cur = stack_size; + ret = setrlimit( RLIMIT_STACK, &rl ); + if( ret != 0 ) + { + fprintf(stderr, "setrlimit returned result = %d\n", ret); + FATAL_ERROR( "Cannot set stack size" ); + } + } + } +} + + +int main( int argc, char *argv[] ) +{ + gid_t gid; + + set_signal_handlers(); + gid = getgid(); + setgroups( 1, &gid ); + + fatal_error_hook = fatal_error_actions; + + selfdir = get_selfdir(); + + errlog = stderr; + + program = basename( argv[0] ); + get_args( argc, argv ); + + /* set_stack_size(); */ + + if( pkginfo_type != PKGINFO_TEXT ) + { + /* Create tmpdir */ + srcdir = _mk_tmpdir(); + if( !srcdir ) + { + FATAL_ERROR( "Cannot create temporary dir" ); + } + rm_srcdir_at_exit = 1; + + /* Unpack SERVICE files */ + { + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *cmd = NULL, *errmsg = NULL, *wmsg = NULL; + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + + errmsg = (char *)malloc( (size_t)PATH_MAX ); + if( !errmsg ) { FATAL_ERROR( "Cannot allocate memory" ); } + + wmsg = (char *)malloc( (size_t)PATH_MAX ); + if( !wmsg ) { FATAL_ERROR( "Cannot allocate memory" ); } + + bzero( (void *)cmd, PATH_MAX ); + bzero( (void *)errmsg, PATH_MAX ); + bzero( (void *)wmsg, PATH_MAX ); + + (void)sprintf( &errmsg[0], "Cannot get SERVICE files from %s file", basename( pkginfo_fname ) ); + + len = snprintf( &cmd[0], PATH_MAX, "tar -C %s -x%sf %s %s > /dev/null 2>&1", + srcdir, uncompress, pkginfo_fname, + ".PKGINFO .REQUIRES .DESCRIPTION .RESTORELINKS .INSTALL .FILELIST" ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( errmsg ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)&wmsg[0], PATH_MAX ); + if( rc != 0 ) + { + if( ! DO_NOT_WARN_ABOUT_SERVICE_FILES ) + { + /***************************************** + if( rc > 0 ) { return TAR exit status } + else { return EXIT_FAILURE } + */ + if( rc > 0 ) exit_status = rc - 1; /* ERROR() will add one */ + ERROR( errmsg ); + if( fatal_error_hook) fatal_error_hook(); + exit( exit_status ); + } + } + + if( cmd ) free( cmd ); + if( errmsg ) free( errmsg ); + if( wmsg ) free( wmsg ); + } + + /* Change input pkginfo file name and type */ + if( pkginfo_fname ) + { + char *buf = NULL; + + buf = (char *)malloc( strlen( pkginfo_fname ) + 10 ); + if( !buf ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + (void)sprintf( &buf[0], "%s/.PKGINFO", srcdir ); + + free( pkginfo_fname ); pkginfo_fname = NULL; + + pkginfo_fname = xstrdup( (const char *)buf ); + free( buf ); + pkginfo_type = PKGINFO_TEXT; + } + + } + else /* TEXT: */ + { + char *buf = NULL; + + buf = (char *)malloc( (size_t)strlen( pkginfo_fname ) + 1 ); + if( !buf ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + /* function dirname() spoils the source contents: */ + (void)sprintf( buf, "%s", pkginfo_fname ); + + srcdir = xstrdup( (const char *)dirname( (char *)&buf[0] ) ); + free( buf ); + rm_srcdir_at_exit = 0; + } + + + write_pkginfo(); + write_requires(); + write_description(); + write_restore_links(); + write_install_script(); + write_file_list(); + + + if( rm_srcdir_at_exit ) _rm_tmpdir( (const char *)srcdir ); + free_resources(); + + exit( exit_status ); +} diff --git a/src/remove-package.c b/src/remove-package.c new file mode 100644 index 0000000..ca83621 --- /dev/null +++ b/src/remove-package.c @@ -0,0 +1,2234 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <sys/types.h> +#include <stdint.h> +#include <dirent.h> +#include <sys/stat.h> /* chmod(2) */ +#include <sys/file.h> /* flock(2) */ +#include <fcntl.h> +#include <linux/limits.h> +#include <alloca.h> /* alloca(3) */ +#include <string.h> /* strdup(3) */ +#include <strings.h> /* index(3) */ +#include <libgen.h> /* basename(3) */ +#include <ctype.h> /* tolower(3) */ +#include <errno.h> +#include <time.h> +#include <sys/time.h> +#include <pwd.h> +#include <grp.h> +#include <stdarg.h> +#include <unistd.h> + +#include <math.h> + +#include <sys/wait.h> + +#include <sys/resource.h> + +#include <signal.h> +#if !defined SIGCHLD && defined SIGCLD +# define SIGCHLD SIGCLD +#endif + +#define _GNU_SOURCE +#include <getopt.h> + +#include <config.h> + +#include <msglog.h> +#include <system.h> + +#include <cmpvers.h> +#include <dlist.h> + +#if defined( HAVE_DIALOG ) +#include <dialog-ui.h> +#endif + +#define PROGRAM_NAME "remove-package" + +#include <defs.h> + + +char *program = PROGRAM_NAME; +char *root = NULL, *pkgs_path = NULL, *rempkgs_path = NULL, + *pkg_fname = NULL, *pkglog_fname = NULL, + *tmpdir = NULL, *rtmpdir = NULL, *curdir = NULL, *log_fname = NULL; + +int quiet = 0, ignore_chrefs_errors = 0; +char *description = NULL; + +int exit_status = EXIT_SUCCESS; /* errors counter */ +char *selfdir = NULL; + +static char *pkgname = NULL, + *pkgver = NULL, + *arch = NULL, + *distroname = NULL, + *distrover = NULL, + *group = NULL, + *short_description = NULL, + *url = NULL, + *license = NULL, + *uncompressed_size = NULL, + *compressed_size = NULL, + *total_files = NULL; + +static char *requested_version = NULL; + +enum _remove_mode { + CONSOLE = 0, + INFODIALOG, + MENUDIALOG +} remove_mode = CONSOLE; + +enum _input_type { + IFMT_PKG = 0, + IFMT_LOG, + + IFMT_UNKNOWN +} input_format = IFMT_PKG; + +char uncompress = '\0'; + +static struct dlist *dirs = NULL; +static struct dlist *files = NULL; +static struct dlist *links = NULL; + +static void free_list( struct dlist *list ); + + +#define FREE_PKGINFO_VARIABLES() \ + if( pkgname ) { free( pkgname ); } pkgname = NULL; \ + if( pkgver ) { free( pkgver ); } pkgver = NULL; \ + if( arch ) { free( arch ); } arch = NULL; \ + if( distroname ) { free( distroname ); } distroname = NULL; \ + if( distrover ) { free( distrover ); } distrover = NULL; \ + if( group ) { free( group ); } group = NULL; \ + if( short_description ) { free( short_description ); } short_description = NULL; \ + if( description ) { free( description ); } description = NULL; \ + if( url ) { free( url ); } url = NULL; \ + if( license ) { free( license ); } license = NULL; \ + if( uncompressed_size ) { free( uncompressed_size ); } uncompressed_size = NULL; \ + if( compressed_size ) { free( compressed_size ); } compressed_size = NULL; \ + if( total_files ) { free( total_files ); } total_files = NULL; \ + if( requested_version ) { free( requested_version ); } requested_version = NULL + +void free_resources() +{ + if( root ) { free( root ); root = NULL; } + if( pkgs_path ) { free( pkgs_path ); pkgs_path = NULL; } + if( rempkgs_path ) { free( rempkgs_path ); rempkgs_path = NULL; } + if( pkg_fname ) { free( pkg_fname ); pkg_fname = NULL; } + + if( dirs ) { free_list( dirs ); dirs = NULL; } + if( files ) { free_list( files ); files = NULL; } + if( links ) { free_list( links ); links = NULL; } + + if( rtmpdir ) { free( rtmpdir ); rtmpdir = NULL; } + if( curdir ) { free( curdir ); curdir = NULL; } + if( log_fname ) { free( log_fname ); log_fname = NULL; } + + if( selfdir ) { free( selfdir ); selfdir = NULL; } + + FREE_PKGINFO_VARIABLES(); +} + +void usage() +{ + free_resources(); + + fprintf( stdout, "\n" ); + fprintf( stdout, "Usage: %s [options] <package|pkglog|pkgname>\n", program ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Remove installed package.\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Options:\n" ); + fprintf( stdout, " -h,--help Display this information.\n" ); + fprintf( stdout, " -v,--version Display the version of %s utility.\n", program ); + fprintf( stdout, " --ignore-chrefs-errors Ignore change references errors (code: 48).\n" ); +#if defined( HAVE_DIALOG ) + fprintf( stdout, " -i,--info-dialog Show package description during remove\n" ); + fprintf( stdout, " process using ncurses dialog.\n" ); + fprintf( stdout, " -m,--menu-dialog Ask for confirmation the removal.\n" ); +#endif + fprintf( stdout, " -q,--quiet Do not display results. This option\n" ); + fprintf( stdout, " works unless options -i, -m\n" ); + fprintf( stdout, " are enabled.\n" ); + fprintf( stdout, " -r,--root=<DIR> Target rootfs path.\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Parameter:\n" ); + fprintf( stdout, " <package|pkglog|pkgname> The PKGNAME, PACKAGE tarball or PKGLOG.\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Return codes:\n" ); + fprintf( stdout, " ------+-------------------------------------------------------\n" ); + fprintf( stdout, " code | status\n" ); + fprintf( stdout, " ------+-------------------------------------------------------\n" ); + fprintf( stdout, " 30 | package is not installed\n" ); + fprintf( stdout, " ----+----\n" ); + fprintf( stdout, " 47 | cannot backup PKGLOG file in the Setup Database\n" ); + fprintf( stdout, " 43 | pre-remove script returned error status\n" ); + fprintf( stdout, " 46 | post-remove script returned error status\n" ); + fprintf( stdout, " 48 | references cannot be updated in Setup Database\n" ); + fprintf( stdout, " ------+-------------------------------------------------------\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Upon successful completion zero is returned. Other non-zero return\n" ); + fprintf( stdout, "codes imply incorrect completion of the deinstallation.\n" ); + fprintf( stdout, "\n" ); + + exit( EXIT_FAILURE ); +} + +void to_lowercase( char *s ) +{ + char *p = s; + while( p && *p ) { int c = *p; *p = tolower( c ); ++p; } +} + +void to_uppercase( char *s ) +{ + char *p = s; + while( p && *p ) { int c = *p; *p = toupper( c ); ++p; } +} + +void version() +{ + char *upper = NULL; + + upper = (char *)alloca( strlen( program ) + 1 ); + + strcpy( (char *)upper, (const char *)program ); + to_uppercase( upper ); + + fprintf( stdout, "%s (%s) %s\n", program, upper, PROGRAM_VERSION ); + + fprintf( stdout, "Copyright (C) 2019 Andrey V.Kosteltsev.\n" ); + fprintf( stdout, "This is free software. There is NO warranty; not even\n" ); + fprintf( stdout, "for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" ); + fprintf( stdout, "\n" ); + + free_resources(); + exit( EXIT_SUCCESS ); +} + + +static void remove_trailing_slash( char *dir ) +{ + char *s; + + if( !dir || dir[0] == '\0' ) return; + + s = dir + strlen( dir ) - 1; + while( *s == '/' ) + { + *s = '\0'; --s; + } +} + + +static int _mkdir_p( const char *dir, const mode_t mode ) +{ + char *buf; + char *p = NULL; + struct stat sb; + + if( !dir ) return -1; + + buf = (char *)alloca( strlen( dir ) + 1 ); + strcpy( buf, dir ); + + remove_trailing_slash( buf ); + + /* check if path exists and is a directory */ + if( stat( buf, &sb ) == 0 ) + { + if( S_ISDIR(sb.st_mode) ) + { + return 0; + } + } + + /* mkdir -p */ + for( p = buf + 1; *p; ++p ) + { + if( *p == '/' ) + { + *p = 0; + /* test path */ + if( stat( buf, &sb ) != 0 ) + { + /* path does not exist - create directory */ + if( mkdir( buf, mode ) < 0 ) + { + return -1; + } + } else if( !S_ISDIR(sb.st_mode) ) + { + /* not a directory */ + return -1; + } + *p = '/'; + } + } + + /* test path */ + if( stat( buf, &sb ) != 0 ) + { + /* path does not exist - create directory */ + if( mkdir( buf, mode ) < 0 ) + { + return -1; + } + } else if( !S_ISDIR(sb.st_mode) ) + { + /* not a directory */ + return -1; + } + + return 0; +} + +static void _rm_tmpdir( const char *dirpath ) +{ + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + if( stat( dirpath, &path_sb ) == -1 ) + { + return; /* stat returns error code; errno is set */ + } + + if( S_ISDIR(path_sb.st_mode) == 0 ) + { + return; /* dirpath is not a directory */ + } + + if( (dir = opendir(dirpath) ) == NULL ) + { + return; /* Cannot open direcroty; errno is set */ + } + + len = strlen( dirpath ); + + while( (entry = readdir( dir )) != NULL) + { + + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( stat( path, &entry_sb ) == 0 ) + { + if( S_ISDIR(entry_sb.st_mode) ) + { + /* recursively remove a nested directory */ + _rm_tmpdir( path ); + } + else + { + /* remove a file object */ + (void)unlink( path ); + } + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + + } + + /* remove the devastated directory and close the object of this directory */ + (void)rmdir( dirpath ); + + closedir( dir ); +} + +static char *_mk_tmpdir( void ) +{ + char *buf = NULL, *p, *tmp = "/tmp"; + size_t len = 0, size = 0; + + (void)umask( S_IWGRP | S_IWOTH ); /* octal 022 */ + + /* Get preferred directory for tmp files */ + if( (p = getenv( "TMP" )) != NULL ) { + tmp = p; + } + else if( (p = getenv( "TEMP" )) != NULL ) { + tmp = p; + } + + size = strlen( tmp ) + strlen( DISTRO_NAME ) + strlen( program ) + 12; + + buf = (char *)malloc( size ); + if( !buf ) return NULL; + + len = snprintf( buf, size, (const char *)"%s/%s/%s-%.7u", tmp, DISTRO_NAME, program, getpid() ); + if( len == 0 || len == size - 1 ) + { + free( buf ); return NULL; + } + + _rm_tmpdir( (const char *)&buf[0] ); + + if( _mkdir_p( buf, S_IRWXU | S_IRWXG | S_IRWXO ) == 0 ) + { + return buf; + } + + free( buf ); return NULL; +} + + +void fatal_error_actions( void ) +{ + logmsg( errlog, MSG_NOTICE, "Free resources on FATAL error..." ); + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); +} + +void sigint( int signum ) +{ + (void)signum; + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); +} + +static void set_signal_handlers() +{ + struct sigaction sa; + sigset_t set; + + memset( &sa, 0, sizeof( sa ) ); + sa.sa_handler = sigint; /* TERM, INT */ + sa.sa_flags = SA_RESTART; + sigemptyset( &set ); + sigaddset( &set, SIGTERM ); + sigaddset( &set, SIGINT ); + sa.sa_mask = set; + sigaction( SIGTERM, &sa, NULL ); + sigaction( SIGINT, &sa, NULL ); + + memset( &sa, 0, sizeof( sa ) ); /* ignore SIGPIPE */ + sa.sa_handler = SIG_IGN; + sa.sa_flags = 0; + sigaction( SIGPIPE, &sa, NULL ); + + /* System V fork+wait does not work if SIGCHLD is ignored */ + signal( SIGCHLD, SIG_DFL ); +} + + +static enum _input_type check_input_file( char *uncompress, const char *fname ) +{ + struct stat st; + size_t pkglog_size = 0; + unsigned char buf[8]; + int rc, fd; + + /* SIGNATURES: https://www.garykessler.net/library/file_sigs.html */ + + if( uncompress ) + { + *uncompress = '\0'; + } + + if( stat( fname, &st ) == -1 ) + { + FATAL_ERROR( "Cannot access %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + + pkglog_size = st.st_size; + + if( (fd = open( fname, O_RDONLY )) == -1 ) + { + FATAL_ERROR( "Cannot open %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + + rc = (int)read( fd, (void *)&buf[0], 7 ); + if( rc != 7 ) + { + close( fd ); return IFMT_UNKNOWN; + } + buf[7] = '\0'; + + /* TEXT */ + if( !strncmp( (const char *)&buf[0], "PACKAGE", 7 ) ) + { + close( fd ); return IFMT_LOG; + } + + /* GZ */ + if( buf[0] == 0x1F && buf[1] == 0x8B && buf[2] == 0x08 ) + { + if( uncompress ) { *uncompress = 'x'; } + close( fd ); return IFMT_PKG; + } + + /* BZ2 */ + if( buf[0] == 0x42 && buf[1] == 0x5A && buf[2] == 0x68 ) + { + if( uncompress ) { *uncompress = 'j'; } + close( fd ); return IFMT_PKG; + } + + /* XZ */ + if( buf[0] == 0xFD && buf[1] == 0x37 && buf[2] == 0x7A && + buf[3] == 0x58 && buf[4] == 0x5A && buf[5] == 0x00 ) + { + if( uncompress ) { *uncompress = 'J'; } + close( fd ); return IFMT_PKG; + } + + if( pkglog_size > 262 ) + { + if( lseek( fd, 257, SEEK_SET ) == -1 ) + { + FATAL_ERROR( "Cannot check signature of %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + rc = (int)read( fd, &buf[0], 5 ); + if( rc != 5 ) + { + FATAL_ERROR( "Cannot read signature of %s file", basename( (char *)fname ) ); + } + /* TAR */ + if( buf[0] == 0x75 && buf[1] == 0x73 && buf[2] == 0x74 && buf[3] == 0x61 && buf[4] == 0x72 ) + { + close( fd ); return IFMT_PKG; + } + } + + close( fd ); return IFMT_UNKNOWN; +} + + +void get_args( int argc, char *argv[] ) +{ +#if defined( HAVE_DIALOG ) + const char* short_options = "hvimqr:"; +#else + const char* short_options = "hvqr:"; +#endif + +#define IGNORE_CHREFS_ERRORS 872 + + const struct option long_options[] = + { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'v' }, + { "ignore-chrefs-errors", no_argument, NULL, IGNORE_CHREFS_ERRORS }, +#if defined( HAVE_DIALOG ) + { "info-dialog", no_argument, NULL, 'i' }, + { "menu-dialog", no_argument, NULL, 'm' }, +#endif + { "quiet", no_argument, NULL, 'q' }, + { "root", required_argument, NULL, 'r' }, + { NULL, 0, NULL, 0 } + }; + + int ret; + int option_index = 0; + + while( (ret = getopt_long( argc, argv, short_options, long_options, &option_index )) != -1 ) + { + switch( ret ) + { + case 'h': + { + usage(); + break; + } + case 'v': + { + version(); + break; + } + +#if defined( HAVE_DIALOG ) + case 'i': + { + remove_mode = INFODIALOG; + break; + } + case 'm': + { + remove_mode = MENUDIALOG; + break; + } +#endif + case 'q': + { + quiet = 1; + break; + } + + case IGNORE_CHREFS_ERRORS: + { + ignore_chrefs_errors = 1; + break; + } + + case 'r': + { + if( optarg != NULL ) + { + char cwd[PATH_MAX]; + + bzero( (void *)cwd, PATH_MAX ); + if( optarg[0] != '/' && curdir ) + { + /* skip current directory definition './' at start of argument: */ + if( !strncmp( optarg, "./", 2 ) && strncmp( optarg, "..", 2 ) ) + (void)sprintf( cwd, "%s/%s", curdir, optarg + 2 ); + else if( (strlen( optarg ) == 1) && !strncmp( optarg, ".", 1 ) ) + (void)sprintf( cwd, "%s", curdir ); + else + (void)sprintf( cwd, "%s/%s", curdir, optarg ); + root = strdup( (const char *)cwd ); + } + else + { + root = strdup( (const char *)optarg ); + } + remove_trailing_slash( root ); + } + else + /* option is present but without value */ + usage(); + break; + } + + case '?': default: + { + usage(); + break; + } + } + } + + + if( optind < argc ) + { + struct stat st; + char *buf = NULL; + + bzero( (void *)&st, sizeof( struct stat ) ); + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + /* absolute path to input package: */ + if( argv[optind][0] != '/' && curdir ) + (void)sprintf( buf, "%s/%s", curdir, (const char *)argv[optind] ); + else + (void)strcpy( buf, (const char *)argv[optind] ); + + pkg_fname = strdup( (const char *)&buf[0] ); + free( buf ); + } + else + { + usage(); + } + + + if( !pkgs_path ) + { + struct stat st; + char *buf = NULL; + int len; + + bzero( (void *)&st, sizeof( struct stat ) ); + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + if( !root ) + { + buf[0] = '/'; buf[1] = '\0'; + root = strdup( (const char *)buf ); + } + else + { + len = strlen( root ); + + (void)strcpy( buf, (const char *)root ); + if( buf[ len - 1 ] != '/' ) + { + buf[len] = '/'; buf[len+1] = '\0'; + free( root ); root = strdup( (const char *)buf ); + } + } + + if( stat( (const char *)&buf[0], &st ) == -1 ) + { + FATAL_ERROR( "Cannot access '%s' file or directory: %s", buf, strerror( errno ) ); + } + if( !S_ISDIR(st.st_mode) ) + { + FATAL_ERROR( "Defined --root '%s' is not a directory", buf ); + } + + len = strlen( (const char *)buf ); + + (void)strcat( buf, PACKAGES_PATH ); + if( _mkdir_p( buf, S_IRWXU | S_IRWXG | S_IRWXO ) != 0 ) + { + FATAL_ERROR( "Cannot access '/%s' directory", PACKAGES_PATH ); + } + pkgs_path = strdup( (const char *)&buf[0] ); + + /********************************************* + Create other directories of Setup Database: + */ + buf[len] = '\0'; + (void)strcat( buf, REMOVED_PKGS_PATH ); + if( _mkdir_p( buf, S_IRWXU | S_IRWXG | S_IRWXO ) != 0 ) + { + FATAL_ERROR( "Cannot access '/%s' directory", REMOVED_PKGS_PATH ); + } + rempkgs_path = strdup( (const char *)&buf[0] ); + + buf[len] = '\0'; + (void)strcat( buf, SETUP_PATH ); + if( _mkdir_p( buf, S_IRWXU | S_IRWXG | S_IRWXO ) != 0 ) + { + FATAL_ERROR( "Cannot access '/%s' directory", SETUP_PATH ); + } + + /********************************************* + Allocate memory for Setup LOG File name: + */ + buf[len] = '\0'; + (void)strcat( buf, LOG_PATH ); + (void)strcat( buf, SETUP_LOG_FILE ); + log_fname = strdup( (const char *)&buf[0] ); + + free( buf ); + + } /* End if( !pkgs_path ) */ +} + +static void setup_log( char *format, ... ) +{ + FILE *fp = NULL; + + time_t t = time( NULL ); + struct tm tm = *localtime(&t); + + va_list argp; + + if( ! format ) return; + + fp = fopen( (const char *)log_fname, "a" ); + if( !fp ) + { + FATAL_ERROR( "Cannot open /%s%s file", LOG_PATH, SETUP_LOG_FILE ); + } + + fprintf( fp, "[%04d-%02d-%02d %02d:%02d:%02d]: ", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec ); + + va_start( argp, format ); + vfprintf( fp, format, argp ); + fprintf( fp, "\n" ); + + fflush( fp ); + fclose( fp ); +} + +/*********************************************************** + Remove leading spaces and take non-space characters only: + (Especialy for pkginfo lines) + */ +static char *skip_spaces( char *s ) +{ + char *q, *p = (char *)0; + + if( !s || *s == '\0' ) return p; + + p = s; + + while( (*p == ' ' || *p == '\t') && *p != '\0' ) { ++p; } q = p; + while( *q != ' ' && *q != '\t' && *q != '\0' ) { ++q; } *q = '\0'; + + if( *p == '\0' ) return (char *)0; + + return( strdup( p ) ); +} + + +/******************************* + remove spaces at end of line: + */ +static void skip_eol_spaces( char *s ) +{ + char *p = (char *)0; + + if( !s || *s == '\0' ) return; + + p = s + strlen( s ) - 1; + while( isspace( *p ) ) { *p-- = '\0'; } +} + + + +/******************************************************** + Read .FILELIST and .RESTORELINKS functions: + */ +static int __cmp_list_items( const void *a, const void *b ) +{ + if( a && b ) + return strcmp( (const char *)a, (const char *)b ); + else if( a ) + return 1; + else + return -1; +} + +static void __free_list( void *data, void *user_data ) +{ + if( data ) { free( data ); } +} + +static void free_list( struct dlist *list ) +{ + if( list ) { dlist_free( list, __free_list ); } +} + +//////////////////////////////////////////////////// +//static void __print_list( void *data, void *user_data ) +//{ +// int *counter = (int *)user_data; +// +// if( counter ) { fprintf( stdout, "item[%.5d]: %s\n", *counter, (char *)data ); ++(*counter); } +// else { fprintf( stdout, "item: %s\n", (char *)data ); } +//} +// +//static void print_list( struct dlist *list ) +//{ +// int cnt = 0; +// if( list ) { dlist_foreach( list, __print_list, (void *)&cnt ); } +//} +//////////////////////////////////////////////////// + +static void read_filelist( void ) +{ + struct stat st; + FILE *fp = NULL; + + char *ln = NULL; + char *line = NULL, *tmp = NULL; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + (void)sprintf( &tmp[0], "%s/.FILELIST", rtmpdir ); + bzero( (void *)&st, sizeof( struct stat ) ); + if( (stat( (const char *)&tmp[0], &st ) == -1) ) + { + FATAL_ERROR( "Cannot get .FILELIST from '%s' file", basename( (char *)pkglog_fname ) ); + } + + fp = fopen( (const char *)&tmp[0], "r" ); + if( !fp ) + { + FATAL_ERROR( "Cannot open .FILELIST file" ); + } + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)line, PATH_MAX ); + + while( (ln = fgets( line, PATH_MAX, fp )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( *(ln + strlen(ln) - 1) == '/' ) + { + *(ln + strlen(ln) - 1) = '\0'; + (void)sprintf( &tmp[0], "%s%s", (const char *)root, (const char *)ln ); + dirs = dlist_append( dirs, strdup( (const char *)&tmp[0] ) ); + } + else + { + (void)sprintf( &tmp[0], "%s%s", (const char *)root, (const char *)ln ); + files = dlist_append( files, strdup( (const char *)&tmp[0] ) ); + } + + } /* End of while( file list entry ) */ + + fclose( fp ); + + free( line ); + free( tmp ); +} + +static void read_restorelinks( void ) +{ + struct stat st; + FILE *fp = NULL; + + char *ln = NULL; + char *line = NULL, *tmp = NULL; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + (void)sprintf( &tmp[0], "%s/.RESTORELINKS", rtmpdir ); + bzero( (void *)&st, sizeof( struct stat ) ); + if( (stat( (const char *)&tmp[0], &st ) == -1) || (st.st_size < 8) ) + { + free( tmp ); + return; + } + + fp = fopen( (const char *)&tmp[0], "r" ); + if( !fp ) + { + FATAL_ERROR( "Cannot open .RESTORELINKS file" ); + } + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)line, PATH_MAX ); + + while( (ln = fgets( line, PATH_MAX, fp )) ) + { + char *match = NULL; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( (match = strstr( ln, "; rm -rf " )) ) + { + char *q = NULL; + char *p = strstr( ln, "cd" ) + 2; + char *f = strstr( ln, "; rm -rf" ) + 8; + + if( !p || !f ) continue; + + while( (*p == ' ' || *p == '\t') && *p != '\0' ) ++p; + while( (*f == ' ' || *f == '\t') && *f != '\0' ) ++f; + + q = p; while( *q != ' ' && *q != '\t' && *q != ';' && *q != '\0' ) ++q; *q = '\0'; + q = f; while( *q != ' ' && *q != '\t' && *q != ';' && *q != '\0' ) ++q; *q = '\0'; + + if( p && f ) + { + (void)sprintf( &tmp[0], "%s%s/%s", (const char *)root, p, f ); + links = dlist_append( links, strdup( (const char *)&tmp[0] ) ); + } + } + } /* End of while( restore links entry ) */ + + fclose( fp ); + + free( line ); + free( tmp ); +} +/* + End of read .FILELIST and .RESTORELINKS functions. + ********************************************************/ + +static void read_description( void ) +{ + struct stat st; + FILE *fp = NULL; + + char *buf = NULL, *tmp = NULL; + char *lp = NULL; + int n = 0; + + char *ln = NULL; + char *line = NULL; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + (void)sprintf( &tmp[0], "%s/.DESCRIPTION", rtmpdir ); + bzero( (void *)&st, sizeof( struct stat ) ); + if( (stat( (const char *)&tmp[0], &st ) == -1) || (st.st_size < 8) ) + { + free( tmp ); + return; + } + + fp = fopen( (const char *)&tmp[0], "r" ); + if( !fp ) + { + FATAL_ERROR( "Cannot open .DESCRIPTION file" ); + } + + (void)sprintf( (char *)&buf[0], "%s:", pkgname ); + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)line, PATH_MAX ); + + lp = (char *)&tmp[0]; + bzero( (void *)tmp, PATH_MAX ); + (void)sprintf( (char *)&tmp[0], "\n" ); + ++lp; + + while( (ln = fgets( line, PATH_MAX, fp )) && n < DESCRIPTION_NUMBER_OF_LINES ) + { + char *match = NULL; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + + if( (match = strstr( (const char *)ln, (const char *)buf )) && match == ln ) /* at start of line only */ + { + int mlen = strlen( match ), plen = strlen( buf ); + int length = ( mlen > plen ) ? (mlen - plen - 1) : 0 ; + + if( length > DESCRIPTION_LENGTH_OF_LINE ) + { + /* WARNING( "Package DESCRIPTION contains lines with length greater than %d characters", DESCRIPTION_LENGTH_OF_LINE ); */ + match[plen + 1 + DESCRIPTION_LENGTH_OF_LINE] = '\0'; /* truncating description line */ + skip_eol_spaces( match ); /* remove spaces at end-of-line */ + } + + match += plen + 1; + if( match[0] != '\0' ) { (void)sprintf( lp, " %s\n", match ); lp += strlen( match ) + 2; } + else { (void)sprintf( lp, "\n" ); ++lp; } + ++n; + } + } /* End of while( ln = fgets() ) */ + + fclose( fp ); + + (void)sprintf( lp, " Uncompressed Size: %s\n", uncompressed_size ); + lp += strlen( uncompressed_size ) + 21; + + description = strdup( (const char *)&tmp[0] ); + + free( buf ); + free( line ); + free( tmp ); +} + +static void pre_remove_routine( void ) +{ + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *cmd = NULL; + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, + "cd %s && %s/.INSTALL pre_remove %s > /dev/null 2>&1", + root, rtmpdir, pkgver ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot run pre-remove script for '%s-%s' package", pkgname, pkgver ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + + free( cmd ); + + if( rc != 0 ) + { + exit_status = 43; + + if( remove_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Remove:", pkgname, pkgver, NULL, + "\n\\Z1Pre-remove script returned error status.\\Zn\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nPre-remove script of '%s-%s' returned error status.\n\n", pkgname, pkgver ); +#endif + } + else + { + fprintf( stdout, "\nPre-remove script of '%s-%s' returned error status.\n\n", pkgname, pkgver ); + } + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + exit( exit_status ); + } +} + +/******************************************************** + Removal functions: + */ +static void __remove_link( void *data, void *user_data ) +{ + const char *fname = (const char *)data; + + if( fname ) + { + (void)unlink( fname ); + } +} + +static void __remove_file( void *data, void *user_data ) +{ + const char *fname = (const char *)data; + + if( fname ) + { + char *p = rindex( fname, '.' ); + /* + Если .new файл остался с тем же именем, это значит что до инсталляции + в системе существовал такой же файл но без расширения .new и при этом + он отличался от нового. В данном случае надо удалять только файл .new. + + Если же файл .new не существует, то надо удалять такой же файл но без + расширения .new . + */ + if( p && !strncmp( (const char *)p, ".new", 4 ) ) + { + struct stat st; + + bzero( (void *)&st, sizeof( struct stat ) ); + if( (stat( fname, &st ) == -1) ) *p = '\0'; + } + + (void)unlink( fname ); + } +} + +static int is_dir_empty( const char *dirpath ) +{ + int ret = 0; + + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + if( stat( dirpath, &path_sb ) == -1 ) return ret; /* stat returns error code; errno is set */ + if( S_ISDIR(path_sb.st_mode) == 0 ) return ret; /* dirpath is not a directory */ + if( (dir = opendir(dirpath) ) == NULL ) return ret; /* Cannot open direcroty; errno is set */ + + ret = 1; + + len = strlen( dirpath ); + while( (entry = readdir( dir )) != NULL) + { + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( stat( path, &entry_sb ) == 0 ) + { + ret = 0; + break; + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + } + closedir( dir ); + + return ret; +} + +static void __remove_dir( void *data, void *user_data ) +{ + const char *dname = (const char *)data; + + if( dname && is_dir_empty( (const char *)dname ) ) + { + (void)rmdir( dname ); + } +} + +static void remove_package( void ) +{ + /* Try to change CWD to the ROOT directory: */ + (void)chdir( (const char *)root ); + + if( links ) { dlist_foreach( links, __remove_link, NULL ); } + + if( files ) { dlist_foreach( files, __remove_file, NULL ); } + + if( dirs ) + { + dirs = dlist_sort( dirs, __cmp_list_items ); + dirs = dlist_reverse( dirs ); + dlist_foreach( dirs, __remove_dir, NULL ); + } + + /* Try to change CWD to the CURRENT directory: */ + (void)chdir( (const char *)curdir ); +} +/* + End of removal functions. + ********************************************************/ + +static void post_remove_routine( void ) +{ + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *cmd = NULL; + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, + "cd %s && %s/.INSTALL post_remove %s > /dev/null 2>&1", + root, rtmpdir, pkgver ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot run post-remove script for '%s-%s' package", pkgname, pkgver ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + + free( cmd ); + + if( rc != 0 ) + { + exit_status = 46; + + if( remove_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Remove:", pkgname, pkgver, NULL, + "\n\\Z1Post-remove script returned error status.\\Zn\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nPost-remove script of '%s-%s' returned error status.\n\n", pkgname, pkgver ); +#endif + } + else + { + fprintf( stdout, "\nPost-remove script of '%s-%s' returned error status.\n\n", pkgname, pkgver ); + } + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + exit( exit_status ); + } +} + +static void finalize_removal( void ) +{ + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *cmd = NULL, *tmp = NULL; + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + /********************************************* + Decrement references in the Setup Database: + */ + if( group ) + len = snprintf( &cmd[0], PATH_MAX, + "%s/chrefs --operation=dec --destination=%s %s/%s > /dev/null 2>&1", + selfdir, pkgs_path, group, basename( (char *)pkglog_fname ) ); + else + len = snprintf( &cmd[0], PATH_MAX, + "%s/chrefs --operation=dec --destination=%s %s > /dev/null 2>&1", + selfdir, pkgs_path, basename( (char *)pkglog_fname ) ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot decrement '%s-%s' package references", pkgname, pkgver ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + if( (rc != 0) && !ignore_chrefs_errors ) + { + free( cmd ); + free( tmp ); + + exit_status = 48; + + if( remove_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Remove:", pkgname, pkgver, NULL, + "\n\\Z1Cannot decrement package references in Setup Database.\\Zn\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nCannot decrement '%s-%s' package references in Setup Database.\n\n", pkgname, pkgver ); +#endif + } + else + { + if( !quiet ) + { + fprintf( stdout, "\nCannot decrement '%s-%s' package references in Setup Database.\n\n", pkgname, pkgver ); + } + } + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + exit( exit_status ); + } + + /***************************************************** + Backup PKGLOG file into removed-packages directory: + */ + bzero( (void *)tmp, PATH_MAX ); + + if( group ) + (void)sprintf( &tmp[0], "%s/%s/", rempkgs_path, group ); + else + (void)sprintf( &tmp[0], "%s/", rempkgs_path ); + + if( _mkdir_p( tmp, S_IRWXU | S_IRWXG | S_IRWXO ) != 0 ) + { + FATAL_ERROR( "Cannot access '/%s' directory", REMOVED_PKGS_PATH ); + } + + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, + "mv %s %s > /dev/null 2>&1", + pkglog_fname, (const char *)&tmp[0] ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot backup '%s' pkglog file", basename( (char *)pkglog_fname ) ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + + free( cmd ); + + if( rc != 0 ) + { + free( tmp ); + + exit_status = 47; + + if( remove_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Remove:", pkgname, pkgver, NULL, + "\n\\Z1Cannot backup PKGLOG file.\\Zn\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nCannot backup '%s' pkglog file.\n\n", basename( (char *)pkglog_fname ) ); +#endif + } + else + { + if( !quiet ) + { + fprintf( stdout, "\nCannot backup '%s' pkglog file.\n\n", basename( (char *)pkglog_fname ) ); + } + } + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + exit( exit_status ); + } + + /**************************************** + Remove group directory if it is empty: + */ + bzero( (void *)tmp, PATH_MAX ); + + if( group ) + { + (void)sprintf( &tmp[0], "%s/%s/", pkgs_path, group ); + + const char *dir = (const char *)&tmp[0]; + if( is_dir_empty( dir ) ) + { + (void)rmdir( dir ); + } + } + + free( tmp ); +} + + +static int ask_for_remove( int prev ) +{ + int ret = 0; /* continue removal */ +#if defined( HAVE_DIALOG ) + /****************************************************** + Ask for remove dialog shown only in MENUDIALOG mode: + */ + if( (remove_mode == MENUDIALOG) ) + { + if( prev < 0 ) + { + char *msg = NULL; + + msg = (char *)malloc( (size_t)PATH_MAX ); + if( !msg ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)msg, PATH_MAX ); + + (void)sprintf( &msg[0], "\nPrevious version '\\Z4%s\\Zn' of requested package installed.\n" + "\n\\Z1Remove a previous vesion?\\Zn\n", pkgver ); + + ret = ask_remove_box( "Remove:", pkgname, requested_version, (const char *)&msg[0], 9, 0, 0 ); + + free( msg ); + } + else if( prev > 0 ) + { + char *msg = NULL; + + msg = (char *)malloc( (size_t)PATH_MAX ); + if( !msg ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)msg, PATH_MAX ); + + (void)sprintf( &msg[0], "\nA newer version '\\Z4%s\\Zn' of requested package installed.\n" + "\n\\Z1Remove a newer vesion?\\Zn\n", pkgver ); + + ret = ask_remove_box( "Remove:", pkgname, requested_version, (const char *)&msg[0], 9, 0, 0 ); + + free( msg ); + } + else + { + ret = ask_remove_box( "Remove:", pkgname, pkgver, description, 18, 0, 0 ); + } + } + + if( ret ) + { + info_pkg_box( "Remove:", pkgname, pkgver, NULL, + "\nPackage removal terminated by user.\n", 5, 0, 0 ); + } +#endif + return ret; +} + + +static void show_removal_progress( void ) +{ + if( remove_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Remove:", pkgname, pkgver, NULL, + description, 16, 1, 0 ); +#else + fprintf( stdout, "\n Remobe: %s-%s ...\n", pkgname, pkgver ); + /************************************************* + Ruler: 68 characters + 2 spaces left and right: + + | ----handy-ruler----------------------------------------------------- | */ + fprintf( stdout, "|======================================================================|\n" ); + fprintf( stdout, "%s\n", description ); + fprintf( stdout, "|======================================================================|\n\n" ); +#endif + } + else + { + if( !quiet ) + { + fprintf( stdout, "\n Remove: %s-%s ...\n", pkgname, pkgver ); + /************************************************* + Ruler: 68 characters + 2 spaces left and right: + + | ----handy-ruler----------------------------------------------------- | */ + fprintf( stdout, "|======================================================================|\n" ); + fprintf( stdout, "%s\n", description ); + fprintf( stdout, "|======================================================================|\n\n" ); + } + } +} + + +static void read_pkginfo( const char *pkginfo_fname ) +{ + char *ln = NULL; + char *line = NULL; + + FILE *pkginfo = NULL; + + if( pkginfo_fname != NULL ) + { + pkginfo = fopen( (const char *)pkginfo_fname, "r" ); + if( !pkginfo ) + { + FATAL_ERROR( "Cannot open %s file", pkginfo_fname ); + } + } + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + while( (ln = fgets( line, PATH_MAX, pkginfo )) ) + { + char *match = NULL; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( (match = strstr( ln, "pkgname" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) + { + if( pkgname ) { free( pkgname ); } + pkgname = skip_spaces( p ); + } + } + if( (match = strstr( ln, "pkgver" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) + { + if( pkgver ) { free( pkgver ); } + pkgver = skip_spaces( p ); + } + } + if( (match = strstr( ln, "arch" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) + { + if( arch ) { free( arch ); } + arch = skip_spaces( p ); + } + } + if( (match = strstr( ln, "distroname" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) + { + if( distroname ) { free( distroname ); } + distroname = skip_spaces( p ); + } + } + if( (match = strstr( ln, "distrover" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) + { + if( distrover ) { free( distrover ); } + distrover = skip_spaces( p ); + } + } + + if( (match = strstr( ln, "group" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) + { + if( group ) { free( group ); } + group = skip_spaces( p ); + } + } + + if( (match = strstr( ln, "short_description" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) + { + char *b = index( p, '"'), + *e = rindex( p, '"'); + if( b && e && ( b != e ) ) + { + p = ++b; *e = '\0'; + if( short_description ) { free( short_description ); } + short_description = strdup( (const char *)p ); + } + } + } + if( (match = strstr( ln, "url" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) + { + if( url ) { free( url ); } + url = skip_spaces( p ); + } + } + if( (match = strstr( ln, "license" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) + { + if( license ) { free( license ); } + license = skip_spaces( p ); + } + } + + if( (match = strstr( ln, "uncompressed_size" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) + { + if( uncompressed_size ) { free( uncompressed_size ); } + uncompressed_size = skip_spaces( p ); + } + } + if( (match = strstr( ln, "total_files" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) + { + if( total_files ) { free( total_files ); } + total_files = skip_spaces( p ); + } + } + } + + free( line ); + + if( !pkgname || !pkgver || !arch || !distroname || !distrover ) + { + FATAL_ERROR( "Invalid input .PKGINFO file" ); + } + + fclose( pkginfo ); +} + + +/*************************************************************** + Probe functions: + */ +static void _probe_pkglog( const char *dirpath, const char *grp ) +{ + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + if( pkglog_fname ) return; + + if( stat( dirpath, &path_sb ) == -1 ) + { + FATAL_ERROR( "%s: Cannot stat Setup Database or destination directory", dirpath ); + } + + if( S_ISDIR(path_sb.st_mode) == 0 ) + { + FATAL_ERROR( "%s: Setup Database or destination is not a directory", dirpath ); + } + + if( (dir = opendir(dirpath) ) == NULL ) + { + FATAL_ERROR( "Canot access %s directory: %s", dirpath, strerror( errno ) ); + } + + len = strlen( dirpath ); + + while( (entry = readdir( dir )) != NULL) + { + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( stat( path, &entry_sb ) == 0 ) + { + if( S_ISREG(entry_sb.st_mode) ) + { + char *match = NULL; + char *pkglog = basename( path ); + + if( (match = strstr( pkglog, (const char *)basename( pkg_fname ) )) && match == pkglog ) + { + char *buf = NULL, *p = NULL, *q = NULL; + + p = q = buf = strdup( (const char *)pkglog ); + ++p; + while( *p != '\0' && !isblank(*p) && !(*q == '-' && isdigit(*p)) ) + { + /* package version starts with a number and separated by '-' */ + ++p; ++q; + } + *(--p) = '\0'; + + /******************************************************* + We have to make sure that the name we are looking for + is not shorter than the name of the found package. + */ + if( strlen(pkg_fname) >= strlen(buf) ) + { + + pkglog_fname = strdup( (const char *)path ); + free( buf ); + closedir( dir ); + return; + } + free( buf ); + } + } + if( S_ISDIR(entry_sb.st_mode) && grp == NULL ) + { + _probe_pkglog( (const char *)path, (const char *)entry->d_name ); + } + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + } + + closedir( dir ); +} + +/*********************************************************** + probe_package(): + --------------- + */ +static char *probe_package( void ) +{ + char *ret = NULL; + + _probe_pkglog( (const char *)pkgs_path, NULL ); + if( pkglog_fname ) + { + free( pkg_fname ); + ret = pkg_fname = pkglog_fname; + } + + return ret; +} +/* + Enf of Probe functions. + ***********************************************************/ + +/*********************************************************** + Find functions: + */ +static void _search_pkglog( const char *dirpath, const char *grp ) +{ + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + char *pname = (char *)dirpath + strlen( root ); /* do not remove leading '/' */ + + if( stat( dirpath, &path_sb ) == -1 ) + { + FATAL_ERROR( "%s: Cannot stat Setup Database or group directory", pname ); + } + + if( S_ISDIR(path_sb.st_mode) == 0 ) + { + FATAL_ERROR( "%s: Setup Database or group is not a directory", pname ); + } + + if( (dir = opendir(dirpath) ) == NULL ) + { + FATAL_ERROR( "Canot access %s directory: %s", pname, strerror( errno ) ); + } + + len = strlen( dirpath ); + + while( (entry = readdir( dir )) != NULL) + { + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( stat( path, &entry_sb ) == 0 ) + { + if( S_ISREG(entry_sb.st_mode) ) + { + char *match = NULL, *name = basename( path ); + + if( (match = strstr( name, pkgname )) && match == name ) + { + /**************************************************************** + Здесь мы еще должны проверить, что найденный пакет не имеет + более длинное имя, которое начинается с имени искомого пакета. + Полагаясь на факт, что версия может начинаться только с цифры, + мы пропускаем символ '-', разделяющий имя и версию пакета, + а затем проверяем начальный символ версии: + */ + if( *(name + strlen( pkgname )) == '-' && isdigit( *(name + strlen( pkgname ) + 1) ) ) + { + pkglog_fname = strdup( (const char *)path ); + closedir( dir ); + return; + } + } + } + if( S_ISDIR(entry_sb.st_mode) && grp == NULL ) + { + /************************************************************************** + NOTE: + In the Setup Database can be only one package with the same pkgname + but in different groups. For example, the package named 'cairo' + has two instance: libs/cairo-1.14.6 and xlibs/cairo-1.14.6. During + system installation the package libs/cairo-1.14.6 installed first + and then updated by xlibs/cairo-1.14.6 and PKGLOG of libs/cairo-1.14.6 + moved from /var/log/radix/packages to /var/log/radix/removed-packages. + + So here we have to look for the PKGLOG in all group directories: + */ + _search_pkglog( (const char *)path, (const char *)entry->d_name ); + } + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + } + + closedir( dir ); +} + +static char *find_package( void ) +{ + char *ret = NULL; + + _search_pkglog( (const char *)pkgs_path, NULL ); + if( pkglog_fname ) + { + free( pkg_fname ); + ret = pkg_fname = pkglog_fname; + } + + return ret; +} +/* + Enf of Find functions. + ***********************************************************/ + + +/*********************************************************** + check_input_package(): + --------------------- + + Возвращает: + -1 если пакет установлен, но его версия меньше + запрашиваемого, + 0 если версия установленного и запрашиваемого равны, + 1 если пакет установлен, но его версия больше + запрашиваемого. + + В случае возврата -1 или 1, устанавливается переменная + requested_version, равная версии пакета который запросили + на удаление. + + Если пакет не установлен, осуществляется выход со статусом 30. + */ +static int check_input_package( void ) +{ + struct stat st; + char *fname = pkg_fname; + + enum _input_type type = IFMT_UNKNOWN; + char uncompress = '\0'; + + int ret = 0; + + bzero( (void *)&st, sizeof( struct stat ) ); + + if( stat( (const char *)fname, &st ) == -1 ) + { + /************************************************* + Specified pkg_fname is not a file or directory. + Try to find installed package with name equal + to pkg_fname: + */ + fname = NULL; + fname = probe_package(); + if( !fname ) + { + if( remove_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Remove:", basename( pkg_fname ), NULL, NULL, + "\nPackage is not installed.\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nPackage '%s' is not installed.\n\n", basename( pkg_fname ) ); +#endif + } + else + { + if( !quiet ) fprintf( stdout, "Specified package '%s' is not installed.\n\n", basename( pkg_fname ) ); + } + + exit_status = 30; /* Package is not installed: install */ + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + + exit( exit_status ); + } + } + else + { + if( S_ISREG(st.st_mode) ) + { + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *tmp= NULL, *cmd = NULL; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + (void)sprintf( &tmp[0], "%s", tmpdir ); + if( _mkdir_p( tmp, S_IRWXU | S_IRWXG | S_IRWXO ) != 0 ) + { + FATAL_ERROR( "Cannot get PKGINFO from '%s' file", basename( (char *)fname ) ); + } + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, + "%s/pkginfo -d %s -o pkginfo %s > /dev/null 2>&1", + selfdir, tmp, fname ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot get PKGINFO from %s file", basename( (char *)fname ) ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + if( rc != 0 ) + { + FATAL_ERROR( "Cannot get PKGINFO from '%s' file", basename( (char *)fname ) ); + } + + (void)strcat( tmp, "/.PKGINFO" ); + read_pkginfo( (const char *)&tmp[0] ); + (void)unlink( (const char *)&tmp[0] ); /* :remove unnecessary .PKGINFO file */ + *(strstr( tmp, "/.PKGINFO" )) = '\0'; /* :restore 'tmpdir' in tmp[] buffer */ + + requested_version = strdup( (const char *)pkgver ); + + fname = NULL; + fname = find_package(); + if( !fname ) + { + if( remove_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Remove:", basename( pkg_fname ), NULL, NULL, + "\nPackage is not installed.\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nPackage '%s' is not installed.\n\n", basename( pkg_fname ) ); +#endif + } + else + { + if( !quiet ) fprintf( stdout, "Specified package '%s' is not installed.\n\n", basename( pkg_fname ) ); + } + + exit_status = 30; /* Package is not installed: install */ + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + + exit( exit_status ); + } + + free( cmd ); + free( tmp ); + } + else + { + FATAL_ERROR( "Input %s file is not a regular file", basename( (char *)fname ) ); + } + } + + /* check pkg_fname again: */ + if( stat( (const char *)fname, &st ) == -1 ) + { + FATAL_ERROR( "Cannot access input '%s' file: %s", fname, strerror( errno ) ); + } + + type = check_input_file( &uncompress, fname ); + if( type == IFMT_UNKNOWN ) + { + FATAL_ERROR( "Unknown format of input '%s' file", fname ); + } + + if( S_ISREG(st.st_mode) ) + { + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *tmp= NULL, *cmd = NULL; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + (void)sprintf( &tmp[0], "%s/to-remove", tmpdir ); + if( _mkdir_p( tmp, S_IRWXU | S_IRWXG | S_IRWXO ) != 0 ) + { + FATAL_ERROR( "Cannot get PKGINFO from '%s' file", basename( (char *)fname ) ); + } + rtmpdir = strdup( (const char *)&tmp[0] ); + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, + "%s/pkginfo -d %s -o pkginfo,description,install-script,restore-links,filelist %s > /dev/null 2>&1", + selfdir, tmp, fname ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot get PKGINFO from %s file", basename( (char *)fname ) ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + if( rc != 0 ) + { + FATAL_ERROR( "Cannot get PKGINFO from '%s' file", basename( (char *)fname ) ); + } + + (void)strcat( tmp, "/.PKGINFO" ); + read_pkginfo( (const char *)&tmp[0] ); + *(strstr( tmp, "/.PKGINFO" )) = '\0'; /* :restore tmpdir in tmp[] buffer */ + + free( cmd ); + free( tmp ); + + if( requested_version ) + { + ret = cmp_version( (const char *)pkgver, (const char *)requested_version ); + } + } + else + { + FATAL_ERROR( "Input %s file is not a regular file", basename( (char *)fname ) ); + } + + return ret; +} + + + +static void dialogrc( void ) +{ + struct stat st; + char *tmp = NULL; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + /* imagine that the utility is in /sbin directory: */ + (void)sprintf( &tmp[0], "%s/../usr/share/%s/.dialogrc", selfdir, PACKAGE_NAME ); + if( stat( (const char *)&tmp[0], &st ) == -1 ) + { + /* finaly assume that /usr/sbin is a sbindir: */ + (void)sprintf( &tmp[0], "%s/../../usr/share/%s/.dialogrc", selfdir, PACKAGE_NAME ); + } + + setenv( "DIALOGRC", (const char *)&tmp[0], 1 ); + + free( tmp ); +} + +static char *get_curdir( void ) +{ + char *cwd = NULL; + + cwd = (char *)malloc( PATH_MAX ); + if( !cwd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cwd, PATH_MAX ); + + if( getcwd( cwd, (size_t)PATH_MAX ) != NULL ) + { + char *p = NULL; + remove_trailing_slash( cwd ); + p = strdup( cwd ); + free( cwd ); + return p; + } + else + { + FATAL_ERROR( "Cannot get absolute path to current directory" ); + } + + return (char *)NULL; +} + + +/********************************************* + Get directory where this program is placed: + */ +char *get_selfdir( void ) +{ + char *buf = NULL; + ssize_t len; + + buf = (char *)malloc( PATH_MAX ); + if( !buf ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + bzero( (void *)buf, PATH_MAX ); + len = readlink( "/proc/self/exe", buf, (size_t)PATH_MAX ); + if( len > 0 && len < PATH_MAX ) + { + char *p = strdup( dirname( buf ) ); + free( buf ); + return p; + } + FATAL_ERROR( "Cannot determine self directory. Please mount /proc filesystem" ); +} + +void set_stack_size( void ) +{ + const rlim_t stack_size = 16 * 1024 * 1024; /* min stack size = 16 MB */ + struct rlimit rl; + int ret; + + ret = getrlimit( RLIMIT_STACK, &rl ); + if( ret == 0 ) + { + if( rl.rlim_cur < stack_size ) + { + rl.rlim_cur = stack_size; + ret = setrlimit( RLIMIT_STACK, &rl ); + if( ret != 0 ) + { + fprintf(stderr, "setrlimit returned result = %d\n", ret); + FATAL_ERROR( "Cannot set stack size" ); + } + } + } +} + + +int main( int argc, char *argv[] ) +{ + gid_t gid; + + set_signal_handlers(); + + gid = getgid(); + setgroups( 1, &gid ); + + fatal_error_hook = fatal_error_actions; + + selfdir = get_selfdir(); + curdir = get_curdir(); + dialogrc(); + + errlog = stderr; + + program = basename( argv[0] ); + get_args( argc, argv ); + + /* set_stack_size(); */ + + tmpdir = _mk_tmpdir(); + if( !tmpdir ) + { + FATAL_ERROR( "Cannot create temporary directory" ); + } + + { + int status = 0; + + /********************************************************** + Fill pkginfo data and put or replace pkglog into tmpdir: + */ + status = check_input_package(); + + read_filelist(); + read_restorelinks(); + read_description(); + + if( ask_for_remove( status ) ) + { + /* Terminate removal: */ + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + exit( exit_status ); + } + } + + show_removal_progress(); + + /************ + DO REMOVE: + */ + pre_remove_routine(); + remove_package(); + post_remove_routine(); + finalize_removal(); + + if( remove_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Remove:", pkgname, pkgver, NULL, + "\nPackage has been removed.\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nPackage '%s-%s' has been removed.\n\n", pkgname, pkgver ); +#endif + } + else + { + if( !quiet ) + { + fprintf( stdout, "\nPackage '%s-%s' has been removed.\n\n", pkgname, pkgver ); + } + } + + setup_log( "Package '%s-%s' has been removed", pkgname, pkgver ); + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + + exit( exit_status ); +} diff --git a/src/system.c b/src/system.c new file mode 100644 index 0000000..c6e7b21 --- /dev/null +++ b/src/system.c @@ -0,0 +1,136 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <error.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <stdarg.h> +#include <unistd.h> + +#include <msglog.h> + + +static void xexec( const char *cmd ) +{ + char *argv[4]; + const char *shell = getenv ("SHELL"); + + if( !shell ) shell = "/bin/sh"; + + argv[0] = (char *) shell; + argv[1] = (char *) "-c"; + argv[2] = (char *) cmd; + argv[3] = NULL; + + execv( shell, argv ); + + /****************************************** + xexec() is called by child process, and + here child process faced to FATAL error: + */ + logmsg( errlog, MSG_FATAL, "%s: Cannot exec", cmd ); + exit( EXIT_FAILURE ); +} + +static pid_t xfork( void ) +{ + pid_t p = fork(); + + if( p == (pid_t) -1 ) + { + FATAL_ERROR( "Cannot %s", "fork" ); + } + + return p; +} + +pid_t sys_exec_command( const char *cmd ) +{ + pid_t pid = xfork(); + + if( pid != 0 ) + { + return pid; + } + + xexec( cmd ); + return pid; /* only to avoid compilaton warning */ +} + + +/***************************************************************** + sys_wait_command() - Wait for pid. + + Return values: + ------------- + 0 - SUCCESS + >=1 - status returned by child process + -1 - Child terminated on signal + -2 - Child terminated on unknown reason + -3 - Cannot waitpid: waitpid() retusrs -1 + + Error message with SIZE length saved into *ERRMSG buffer. + *****************************************************************/ +int sys_wait_command( pid_t pid, char *errmsg, size_t size ) +{ + int status; + + if( pid < 0 ) return (pid_t) -1; + + while( waitpid( pid, &status, 0 ) == -1 ) + if( errno != EINTR ) + { + if( errmsg && size ) { + (void)snprintf( errmsg, size, "PID %lu: Cannot %s", (unsigned long)pid, "waitpid" ); + } + return (int) -3; + } + + if( WIFEXITED( status ) ) + { + if( WEXITSTATUS (status) ) + { + if( errmsg && size ) { + (void)snprintf( errmsg, size, "PID %lu: Child returned status %d", (unsigned long)pid, WEXITSTATUS( status ) ); + } + return (int) WEXITSTATUS( status ); + } + } + else if( WIFSIGNALED( status ) ) + { + if( errmsg && size ) { + (void)snprintf( errmsg, size, "PID %lu: Child terminated on signal %d", (unsigned long)pid, WTERMSIG( status ) ); + } + return (int) -1; + } + else + { + if( errmsg && size ) { + (void)snprintf( errmsg, size, "PID %lu: Child terminated on unknown reason", (unsigned long)pid ); + } + return (int) -2; + } + + return 0; +} diff --git a/src/system.h b/src/system.h new file mode 100644 index 0000000..8da6590 --- /dev/null +++ b/src/system.h @@ -0,0 +1,49 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +#ifndef _SYSTEM_H_ +#define _SYSTEM_H_ + +#ifdef __cplusplus +extern "C" { +#endif + + +extern pid_t sys_exec_command( const char *cmd ); + +/***************************************************************** + sys_wait_command() - Wait for pid. + + Return values: + ------------- + 0 - SUCCESS + >=1 - status returned by child process + -1 - Child terminated on signal + -2 - Child terminated on unknown reason + -3 - Cannot waitpid: waitpid() retusrs -1 + + Error message with SIZE length saved into *ERRMSG buffer. + *****************************************************************/ +extern int sys_wait_command( pid_t pid, char *errmsg, size_t size ); + + +#ifdef __cplusplus +} /* ... extern "C" */ +#endif + +#endif /* _SYSTEM_H_ */ diff --git a/src/update-package.c b/src/update-package.c new file mode 100644 index 0000000..d11a97c --- /dev/null +++ b/src/update-package.c @@ -0,0 +1,3425 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <sys/sysinfo.h> +#include <sys/types.h> +#include <stdint.h> +#include <dirent.h> +#include <sys/stat.h> /* chmod(2) */ +#include <sys/file.h> /* flock(2) */ +#include <fcntl.h> +#include <linux/limits.h> +#include <alloca.h> /* alloca(3) */ +#include <string.h> /* strdup(3) */ +#include <strings.h> /* index(3) */ +#include <libgen.h> /* basename(3) */ +#include <ctype.h> /* tolower(3) */ +#include <errno.h> +#include <time.h> +#include <sys/time.h> +#include <pwd.h> +#include <grp.h> +#include <stdarg.h> +#include <unistd.h> + +#include <math.h> + +#include <sys/wait.h> + +#include <sys/resource.h> + +#include <signal.h> +#if !defined SIGCHLD && defined SIGCLD +# define SIGCHLD SIGCLD +#endif + +#define _GNU_SOURCE +#include <getopt.h> + +#include <config.h> + +#include <msglog.h> +#include <wrapper.h> +#include <system.h> + +#include <cmpvers.h> +#include <dlist.h> + +#if defined( HAVE_DIALOG ) +#include <dialog-ui.h> +#endif + +#define PROGRAM_NAME "update-package" + +#include <defs.h> + + +char *program = PROGRAM_NAME; +char *root = NULL, *pkgs_path = NULL, *rempkgs_path = NULL, *remlog_fname = NULL, + *pkg_fname = NULL, *asc_fname = NULL, *pkglog_fname = NULL, *pkglist_fname = NULL, + *tmpdir = NULL, *rtmpdir = NULL, *curdir = NULL, *log_fname = NULL; + +int ask = 0, rqck = 0, gpgck = 0, reinstall = 0, ignore_chrefs_errors = 0; +char *description = NULL; + +int exit_status = EXIT_SUCCESS; /* errors counter */ +char *selfdir = NULL; + +static char *pkgname = NULL, + *pkgver = NULL, + *arch = NULL, + *distroname = NULL, + *distrover = NULL, + *group = NULL, + *short_description = NULL, + *url = NULL, + *license = NULL, + *uncompressed_size = NULL, + *compressed_size = NULL, + *total_files = NULL; + +static char *installed_version = NULL; +static char *installed_group = NULL; + +enum _update_mode { + CONSOLE = 0, + INFODIALOG, + MENUDIALOG +} update_mode = CONSOLE; + +enum _priority { + REQUIRED = 0, /* synonims: REQUIRED | required | REQ | req */ + RECOMMENDED, /* synonims: RECOMMENDED | recommended | REC | rec */ + OPTIONAL, /* synonims: OPTIONAL | optional | OPT | opt */ + SKIP /* synonims: SKIP | skip | SKP | skp */ +} priority = REQUIRED; + +enum _procedure +{ + INSTALL = 0, /* 'install' */ + UPDATE /* 'update' */ +} procedure = UPDATE; + +enum _input_type { + IFMT_PKG = 0, + IFMT_LOG, + + IFMT_UNKNOWN +} input_format = IFMT_PKG; + +char uncompress = '\0'; + +static struct dlist *rdirs = NULL; +static struct dlist *rfiles = NULL; +static struct dlist *rlinks = NULL; + +static struct dlist *dirs = NULL; +static struct dlist *files = NULL; +static struct dlist *links = NULL; + +static void free_list( struct dlist *list ); + + +#define FREE_PKGINFO_VARIABLES() \ + if( pkgname ) { free( pkgname ); } pkgname = NULL; \ + if( pkgver ) { free( pkgver ); } pkgver = NULL; \ + if( arch ) { free( arch ); } arch = NULL; \ + if( distroname ) { free( distroname ); } distroname = NULL; \ + if( distrover ) { free( distrover ); } distrover = NULL; \ + if( group ) { free( group ); } group = NULL; \ + if( short_description ) { free( short_description ); } short_description = NULL; \ + if( description ) { free( description ); } description = NULL; \ + if( url ) { free( url ); } url = NULL; \ + if( license ) { free( license ); } license = NULL; \ + if( uncompressed_size ) { free( uncompressed_size ); } uncompressed_size = NULL; \ + if( compressed_size ) { free( compressed_size ); } compressed_size = NULL; \ + if( total_files ) { free( total_files ); } total_files = NULL; \ + if( installed_version ) { free( installed_version ); } installed_version = NULL; \ + if( installed_group ) { free( installed_group ); } installed_group = NULL + +void free_resources() +{ + if( root ) { free( root ); root = NULL; } + if( pkgs_path ) { free( pkgs_path ); pkgs_path = NULL; } + if( rempkgs_path ) { free( rempkgs_path ); rempkgs_path = NULL; } + if( pkg_fname ) { free( pkg_fname ); pkg_fname = NULL; } + if( asc_fname ) { free( asc_fname ); asc_fname = NULL; } + if( pkglog_fname ) { free( pkglog_fname ); pkglog_fname = NULL; } + if( remlog_fname ) { free( remlog_fname ); remlog_fname = NULL; } + + if( pkglist_fname ) { free( pkglist_fname ); pkglist_fname = NULL; } + + if( rdirs ) { free_list( rdirs ); rdirs = NULL; } + if( rfiles ) { free_list( rfiles ); rfiles = NULL; } + if( rlinks ) { free_list( rlinks ); rlinks = NULL; } + + if( dirs ) { free_list( dirs ); dirs = NULL; } + if( files ) { free_list( files ); files = NULL; } + if( links ) { free_list( links ); links = NULL; } + + if( rtmpdir ) { free( rtmpdir ); rtmpdir = NULL; } + if( curdir ) { free( curdir ); curdir = NULL; } + if( log_fname ) { free( log_fname ); log_fname = NULL; } + + if( selfdir ) { free( selfdir ); selfdir = NULL; } + + FREE_PKGINFO_VARIABLES(); +} + +void usage() +{ + free_resources(); + + fprintf( stdout, "\n" ); + fprintf( stdout, "Usage: %s [options] <package>\n", program ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Update package.\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Options:\n" ); + fprintf( stdout, " -h,--help Display this information.\n" ); + fprintf( stdout, " -v,--version Display the version of %s utility.\n", program ); + fprintf( stdout, " -a,--always-ask Used with menudialog mode: always ask\n" ); + fprintf( stdout, " if a package should be updated regardless\n" ); + fprintf( stdout, " of what the package priority is. Without\n" ); + fprintf( stdout, " this option, if the priority is equal to\n" ); + fprintf( stdout, " REQUIRED, the package is updateded without\n" ); + fprintf( stdout, " asking for confirmation the update.\n" ); + fprintf( stdout, " -c,--check-requires Check package requires before update.\n" ); +#if defined( HAVE_GPG2 ) + fprintf( stdout, " -g,--gpg-verify Verify GPG2 signature. The signature must be\n" ); + fprintf( stdout, " saved in a file whose name is the same as the\n" ); + fprintf( stdout, " package file name, but with the extension '.asc'\n" ); + fprintf( stdout, " and located in the same directory as the package.\n" ); +#endif + fprintf( stdout, " --ignore-chrefs-errors Ignore change references errors (code: 48).\n" ); +#if defined( HAVE_DIALOG ) + fprintf( stdout, " -i,--info-dialog Show package description during update\n" ); + fprintf( stdout, " process using ncurses dialog.\n" ); + fprintf( stdout, " -m,--menu-dialog Ask for confirmation the update,\n" ); + fprintf( stdout, " unless the priority is REQUIRED.\n" ); +#endif + fprintf( stdout, " -l,--pkglist=<FILENAME> Specify a different package list file\n" ); + fprintf( stdout, " to use for read package priority and type\n" ); + fprintf( stdout, " of install procedure. By default used the\n" ); + fprintf( stdout, " '.pkglist' file found in the directory\n" ); + fprintf( stdout, " where source package is placed.\n" ); + fprintf( stdout, " -p,--priority=<required|recommended|optional|skip>\n" ); + fprintf( stdout, " Provides a priority of package instead of\n" ); + fprintf( stdout, " the priority defined in the .pkglist file.\n" ); + fprintf( stdout, " --reinstall Reinstall even if the package is already\n" ); + fprintf( stdout, " installed correctly.\n" ); + fprintf( stdout, " Without this option, the already installed package\n" ); + fprintf( stdout, " will not be updated. The update procedure will be\n" ); + fprintf( stdout, " stopped with success return code.\n" ); + fprintf( stdout, " -r,--root=<DIR> Target rootfs path.\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Parameter:\n" ); + fprintf( stdout, " <package> The PACKAGE tarball.\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Return codes:\n" ); + fprintf( stdout, " ------+-------------------------------------------------------\n" ); + fprintf( stdout, " code | status\n" ); + fprintf( stdout, " ------+-------------------------------------------------------\n" ); + fprintf( stdout, " 30 | package is not installed\n" ); + fprintf( stdout, " ----+----\n" ); + fprintf( stdout, " 41 | update is aborted due to priority=SKIP\n" ); + fprintf( stdout, " 42 | .pkglist appointed the 'install' procedure instead\n" ); + fprintf( stdout, " 43 | pre-update script returned error status\n" ); + fprintf( stdout, " 44 | uncompress process returned error status\n" ); + fprintf( stdout, " 45 | restore-links script returned error status\n" ); + fprintf( stdout, " 46 | post-update script returned error status\n" ); + fprintf( stdout, " 47 | PKGLOG cannot be stored in the Setup Database\n" ); + fprintf( stdout, " 48 | references cannot be updated in Setup Database\n" ); + fprintf( stdout, " 49 | requires cannot be updated in Setup Database\n" ); +#if defined( HAVE_GPG2 ) + fprintf( stdout, " ----+----\n" ); + fprintf( stdout, " 51 | signature verification returned error status\n" ); +#endif + fprintf( stdout, " ------+-------------------------------------------------------\n" ); + fprintf( stdout, "\n" ); + fprintf( stdout, "Upon successful completion zero is returned. Other non-zero return\n" ); + fprintf( stdout, "codes imply incorrect completion of the update procedure.\n" ); + fprintf( stdout, "\n" ); + + exit( EXIT_FAILURE ); +} + +void to_lowercase( char *s ) +{ + char *p = s; + while( p && *p ) { int c = *p; *p = tolower( c ); ++p; } +} + +void to_uppercase( char *s ) +{ + char *p = s; + while( p && *p ) { int c = *p; *p = toupper( c ); ++p; } +} + +void version() +{ + char *upper = NULL; + + upper = (char *)alloca( strlen( program ) + 1 ); + + strcpy( (char *)upper, (const char *)program ); + to_uppercase( upper ); + + fprintf( stdout, "%s (%s) %s\n", program, upper, PROGRAM_VERSION ); + + fprintf( stdout, "Copyright (C) 2019 Andrey V.Kosteltsev.\n" ); + fprintf( stdout, "This is free software. There is NO warranty; not even\n" ); + fprintf( stdout, "for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" ); + fprintf( stdout, "\n" ); + + free_resources(); + exit( EXIT_SUCCESS ); +} + + +static void remove_trailing_slash( char *dir ) +{ + char *s; + + if( !dir || dir[0] == '\0' ) return; + + s = dir + strlen( dir ) - 1; + while( *s == '/' ) + { + *s = '\0'; --s; + } +} + + +static void bind_asc_extention( char *name ) +{ + char *p = NULL, *q = NULL; + + if( (p = rindex( name, '.' )) && (strlen(p) < 5) ) + { + if( !strncmp( p, ".gz", 3 ) || + !strncmp( p, ".bz2", 4 ) || + !strncmp( p, ".xz", 3 ) ) + { + *p = '\0'; + q = rindex( name, '.' ); + if( q && (strlen(q) < 5) && !strncmp( q, ".tar", 4 ) ) + { + *q = '\0'; + } + } + else if( !strncmp( p, ".tar", 4 ) || + !strncmp( p, ".tbz", 4 ) || + !strncmp( p, ".tgz", 4 ) || + !strncmp( p, ".txz", 4 ) ) + { + *p = '\0'; + } + } + + (void)strcat( name, ".asc" ); +} + +//////////////////////////////////////////////////// +//static char *strmode( enum _update_mode mode ) +//{ +// char *p = NULL; +// +// switch( mode ) +// { +// case CONSOLE: +// p = "CONSOLE"; +// break; +// case INFODIALOG: +// p = "INFODIALOG"; +// break; +// case MENUDIALOG: +// p = "MENUDIALOG"; +// break; +// } +// return p; +//} +//////////////////////////////////////////////////// + +static char *strprio( enum _priority priority, int short_name ) +{ + char *p = NULL; + + switch( priority ) + { + case REQUIRED: + p = ( short_name ) ? "REQ" : "required"; + break; + case RECOMMENDED: + p = ( short_name ) ? "REC" : "recommended"; + break; + case OPTIONAL: + p = ( short_name ) ? "OPT" : "optional"; + break; + case SKIP: + p = ( short_name ) ? "SKP" : "skip"; + break; + } + return p; +} + +static char *strproc( enum _procedure procedure ) +{ + char *p = NULL; + + switch( procedure ) + { + case INSTALL: + p = "install"; + break; + case UPDATE: + p = "update"; + break; + } + return p; +} + + +static int _mkdir_p( const char *dir, const mode_t mode ) +{ + char *buf; + char *p = NULL; + struct stat sb; + + if( !dir ) return -1; + + buf = (char *)alloca( strlen( dir ) + 1 ); + strcpy( buf, dir ); + + remove_trailing_slash( buf ); + + /* check if path exists and is a directory */ + if( stat( buf, &sb ) == 0 ) + { + if( S_ISDIR(sb.st_mode) ) + { + return 0; + } + } + + /* mkdir -p */ + for( p = buf + 1; *p; ++p ) + { + if( *p == '/' ) + { + *p = 0; + /* test path */ + if( stat( buf, &sb ) != 0 ) + { + /* path does not exist - create directory */ + if( mkdir( buf, mode ) < 0 ) + { + return -1; + } + } else if( !S_ISDIR(sb.st_mode) ) + { + /* not a directory */ + return -1; + } + *p = '/'; + } + } + + /* test path */ + if( stat( buf, &sb ) != 0 ) + { + /* path does not exist - create directory */ + if( mkdir( buf, mode ) < 0 ) + { + return -1; + } + } else if( !S_ISDIR(sb.st_mode) ) + { + /* not a directory */ + return -1; + } + + return 0; +} + +static void _rm_tmpdir( const char *dirpath ) +{ + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + if( stat( dirpath, &path_sb ) == -1 ) + { + return; /* stat returns error code; errno is set */ + } + + if( S_ISDIR(path_sb.st_mode) == 0 ) + { + return; /* dirpath is not a directory */ + } + + if( (dir = opendir(dirpath) ) == NULL ) + { + return; /* Cannot open direcroty; errno is set */ + } + + len = strlen( dirpath ); + + while( (entry = readdir( dir )) != NULL) + { + + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( stat( path, &entry_sb ) == 0 ) + { + if( S_ISDIR(entry_sb.st_mode) ) + { + /* recursively remove a nested directory */ + _rm_tmpdir( path ); + } + else + { + /* remove a file object */ + (void)unlink( path ); + } + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + + } + + /* remove the devastated directory and close the object of this directory */ + (void)rmdir( dirpath ); + + closedir( dir ); +} + +static char *_mk_tmpdir( void ) +{ + char *buf = NULL, *p, *tmp = "/tmp"; + size_t len = 0, size = 0; + + (void)umask( S_IWGRP | S_IWOTH ); /* octal 022 */ + + /* Get preferred directory for tmp files */ + if( (p = getenv( "TMP" )) != NULL ) { + tmp = p; + } + else if( (p = getenv( "TEMP" )) != NULL ) { + tmp = p; + } + + size = strlen( tmp ) + strlen( DISTRO_NAME ) + strlen( program ) + 12; + + buf = (char *)malloc( size ); + if( !buf ) return NULL; + + len = snprintf( buf, size, (const char *)"%s/%s/%s-%.7u", tmp, DISTRO_NAME, program, getpid() ); + if( len == 0 || len == size - 1 ) + { + free( buf ); return NULL; + } + + _rm_tmpdir( (const char *)&buf[0] ); + + if( _mkdir_p( buf, S_IRWXU | S_IRWXG | S_IRWXO ) == 0 ) + { + return buf; + } + + free( buf ); return NULL; +} + + +void fatal_error_actions( void ) +{ + logmsg( errlog, MSG_NOTICE, "Free resources on FATAL error..." ); + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); +} + +void sigint( int signum ) +{ + (void)signum; + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); +} + +static void set_signal_handlers() +{ + struct sigaction sa; + sigset_t set; + + memset( &sa, 0, sizeof( sa ) ); + sa.sa_handler = sigint; /* TERM, INT */ + sa.sa_flags = SA_RESTART; + sigemptyset( &set ); + sigaddset( &set, SIGTERM ); + sigaddset( &set, SIGINT ); + sa.sa_mask = set; + sigaction( SIGTERM, &sa, NULL ); + sigaction( SIGINT, &sa, NULL ); + + memset( &sa, 0, sizeof( sa ) ); /* ignore SIGPIPE */ + sa.sa_handler = SIG_IGN; + sa.sa_flags = 0; + sigaction( SIGPIPE, &sa, NULL ); + + /* System V fork+wait does not work if SIGCHLD is ignored */ + signal( SIGCHLD, SIG_DFL ); +} + + +static enum _input_type check_input_file( char *uncompress, const char *fname ) +{ + struct stat st; + size_t pkglog_size = 0; + unsigned char buf[8]; + int rc, fd; + + /* SIGNATURES: https://www.garykessler.net/library/file_sigs.html */ + + if( uncompress ) + { + *uncompress = '\0'; + } + + if( stat( fname, &st ) == -1 ) + { + FATAL_ERROR( "Cannot access %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + + pkglog_size = st.st_size; + + if( (fd = open( fname, O_RDONLY )) == -1 ) + { + FATAL_ERROR( "Cannot open %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + + rc = (int)read( fd, (void *)&buf[0], 7 ); + if( rc != 7 ) + { + close( fd ); return IFMT_UNKNOWN; + } + buf[7] = '\0'; + + /* TEXT */ + if( !strncmp( (const char *)&buf[0], "PACKAGE", 7 ) ) + { + close( fd ); return IFMT_LOG; + } + + /* GZ */ + if( buf[0] == 0x1F && buf[1] == 0x8B && buf[2] == 0x08 ) + { + if( uncompress ) { *uncompress = 'x'; } + close( fd ); return IFMT_PKG; + } + + /* BZ2 */ + if( buf[0] == 0x42 && buf[1] == 0x5A && buf[2] == 0x68 ) + { + if( uncompress ) { *uncompress = 'j'; } + close( fd ); return IFMT_PKG; + } + + /* XZ */ + if( buf[0] == 0xFD && buf[1] == 0x37 && buf[2] == 0x7A && + buf[3] == 0x58 && buf[4] == 0x5A && buf[5] == 0x00 ) + { + if( uncompress ) { *uncompress = 'J'; } + close( fd ); return IFMT_PKG; + } + + if( pkglog_size > 262 ) + { + if( lseek( fd, 257, SEEK_SET ) == -1 ) + { + FATAL_ERROR( "Cannot check signature of %s file: %s", basename( (char *)fname ), strerror( errno ) ); + } + rc = (int)read( fd, &buf[0], 5 ); + if( rc != 5 ) + { + FATAL_ERROR( "Cannot read signature of %s file", basename( (char *)fname ) ); + } + /* TAR */ + if( buf[0] == 0x75 && buf[1] == 0x73 && buf[2] == 0x74 && buf[3] == 0x61 && buf[4] == 0x72 ) + { + close( fd ); return IFMT_PKG; + } + } + + close( fd ); return IFMT_UNKNOWN; +} + + +void get_args( int argc, char *argv[] ) +{ +#if defined( HAVE_GPG2 ) +#if defined( HAVE_DIALOG ) + const char* short_options = "hvacgiml:p:r:"; +#else + const char* short_options = "hvacgl:p:r:"; +#endif +#else +#if defined( HAVE_DIALOG ) + const char* short_options = "hvaciml:p:r:"; +#else + const char* short_options = "hvacl:p:r:"; +#endif +#endif + +#define REINSTALL 812 +#define IGNORE_CHREFS_ERRORS 872 + + const struct option long_options[] = + { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'v' }, + { "always-ask", no_argument, NULL, 'a' }, + { "check-requires", no_argument, NULL, 'c' }, +#if defined( HAVE_GPG2 ) + { "gpg-verify", no_argument, NULL, 'g' }, +#endif + { "ignore-chrefs-errors", no_argument, NULL, IGNORE_CHREFS_ERRORS }, +#if defined( HAVE_DIALOG ) + { "info-dialog", no_argument, NULL, 'i' }, + { "menu-dialog", no_argument, NULL, 'm' }, +#endif + { "pkglist", required_argument, NULL, 'l' }, + { "priority", required_argument, NULL, 'p' }, + { "reinstall", no_argument, NULL, REINSTALL }, + { "root", required_argument, NULL, 'r' }, + { NULL, 0, NULL, 0 } + }; + + int ret; + int option_index = 0; + + while( (ret = getopt_long( argc, argv, short_options, long_options, &option_index )) != -1 ) + { + switch( ret ) + { + case 'h': + { + usage(); + break; + } + case 'v': + { + version(); + break; + } + case 'a': + { + ask = 1; + break; + } + case 'c': + { + rqck = 1; + break; + } +#if defined( HAVE_GPG2 ) + case 'g': + { + gpgck = 1; + break; + } +#endif + +#if defined( HAVE_DIALOG ) + case 'i': + { + update_mode = INFODIALOG; + break; + } + case 'm': + { + update_mode = MENUDIALOG; + break; + } +#endif + + case REINSTALL: + { + reinstall = 1; + break; + } + case IGNORE_CHREFS_ERRORS: + { + ignore_chrefs_errors = 1; + break; + } + + case 'p': + { + if( optarg != NULL ) + { + char *match = NULL; + + if( strlen( (const char *)optarg ) > 2 ) + { + to_lowercase( optarg ); + if( (match = strstr( optarg, "req" )) && match == optarg ) { + priority = REQUIRED; + } + else if( (match = strstr( optarg, "rec" )) && match == optarg ) { + priority = RECOMMENDED; + } + + else if( (match = strstr( optarg, "opt" )) && match == optarg ) { + priority = OPTIONAL; + } + else if( (match = strstr( optarg, "sk" )) && match == optarg ) { + priority = SKIP; + } + else { + FATAL_ERROR( "Unknown --priority '%s' value", optarg ); + } + } + else + { + FATAL_ERROR( "Unknown --priority '%s' value", optarg ); + } + } + else + /* option is present but without value */ + usage(); + break; + } + + case 'l': + { + if( optarg != NULL ) + { + pkglist_fname = xstrdup( (const char *)optarg ); + } + else + /* option is present but without value */ + usage(); + break; + } + + case 'r': + { + if( optarg != NULL ) + { + char cwd[PATH_MAX]; + + bzero( (void *)cwd, PATH_MAX ); + if( optarg[0] != '/' && curdir ) + { + /* skip current directory definition './' at start of argument: */ + if( !strncmp( optarg, "./", 2 ) && strncmp( optarg, "..", 2 ) ) + (void)sprintf( cwd, "%s/%s", curdir, optarg + 2 ); + else if( (strlen( optarg ) == 1) && !strncmp( optarg, ".", 1 ) ) + (void)sprintf( cwd, "%s", curdir ); + else + (void)sprintf( cwd, "%s/%s", curdir, optarg ); + root = xstrdup( (const char *)cwd ); + } + else + { + root = xstrdup( (const char *)optarg ); + } + remove_trailing_slash( root ); + } + else + /* option is present but without value */ + usage(); + break; + } + + case '?': default: + { + usage(); + break; + } + } + } + + + if( optind < argc ) + { + struct stat st; + char *buf = NULL; + + bzero( (void *)&st, sizeof( struct stat ) ); + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + /* absolute path to input package: */ + if( argv[optind][0] != '/' && curdir ) + (void)sprintf( buf, "%s/%s", curdir, (const char *)argv[optind] ); + else + (void)strcpy( buf, (const char *)argv[optind] ); + + if( stat( (const char *)&buf[0], &st ) == -1 ) + { + FATAL_ERROR( "Cannot access '%s' file: %s", buf, strerror( errno ) ); + } + + if( S_ISREG(st.st_mode) ) + { + pkg_fname = xstrdup( (const char *)&buf[0] ); + bind_asc_extention( buf ); + asc_fname = xstrdup( (const char *)&buf[0] ); + free( buf ); + } + else + { + FATAL_ERROR( "Input package '%s' is not a regular file", (const char *)argv[optind] ); + } + } + else + { + usage(); + } + + + if( !pkgs_path ) + { + struct stat st; + char *buf = NULL; + int len; + + bzero( (void *)&st, sizeof( struct stat ) ); + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + if( !root ) + { + buf[0] = '/'; buf[1] = '\0'; + root = xstrdup( (const char *)buf ); + } + else + { + len = strlen( root ); + + (void)strcpy( buf, (const char *)root ); + if( buf[ len - 1 ] != '/' ) + { + buf[len] = '/'; buf[len+1] = '\0'; + free( root ); root = xstrdup( (const char *)buf ); + } + } + + if( stat( (const char *)&buf[0], &st ) == -1 ) + { + FATAL_ERROR( "Cannot access '%s' file or directory: %s", buf, strerror( errno ) ); + } + if( !S_ISDIR(st.st_mode) ) + { + FATAL_ERROR( "Defined --root '%s' is not a directory", buf ); + } + + len = strlen( (const char *)buf ); + + (void)strcat( buf, PACKAGES_PATH ); + if( _mkdir_p( buf, S_IRWXU | S_IRWXG | S_IRWXO ) != 0 ) + { + FATAL_ERROR( "Cannot access '/%s' directory", PACKAGES_PATH ); + } + pkgs_path = xstrdup( (const char *)&buf[0] ); + + /********************************************* + Create other directories of Setup Database: + */ + buf[len] = '\0'; + (void)strcat( buf, REMOVED_PKGS_PATH ); + if( _mkdir_p( buf, S_IRWXU | S_IRWXG | S_IRWXO ) != 0 ) + { + FATAL_ERROR( "Cannot access '/%s' directory", REMOVED_PKGS_PATH ); + } + rempkgs_path = xstrdup( (const char *)&buf[0] ); + + buf[len] = '\0'; + (void)strcat( buf, SETUP_PATH ); + if( _mkdir_p( buf, S_IRWXU | S_IRWXG | S_IRWXO ) != 0 ) + { + FATAL_ERROR( "Cannot access '/%s' directory", SETUP_PATH ); + } + + /********************************************* + Allocate memory for Setup LOG File name: + */ + buf[len] = '\0'; + (void)strcat( buf, LOG_PATH ); + (void)strcat( buf, SETUP_LOG_FILE ); + log_fname = xstrdup( (const char *)&buf[0] ); + + free( buf ); + + } /* End if( !pkgs_path ) */ +} + +static void setup_log( char *format, ... ) +{ + FILE *fp = NULL; + + time_t t = time( NULL ); + struct tm tm = *localtime(&t); + + va_list argp; + + if( ! format ) return; + + fp = fopen( (const char *)log_fname, "a" ); + if( !fp ) + { + FATAL_ERROR( "Cannot open /%s%s file", LOG_PATH, SETUP_LOG_FILE ); + } + + fprintf( fp, "[%04d-%02d-%02d %02d:%02d:%02d]: ", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec ); + + va_start( argp, format ); + vfprintf( fp, format, argp ); + fprintf( fp, "\n" ); + + fflush( fp ); + fclose( fp ); +} + +/*********************************************************** + Remove leading spaces and take non-space characters only: + (Especialy for pkginfo lines) + */ +static char *skip_spaces( char *s ) +{ + char *q, *p = (char *)0; + + if( !s || *s == '\0' ) return p; + + p = s; + + while( (*p == ' ' || *p == '\t') && *p != '\0' ) { ++p; } q = p; + while( *q != ' ' && *q != '\t' && *q != '\0' ) { ++q; } *q = '\0'; + + if( *p == '\0' ) return (char *)0; + + return( xstrdup( (const char *)p ) ); +} + + +/******************************* + remove spaces at end of line: + */ +static void skip_eol_spaces( char *s ) +{ + char *p = (char *)0; + + if( !s || *s == '\0' ) return; + + p = s + strlen( s ) - 1; + while( isspace( *p ) ) { *p-- = '\0'; } +} + + +static char *trim( char *s ) +{ + char *p = (char *)0; + + if( !s || *s == '\0' ) return p; + + p = s + strlen( s ) - 1; + while( isspace( *p ) ) { *p-- = '\0'; } + p = s; while( isspace( *p ) ) { ++p; } + + return( p ); +} + + +static char *size_to_string( size_t pkgsize ) +{ + int nd; + double sz = (double)pkgsize / (double)1024; + + char *ret = NULL; + char *tmp = NULL; + + tmp = (char *)malloc( PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + if( sz > (double)1048576 ) + { + sz = sz / (double)1048576; + /* + NOTE: + ---- + Операция округления до одного знака после десятичной точки: sz = round(sz*10.0)/10.0; + здесь не нужна; можно обойтись вычислением количества цифр, выводимых на печать с помощью + формата '%.*g': + + Количество десятичных цифр, необходимое для предстваления целой части числа + 1(одна) + десятичная цифра после десятичной точки. Формат %.*g не будет выводить дробную часть + числа, если после округления, до одного знака после десятичной точки, дробная часть + равна нулю: + */ + nd = (int)ceil(log10(floor(sz) + 1.0)) + 1; + (void)sprintf( (char *)&tmp[0], "%.*gG", nd, sz ); + } + else if( sz > (double)1024 ) + { + sz = sz / (double)1024; + nd = (int)ceil(log10(floor(sz) + 1.0)) + 1; + (void)sprintf( (char *)&tmp[0], "%.*gM", nd, sz ); + } + else + { + nd = (int)ceil(log10(floor(sz) + 1.0)) + 1; + (void)sprintf( (char *)&tmp[0], "%.*gK", nd, sz ); + } + + ret = xstrdup( (const char *)&tmp[0] ); + free( tmp ); + + return ret; +} + +static void read_pkginfo( const char *pkginfo_fname ) +{ + char *ln = NULL; + char *line = NULL; + + FILE *pkginfo = NULL; + + if( pkginfo_fname != NULL ) + { + pkginfo = fopen( (const char *)pkginfo_fname, "r" ); + if( !pkginfo ) + { + FATAL_ERROR( "Cannot open %s file", pkginfo_fname ); + } + } + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + while( (ln = fgets( line, PATH_MAX, pkginfo )) ) + { + char *match = NULL; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( (match = strstr( ln, "pkgname" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) + { + if( pkgname ) { free( pkgname ); } + pkgname = skip_spaces( p ); + } + } + if( (match = strstr( ln, "pkgver" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) + { + if( pkgver ) { free( pkgver ); } + pkgver = skip_spaces( p ); + } + } + if( (match = strstr( ln, "arch" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) + { + if( arch ) { free( arch ); } + arch = skip_spaces( p ); + } + } + if( (match = strstr( ln, "distroname" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) + { + if( distroname ) { free( distroname ); } + distroname = skip_spaces( p ); + } + } + if( (match = strstr( ln, "distrover" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) + { + if( distrover ) { free( distrover ); } + distrover = skip_spaces( p ); + } + } + + if( (match = strstr( ln, "group" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) + { + if( group ) { free( group ); } + group = skip_spaces( p ); + } + } + + if( (match = strstr( ln, "short_description" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) + { + char *b = index( p, '"'), + *e = rindex( p, '"'); + if( b && e && ( b != e ) ) + { + p = ++b; *e = '\0'; + if( short_description ) { free( short_description ); } + short_description = xstrdup( (const char *)p ); + } + } + } + if( (match = strstr( ln, "url" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) + { + if( url ) { free( url ); } + url = skip_spaces( p ); + } + } + if( (match = strstr( ln, "license" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) + { + if( license ) { free( license ); } + license = skip_spaces( p ); + } + } + + if( (match = strstr( ln, "uncompressed_size" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) + { + if( uncompressed_size ) { free( uncompressed_size ); } + uncompressed_size = skip_spaces( p ); + } + } + if( (match = strstr( ln, "total_files" )) && match == ln ) { + char *p = index( match, '=' ) + 1; + if( p != NULL ) + { + if( total_files ) { free( total_files ); } + total_files = skip_spaces( p ); + } + } + } + + free( line ); + + if( !pkgname || !pkgver || !arch || !distroname || !distrover ) + { + FATAL_ERROR( "Invalid input .PKGINFO file" ); + } + + fclose( pkginfo ); +} + + +/*********************************************************** + Find functions: + */ +static void _search_pkglog( const char *dirpath, const char *grp ) +{ + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + char *pname = (char *)dirpath + strlen( root ); /* do not remove leading '/' */ + + if( stat( dirpath, &path_sb ) == -1 ) + { + FATAL_ERROR( "%s: Cannot stat Setup Database or group directory", pname ); + } + + if( S_ISDIR(path_sb.st_mode) == 0 ) + { + FATAL_ERROR( "%s: Setup Database or group is not a directory", pname ); + } + + if( (dir = opendir(dirpath) ) == NULL ) + { + FATAL_ERROR( "Canot access %s directory: %s", pname, strerror( errno ) ); + } + + len = strlen( dirpath ); + + while( (entry = readdir( dir )) != NULL) + { + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( stat( path, &entry_sb ) == 0 ) + { + if( S_ISREG(entry_sb.st_mode) ) + { + char *match = NULL, *name = basename( path ); + + if( (match = strstr( name, pkgname )) && match == name ) + { + /**************************************************************** + Здесь мы еще должны проверить, что найденный пакет не имеет + более длинное имя, которое начинается с имени искомого пакета. + Полагаясь на факт, что версия может начинаться только с цифры, + мы пропускаем символ '-', разделяющий имя и версию пакета, + а затем проверяем начальный символ версии: + */ + if( *(name + strlen( pkgname )) == '-' && isdigit( *(name + strlen( pkgname ) + 1) ) ) + { + remlog_fname = xstrdup( (const char *)path ); + closedir( dir ); + return; + } + } + } + if( S_ISDIR(entry_sb.st_mode) && grp == NULL ) + { + /************************************************************************** + NOTE: + In the Setup Database can be only one package with the same pkgname + but in different groups. For example, the package named 'cairo' + has two instance: libs/cairo-1.14.6 and xlibs/cairo-1.14.6. During + system installation the package libs/cairo-1.14.6 installed first + and then updated by xlibs/cairo-1.14.6 and PKGLOG of libs/cairo-1.14.6 + moved from /var/log/radix/packages to /var/log/radix/removed-packages. + + So here we have to look for the PKGLOG in all group directories: + */ + _search_pkglog( (const char *)path, (const char *)entry->d_name ); + } + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + } + + closedir( dir ); +} + +static char *find_package( void ) +{ + char *ret = NULL; + + _search_pkglog( (const char *)pkgs_path, NULL ); + if( remlog_fname ) + { + ret = remlog_fname; + } + + return ret; +} +/* + Enf of Find functions. + ***********************************************************/ + +/*********************************************************** + check_installed_package(): + --------------------- + + Возвращает: + -1 если пакет установлен, но его версия меньше + запрашиваемого, + 0 если версия установленного и запрашиваемого равны, + 1 если пакет установлен, но его версия больше + запрашиваемого. + + В случае возврата -1 или 1, устанавливается переменная + instaled_version, равная версии существующего пакета. + + Если пакет не установлен, осуществляется выход со статусом 30. + */ +static int check_installed_package( void ) +{ + struct stat st; + char *fname = pkg_fname; + + enum _input_type type = IFMT_UNKNOWN; + char uncompress = '\0'; + + char *requested_version = NULL; + char *requested_group = NULL; + + int ret = 0; + + bzero( (void *)&st, sizeof( struct stat ) ); + + if( stat( (const char *)fname, &st ) == -1 ) + { + FATAL_ERROR( "Cannot access input '%s' file: %s", fname, strerror( errno ) ); + } + + type = check_input_file( &uncompress, fname ); + if( type != IFMT_PKG ) + { + FATAL_ERROR( "Unknown format of input '%s' file", fname ); + } + + if( S_ISREG(st.st_mode) ) + { + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *tmp= NULL, *cmd = NULL; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + (void)sprintf( &tmp[0], "%s", tmpdir ); + if( _mkdir_p( tmp, S_IRWXU | S_IRWXG | S_IRWXO ) != 0 ) + { + FATAL_ERROR( "Cannot get PKGINFO from '%s' file", basename( (char *)fname ) ); + } + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, + "%s/pkginfo -d %s -o pkginfo %s > /dev/null 2>&1", + selfdir, tmp, fname ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot get PKGINFO from %s file", basename( (char *)fname ) ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + if( rc != 0 ) + { + FATAL_ERROR( "Cannot get PKGINFO from '%s' file", basename( (char *)fname ) ); + } + + (void)strcat( tmp, "/.PKGINFO" ); + read_pkginfo( (const char *)&tmp[0] ); + (void)unlink( (const char *)&tmp[0] ); /* :remove unnecessary .PKGINFO file */ + *(strstr( tmp, "/.PKGINFO" )) = '\0'; /* :restore 'tmpdir' in tmp[] buffer */ + + requested_version = xstrdup( (const char *)pkgver ); + requested_group = ( group ) ? xstrdup( (const char *)group ) : NULL; + + fname = NULL; + fname = find_package(); + if( !fname ) + { + if( update_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Update:", basename( pkg_fname ), NULL, NULL, + "\nPackage is not installed.\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nPackage '%s' is not installed.\n\n", basename( pkg_fname ) ); +#endif + } + else + { + fprintf( stdout, "Specified package '%s' is not installed.\n\n", basename( pkg_fname ) ); + } + + exit_status = 30; /* Package is not installed: install */ + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + + exit( exit_status ); + } + + free( cmd ); + free( tmp ); + } + else + { + FATAL_ERROR( "Input %s file is not a regular file", basename( (char *)fname ) ); + } + + + /* Now fname is a name of installed PKGLOG file; check fname again: */ + if( stat( (const char *)fname, &st ) == -1 ) + { + FATAL_ERROR( "Cannot access input '%s' file: %s", fname, strerror( errno ) ); + } + + type = check_input_file( &uncompress, fname ); + if( type != IFMT_LOG ) + { + FATAL_ERROR( "Unknown format of input '%s' file", fname ); + } + + if( S_ISREG(st.st_mode) ) + { + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *tmp= NULL, *cmd = NULL; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + (void)sprintf( &tmp[0], "%s/to-remove", tmpdir ); + if( _mkdir_p( tmp, S_IRWXU | S_IRWXG | S_IRWXO ) != 0 ) + { + FATAL_ERROR( "Cannot get PKGINFO from '%s' file", basename( (char *)fname ) ); + } + rtmpdir = xstrdup( (const char *)&tmp[0] ); + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, + "%s/pkginfo -d %s -o pkginfo,description,restore-links,filelist %s > /dev/null 2>&1", + selfdir, tmp, fname ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot get PKGINFO from %s file", basename( (char *)fname ) ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + if( rc != 0 ) + { + FATAL_ERROR( "Cannot get PKGINFO from '%s' file", basename( (char *)fname ) ); + } + + (void)strcat( tmp, "/.PKGINFO" ); + read_pkginfo( (const char *)&tmp[0] ); + *(strstr( tmp, "/.PKGINFO" )) = '\0'; /* :restore tmpdir in tmp[] buffer */ + + installed_version = xstrdup( (const char *)pkgver ); + installed_group = ( group ) ? xstrdup( (const char *)group ) : NULL; + + free( cmd ); + free( tmp ); + + if( requested_group && installed_group ) + { + if( !(ret = strcmp( (const char *)installed_group, (const char *)requested_group )) ) + { + ret = cmp_version( (const char *)installed_version, (const char *)requested_version ); + } + } + else if( !requested_group && !installed_group ) + { + ret = cmp_version( (const char *)installed_version, (const char *)requested_version ); + } + else if( requested_group ) + { + ret = -1; + } + else + { + ret = 1; + } + + if( requested_version ) { free( requested_version ); } + if( requested_group ) { free( requested_group ); } + } + else + { + FATAL_ERROR( "Input %s file is not a regular file", basename( (char *)fname ) ); + } + + return ret; +} + +static int check_installed_pkg_integrity( void ) +{ + struct stat st; + FILE *fp = NULL; + + char *ln = NULL; + char *line = NULL; + + char *buf = NULL, *tmp = NULL; + + int restore_links = 0; + int ret = 1; + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + /* Check if .RESTORELINKS is present */ + (void)sprintf( &tmp[0], "%s/.RESTORELINKS", rtmpdir ); + bzero( (void *)&st, sizeof( struct stat ) ); + if( (stat( (const char *)&tmp[0], &st ) == 0) && (st.st_size > 8) ) + { + restore_links = 1; + } + + (void)sprintf( &tmp[0], "%s/.FILELIST", rtmpdir ); + fp = fopen( (const char *)&tmp[0], "r" ); + if( !fp ) + { + FATAL_ERROR( "Cannot open .FILELIST file" ); + } + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)line, PATH_MAX ); + + while( (ln = fgets( line, PATH_MAX, fp )) ) + { + int dir = 0; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( *(ln + strlen(ln) - 1) == '/' ) { dir = 1; *(ln + strlen(ln) - 1) = '\0'; } + else { dir = 0; } + + if( !dir ) + { + char *p = rindex( ln, '.' ); + if( p && !strncmp( (const char *)p, ".new", 4 ) ) + { + /************************** + Do not check .new files: + */ + *p = '\0'; + } + } + + (void)sprintf( &buf[0], "%s%s", root, ln ); + bzero( (void *)&st, sizeof( struct stat ) ); + + if( lstat( (const char *)&buf[0], &st ) == -1 ) + { + /* cannot access file list entry */ + ret = 0; continue; + } + + if( dir ) + { + if( S_ISDIR(st.st_mode) == 0 ) + { + /* not a directory */ + ret = 0; continue; + } + } + else + { + if( S_ISREG(st.st_mode) == 0 ) + { + /* not a regular file */ + ret = 0; continue; + } + if( !restore_links ) + { + if( S_ISLNK(st.st_mode) == 0 ) + { + /* not a symbolic link */ + ret = 0; continue; + } + } + } + } /* End of while( file list entry ) */ + fclose( fp ); + + + (void)sprintf( &tmp[0], "%s/.RESTORELINKS", rtmpdir ); + bzero( (void *)&st, sizeof( struct stat ) ); + + if( stat( (const char *)&tmp[0], &st ) == 0 ) + { + fp = fopen( (const char *)&tmp[0], "r" ); + if( !fp ) + { + FATAL_ERROR( "Cannot open .RESTORELINKS file" ); + } + + while( (ln = fgets( line, PATH_MAX, fp )) ) + { + char *match = NULL; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( (match = strstr( ln, "; rm -rf " )) ) + { + char *q = NULL; + char *p = strstr( ln, "cd" ) + 2; + char *f = strstr( ln, "; rm -rf" ) + 8; + + if( !p || !f ) continue; + + while( (*p == ' ' || *p == '\t') && *p != '\0' ) ++p; + while( (*f == ' ' || *f == '\t') && *f != '\0' ) ++f; + + q = p; while( *q != ' ' && *q != '\t' && *q != ';' && *q != '\0' ) ++q; *q = '\0'; + q = f; while( *q != ' ' && *q != '\t' && *q != ';' && *q != '\0' ) ++q; *q = '\0'; + + if( p && f ) + { + (void)sprintf( &buf[0], "%s/%s/%s", root, p, f ); + bzero( (void *)&st, sizeof( struct stat ) ); + + if( lstat( (const char *)&buf[0], &st ) == -1 ) + { + /* cannot access restore links entry */ + ret = 0; continue; + } + + if( S_ISLNK(st.st_mode) == 0 ) + { + /* not a symbolic link */ + ret = 0; continue; + } + } + } + } /* End of while( restore links entry ) */ + fclose( fp ); + } + + free( line ); + free( tmp ); + free( buf ); + + return ret; +} + + +static void read_service_files( void ) +{ + struct stat st; + char *fname = pkg_fname; + + enum _input_type type = IFMT_UNKNOWN; + + bzero( (void *)&st, sizeof( struct stat ) ); + + if( stat( (const char *)fname, &st ) == -1 ) + { + FATAL_ERROR( "Cannot access input '%s' file: %s", fname, strerror( errno ) ); + } + + type = check_input_file( &uncompress, fname ); + if( type != IFMT_PKG ) + { + FATAL_ERROR( "Unknown format of input '%s' file", fname ); + } + + if( S_ISREG(st.st_mode) ) + { + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *tmp= NULL, *cmd = NULL; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + (void)sprintf( &tmp[0], "%s", tmpdir ); + if( _mkdir_p( tmp, S_IRWXU | S_IRWXG | S_IRWXO ) != 0 ) + { + FATAL_ERROR( "Cannot get PKGINFO from '%s' file", basename( (char *)fname ) ); + } + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, + "%s/pkginfo -d %s" + " -o pkginfo,description,requires,restore-links,install-script,filelist" + " %s > /dev/null 2>&1", + selfdir, tmp, fname ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot get PKGINFO from %s file", basename( (char *)fname ) ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + if( rc != 0 ) + { + FATAL_ERROR( "Cannot get PKGINFO from '%s' file", basename( (char *)fname ) ); + } + + (void)strcat( tmp, "/.PKGINFO" ); + read_pkginfo( (const char *)&tmp[0] ); + *(strstr( tmp, "/.PKGINFO" )) = '\0'; /* :restore tmpdir in tmp[] buffer */ + + compressed_size = size_to_string( st.st_size ); + + /****************** + Get PKGLOG file: + */ + len = snprintf( &cmd[0], PATH_MAX, + "%s/pkglog -m -d %s %s > /dev/null 2>&1", + selfdir, tmp, tmp ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot get PKGLOG from %s file", basename( (char *)fname ) ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + if( rc != 0 ) + { + FATAL_ERROR( "Cannot get PKGLOG from '%s' file", basename( (char *)fname ) ); + } + + if( group ) + (void)sprintf( cmd, "%s/%s/%s-%s-%s-%s-%s", tmp, group, pkgname, pkgver, arch, distroname, distrover ); + else + (void)sprintf( cmd, "%s/%s-%s-%s-%s-%s", tmp, pkgname, pkgver, arch, distroname, distrover ); + + bzero( (void *)&st, sizeof( struct stat ) ); + if( stat( (const char *)cmd, &st ) == -1 ) + { + FATAL_ERROR( "Cannot get PKGLOG from '%s' file: %s", basename( (char *)fname ), strerror( errno ) ); + } + + pkglog_fname = xstrdup( (const char *)cmd ); + + /************************************* + Attempt to read packages list file: + */ + { + if( !pkglist_fname ) + { + /***************************************** + Get source packages path if applicable: + */ + (void)strcpy( cmd, (const char *)fname ); + (void)strcpy( tmp, dirname( cmd ) ); + + if( group && !strcmp( group, basename( tmp ) ) ) + (void)strcpy( cmd, (const char *)dirname( tmp ) ); + else + (void)strcpy( cmd, (const char *)tmp ); + + /***************************************** + Save default packages list file name: + */ + (void)strcat( cmd, "/.pkglist" ); + pkglist_fname = xstrdup( (const char *)cmd ); + } + + /************************** + read .pkglist if exists: + */ + bzero( (void *)&st, sizeof( struct stat ) ); + if( (stat( (const char *)pkglist_fname, &st ) == 0) && S_ISREG(st.st_mode) ) + { + char *ln = NULL; + char *line = NULL; + + FILE *pkglist = NULL; + + pkglist = fopen( (const char *)pkglist_fname, "r" ); + if( !pkglist ) + { + FATAL_ERROR( "Cannot open %s file", pkglist_fname ); + } + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + while( (ln = fgets( line, PATH_MAX, pkglist )) ) + { + char *match = NULL; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( (match = strstr( ln, pkgname )) && match == ln ) + { + char *p = NULL; + char *name = NULL, *vers = NULL, *desc = NULL, *ball = NULL, *proc = NULL, *prio = NULL; + + name = ln; + if( (p = index( (const char *)name, ':' )) ) { *p = '\0'; vers = ++p; name = trim( name ); } else continue; + if( (p = index( (const char *)vers, ':' )) ) { *p = '\0'; desc = ++p; vers = trim( vers ); } else continue; + if( (p = index( (const char *)desc, ':' )) ) { *p = '\0'; ball = ++p; desc = trim( desc ); } else continue; + if( (p = index( (const char *)ball, ':' )) ) { *p = '\0'; proc = ++p; ball = trim( ball ); } else continue; + if( (p = index( (const char *)proc, ':' )) ) { *p = '\0'; prio = ++p; proc = trim( proc ); } else continue; + prio = trim( prio ); + + if( name && vers && desc && ball && proc && prio ) + { + char *grp = index( (const char *)ball, '/' ); + if( grp ) + { + *grp = '\0'; grp = ball; grp = trim( grp ); + if( strcmp( group, grp ) ) continue; + } + + /* read priority: */ + if( strlen( (const char*)prio ) > 2 ) + { + char *m = NULL; + + to_lowercase( prio ); + if( (m = strstr( prio, "req" )) && m == prio ) { + priority = REQUIRED; + } + else if( (m = strstr( prio, "rec" )) && m == prio ) { + priority = RECOMMENDED; + } + else if( (m = strstr( prio, "opt" )) && m == prio ) { + priority = OPTIONAL; + } + else if( (m = strstr( prio, "sk" )) && m == prio ) { + priority = SKIP; + } + else { + priority = REQUIRED; + } + } + else + { + priority = REQUIRED; + } + + /* read procedure: */ + if( strlen( (const char*)proc ) > 5 ) + { + char *m = NULL; + + to_lowercase( proc ); + if( (m = strstr( proc, "install" )) && m == proc ) { + procedure = INSTALL; + } + else if( (m = strstr( proc, "update" )) && m == proc ) { + procedure = UPDATE; + } + else { + procedure = UPDATE; + } + } + else + { + procedure = UPDATE; + } + } + + } /* End if( match ) */ + + } /* End of while( ln = fgets() ) */ + + free( line ); + fclose( pkglist ); + + } /* End of reading .pkglist */ + + } /* End of attemption of reading .pkflist file */ + + free( cmd ); + free( tmp ); + + if( priority == SKIP ) + { + exit_status = 41; + + if( update_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + char *tmp = NULL; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + (void)sprintf( &tmp[0], + "\nUpdate procedure is skipped due to specified '%s' priority.\n", + strprio( priority, 0 ) ); + + info_pkg_box( "Update:", pkgname, pkgver, strprio( priority, 0 ), + (const char *)&tmp[0], 5, 0, 0 ); + + free( tmp ); +#else + fprintf( stdout, + "\nUpdate procedure of package '%s-%s' is skipped due to specified '%s' priority.\n\n", + pkgname, pkgver, strprio( priority, 0 ) ); +#endif + } + else + { + fprintf( stdout, + "\nUpdate procedure of package '%s-%s' is skipped due to specified '%s' priority.\n\n", + pkgname, pkgver, strprio( priority, 0 ) ); + } + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + + exit( exit_status ); + } + + if( procedure != UPDATE ) + { + exit_status = 42; + + if( update_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + char *tmp = NULL; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + (void)sprintf( &tmp[0], + "\nUpdate is skipped because the '%s' procedure is specified.\n", + strproc( procedure ) ); + + info_pkg_box( "Update:", pkgname, pkgver, strprio( priority, 0 ), + (const char *)&tmp[0], 6, 0, 0 ); + + free( tmp ); +#else + fprintf( stdout, + "\nUpdate procedure of package '%s-%s' is skipped because the '%s' procedure is specified.\n\n", + pkgname, pkgver, strproc( procedure ) ); +#endif + } + else + { + fprintf( stdout, + "\nUpdate procedure of package '%s-%s' is skipped because the '%s' procedure is specified.\n\n", + pkgname, pkgver, strproc( procedure ) ); + } + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + + exit( exit_status ); + } + + } + else + { + FATAL_ERROR( "Input %s file is not a regular file", basename( (char *)fname ) ); + } +} + + +static void check_requires( void ) +{ + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *cmd = NULL; + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, + "%s/check-requires --root=%s %s > /dev/null 2>&1", + selfdir, root, pkglog_fname ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot check required packages for '%s-%s' package", pkgname, pkgver ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + + free( cmd ); + + if( rc != 0 ) + { + if( update_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Update:", pkgname, pkgver, strprio( priority, 0 ), + "\nPackage requires other packages to be installed.\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nPackage '%s-%s' requires other packages to be installed.\n\n", pkgname, pkgver ); +#endif + } + else + { + fprintf( stdout, "\nPackage '%s-%s' requires other packages to be installed.\n\n", pkgname, pkgver ); + } + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + exit( rc ); + } +} + +/******************************************************** + Read .FILELIST and .RESTORELINKS functions used for + roolback/remove in case post-update errors: + */ +static int __cmp_list_items( const void *a, const void *b ) +{ + if( a && b ) + return strcmp( (const char *)a, (const char *)b ); + else if( a ) + return 1; + else + return -1; +} + +static void __free_list( void *data, void *user_data ) +{ + if( data ) { free( data ); } +} + +static void free_list( struct dlist *list ) +{ + if( list ) { dlist_free( list, __free_list ); } +} + +//////////////////////////////////////////////////// +//static void __print_list( void *data, void *user_data ) +//{ +// int *counter = (int *)user_data; +// +// if( counter ) { fprintf( stdout, "item[%.5d]: %s\n", *counter, (char *)data ); ++(*counter); } +// else { fprintf( stdout, "item: %s\n", (char *)data ); } +//} +// +//static void print_list( struct dlist *list ) +//{ +// int cnt = 0; +// if( list ) { dlist_foreach( list, __print_list, (void *)&cnt ); } +//} +//////////////////////////////////////////////////// + +static void read_filelist( void **d, void **f, const char *path ) +{ + struct stat st; + FILE *fp = NULL; + + char *ln = NULL; + char *line = NULL, *tmp = NULL; + + struct dlist *dirs = (struct dlist *)(*d); + struct dlist *files = (struct dlist *)(*f); + + if( !dirs || !files || !path ) return; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + (void)sprintf( &tmp[0], "%s/.FILELIST", path ); + bzero( (void *)&st, sizeof( struct stat ) ); + if( (stat( (const char *)&tmp[0], &st ) == -1) ) + { + FATAL_ERROR( "Cannot get .FILELIST from '%s' file", basename( (char *)pkglog_fname ) ); + } + + fp = fopen( (const char *)&tmp[0], "r" ); + if( !fp ) + { + FATAL_ERROR( "Cannot open .FILELIST file" ); + } + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)line, PATH_MAX ); + + while( (ln = fgets( line, PATH_MAX, fp )) ) + { + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( *(ln + strlen(ln) - 1) == '/' ) + { + *(ln + strlen(ln) - 1) = '\0'; + (void)sprintf( &tmp[0], "%s%s", (const char *)root, (const char *)ln ); + dirs = dlist_append( dirs, xstrdup( (const char *)&tmp[0] ) ); + } + else + { + (void)sprintf( &tmp[0], "%s%s", (const char *)root, (const char *)ln ); + files = dlist_append( files, xstrdup( (const char *)&tmp[0] ) ); + } + + } /* End of while( file list entry ) */ + + fclose( fp ); + + free( line ); + free( tmp ); +} + +static void read_restorelinks( void **l, const char *path ) +{ + struct stat st; + FILE *fp = NULL; + + char *ln = NULL; + char *line = NULL, *tmp = NULL; + + struct dlist *links = (struct dlist *)(*l); + + if( !links || !path ) return; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + (void)sprintf( &tmp[0], "%s/.RESTORELINKS", path ); + bzero( (void *)&st, sizeof( struct stat ) ); + if( (stat( (const char *)&tmp[0], &st ) == -1) || (st.st_size < 8) ) + { + free( tmp ); + return; + } + + fp = fopen( (const char *)&tmp[0], "r" ); + if( !fp ) + { + FATAL_ERROR( "Cannot open .RESTORELINKS file" ); + } + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)line, PATH_MAX ); + + while( (ln = fgets( line, PATH_MAX, fp )) ) + { + char *match = NULL; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + skip_eol_spaces( ln ); /* remove spaces at end-of-line */ + + if( (match = strstr( ln, "; rm -rf " )) ) + { + char *q = NULL; + char *p = strstr( ln, "cd" ) + 2; + char *f = strstr( ln, "; rm -rf" ) + 8; + + if( !p || !f ) continue; + + while( (*p == ' ' || *p == '\t') && *p != '\0' ) ++p; + while( (*f == ' ' || *f == '\t') && *f != '\0' ) ++f; + + q = p; while( *q != ' ' && *q != '\t' && *q != ';' && *q != '\0' ) ++q; *q = '\0'; + q = f; while( *q != ' ' && *q != '\t' && *q != ';' && *q != '\0' ) ++q; *q = '\0'; + + if( p && f ) + { + (void)sprintf( &tmp[0], "%s%s/%s", (const char *)root, p, f ); + links = dlist_append( links, xstrdup( (const char *)&tmp[0] ) ); + } + } + } /* End of while( restore links entry ) */ + + fclose( fp ); + + free( line ); + free( tmp ); +} +/* + End of read .FILELIST and .RESTORELINKS functions. + ********************************************************/ + +/******************************************************** + Rollback/Remove functions: + */ +static void __remove_link( void *data, void *user_data ) +{ + const char *fname = (const char *)data; + + if( fname ) + { + (void)unlink( fname ); + } +} + +static void __remove_file( void *data, void *user_data ) +{ + const char *fname = (const char *)data; + + if( fname ) + { + char *p = rindex( fname, '.' ); + /* + Если .new файл остался с тем же именем, это значит что до инсталляции + в системе существовал такой же файл но без расширения .new и при этом + он отличался от нового. В данном случае надо удалять только файл .new. + + Если же файл .new не существует, то надо удалять такой же файл но без + расширения .new . + */ + if( p && !strncmp( (const char *)p, ".new", 4 ) ) + { + struct stat st; + + bzero( (void *)&st, sizeof( struct stat ) ); + if( (stat( fname, &st ) == -1) ) *p = '\0'; + } + + (void)unlink( fname ); + } +} + +static int is_dir_empty( const char *dirpath ) +{ + int ret = 0; + + DIR *dir; + char *path; + size_t len; + + struct stat path_sb, entry_sb; + struct dirent *entry; + + if( stat( dirpath, &path_sb ) == -1 ) return ret; /* stat returns error code; errno is set */ + if( S_ISDIR(path_sb.st_mode) == 0 ) return ret; /* dirpath is not a directory */ + if( (dir = opendir(dirpath) ) == NULL ) return ret; /* Cannot open direcroty; errno is set */ + + ret = 1; + + len = strlen( dirpath ); + while( (entry = readdir( dir )) != NULL) + { + /* skip entries '.' and '..' */ + if( ! strcmp( entry->d_name, "." ) || ! strcmp( entry->d_name, ".." ) ) continue; + + /* determinate a full name of an entry */ + path = alloca( len + strlen( entry->d_name ) + 2 ); + strcpy( path, dirpath ); + strcat( path, "/" ); + strcat( path, entry->d_name ); + + if( stat( path, &entry_sb ) == 0 ) + { + ret = 0; + break; + } + /* else { stat() returns error code; errno is set; and we have to continue the loop } */ + } + closedir( dir ); + + return ret; +} + +static void __remove_dir( void *data, void *user_data ) +{ + const char *dname = (const char *)data; + + if( dname && is_dir_empty( (const char *)dname ) ) + { + (void)rmdir( dname ); + } +} + +static void rollback( void ) +{ + /* Try to change CWD to the ROOT directory: */ + (void)chdir( (const char *)root ); + + if( links ) { dlist_foreach( links, __remove_link, NULL ); } + + if( files ) { dlist_foreach( files, __remove_file, NULL ); } + + if( dirs ) + { + dirs = dlist_sort( dirs, __cmp_list_items ); + dirs = dlist_reverse( dirs ); + dlist_foreach( dirs, __remove_dir, NULL ); + } + + /* Try to remove PKGLOG file */ + { + char *tmp = NULL; + + tmp = (char *)malloc( PATH_MAX ); + if( tmp ) + { + const char *fname = basename( (char *)pkglog_fname ); + + bzero( (void *)tmp, PATH_MAX ); + + if( group ) + (void)sprintf( &tmp[0], "%s/%s/%s", pkgs_path, group, fname ); + else + (void)sprintf( &tmp[0], "%s/%s", pkgs_path, fname ); + + (void)unlink( (const char *)&tmp[0] ); + + if( group ) + { + const char *dir = (const char *)dirname( (char *)&tmp[0] ); + if( is_dir_empty( dir ) ) + { + (void)rmdir( dir ); + } + } + free( tmp ); + } + } + + /* Try to change CWD to the CURRENT directory: */ + (void)chdir( (const char *)curdir ); +} + +static void remove_package( void ) +{ + /* Try to change CWD to the ROOT directory: */ + (void)chdir( (const char *)root ); + + if( rlinks ) { dlist_foreach( rlinks, __remove_link, NULL ); } + + if( rfiles ) { dlist_foreach( rfiles, __remove_file, NULL ); } + + if( rdirs ) + { + rdirs = dlist_sort( rdirs, __cmp_list_items ); + rdirs = dlist_reverse( rdirs ); + dlist_foreach( rdirs, __remove_dir, NULL ); + } + + /* Try to change CWD to the CURRENT directory: */ + (void)chdir( (const char *)curdir ); +} + +static void finalize_removal( void ) +{ + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *cmd = NULL, *tmp = NULL; + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + /********************************************* + Decrement references in the Setup Database: + */ + if( installed_group ) + len = snprintf( &cmd[0], PATH_MAX, + "%s/chrefs --operation=dec --destination=%s %s/%s > /dev/null 2>&1", + selfdir, pkgs_path, installed_group, basename( (char *)remlog_fname ) ); + else + len = snprintf( &cmd[0], PATH_MAX, + "%s/chrefs --operation=dec --destination=%s %s > /dev/null 2>&1", + selfdir, pkgs_path, basename( (char *)remlog_fname ) ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot decrement '%s-%s' package references", pkgname, pkgver ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + if( (rc != 0) && !ignore_chrefs_errors ) + { + free( cmd ); + free( tmp ); + + exit_status = 48; + + if( update_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Update:", pkgname, pkgver, NULL, + "\n\\Z1Cannot decrement package references in Setup Database.\\Zn\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nCannot decrement '%s-%s' package references in Setup Database.\n\n", pkgname, pkgver ); +#endif + } + else + { + fprintf( stdout, "\nCannot decrement '%s-%s' package references in Setup Database.\n\n", pkgname, pkgver ); + } + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + exit( exit_status ); + } + + /***************************************************** + Backup PKGLOG file into removed-packages directory: + */ + bzero( (void *)tmp, PATH_MAX ); + + if( installed_group ) + (void)sprintf( &tmp[0], "%s/%s/", rempkgs_path, installed_group ); + else + (void)sprintf( &tmp[0], "%s/", rempkgs_path ); + + if( _mkdir_p( tmp, S_IRWXU | S_IRWXG | S_IRWXO ) != 0 ) + { + FATAL_ERROR( "Cannot access '/%s' directory", REMOVED_PKGS_PATH ); + } + + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, + "mv %s %s > /dev/null 2>&1", + remlog_fname, (const char *)&tmp[0] ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot backup '%s' pkglog file", basename( (char *)remlog_fname ) ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + + free( cmd ); + + if( rc != 0 ) + { + free( tmp ); + + exit_status = 47; + + if( update_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Update:", pkgname, pkgver, NULL, + "\n\\Z1Cannot backup PKGLOG file.\\Zn\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nCannot backup '%s' pkglog file.\n\n", basename( (char *)remlog_fname ) ); +#endif + } + else + { + fprintf( stdout, "\nCannot backup '%s' pkglog file.\n\n", basename( (char *)remlog_fname ) ); + } + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + exit( exit_status ); + } + + /**************************************** + Remove group directory if it is empty: + */ + bzero( (void *)tmp, PATH_MAX ); + + if( installed_group ) + { + (void)sprintf( &tmp[0], "%s/%s/", pkgs_path, installed_group ); + + const char *dir = (const char *)&tmp[0]; + if( is_dir_empty( dir ) ) + { + (void)rmdir( dir ); + } + } + + free( tmp ); +} + +/* + End of rollback/remove functions. + ********************************************************/ + +static void read_description( const char *path, int show_compressed_size ) +{ + struct stat st; + FILE *fp = NULL; + + char *buf = NULL, *tmp = NULL; + char *lp = NULL; + int n = 0; + + char *ln = NULL; + char *line = NULL; + + if( !path ) return; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + buf = (char *)malloc( (size_t)PATH_MAX ); + if( !buf ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)buf, PATH_MAX ); + + (void)sprintf( &tmp[0], "%s/.DESCRIPTION", path ); + bzero( (void *)&st, sizeof( struct stat ) ); + if( (stat( (const char *)&tmp[0], &st ) == -1) || (st.st_size < 8) ) + { + free( tmp ); + return; + } + + fp = fopen( (const char *)&tmp[0], "r" ); + if( !fp ) + { + FATAL_ERROR( "Cannot open .DESCRIPTION file" ); + } + + (void)sprintf( (char *)&buf[0], "%s:", pkgname ); + + line = (char *)malloc( (size_t)PATH_MAX ); + if( !line ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)line, PATH_MAX ); + + lp = (char *)&tmp[0]; + bzero( (void *)tmp, PATH_MAX ); + (void)sprintf( (char *)&tmp[0], "\n" ); + ++lp; + + while( (ln = fgets( line, PATH_MAX, fp )) && n < DESCRIPTION_NUMBER_OF_LINES ) + { + char *match = NULL; + + ln[strlen(ln) - 1] = '\0'; /* replace new-line symbol */ + + if( (match = strstr( (const char *)ln, (const char *)buf )) && match == ln ) /* at start of line only */ + { + int mlen = strlen( match ), plen = strlen( buf ); + int length = ( mlen > plen ) ? (mlen - plen - 1) : 0 ; + + if( length > DESCRIPTION_LENGTH_OF_LINE ) + { + /* WARNING( "Package DESCRIPTION contains lines with length greater than %d characters", DESCRIPTION_LENGTH_OF_LINE ); */ + match[plen + 1 + DESCRIPTION_LENGTH_OF_LINE] = '\0'; /* truncating description line */ + skip_eol_spaces( match ); /* remove spaces at end-of-line */ + } + + match += plen + 1; + if( match[0] != '\0' ) { (void)sprintf( lp, " %s\n", match ); lp += strlen( match ) + 2; } + else { (void)sprintf( lp, "\n" ); ++lp; } + ++n; + } + } /* End of while( ln = fgets() ) */ + + fclose( fp ); + + (void)sprintf( lp, " Uncompressed Size: %s\n", uncompressed_size ); + lp += strlen( uncompressed_size ) + 21; + if( show_compressed_size ) + { + (void)sprintf( lp, " Compressed Size: %s\n", compressed_size ); + lp += strlen( compressed_size ) + 21; + } + + if( description ) { free( description ); description = NULL; } + description = xstrdup( (const char *)&tmp[0] ); + + free( buf ); + free( line ); + free( tmp ); +} + +static int ask_for_update( void ) +{ + int ret = 0; /* continue update */ +#if defined( HAVE_DIALOG ) + /****************************************************** + Ask for update dialog shown only in MENUDIALOG mode + when priority != REQUIRED or --always-ask=yes: + */ + if( (update_mode == MENUDIALOG) && (((priority == REQUIRED) && ask) || (priority != REQUIRED)) ) + { + ret = ask_update_box( "Update:", pkgname, pkgver, strprio( priority, 0 ), + description, 18, 0, 0 ); + } + + if( ret ) + { + info_pkg_box( "Update:", pkgname, pkgver, strprio( priority, 0 ), + "\nUpdate terminated by user.\n", 5, 0, 0 ); + } +#endif + return ret; +} + +static int ask_for_reinstall( void ) +{ + int ret = 0; /* continue update */ +#if defined( HAVE_DIALOG ) + /*************************************************************** + Ask for remove dialog shown only in INFO or MENU DIALOG mode: + */ + if( update_mode == MENUDIALOG ) + { + char *msg = NULL; + + msg = (char *)malloc( (size_t)PATH_MAX ); + if( !msg ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)msg, PATH_MAX ); + + (void)sprintf( &msg[0], "\nThe same version of requested package installed and correct.\n" + "\n\\Z1Re-install this vesion?\\Zn\n" ); + + if( (ret = ask_reinstall_box( "Update:", pkgname, installed_version, (const char *)&msg[0], 9, 0, 0 )) ) + { + info_pkg_box( "Update:", pkgname, installed_version, NULL, + "\nPackage update terminated by user.\n", 5, 0, 0 ); + } + + free( msg ); + } + else if( update_mode == INFODIALOG ) + { + if( !reinstall ) + { + info_pkg_box( "Update:", pkgname, installed_version, NULL, + "\nThe same version of requested package installed and correct.\n", 5, 0, 0 ); + ret = 1; + } + } + else + { + if( !reinstall ) + { + fprintf( stdout, "\nThe same version of '%s-%s' package is installed and correct.\n\n", pkgname, installed_version ); + ret = 1; + } + } +#else + { + if( !reinstall ) + { + fprintf( stdout, "\nThe same version of '%s-%s' package is installed and correct.\n\n", pkgname, installed_version ); + ret = 1; + } + } +#endif + return ret; +} + + +static void show_update_progress( void ) +{ + fprintf( stdout, "\033[2J" ); /* clear screen */ + + if( update_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Update:", pkgname, pkgver, strprio( priority, 0 ), + description, 16, 0, 0 ); +#else + fprintf( stdout, "\n Update: %s-%s [%s]...\n", pkgname, pkgver, strprio( priority, 0 )); + /************************************************* + Ruler: 68 characters + 2 spaces left and right: + + | ----handy-ruler----------------------------------------------------- | */ + fprintf( stdout, "|======================================================================|\n" ); + fprintf( stdout, "%s\n", description ); + fprintf( stdout, "|======================================================================|\n\n" ); + fprintf( stdout, "\n\n\n" ); /* 3 lines up for final message */ +#endif + } + else + { + fprintf( stdout, "\n Update: %s-%s [%s]...\n", pkgname, pkgver, strprio( priority, 0 )); + /************************************************* + Ruler: 68 characters + 2 spaces left and right: + + | ----handy-ruler----------------------------------------------------- | */ + fprintf( stdout, "|======================================================================|\n" ); + fprintf( stdout, "%s\n", description ); + fprintf( stdout, "|======================================================================|\n\n" ); + fprintf( stdout, "\n\n\n" ); /* 3 lines up for final message */ + } +} + + +static void pre_update_routine( void ) +{ + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *cmd = NULL; + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, + "cd %s && %s/.INSTALL pre_update %s %s > /dev/null 2>&1", + root, tmpdir, pkgver, installed_version ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot run pre-update script for '%s-%s' package", pkgname, pkgver ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + + free( cmd ); + + if( rc != 0 ) + { + exit_status = 43; + + if( update_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Update:", pkgname, pkgver, strprio( priority, 0 ), + "\n\\Z1Pre-update script returned error status.\\Zn\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nPre-update script of '%s-%s' returned error status.\n\n", pkgname, pkgver ); +#endif + } + else + { + fprintf( stdout, "\nPre-update script of '%s-%s' returned error status.\n\n", pkgname, pkgver ); + } + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + exit( exit_status ); + } +} + +static const char *fill_decompressor( char *buffer, char compressor ) +{ + switch( compressor ) + { + case 'J': + (void)sprintf( buffer, "xz --threads=%d -dc", get_nprocs() ); + break; + case 'j': + (void)sprintf( buffer, "bzip2 -dc" ); + break; + case 'z': + (void)sprintf( buffer, "gzip -dc" ); + break; + default: + (void)sprintf( buffer, "cat -" ); + break; + } + return (const char *)buffer; +} + +static void uncompress_package( void ) +{ + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *cmd = NULL; + + char decompressor[64]; + + (void)fill_decompressor( (char *)&decompressor[0], uncompress ); + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, + "cat %s | %s | tar -C %s " + "--exclude='.DESCRIPTION' " + "--exclude='.FILELIST' " + "--exclude='.INSTALL' " + "--exclude='.PKGINFO' " + "--exclude='.REQUIRES' " + "--exclude='.RESTORELINKS' " + "-xf - > /dev/null 2>&1", + pkg_fname, decompressor, root ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot uncompress '%s-%s' package", pkgname, pkgver ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + + free( cmd ); + + if( rc != 0 ) + { + exit_status = 44; + + if( update_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Update:", pkgname, pkgver, strprio( priority, 0 ), + "\n\\Z1Cannot uncompress package.\\Zn\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nCannot uncompress '%s-%s' package.\n\n", pkgname, pkgver ); +#endif + } + else + { + fprintf( stdout, "\nCannot uncompress '%s-%s' package.\n\n", pkgname, pkgver ); + } + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + exit( exit_status ); + } +} + +static void restore_links( void ) +{ + struct stat st; + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *cmd = NULL; + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + (void)sprintf( &cmd[0], "%s/.RESTORELINKS", tmpdir ); + bzero( (void *)&st, sizeof( struct stat ) ); + if( (stat( (const char *)&cmd[0], &st ) == -1) || (st.st_size < 8) ) + { + free( cmd ); + return; + } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, + "cd %s && sh %s/.RESTORELINKS > /dev/null 2>&1", + root, tmpdir ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot restore links for '%s-%s' package", pkgname, pkgver ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + + free( cmd ); + + if( rc != 0 ) + { + rollback(); + + exit_status = 45; + + if( update_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Update:", pkgname, pkgver, strprio( priority, 0 ), + "\n\\Z1Restore-links script returned error status.\\Zn\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nRestore-links script of '%s-%s' returned error status.\n\n", pkgname, pkgver ); +#endif + } + else + { + fprintf( stdout, "\nRestore-links script of '%s-%s' returned error status.\n\n", pkgname, pkgver ); + } + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + exit( exit_status ); + } +} + +static void post_update_routine( void ) +{ + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *cmd = NULL; + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, + "cd %s && %s/.INSTALL post_update %s %s > /dev/null 2>&1", + root, tmpdir, pkgver, installed_version ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot run post-update script for '%s-%s' package", pkgname, pkgver ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + + free( cmd ); + + if( rc != 0 ) + { + rollback(); + + exit_status = 46; + + if( update_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Update:", pkgname, pkgver, strprio( priority, 0 ), + "\n\\Z1Post-update script returned error status.\\Zn\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nPost-update script of '%s-%s' returned error status.\n\n", pkgname, pkgver ); +#endif + } + else + { + fprintf( stdout, "\nPost-update script of '%s-%s' returned error status.\n\n", pkgname, pkgver ); + } + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + exit( exit_status ); + } +} + +static void finalize_update( void ) +{ + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *cmd = NULL, *tmp = NULL; + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + if( group ) + (void)sprintf( &tmp[0], "%s/%s/", pkgs_path, group ); + else + (void)sprintf( &tmp[0], "%s/", pkgs_path ); + + if( _mkdir_p( tmp, S_IRWXU | S_IRWXG | S_IRWXO ) != 0 ) + { + FATAL_ERROR( "Cannot access '/%s' directory", PACKAGES_PATH ); + } + + /**************************************** + Store PKGLOG file into Setup Database: + */ + len = snprintf( &cmd[0], PATH_MAX, + "cp %s %s > /dev/null 2>&1", + pkglog_fname, (const char *)&tmp[0] ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot store '%s' pkglog file", basename( (char *)pkglog_fname ) ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + if( rc != 0 ) + { + rollback(); + + free( cmd ); + free( tmp ); + + exit_status = 47; + + if( update_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Update:", pkgname, pkgver, strprio( priority, 0 ), + "\n\\Z1Cannot store PKGLOG file into Setup Database.\\Zn\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nCannot store '%s' pkglog file into Setup Database.\n\n", basename( (char *)pkglog_fname ) ); +#endif + } + else + { + fprintf( stdout, "\nCannot store '%s' pkglog file into Setup Database.\n\n", basename( (char *)pkglog_fname ) ); + } + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + exit( exit_status ); + } + + /********************************************* + Increment references in the Setup Database: + */ + if( group ) + len = snprintf( &cmd[0], PATH_MAX, + "%s/chrefs --operation=inc --destination=%s %s/%s > /dev/null 2>&1", + selfdir, pkgs_path, group, basename( (char *)pkglog_fname ) ); + else + len = snprintf( &cmd[0], PATH_MAX, + "%s/chrefs --operation=inc --destination=%s %s > /dev/null 2>&1", + selfdir, pkgs_path, basename( (char *)pkglog_fname ) ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot increment '%s-%s' package references", pkgname, pkgver ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + + free( cmd ); + + if( (rc != 0) && !ignore_chrefs_errors ) + { + free( tmp ); + + rollback(); + + exit_status = 48; + + if( update_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Update:", pkgname, pkgver, strprio( priority, 0 ), + "\n\\Z1Cannot increment package references in Setup Database.\\Zn\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nCannot increment '%s-%s' package references in Setup Database.\n\n", pkgname, pkgver ); +#endif + } + else + { + fprintf( stdout, "\nCannot increment '%s-%s' package references in Setup Database.\n\n", pkgname, pkgver ); + } + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + exit( exit_status ); + } + + /************************************************* + Remove backup PKGLOG file from removed-packages + directory if exists: + */ + bzero( (void *)tmp, PATH_MAX ); + { + const char *fname = basename( (char *)pkglog_fname ); + + if( group ) + (void)sprintf( &tmp[0], "%s/%s/%s", rempkgs_path, group, fname ); + else + (void)sprintf( &tmp[0], "%s/%s", rempkgs_path, fname ); + + (void)unlink( (const char *)&tmp[0] ); + + if( group ) + { + const char *dir = (const char *)dirname( (char *)&tmp[0] ); + if( is_dir_empty( dir ) ) + { + (void)rmdir( dir ); + } + } + } + + free( tmp ); +} + +#if defined( HAVE_GPG2 ) +static void verify_gpg_signature( void ) +{ + struct stat st; + pid_t p = (pid_t) -1; + int rc; + + int len = 0; + char *cmd = NULL; + + bzero( (void *)&st, sizeof( struct stat ) ); + + /****************************************************************** + Do not try to verify signature if '.asc' file is not accessible: + */ + if( stat( (const char *)asc_fname, &st ) == -1 ) return; + + cmd = (char *)malloc( (size_t)PATH_MAX ); + if( !cmd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cmd, PATH_MAX ); + + len = snprintf( &cmd[0], PATH_MAX, + "gpg2 --verify %s %s > /dev/null 2>&1", + asc_fname, pkg_fname ); + if( len == 0 || len == PATH_MAX - 1 ) + { + FATAL_ERROR( "Cannot verify GPG2 signature of '%s-%s' package", pkgname, pkgver ); + } + p = sys_exec_command( cmd ); + rc = sys_wait_command( p, (char *)NULL, PATH_MAX ); + + free( cmd ); + + if( rc != 0 ) + { + exit_status = 51; + + if( update_mode != CONSOLE ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Update:", pkgname, pkgver, strprio( priority, 0 ), + "\n\\Z1Cannot verify GPG2 signature of the package.\\Zn\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nGPG2 signature verification of '%s-%s' package returned error status.\n\n", pkgname, pkgver ); +#endif + } + else + { + fprintf( stdout, "\nGPG2 signature verification of '%s-%s' package returned error status.\n\n", pkgname, pkgver ); + } + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + exit( exit_status ); + } +} +#endif + + +static void dialogrc( void ) +{ + struct stat st; + char *tmp = NULL; + + tmp = (char *)malloc( (size_t)PATH_MAX ); + if( !tmp ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)tmp, PATH_MAX ); + + /* imagine that the utility is in /sbin directory: */ + (void)sprintf( &tmp[0], "%s/../usr/share/%s/.dialogrc", selfdir, PACKAGE_NAME ); + if( stat( (const char *)&tmp[0], &st ) == -1 ) + { + /* finaly assume that /usr/sbin is a sbindir: */ + (void)sprintf( &tmp[0], "%s/../../usr/share/%s/.dialogrc", selfdir, PACKAGE_NAME ); + } + + setenv( "DIALOGRC", (const char *)&tmp[0], 1 ); + + free( tmp ); +} + +static char *get_curdir( void ) +{ + char *cwd = NULL; + + cwd = (char *)malloc( PATH_MAX ); + if( !cwd ) { FATAL_ERROR( "Cannot allocate memory" ); } + bzero( (void *)cwd, PATH_MAX ); + + if( getcwd( cwd, (size_t)PATH_MAX ) != NULL ) + { + char *p = NULL; + remove_trailing_slash( cwd ); + p = xstrdup( (const char *)cwd ); + free( cwd ); + return p; + } + else + { + FATAL_ERROR( "Cannot get absolute path to current directory" ); + } + + return (char *)NULL; +} + + +/********************************************* + Get directory where this program is placed: + */ +char *get_selfdir( void ) +{ + char *buf = NULL; + ssize_t len; + + buf = (char *)malloc( PATH_MAX ); + if( !buf ) + { + FATAL_ERROR( "Cannot allocate memory" ); + } + + bzero( (void *)buf, PATH_MAX ); + len = readlink( "/proc/self/exe", buf, (size_t)PATH_MAX ); + if( len > 0 && len < PATH_MAX ) + { + char *p = xstrdup( (const char *)dirname( buf ) ); + free( buf ); + return p; + } + FATAL_ERROR( "Cannot determine self directory. Please mount /proc filesystem" ); +} + +void set_stack_size( void ) +{ + const rlim_t stack_size = 16 * 1024 * 1024; /* min stack size = 16 MB */ + struct rlimit rl; + int ret; + + ret = getrlimit( RLIMIT_STACK, &rl ); + if( ret == 0 ) + { + if( rl.rlim_cur < stack_size ) + { + rl.rlim_cur = stack_size; + ret = setrlimit( RLIMIT_STACK, &rl ); + if( ret != 0 ) + { + fprintf(stderr, "setrlimit returned result = %d\n", ret); + FATAL_ERROR( "Cannot set stack size" ); + } + } + } +} + + +int main( int argc, char *argv[] ) +{ + gid_t gid; + + set_signal_handlers(); + + gid = getgid(); + setgroups( 1, &gid ); + + fatal_error_hook = fatal_error_actions; + + selfdir = get_selfdir(); + curdir = get_curdir(); + dialogrc(); + + errlog = stderr; + + program = basename( argv[0] ); + get_args( argc, argv ); + + /* set_stack_size(); */ + + tmpdir = _mk_tmpdir(); + if( !tmpdir ) + { + FATAL_ERROR( "Cannot create temporary directory" ); + } + + + { + int status = 0; + + /********************************************************** + Fill pkginfo data and put or replace pkglog into tmpdir: + */ + status = check_installed_package(); + + read_filelist( (void *)&rdirs, (void *)&rfiles, (const char *)rtmpdir ); + read_restorelinks( (void *)&rlinks, (const char *)rtmpdir ); + read_description( (const char *)rtmpdir, 0 ); + if( status == 0 ) + { + int integrity = check_installed_pkg_integrity(); + if( integrity ) /* not depends on --always-ask option */ + { + /* same version of requested package is already installed and correct: */ + if( ask_for_reinstall() ) + { + /* Terminate update procedure with success return code: */ + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + exit( exit_status ); + } + } + } + } + + /************************************************************ + Getting Service Files, reading pkginfo, preserving pkglog: + */ + read_service_files(); + + if( rqck ) check_requires(); +#if defined( HAVE_GPG2 ) + if( gpgck ) verify_gpg_signature(); +#endif + + read_filelist( (void *)&dirs, (void *)&files, (const char *)tmpdir ); + read_restorelinks( (void *)&links, (const char *)tmpdir ); + + read_description( (const char *)tmpdir, 1 ); + + if( ask_for_update() ) + { + /* Terminate update: */ + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + exit( exit_status ); + } + + /************ + DO REMOVE: + */ + remove_package(); + finalize_removal(); + + show_update_progress(); + + /************ + DO UPDATE: + */ + pre_update_routine(); + uncompress_package(); + restore_links(); + post_update_routine(); + finalize_update(); + + fprintf( stdout, "\033[3A" ); /* move cursor up 3 lines */ + + if( (update_mode != CONSOLE) && (update_mode == MENUDIALOG) ) + { +#if defined( HAVE_DIALOG ) + info_pkg_box( "Update:", pkgname, pkgver, strprio( priority, 0 ), + "\nPackage has been updated.\n", 5, 0, 0 ); +#else + fprintf( stdout, "\nPackage '%s-%s' has been updated.\n\n", pkgname, pkgver ); +#endif + } + else + { + if( (update_mode != INFODIALOG) ) + { + fprintf( stdout, "\nPackage '%s-%s' has been updated.\n\n", pkgname, pkgver ); + } + } + + setup_log( "Package '%s-%s' has been updated", pkgname, pkgver ); + + if( tmpdir ) { _rm_tmpdir( (const char *)tmpdir ); free( tmpdir ); } + free_resources(); + + exit( exit_status ); +} diff --git a/src/wrapper.c b/src/wrapper.c new file mode 100644 index 0000000..2b311aa --- /dev/null +++ b/src/wrapper.c @@ -0,0 +1,75 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <error.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <stdarg.h> +#include <unistd.h> + +#include <msglog.h> + + +char *xstrdup( const char *str ) +{ + char *ret = (char *)NULL; + size_t len; + + if( !str ) return ret; + + len = strlen( str ) + 1; + + ret = (char *)malloc( len ); + if( !ret ) + FATAL_ERROR( "Out of memory, strdup failed (tried to allocate %lu bytes)", (unsigned long)len ); + + ret[len-1] = '\0'; + ret = strncpy( ret, str, len-1 ); + return ret; +} + +void *xmalloc( size_t size ) +{ + void *ret = NULL; + + ret = malloc( size ); + if( !ret ) + { + FATAL_ERROR( "Out of memory, malloc failed (tried to allocate %lu bytes)", (unsigned long)size ); + } + memset( ret, 0, size ); + return ret; +} + +void *xrealloc( void *ptr, size_t size ) +{ + void *ret = NULL; + + ret = realloc( ptr, size ); + if( !ret && !size ) + ret = realloc( ptr, 1 ); + if( !ret ) + FATAL_ERROR( "Out of memory, realloc failed" ); + return ret; +} diff --git a/src/wrapper.h b/src/wrapper.h new file mode 100644 index 0000000..c02ea6c --- /dev/null +++ b/src/wrapper.h @@ -0,0 +1,36 @@ + +/********************************************************************** + + Copyright 2019 Andrey V.Kosteltsev + + Licensed under the Radix.pro License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://radix.pro/licenses/LICENSE-1.0-en_US.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. + + **********************************************************************/ + +#ifndef _WRAPPER_H_ +#define _WRAPPER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + + +extern char *xstrdup( const char *str ); +extern void *xmalloc( size_t size ); +extern void *xrealloc( void *ptr, size_t size ); + + +#ifdef __cplusplus +} /* ... extern "C" */ +#endif + +#endif /* _WRAPPER_H_ */ |