Radix cross Linux Package Tools

Package Tools – is a set of utilities to create, install, and update RcL packages

8 Commits   0 Branches   2 Tags
Index: .dialogrc
===================================================================
--- .dialogrc	(nonexistent)
+++ .dialogrc	(revision 5)
@@ -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
Index: Makefile.am
===================================================================
--- Makefile.am	(nonexistent)
+++ Makefile.am	(revision 5)
@@ -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
Index: btree.c
===================================================================
--- btree.c	(nonexistent)
+++ btree.c	(revision 5)
@@ -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 );
+}
Index: btree.h
===================================================================
--- btree.h	(nonexistent)
+++ btree.h	(revision 5)
@@ -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_ */
Index: check-db-integrity.c
===================================================================
--- check-db-integrity.c	(nonexistent)
+++ check-db-integrity.c	(revision 5)
@@ -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 );
+}
Index: check-package.c
===================================================================
--- check-package.c	(nonexistent)
+++ check-package.c	(revision 5)
@@ -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 );
+}
Index: check-requires.c
===================================================================
--- check-requires.c	(nonexistent)
+++ check-requires.c	(revision 5)
@@ -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 );
+}
Index: chrefs.c
===================================================================
--- chrefs.c	(nonexistent)
+++ chrefs.c	(revision 5)
@@ -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 );
+}
Index: cmpvers.c
===================================================================
--- cmpvers.c	(nonexistent)
+++ cmpvers.c	(revision 5)
@@ -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;
+}
Index: cmpvers.h
===================================================================
--- cmpvers.h	(nonexistent)
+++ cmpvers.h	(revision 5)
@@ -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_ */
Index: defs.h
===================================================================
--- defs.h	(nonexistent)
+++ defs.h	(revision 5)
@@ -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_ */
Index: dialog-ui.c
===================================================================
--- dialog-ui.c	(nonexistent)
+++ dialog-ui.c	(revision 5)
@@ -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();
+    }
+  }
+}
Index: dialog-ui.h
===================================================================
--- dialog-ui.h	(nonexistent)
+++ dialog-ui.h	(revision 5)
@@ -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_ */
Index: dlist.c
===================================================================
--- dlist.c	(nonexistent)
+++ dlist.c	(revision 5)
@@ -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 );
+}
Index: dlist.h
===================================================================
--- dlist.h	(nonexistent)
+++ dlist.h	(revision 5)
@@ -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_ */
Index: install-package.c
===================================================================
--- install-package.c	(nonexistent)
+++ install-package.c	(revision 5)
@@ -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 );
+}
Index: install-pkglist.c
===================================================================
--- install-pkglist.c	(nonexistent)
+++ install-pkglist.c	(revision 5)
@@ -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 );
+}
Index: jsmin.c
===================================================================
--- jsmin.c	(nonexistent)
+++ jsmin.c	(revision 5)
@@ -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;
+}
Index: jsmin.h
===================================================================
--- jsmin.h	(nonexistent)
+++ jsmin.h	(revision 5)
@@ -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_ */
Index: make-package.c
===================================================================
--- make-package.c	(nonexistent)
+++ make-package.c	(revision 5)
@@ -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 );
+}
Index: make-pkglist.c
===================================================================
--- make-pkglist.c	(nonexistent)
+++ make-pkglist.c	(revision 5)
@@ -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 );
+}
Index: make-pkglist.h
===================================================================
--- make-pkglist.h	(nonexistent)
+++ make-pkglist.h	(revision 5)
@@ -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_ */
Index: msglog.c
===================================================================
--- msglog.c	(nonexistent)
+++ msglog.c	(revision 5)
@@ -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 );
+}
Index: msglog.h
===================================================================
--- msglog.h	(nonexistent)
+++ msglog.h	(revision 5)
@@ -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_ */
Index: pkginfo.c
===================================================================
--- pkginfo.c	(nonexistent)
+++ pkginfo.c	(revision 5)
@@ -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 );
+}
Index: pkglist.c
===================================================================
--- pkglist.c	(nonexistent)
+++ pkglist.c	(revision 5)
@@ -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.
+ ***************************************************************/
Index: pkglist.h
===================================================================
--- pkglist.h	(nonexistent)
+++ pkglist.h	(revision 5)
@@ -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_ */
Index: pkglist.html.c
===================================================================
--- pkglist.html.c	(nonexistent)
+++ pkglist.html.c	(revision 5)
@@ -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" );
+}
Index: pkglist.html.v3.c
===================================================================
--- pkglist.html.v3.c	(nonexistent)
+++ pkglist.html.v3.c	(revision 5)
@@ -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" );
+}
Index: pkglog.c
===================================================================
--- pkglog.c	(nonexistent)
+++ pkglog.c	(revision 5)
@@ -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 );
+}
Index: remove-package.c
===================================================================
--- remove-package.c	(nonexistent)
+++ remove-package.c	(revision 5)
@@ -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 );
+}
Index: system.c
===================================================================
--- system.c	(nonexistent)
+++ system.c	(revision 5)
@@ -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;
+}
Index: system.h
===================================================================
--- system.h	(nonexistent)
+++ system.h	(revision 5)
@@ -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_ */
Index: update-package.c
===================================================================
--- update-package.c	(nonexistent)
+++ update-package.c	(revision 5)
@@ -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 );
+}
Index: wrapper.c
===================================================================
--- wrapper.c	(nonexistent)
+++ wrapper.c	(revision 5)
@@ -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;
+}
Index: wrapper.h
===================================================================
--- wrapper.h	(nonexistent)
+++ wrapper.h	(revision 5)
@@ -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_ */
Index: .
===================================================================
--- .	(nonexistent)
+++ .	(revision 5)

Property changes on: .
___________________________________________________________________
Added: svn:ignore
## -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