summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkx <kx@radix.pro>2023-04-11 01:18:34 +0300
committerkx <kx@radix.pro>2023-04-11 01:18:34 +0300
commit11c606a6888dc269ef018359469a7276c3ad8f67 (patch)
tree368294bb7cadcd5c44ccd082187d6a4433401027
parent8c55752ed5b29a22fdab9faaa6ff27b7cafa6791 (diff)
downloadpkgtools-11c606a6888dc269ef018359469a7276c3ad8f67.tar.xz
Version 0.2.1pkgtools-0.2.1
-rw-r--r--.gitignore45
-rw-r--r--.svnignore42
-rw-r--r--Makefile.am10
-rw-r--r--README2
-rw-r--r--README.md146
-rw-r--r--acsite.m456
-rwxr-xr-xbootstrap81
-rw-r--r--configure.ac251
-rwxr-xr-xdoc/autogen-examples/auto-clean.sh27
-rwxr-xr-xdoc/autogen-examples/bootstrap-cross.sh6
-rwxr-xr-xdoc/autogen-examples/bootstrap.sh4
-rwxr-xr-xdoc/autogen-examples/configure-cross.sh22
-rwxr-xr-xdoc/autogen-examples/configure.sh9
-rw-r--r--doc/dialog/dialog-1.3-20190211.patch453
-rw-r--r--doc/dialog/dialog-1.3-20190724.patch467
-rw-r--r--doc/dialog/dialog-1.3-20190728.patch467
-rw-r--r--doc/dialog/dialog-1.3-20190808.patch460
-rw-r--r--doc/dialog/dialog-1.3-20201126.patch462
-rw-r--r--doc/dialog/dialog-1.3-20210117.patch462
-rw-r--r--doc/dialog/dialog-1.3-20210621.patch454
-rw-r--r--src/.dialogrc144
-rw-r--r--src/Makefile.am64
-rw-r--r--src/btree.c1137
-rw-r--r--src/btree.h93
-rw-r--r--src/check-db-integrity.c3241
-rw-r--r--src/check-package.c1511
-rw-r--r--src/check-requires.c2616
-rw-r--r--src/chrefs.c1933
-rw-r--r--src/cmpvers.c110
-rw-r--r--src/cmpvers.h35
-rw-r--r--src/defs.h51
-rw-r--r--src/dialog-ui.c401
-rw-r--r--src/dialog-ui.h59
-rw-r--r--src/dlist.c680
-rw-r--r--src/dlist.h83
-rw-r--r--src/install-package.c2764
-rw-r--r--src/install-pkglist.c2033
-rw-r--r--src/jsmin.c358
-rw-r--r--src/jsmin.h34
-rw-r--r--src/make-package.c1983
-rw-r--r--src/make-pkglist.c3155
-rw-r--r--src/make-pkglist.h36
-rw-r--r--src/msglog.c67
-rw-r--r--src/msglog.h98
-rw-r--r--src/pkginfo.c1573
-rw-r--r--src/pkglist.c2198
-rw-r--r--src/pkglist.h169
-rw-r--r--src/pkglist.html.c1011
-rw-r--r--src/pkglist.html.v3.c992
-rw-r--r--src/pkglog.c1316
-rw-r--r--src/remove-package.c2234
-rw-r--r--src/system.c136
-rw-r--r--src/system.h49
-rw-r--r--src/update-package.c3425
-rw-r--r--src/wrapper.c75
-rw-r--r--src/wrapper.h36
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
diff --git a/README b/README
new file mode 100644
index 0000000..fa88f1c
--- /dev/null
+++ b/README
@@ -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, &current );
+
+ 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 &#8211; 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&nbsp;&nbsp;<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> &#8211; 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\">&#169; %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 &#8211; 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&nbsp;&nbsp;<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> &#8211; 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\">&#169; %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_ */