polserver/pol-core/plib/pkg.cpp

515 lines
14 KiB
C++

/** @file
*
* @par History
*/
#include "pkg.h"
#include "pol_global_config.h"
#include <algorithm>
#include <stdlib.h>
#include "../clib/cfgelem.h"
#include "../clib/cfgfile.h"
#include "../clib/clib.h"
#include "../clib/dirlist.h"
#include "../clib/fileutil.h"
#include "../clib/logfacility.h"
#include "../clib/passert.h"
#include "../clib/stlutil.h"
#include "../clib/strutil.h"
#include "systemstate.h"
namespace Pol
{
namespace Plib
{
bool Package::provides_system_home_page() const
{
return provides_system_home_page_;
}
Package* find_package( const std::string& pkgname )
{
auto itr = systemstate.packages_byname.find( pkgname );
if ( itr != systemstate.packages_byname.end() )
{
return ( *itr ).second;
}
else
{
return nullptr;
}
}
void remove_package( Package* pkg )
{
auto last = std::remove( systemstate.packages.begin(), systemstate.packages.end(), pkg );
systemstate.packages.erase( last, systemstate.packages.end() );
auto itr = systemstate.packages_byname.begin();
while ( itr != systemstate.packages_byname.end() )
{
if ( itr->second == pkg )
{
systemstate.packages_byname.erase( itr );
break;
}
++itr;
}
}
void compare_versions( const std::string& verleft, const std::string& verright, bool& isgreater,
bool& isequal )
{
isgreater = isequal = false;
const char* vneed = verright.c_str();
const char* vhave = verleft.c_str();
while ( ( vneed != nullptr && vneed[0] != '\0' ) || ( vhave != nullptr && vhave[0] != '\0' ) )
{
char* new_vneed = nullptr;
char* new_vhave = nullptr;
unsigned int vneedpart, vhavepart;
vneedpart = ( vneed != nullptr ) ? strtoul( vneed, &new_vneed, 0 ) : 0;
vhavepart = ( vhave != nullptr ) ? strtoul( vhave, &new_vhave, 0 ) : 0;
if ( vhavepart > vneedpart )
{
isgreater = true;
return;
}
else if ( vhavepart < vneedpart )
{
return;
}
else // same, so check the next one
{
vneed = new_vneed;
if ( vneed && *vneed )
++vneed;
vhave = new_vhave;
if ( vhave && *vhave )
++vhave;
}
}
isequal = true;
}
bool check_version2( const std::string& version_have, const std::string& version_need )
{
bool isgreater;
bool isequal;
compare_versions( version_have, version_need, isgreater, isequal );
return ( isequal || isgreater );
}
void test_check_version()
{ // have // need
passert( check_version2( "0", "0" ) == true );
passert( check_version2( "1", "0" ) == true );
passert( check_version2( "0", "1" ) == false );
passert( check_version2( "0.5", "1" ) == false );
passert( check_version2( "0.5", "0" ) == true );
passert( check_version2( "1.2", "1.12" ) == false );
passert( check_version2( "1.12", "1.2" ) == true );
passert( check_version2( "1.2.3", "1" ) == true );
passert( check_version2( "1.1", "1.2.3" ) == false );
passert( check_version2( "1.3", "1.2.3" ) == true );
}
PackageList::PackageList( Clib::ConfigElem& elem, const char* tag )
{
std::string tmp;
while ( elem.remove_prop( tag, &tmp ) )
{
Clib::mklowerASCII( tmp );
ISTRINGSTREAM is( tmp );
Elem _elem;
if ( is >> _elem.pkgname )
{
if ( !( is >> _elem.version ) )
_elem.version = "0";
elems.push_back( _elem );
}
}
}
size_t PackageList::sizeEstimate() const
{
size_t size = sizeof( PackageList );
for ( const auto& elem : elems )
size += elem.pkgname.capacity() + elem.version.capacity();
return size;
}
Package::Package( const std::string& pkg_dir, Clib::ConfigElem& elem )
: dir_( pkg_dir ),
name_( elem.remove_string( "Name" ) ),
version_( elem.remove_string( "Version", "0" ) ),
core_required_( 0 ),
requires_( elem, "Requires" ),
conflicts_( elem, "Conflicts" ),
replaces_( elem, "Replaces" ),
provides_system_home_page_( elem.remove_bool( "ProvidesSystemHomePage", false ) )
{
Clib::mklowerASCII( name_ );
// CoreRequired can either be a number (94,,95 etc)
// or a version string (POL095-2003-01-28)
std::string tmp = elem.read_string( "CoreRequired", "0" );
if ( isdigit( tmp[0] ) )
{
// the first kind - a number.
core_required_ = elem.remove_ushort( "CoreRequired", 0 );
}
else
{
core_versionstring_required_ = elem.remove_string( "CoreRequired" );
}
}
std::string Package::desc() const
{
return name() + " (" + dir() + ")";
}
bool Package::check_replacements() const
{
bool any = false;
for ( const auto& elem : replaces_.elems )
{
Package* found = find_package( elem.pkgname );
if ( found != nullptr )
{
any = true;
INFO_PRINT << "Package " << desc() << " replaces package " << found->desc() << "\n";
remove_package( found );
delete found;
found = nullptr;
}
}
return any;
}
void Package::check_dependencies() const
{
if ( core_required_ )
{
if ( core_required_ > POL_VERSION ) // TODO: use a more fine grained check here
{
ERROR_PRINT << "Error in package " << desc() << ":\n"
<< " Core version " << core_required_ << " is required, but version "
<< POL_VERSION << " is running.\n";
throw std::runtime_error( "Package requires a newer core version" );
}
}
else if ( !core_versionstring_required_.empty() )
{
int cmp = stricmp( POL_VERSION_ID, core_versionstring_required_.c_str() );
if ( cmp < 0 )
{
ERROR_PRINT << "Error in package " << desc() << ":\n"
<< " Core version " << core_versionstring_required_
<< " is required, but version " << POL_VERSION_ID << " is running.\n";
throw std::runtime_error( "Package requires a newer core version" );
}
}
for ( const auto& elem : requires_.elems )
{
Package* found = find_package( elem.pkgname );
if ( found == nullptr )
{
ERROR_PRINT << "Error in package '" << name_ << "' (" << dir_ << "):\n"
<< " Package '" << elem.pkgname << "' is required, but is not installed.\n";
throw std::runtime_error( "Package dependency error" );
}
else
{
if ( !check_version2( found->version_, elem.version ) )
{
ERROR_PRINT << "Error in package '" << name_ << "' (" << dir_ << "):\n"
<< " Package '" << elem.pkgname << "' version " << elem.version
<< " is required, but version " << found->version_ << " was found\n";
throw std::runtime_error( "Package dependency error" );
}
}
}
}
void Package::check_conflicts() const
{
for ( const auto& elem : conflicts_.elems )
{
Package* found = find_package( elem.pkgname );
if ( found != nullptr )
{
ERROR_PRINT << "Error in package " << desc() << ":\n"
<< " Package conflicts with package " << found->desc() << "\n";
throw std::runtime_error( "Package dependency error" );
}
}
}
size_t Package::estimateSize() const
{
size_t size = dir_.capacity() + name_.capacity() + version_.capacity() +
sizeof( unsigned short ) /*core_required*/
+ core_versionstring_required_.capacity() + requires_.sizeEstimate() +
conflicts_.sizeEstimate() + replaces_.sizeEstimate() +
sizeof( bool ) /*provides_system_home_page_*/
;
return size;
}
void load_package( const std::string& pkg_dir, Clib::ConfigElem& elem, bool quiet )
{
std::unique_ptr<Package> pkg( new Package( pkg_dir, elem ) );
Package* existing_pkg = find_package( pkg->name() );
if ( existing_pkg != nullptr )
{
bool isgreater, isequal;
compare_versions( pkg->version(), existing_pkg->version(), isgreater, isequal );
if ( isgreater )
{
// replace existing package with newer version
if ( !quiet )
INFO_PRINT << "Replacing package " << existing_pkg->desc() << " version "
<< existing_pkg->version() << " with version " << pkg->version() << " found in "
<< pkg->dir() << "\n";
remove_package( existing_pkg );
delete existing_pkg;
existing_pkg = nullptr;
}
else if ( isequal )
{
ERROR_PRINT << "Error in package " << pkg->desc() << ":\n"
<< " Package by same name already found in " << existing_pkg->desc() << "\n";
throw std::runtime_error( "Duplicate package found" );
}
else
{
// skip this package, its version is older
if ( !quiet )
INFO_PRINT << "Skipping package " << pkg->desc() << " version " << pkg->version()
<< " because version " << existing_pkg->version() << " was already found in "
<< existing_pkg->dir() << "\n";
return;
}
}
systemstate.packages.push_back( pkg.get() );
Package* ppkg = pkg.release();
systemstate.packages_byname.insert( PackagesByName::value_type( ppkg->name(), ppkg ) );
}
void load_packages( const std::string& basedir, bool quiet )
{
for ( Clib::DirList dl( basedir.c_str() ); !dl.at_end(); dl.next() )
{
std::string dirname = dl.name();
if ( dirname[0] == '.' )
continue;
if ( dirname == "template" )
continue;
std::string pkg_dir = basedir + dirname + "/";
std::string pkg_cfg = pkg_dir + "pkg.cfg";
if ( Clib::FileExists( pkg_cfg.c_str() ) )
{
Clib::ConfigFile cf( pkg_cfg.c_str() );
Clib::ConfigElem elem;
cf.readraw( elem );
std::string enabled_pkg = pkg_dir + "enabled.pkg";
std::string disabled_pkg = pkg_dir + "disabled.pkg";
if ( ( elem.remove_bool( "Enabled" ) == true || Clib::FileExists( enabled_pkg.c_str() ) ) &&
!Clib::FileExists( disabled_pkg.c_str() ) )
{
if ( !quiet )
INFO_PRINT << "Loading package in " << pkg_dir << "\n";
load_package( pkg_dir, elem, quiet );
load_packages( pkg_dir, quiet );
}
}
else
{
load_packages( pkg_dir, quiet );
}
}
}
void check_deps_for_package( const Package* pkg )
{
pkg->check_dependencies();
pkg->check_conflicts();
}
void replace_packages()
{
bool done;
do
{
done = true;
for ( const auto& pkg : systemstate.packages )
{
bool change = pkg->check_replacements();
if ( change )
{
done = false;
break;
}
}
} while ( !done );
}
void check_package_deps()
{
for ( const auto& pkg : systemstate.packages )
check_deps_for_package( pkg );
}
void load_packages( bool quiet )
{
test_check_version();
load_packages( "pkg/", quiet );
if ( Clib::FileExists( "config/pkgroots.cfg" ) )
{
Clib::ConfigFile cf( "config/pkgroots.cfg", "PackageRoot" );
Clib::ConfigElem elem;
while ( cf.read( elem ) )
{
std::string dir;
while ( elem.remove_prop( "dir", &dir ) )
{
dir = Clib::normalized_dir_form( dir );
if ( !quiet )
INFO_PRINT << "Searching for packages under " << dir << "\n";
load_packages( dir.c_str(), quiet );
}
}
}
replace_packages();
check_package_deps();
// sort pkg vector by name, so e.g. startup order is in a defined and maybe also expected order.
std::sort(
systemstate.packages.begin(), systemstate.packages.end(),
[]( const Package* pkg1, const Package* pkg2 ) { return pkg1->name() < pkg2->name(); } );
}
bool pkgdef_split( const std::string& spec, const Package* inpkg, const Package** outpkg,
std::string* path )
{
if ( spec[0] == ':' )
{
if ( spec[1] == ':' ) // '::corefile' -- a core file
{
*outpkg = nullptr;
*path = spec.substr( 2, std::string::npos );
}
else // ':pkgname:pkgfile' -- a packaged file
{
std::string::size_type second_colon = spec.find( ':', 2 );
if ( second_colon != std::string::npos )
{
std::string pkgname = spec.substr( 1, second_colon - 1 );
std::string pkgfile = spec.substr( second_colon + 1, std::string::npos );
Package* dstpkg = find_package( pkgname );
if ( dstpkg != nullptr )
{
*outpkg = dstpkg;
*path = pkgfile;
}
else
{
ERROR_PRINT << "Unable to find package '" << pkgname << "'\n";
return false;
}
}
else
{
ERROR_PRINT << "Poorly formed packagefile descriptor: '" << spec << "'\n";
return false;
}
}
}
else
{
*outpkg = inpkg;
*path = spec;
}
return true;
}
void load_packaged_cfgs( const char* cfgname, const char* taglist,
void ( *loadentry )( const Package*, Clib::ConfigElem& ) )
{
for ( const auto& pkg : systemstate.packages )
{
std::string filename = GetPackageCfgPath( pkg, cfgname );
if ( Clib::FileExists( filename.c_str() ) )
{
Clib::ConfigFile cf( filename.c_str(), taglist );
Clib::ConfigElem elem;
while ( cf.read( elem ) )
{
loadentry( pkg, elem );
}
}
}
}
void load_all_cfgs( const char* cfgname, const char* taglist,
void ( *loadentry )( const Package*, Clib::ConfigElem& ) )
{
std::string filename = std::string( "config/" ) + cfgname;
if ( Clib::FileExists( filename ) )
{
Clib::ConfigFile cf( filename.c_str(), taglist );
Clib::ConfigElem elem;
while ( cf.read( elem ) )
{
loadentry( nullptr, elem );
}
}
load_packaged_cfgs( cfgname, taglist, loadentry );
}
std::string GetPackageCfgPath( const Package* pkg, const std::string& filename )
{
std::string filepath;
if ( pkg == nullptr )
{ // If no package is sent, assume pol/config/file.xxx
filepath = "config/" + filename;
}
else
{ // ** Going to save this feature for 097 **
// With packages, first try for /pkg/config/file.xxx
filepath = pkg->dir() + "config/" + filename;
if ( !Clib::FileExists( filepath ) )
{
// Lastly, assume /pkg/file.xxx
filepath = pkg->dir() + filename;
}
}
return filepath;
}
} // namespace Plib
} // namespace Pol