diff options
author | Enrico Zini <enrico@enricozini.org> | 2009-09-29 15:08:02 +0100 |
---|---|---|
committer | Enrico Zini <enrico@enricozini.org> | 2009-09-29 15:08:02 +0100 |
commit | 006621455482edd8abf46a05292120745f99f7ec (patch) | |
tree | ff1318ccc409a3dd0519035ce2949ac8a48e969e /ept/apt | |
download | libept-006621455482edd8abf46a05292120745f99f7ec.tar.gz |
Initial import
Diffstat (limited to 'ept/apt')
-rw-r--r-- | ept/apt/apt.cc | 654 | ||||
-rw-r--r-- | ept/apt/apt.h | 284 | ||||
-rw-r--r-- | ept/apt/apt.test.h | 192 | ||||
-rw-r--r-- | ept/apt/packagerecord.cc | 118 | ||||
-rw-r--r-- | ept/apt/packagerecord.h | 176 | ||||
-rw-r--r-- | ept/apt/packagerecord.test.h | 138 | ||||
-rw-r--r-- | ept/apt/recordparser.cc | 170 | ||||
-rw-r--r-- | ept/apt/recordparser.h | 95 | ||||
-rw-r--r-- | ept/apt/recordparser.test.h | 228 | ||||
-rw-r--r-- | ept/apt/version.cc | 87 | ||||
-rw-r--r-- | ept/apt/version.h | 94 | ||||
-rw-r--r-- | ept/apt/version.test.h | 136 |
12 files changed, 2372 insertions, 0 deletions
diff --git a/ept/apt/apt.cc b/ept/apt/apt.cc new file mode 100644 index 0000000..159b83a --- /dev/null +++ b/ept/apt/apt.cc @@ -0,0 +1,654 @@ +/** \file + * High-level front-end to libapt-pkg, as a data provider for the ept framework. + */ + +/* + * Copyright (C) 2007,2008 Enrico Zini <enrico@enricozini.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <ept/apt/apt.h> + +#include <apt-pkg/error.h> +#include <apt-pkg/init.h> +#include <apt-pkg/progress.h> +#include <apt-pkg/sourcelist.h> +#include <apt-pkg/pkgcachegen.h> +#include <apt-pkg/policy.h> +#include <apt-pkg/cachefile.h> + +#include <wibble/sys/fs.h> +#include <sys/stat.h> + +#include <vector> +#include <algorithm> + +#include <iostream> + +using namespace std; + +namespace ept { +namespace apt { + +static time_t aptTimestamp() +{ + namespace wfs = wibble::sys::fs; + + std::auto_ptr<struct stat> st = wfs::stat( + _config->FindFile( "Dir::Cache::pkgcache" ) ); + time_t t1 = st.get() == NULL ? 0 : st->st_mtime; + + std::auto_ptr<struct stat> st1 = wfs::stat( + _config->FindFile( "Dir::State::status" ) ); + time_t t2 = st1.get() == NULL ? 0 : st1->st_mtime; + + return t1 > t2 ? t1 : t2; +} + +Exception::Exception(const std::string& context) throw () + : Generic(context) +{ + // Concatenate all errors and warnings found + string err; + while (!_error->empty()) + { + bool type = _error->PopMessage(err); + if (type) + m_message += "E: " + err + "\n"; + else + m_message += "W: " + err + "\n"; + } +} + +static void aptInit () +{ + if (_config->FindB("Initialized")) + return; + + if (!pkgInitConfig (*_config)) + throw Exception("initialising apt configuration"); + + _config->Set("Initialized", 1); + + /* + _config->Set("Dir", CACHE_DIR); + _config->Set("Dir::Cache", "cache"); + _config->Set("Dir::State", "state"); + _config->Set("Dir::Etc", "etc"); + _config->Set("Dir::State::status", CACHE_DIR "dpkg-status"); + */ + if (!pkgInitSystem (*_config, _system)) + throw Exception("initialising apt system"); +} + +struct AptImplementation +{ + pkgSourceList* m_list; + MMap *m; + OpProgress progress; + pkgCache* m_cache; + pkgPolicy* m_policy; + pkgCacheFile* m_depcache; + time_t m_open_timestamp; + + AptImplementation() : m_list(0), m(0), m_cache(0), m_policy(0), m_depcache(0), m_open_timestamp(0) + { + // Init the apt library if needed + aptInit(); + + m_open_timestamp = aptTimestamp(); + + m_list = new pkgSourceList; + if (!m_list->ReadMainList()) + throw Exception("reading list of sources"); + + bool res = pkgMakeStatusCache(*m_list, progress, &m, true); + progress.Done(); + if (!res) + throw Exception("Reading the package lists or status file"); + + m_cache = new pkgCache(m); + m_policy = new pkgPolicy(m_cache); + if (!ReadPinFile(*m_policy)) + throw Exception("Reading the policy pin file"); + } + + ~AptImplementation() + { + if (m_depcache) delete m_depcache; + if (m_policy) delete m_policy; + if (m_cache) delete m_cache; + if (m) delete m; + if (m_list) delete m_list; + } + + pkgCache& cache() + { + return *m_cache; + } + + pkgPolicy& policy() + { + return *m_policy; + } + + pkgCacheFile& depcache() + { + if (!m_depcache) + { + m_depcache = new pkgCacheFile; + if (!m_depcache->Open(progress, false)) + throw Exception("Opening the cache file"); + } + return *m_depcache; + } +}; + +// Sort a version list by package file locality +bool localityCompare(const pkgCache::VerFile* a, const pkgCache::VerFile* b) +{ + if (a == 0 && b == 0) + return false; + if (a == 0) + return true; + if (b == 0) + return false; + + if (a->File == b->File) + return a->Offset < b->Offset; + return a->File < b->File; +} + +// Iterate records using the algorithm used by apt-cache dumpavail +struct RecordIteratorImpl +{ + mutable int _ref; + AptImplementation& apt; + vector<pkgCache::VerFile*> vflist; + pkgCache::PkgFileIterator lastFile; + FileFd file; + size_t lastOffset; + + RecordIteratorImpl(AptImplementation& apt) : _ref(0), apt(apt), vflist(0) + { + // We already have an estimate of how many versions we're about to find + vflist.reserve(apt.cache().HeaderP->PackageCount + 1); + + // Populate the vector of versions to print + for (pkgCache::PkgIterator pi = apt.cache().PkgBegin(); !pi.end(); ++pi) + { + if (pi->VersionList == 0) + continue; + + /* Get the candidate version or fallback on the installed version, + * as usual */ + pkgCache::VerIterator vi = apt.policy().GetCandidateVer(pi); + if (vi.end() == true) + { + if (pi->CurrentVer == 0) + continue; + vi = pi.CurrentVer(); + } + + // Choose a valid file that contains the record for this version + pkgCache::VerFileIterator vfi = vi.FileList(); + for ( ; !vfi.end(); ++vfi) + if ((vfi.File()->Flags & pkgCache::Flag::NotSource) == 0) + break; + + // Handle packages whose candidate version is currently installed + // from outside the archives (like from a locally built .deb + if (vfi.end() == true) + { + for (pkgCache::VerIterator cur = pi.VersionList(); cur.end() != true; cur++) + { + for (vfi = cur.FileList(); vfi.end() == false; vfi++) + { + if ((vfi.File()->Flags & pkgCache::Flag::NotSource) == 0) + { + vfi = vi.FileList(); + break; + } + } + + if (vfi.end() == false) + break; + } + } + if (!vfi.end()) + vflist.push_back(vfi); + } + + //cerr << vflist.size() << " versions found" << endl; + + sort(vflist.begin(), vflist.end(), localityCompare); + + //for (size_t i = 0; i < vflist.size(); ++i) + //{ + // pkgCache::PkgFileIterator fi(apt.cache(), vflist[i]->File + apt.cache().PkgFileP); + // cerr << i << ": " << fi.FileName() << ":" << vflist[i]->Offset << "-" << vflist[i]->Size << endl; + //} + //cerr << "Done indexing." << endl; + } + + ~RecordIteratorImpl() + { + if (file.IsOpen()) + file.Close(); + } + + void ref() { ++_ref; } + bool unref() { return --_ref == 0; } + + size_t size() { return vflist.size(); } + + string record(size_t idx) + { + //cerr << "Access record " << idx << endl; + //cerr << "lastfile: " << (lastFile.Cache() != 0) << endl; + // We can't reuse the file that was already open: open the new one + if ((lastFile.Cache() == 0) || vflist[idx]->File + apt.cache().PkgFileP != lastFile) + { + //cerr << "Needs open/reopen" << endl; + lastFile = pkgCache::PkgFileIterator(apt.cache(), vflist[idx]->File + apt.cache().PkgFileP); + if (!lastFile.IsOk()) + throw Exception(string("Reading the data record for a package from file ") + lastFile.FileName()); + //cerr << "Ok for " << lastFile.FileName() << endl; + if (file.IsOpen()) + file.Close(); + if (!file.Open(lastFile.FileName(), FileFd::ReadOnly)) + throw Exception(string("Opening file ") + lastFile.FileName()); + //cerr << "Opened " << lastFile.FileName() << endl; + lastOffset = 0; + } + + //cerr << "Reading from " << lastFile.FileName() << ":" << vflist[idx]->Offset << "-" << vflist[idx]->Size << " (lastOffset: " << lastOffset << ")" << endl; + + // If we start near were we ended, avoid a seek and enlarge the read a bit + size_t slack = vflist[idx]->Offset - lastOffset; + //cerr << "Slack: " << slack << endl; + if (slack > 8) + { + //cerr << "Slack too big: seek to " << vflist[idx]->Offset << endl; + slack = 0; + if (!file.Seek(vflist[idx]->Offset)) + throw Exception(string("Cannot seek to package record in file ") + lastFile.FileName()); + } + + char buffer[vflist[idx]->Size + slack + 1]; + if (!file.Read(buffer, vflist[idx]->Size + slack)) + throw Exception(string("Cannot read package record in file ") + lastFile.FileName()); + buffer[vflist[idx]->Size + slack] = '\n'; + //cerr << "Data read (slack: " << slack << ")" << endl; + + lastOffset = vflist[idx]->Offset + vflist[idx]->Size; + + return string(buffer+slack); + } +}; + +Apt::Iterator::Iterator(const Iterator& i) +{ + if (i.cur) + { + pkgCache::PkgIterator* p = new pkgCache::PkgIterator; + *p = *static_cast<pkgCache::PkgIterator*>(i.cur); + cur = p; + } else + cur = 0; +} + +Apt::Iterator& Apt::Iterator::operator=(const Iterator& i) +{ + if (cur != i.cur) + { + if (cur) delete static_cast<pkgCache::PkgIterator*>(cur); + if (i.cur) + { + pkgCache::PkgIterator* p = new pkgCache::PkgIterator; + *p = *static_cast<pkgCache::PkgIterator*>(i.cur); + cur = p; + } else + cur = 0; + } + return *this; +} + +Apt::Iterator::~Iterator() +{ + if (cur) delete static_cast<pkgCache::PkgIterator*>(cur); +} +std::string Apt::Iterator::operator*() +{ + return static_cast<pkgCache::PkgIterator*>(cur)->Name(); +} +Apt::Iterator& Apt::Iterator::operator++() +{ + pkgCache::PkgIterator* iter = static_cast<pkgCache::PkgIterator*>(cur); + ++*iter; + while (!iter->end() && (*iter)->VersionList == 0) + ++*iter; + if (iter->end()) + { + delete iter; + cur = 0; + } + return *this; +} +bool Apt::Iterator::operator==(const Iterator& i) const +{ + if (cur == 0 && i.cur == 0) + return true; + if (cur == 0 || i.cur == 0) + return false; + pkgCache::PkgIterator* iter1 = static_cast<pkgCache::PkgIterator*>(cur); + pkgCache::PkgIterator* iter2 = static_cast<pkgCache::PkgIterator*>(i.cur); + return *iter1 == *iter2; +} +bool Apt::Iterator::operator!=(const Iterator& i) const +{ + if (cur == 0 && i.cur == 0) + return false; + if (cur == 0 || i.cur == 0) + return true; + pkgCache::PkgIterator* iter1 = static_cast<pkgCache::PkgIterator*>(cur); + pkgCache::PkgIterator* iter2 = static_cast<pkgCache::PkgIterator*>(i.cur); + return *iter1 != *iter2; +} + + +Apt::RecordIterator::RecordIterator(RecordIteratorImpl* impl, size_t pos) + : impl(impl), pos(pos), cur_pos(pos) +{ + if (impl) + { + impl->ref(); + cur = impl->record(pos); + cur_pos = pos; + } +} +Apt::RecordIterator::RecordIterator(const RecordIterator& r) + : impl(r.impl), pos(r.pos), cur(r.cur), cur_pos(r.cur_pos) +{ + if (impl) + impl->ref(); +} +Apt::RecordIterator::~RecordIterator() +{ + if (impl && impl->unref()) + delete impl; +} +std::string Apt::RecordIterator::operator*() +{ + if (cur_pos != pos) + { + cur = impl->record(pos); + cur_pos = pos; + } + return cur; +} +std::string* Apt::RecordIterator::operator->() +{ + if (cur_pos != pos) + { + cur = impl->record(pos); + cur_pos = pos; + } + return &cur; +} +Apt::RecordIterator& Apt::RecordIterator::operator++() +{ + ++pos; + if (pos >= impl->size()) + { + // If we reach the end, we become an end iterator + if (impl && impl->unref()) + delete impl; + impl = 0; + pos = 0; + } + return *this; +} +Apt::RecordIterator& Apt::RecordIterator::operator=(const RecordIterator& r) +{ + // Increment first, to avoid it reaching zero on assignment to self + if (r.impl) r.impl->ref(); + if (impl && impl->unref()) + delete impl; + impl = r.impl; + pos = r.pos; + cur = r.cur; + cur_pos = r.cur_pos; + return *this; +} +bool Apt::RecordIterator::operator==(const RecordIterator& ri) const +{ + return impl == ri.impl && pos == ri.pos; +} +bool Apt::RecordIterator::operator!=(const RecordIterator& ri) const +{ + return impl != ri.impl || pos != ri.pos; +} + + +Apt::Apt() : impl(new AptImplementation()) {} +Apt::~Apt() { delete impl; } + +Apt::iterator Apt::begin() const +{ + pkgCache::PkgIterator* p = new pkgCache::PkgIterator; + *p = impl->cache().PkgBegin(); + return Apt::Iterator(p); +} + +Apt::iterator Apt::end() const +{ + return Apt::Iterator(); +} + +Apt::record_iterator Apt::recordBegin() const +{ + return Apt::RecordIterator(new RecordIteratorImpl(*impl)); +} + +Apt::record_iterator Apt::recordEnd() const +{ + return Apt::RecordIterator(); +} + +size_t Apt::size() const +{ + return impl->cache().HeaderP->PackageCount; +} + +time_t Apt::timestamp() +{ + return aptTimestamp(); +} + +bool Apt::isValid(const std::string& pkg) const +{ + pkgCache::PkgIterator pi = impl->cache().FindPkg(pkg); + return !pi.end(); +} + +Version Apt::validate(const Version& ver) const +{ + pkgCache::PkgIterator pi = impl->cache().FindPkg(ver.name()); + if (pi.end()) return Version(); + for (pkgCache::VerIterator vi = pi.VersionList(); !vi.end(); vi++) + { + const char* v = vi.VerStr(); + if (v == 0) continue; + if (ver.version() == v) + return ver; + } + return Version(); +} + +Version Apt::candidateVersion(const std::string& pkg) const +{ + pkgCache::PkgIterator pi = impl->cache().FindPkg(pkg); + if (pi.end()) return Version(); + pkgCache::VerIterator vi = impl->policy().GetCandidateVer(pi); + if (vi.end()) return Version(); + return Version(pkg, vi.VerStr()); +} + +Version Apt::installedVersion(const std::string& pkg) const +{ + pkgCache::PkgIterator pi = impl->cache().FindPkg(pkg); + if (pi.end()) return Version(); + if (pi->CurrentVer == 0) return Version(); + pkgCache::VerIterator vi = pi.CurrentVer(); + if (vi.end()) return Version(); + return Version(pkg, vi.VerStr()); +} + +Version Apt::anyVersion(const std::string& pkg) const +{ + pkgCache::PkgIterator pi = impl->cache().FindPkg(pkg); + if (pi.end()) return Version(); + + pkgCache::VerIterator vi = impl->policy().GetCandidateVer(pi); + if (vi.end()) + { + if (pi->CurrentVer == 0) return Version(); + vi = pi.CurrentVer(); + if (vi.end()) return Version(); + } + return Version(pkg, vi.VerStr()); +} + +PackageState Apt::state(const std::string& pkg) const +{ + pkgCache::PkgIterator pi = impl->cache().FindPkg(pkg); + if (pi.end()) return PackageState(); + pkgDepCache::StateCache sc = impl->depcache()[pi]; + + unsigned int flags = PackageState::Valid; + + // Check if the package is installed + if (pi->CurrentState != pkgCache::State::ConfigFiles && + pi->CurrentState != pkgCache::State::NotInstalled && + pi->CurrentVer != 0) + { + // Try to get a VerIterator to the installed version + pkgCache::VerIterator inst = pi.CurrentVer(); + if (!inst.end()) + { + // If we made it so far, it is installed + flags |= PackageState::Installed; + + // Now check if it is upgradable + pkgCache::VerIterator cand = impl->policy().GetCandidateVer(pi); + + // If the candidate version is different than the installed one, then + // it is installable + if (!cand.end() && inst != cand) + flags |= PackageState::Upgradable; + } + } + if (sc.Install()) + flags |= PackageState::Install; + if ((sc.iFlags & pkgDepCache::ReInstall) == pkgDepCache::ReInstall) + flags |= PackageState::ReInstall; + if (sc.Keep()) + flags |= PackageState::Keep; + if (sc.Delete()) + flags |= PackageState::Remove; + if ((sc.iFlags & pkgDepCache::Purge) == pkgDepCache::Purge) + flags |= PackageState::Purge; + if (sc.NowBroken()) + flags |= PackageState::NowBroken; + if (sc.InstBroken()) + flags |= PackageState::WillBreak; + + return PackageState(flags); +} + +std::string Apt::rawRecord(const std::string& ver) const +{ + // TODO: possibly reimplement using a single lump of apt code, to avoid + // repeating lookups + return rawRecord(anyVersion(ver)); +} + +std::string Apt::rawRecord(const Version& ver) const +{ + pkgCache::PkgIterator pi = impl->cache().FindPkg(ver.name()); + if (pi.end()) return std::string(); + for (pkgCache::VerIterator vi = pi.VersionList(); !vi.end(); vi++) + { + const char* v = vi.VerStr(); + if (v == 0) continue; + if (ver.version() == v) + { + // Code taken and adapted from apt-cache's DisplayRecord + + // Find an appropriate file + pkgCache::VerFileIterator vfi = vi.FileList(); + for (; !vfi.end(); vfi++) + if ((vfi.File()->Flags & pkgCache::Flag::NotSource) == 0) + break; + if (vfi.end()) + vfi = vi.FileList(); + + // Check and load the package list file + pkgCache::PkgFileIterator pfi = vfi.File(); + if (!pfi.IsOk()) + throw Exception(string("Reading the data record for a package version from file ") + pfi.FileName()); + + FileFd pkgf(pfi.FileName(), FileFd::ReadOnly); + if (_error->PendingError() == true) + return std::string(); + + // Read the record and then write it out again. + char* buffer = new char[vfi->Size+1]; + buffer[vfi->Size] = '\n'; + if (!pkgf.Seek(vfi->Offset) || !pkgf.Read(buffer, vfi->Size)) + { + delete[] buffer; + return std::string(); + } + + std::string res(buffer, vfi->Size); + delete[] buffer; + return res; + } + } + return std::string(); +} + +void Apt::checkCacheUpdates() +{ + if (impl->m_open_timestamp < timestamp()) + { + // Crudely reopen everything + delete impl; + impl = new AptImplementation; + } +} + +void Apt::invalidateTimestamp() +{ + impl->m_open_timestamp = 0; +} + +} +} + +// vim:set ts=4 sw=4: diff --git a/ept/apt/apt.h b/ept/apt/apt.h new file mode 100644 index 0000000..0cfff60 --- /dev/null +++ b/ept/apt/apt.h @@ -0,0 +1,284 @@ +// -*- C++ -*- +#ifndef EPT_APT_APT_H +#define EPT_APT_APT_H + +/** \file + * High-level front-end to libapt-pkg, as a data provider for the ept framework. + */ + +/* + * Copyright (C) 2007,2008 Enrico Zini <enrico@enricozini.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <wibble/exception.h> +#include <ept/apt/version.h> + +#include <iterator> + +namespace ept { +namespace apt { + +class Exception : public wibble::exception::Generic +{ +protected: + std::string m_message; + +public: + Exception(const std::string& context) throw (); + ~Exception() throw () {} + + virtual const char* type() const throw () { return "Apt"; } + virtual std::string desc() const throw () { return m_message; } +}; + +class Apt; +class AptImplementation; +class RecordIteratorImpl; + +struct PackageState { + enum Query { + Install = 1 << 0, + Upgrade = 1 << 1, + Keep = 1 << 2, + Remove = 1 << 3, + Installed = 1 << 4, + Upgradable = 1 << 5, + NowBroken = 1 << 6, + WillBreak = 1 << 7, + ReInstall = 1 << 8, + Purge = 1 << 9, + Hold = 1 << 10, + Valid = 1 << 11 + }; + + typedef unsigned state; + + operator unsigned() { return m_state; }; + + PackageState &operator=( unsigned i ) { + m_state = i; + return *this; + } + + PackageState &operator|=( const PackageState &s ) { + m_state |= s.m_state; + return *this; + } + + PackageState( unsigned a ) { + m_state = a; + } + + PackageState() : m_state( 0 ) {} + + // FIXME this probably needs to be used consistently in core and out of core + bool isValid() const { return m_state & Valid; } + // FIXME compatibility API for non-core apt + bool isInstalled() const { return installed(); } + + bool install() const { return m_state & Install; } + // reinstall() implies install() + bool reinstall() const { return m_state & ReInstall; } + bool remove() const { return m_state & Remove; } + // purge() implies remove() + bool purge() const { return m_state & Purge; } + bool keep() const { return m_state & Keep; } + bool willBreak() const { return m_state & WillBreak; } + // upgrade() implies install() + bool upgrade() const { return hasNewVersion() && install(); } + // newInsstal() implies install() + bool newInstall() const { return !installed() && install(); } + bool hold() const { return m_state & Hold; } + + bool installed() const { return m_state & Installed; } + bool hasNewVersion() const { return m_state & Upgradable; } + bool upgradable() const { return hasNewVersion() && !hold(); } + bool held() const { return hasNewVersion() && hold(); } + bool nowBroken() const { return m_state & NowBroken; } + + bool modify() const { return install() || remove(); } + +protected: + unsigned m_state; +}; + +/** + * High-level access to the Apt cache, as a data provider for the ept + * framework. + * + * This class wraps the Apt cache and allows to query it in various ways. + */ +class Apt +{ +protected: + AptImplementation* impl; + +public: + // Iterate Packages in the Apt cache + class Iterator : public std::iterator<std::input_iterator_tag, std::string, void, void, void> + { + void* cur; + + protected: + // Construct a valid iterator + Iterator(void* cur) : cur(cur) {} + + // Construct and end iterator + Iterator() : cur(0) {} + + public: + // Copy constructor + Iterator(const Iterator&); + ~Iterator(); + std::string operator*(); + Iterator& operator++(); + Iterator& operator=(const Iterator&); + bool operator==(const Iterator&) const; + bool operator!=(const Iterator&) const; + + // FIXME: Iterator operator++(int); cannot be easily implemented + // because of how Apt's pkgIterator works + + friend class Apt; + }; + + // Iterate Package records in the Apt cache + class RecordIterator : public std::iterator<std::input_iterator_tag, std::string, void, void, void> + { + RecordIteratorImpl* impl; + size_t pos; + std::string cur; + size_t cur_pos; + + protected: + // Construct a valid iterator + RecordIterator(RecordIteratorImpl* cur, size_t pos = 0); + + // Construct and end iterator + RecordIterator() : impl(0), pos(0), cur_pos(0) {} + + public: + // Copy constructor + RecordIterator(const RecordIterator& r); + + ~RecordIterator(); + std::string operator*(); + std::string* operator->(); + RecordIterator& operator++(); + RecordIterator& operator=(const RecordIterator& r); + bool operator==(const RecordIterator&) const; + bool operator!=(const RecordIterator&) const; + + // FIXME: Iterator operator++(int); cannot be easily implemented + // because of how Apt's pkgIterator works + + friend class Apt; + }; + + typedef Iterator iterator; + typedef RecordIterator record_iterator; + + /** + * Create the Apt data provider + */ + Apt(); + ~Apt(); + + iterator begin() const; + iterator end() const; + + record_iterator recordBegin() const; + record_iterator recordEnd() const; + + + /// Return the number of packages in the archive + size_t size() const; + + /** + * Validate a package name, returning trye if it exists in the APT database, + * or false if it does not. + */ + bool isValid(const std::string& pkg) const; + + /// Validate a package name, returning it if it exists in the APT database, + /// or returning the empty string if it does not. + std::string validate(const std::string& pkg) const + { + if (isValid(pkg)) + return pkg; + return std::string(); + } + + /// Validate a Version, returning it if it exists in the APT database, or + /// returning the invalid version if it does not. + Version validate(const Version& ver) const; + + /// Return the installed version for a package + Version installedVersion(const std::string& pkg) const; + + /// Return the candidate version for a package + Version candidateVersion(const std::string& pkg) const; + + /** + * Return the candidate version for a package, if available, or the + * installed version otherwise + */ + Version anyVersion(const std::string& pkg) const; + + /// Return state information on a package + PackageState state(const std::string& pkg) const; + + /** + * Perform a package search. + * + * All packages for which the functor filter returns true, are passed to + * the functor out. + */ + //template<typename FILTER, typename OUT> + //void search(const FILTER& filter, OUT& out); + + /// Get the raw package record for the given Version + std::string rawRecord(const std::string& pkg) const; + + /// Get the raw package record for the given Version + std::string rawRecord(const Version& ver) const; + + /// Timestamp of when the apt index was last modified + time_t timestamp(); + + /** + * Check if the cache has been changed by another process, and reopen it if + * that is the case. + * + * Note that this method can invalidate all existing iterators. + */ + void checkCacheUpdates(); + + /** + * Invalidate the cache timestamp used to track cache updates. + * + * @warning Do not use this method: it is here only to support the test + * cases, and may disappear in any future version. + */ + void invalidateTimestamp(); +}; + +} +} + +// vim:set ts=4 sw=4: +#endif diff --git a/ept/apt/apt.test.h b/ept/apt/apt.test.h new file mode 100644 index 0000000..cc9602e --- /dev/null +++ b/ept/apt/apt.test.h @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2007 Enrico Zini <enrico@enricozini.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <ept/test.h> +#include <ept/apt/apt.h> +#include <set> +#include <algorithm> + +using namespace std; +using namespace ept; +using namespace ept::apt; + +struct TestApt : AptTestEnvironment { + Apt apt; + + // Check that iterations iterates among some packages + Test iterators() + { + Apt::iterator i = apt.begin(); + assert(i != apt.end()); + + size_t count = 0; + for (; i != apt.end(); ++i) + ++count; + + assert(count > 100); + } + + // Check that iteration gives some well-known packages + Test aptExists() + { + set<string> packages; + + std::copy(apt.begin(), apt.end(), inserter(packages, packages.begin())); + + assert(packages.find("libsp1") != packages.end()); + // TODO this exposes a bug somewhere... sp definitely is among + // the packages + // assert(packages.find("sp") != packages.end()); + assert(packages.find("") == packages.end()); + } + + // Check that timestamp gives some meaningful timestamp + Test timestamp() + { + time_t ts = apt.timestamp(); + assert(ts > 1000000); + } + + // Check the package validator + Test validity() + { + assert(apt.isValid("apt")); + assert(!apt.isValid("this-package-does-not-really-exists")); + } + + // Check the version instantiators + Test versions() + { + std::string pkg("apt"); + Version ver = apt.candidateVersion(pkg); + assert(ver.isValid()); + + ver = apt.installedVersion(pkg); + assert(ver.isValid()); + + ver = apt.anyVersion(pkg); + assert(ver.isValid()); + + std::string pkg1("this-package-does-not-really-exists"); + ver = apt.candidateVersion(pkg1); + assert(!ver.isValid()); + + ver = apt.installedVersion(pkg1); + assert(!ver.isValid()); + + ver = apt.anyVersion(pkg1); + assert(!ver.isValid()); + } + + // Check the version validator + Test versionValidity() + { + Version ver = apt.candidateVersion("apt"); + assert(apt.validate(ver) == ver); + + ver = Version("this-package-does-not-really-exists", "0.1"); + assert(!apt.validate(ver).isValid()); + + ver = Version("apt", "0.31415"); + assert(!apt.validate(ver).isValid()); + } + + // Check the raw record accessor + Test rawRecord() + { + string pkg("sp"); + Version ver = apt.candidateVersion(pkg); + assert(apt.validate(ver) == ver); + + string record = apt.rawRecord(ver); + assert(record.find("Package: sp") != string::npos); + assert(record.find("Section: text") != string::npos); + + record = apt.rawRecord(Version("sp", "0.31415")); + assert_eq(record, string()); + + assert_eq(apt.rawRecord(pkg), apt.rawRecord(apt.anyVersion(pkg))); + } + + // Check the package state accessor + Test state() + { + PackageState s = apt.state("kdenetwork"); + assert(s.isValid()); + assert(s.isInstalled()); + + s = apt.state("this-package-does-not-really-exists"); + assert(!s.isValid()); + } + + // Check the record iterator (accessing with *) + Test recordIteration() + { + size_t count = 0; + for (Apt::record_iterator i = apt.recordBegin(); + i != apt.recordEnd(); ++i) + { + assert((*i).size() > 8); + assert_eq((*i).substr(0, 8), "Package:"); + ++count; + } + assert(count > 200); + } + + // Check the record iterator (accessing with ->) + Test recordIteration2() + { + size_t count = 0; + for (Apt::record_iterator i = apt.recordBegin(); + i != apt.recordEnd(); ++i) + { + assert(i->size() > 8); + assert_eq(i->substr(0, 8), "Package:"); + ++count; + } + assert(count > 200); + } + + // Check that the iterators can be used with the algorithms + Test stlIteration() + { + vector<string> out; + std::copy(apt.begin(), apt.end(), back_inserter(out)); + } + + // Check that the iterators can be used with the algorithms + Test stlRecordIteration() + { + vector<string> out; + std::copy(apt.recordBegin(), apt.recordEnd(), back_inserter(out)); + } + + // Check that checkUpdates will keep a working Apt object + Test checkUpdates() + { + assert(apt.isValid("apt")); + apt.checkCacheUpdates(); + assert(apt.isValid("apt")); + apt.invalidateTimestamp(); + apt.checkCacheUpdates(); + assert(apt.isValid("apt")); + } + +}; + +// vim:set ts=4 sw=4: diff --git a/ept/apt/packagerecord.cc b/ept/apt/packagerecord.cc new file mode 100644 index 0000000..f842bac --- /dev/null +++ b/ept/apt/packagerecord.cc @@ -0,0 +1,118 @@ +/** \file + * Parser for APT records, with specialised accessors for package records + */ + +/* + * Copyright (C) 2007 Enrico Zini <enrico@enricozini.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <ept/apt/packagerecord.h> + +#include <cctype> +#include <cstdlib> + +//#include <iostream> + +using namespace std; + +namespace ept { +namespace apt { + +size_t PackageRecord::parseSize(size_t def, const std::string& str) const +{ + if (str == string()) + return def; + return (size_t)strtoul(str.c_str(), NULL, 10); +} + +std::string PackageRecord::parseShortDescription(const std::string& def, const std::string& str) const +{ + if (str == std::string()) + return def; + size_t pos = str.find("\n"); + if (pos == std::string::npos) + return str; + else + return str.substr(0, pos); +} + +std::string PackageRecord::parseLongDescription(const std::string& def, const std::string& str) const +{ + if (str == std::string()) + return def; + size_t pos = str.find("\n"); + if (pos == std::string::npos) + return str; + else + { + // Trim trailing spaces + for (++pos; pos < str.size() && isspace(str[pos]); ++pos) + ; + return str.substr(pos); + } +} + +std::set<std::string> PackageRecord::parseTags(const std::set<std::string>& def, const std::string& str) const +{ + if (str == string()) + return def; + + set<string> res; + + size_t pos = 0; + while (pos < str.size()) + { + string tag; + size_t i = str.find(", ", pos); + if (i == string::npos) + tag = str.substr(pos); + else + tag = str.substr(pos, i-pos); + + // Check if we need curly brace expansion + if (tag[tag.size() - 1] == '}') + { + size_t begin = tag.find('{'); + if (begin != string::npos) + { + string prefix(tag, 0, begin); + ++begin; + size_t end; + while ((end = tag.find(',', begin)) != string::npos) + { + res.insert(prefix + tag.substr(begin, end-begin)); + begin = end + 1; + } + res.insert(prefix + tag.substr(begin, tag.size() - 1 - begin)); + } + } else { + res.insert(tag); + } + + if (i == string::npos) + break; + else + pos = i + 2; + } + + return res; +} + +} +} + +// vim:set ts=4 sw=4: diff --git a/ept/apt/packagerecord.h b/ept/apt/packagerecord.h new file mode 100644 index 0000000..94e0435 --- /dev/null +++ b/ept/apt/packagerecord.h @@ -0,0 +1,176 @@ +#ifndef EPT_APT_PACKAGERECORD_H +#define EPT_APT_PACKAGERECORD_H + +/** \file + * Parser for APT records, with specialised accessors for package records + */ + +/* + * Copyright (C) 2007 Enrico Zini <enrico@enricozini.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <ept/apt/recordparser.h> +#include <set> + +namespace ept { +namespace apt { + +/** + * RecordParser specialised with access methods for common Debian package + * information. + */ +class PackageRecord : public RecordParser +{ + bool parseBool(bool& def, const std::string& str) const + { + // Believe it or not, this is what apt does to interpret bool fields + if (str == "no" || str == "false" || str == "without" || + str == "off" || str == "disable") + return false; + + if (str == "yes" || str == "true" || str == "with" || + str == "on" || str == "enable") + return true; + + return def; + } + std::string parseString(const std::string& def, const std::string& str) const + { + if (str == std::string()) + return def; + return str; + } + std::string parseShortDescription(const std::string& def, const std::string& str) const; + std::string parseLongDescription(const std::string& def, const std::string& str) const; + size_t parseSize(size_t def, const std::string& str) const; + std::set<std::string> parseTags(const std::set<std::string>& def, const std::string& str) const; + +public: + PackageRecord() : RecordParser() {} + PackageRecord(const std::string& str) : RecordParser(str) {} + + std::string package(const std::string& def = std::string()) const + { + return parseString(def, lookup("Package")); + } + std::string priority(const std::string& def = std::string()) const + { + return parseString(def, lookup("Priority")); + } + std::string section(const std::string& def = std::string()) const + { + return parseString(def, lookup("Section")); + } + size_t installedSize(size_t def = 0) const + { + return parseSize(def, lookup("Installed-Size")); + } + std::string maintainer(const std::string& def = std::string()) const + { + return parseString(def, lookup("Maintainer")); + } + std::string architecture(const std::string& def = std::string()) const + { + return parseString(def, lookup("Architecture")); + } + std::string source(const std::string& def = std::string()) const + { + return parseString(def, lookup("Source")); + } + std::string version(const std::string& def = std::string()) const + { + return parseString(def, lookup("Version")); + } + std::string replaces(const std::string& def = std::string()) const + { + return parseString(def, lookup("Replaces")); + } + std::string depends(const std::string& def = std::string()) const + { + return parseString(def, lookup("Depends")); + } + std::string preDepends(const std::string& def = std::string()) const + { + return parseString(def, lookup("Pre-Depends")); + } + std::string recommends(const std::string& def = std::string()) const + { + return parseString(def, lookup("Recommends")); + } + std::string suggests(const std::string& def = std::string()) const + { + return parseString(def, lookup("Suggests")); + } + std::string enhances(const std::string& def = std::string()) const + { + return parseString(def, lookup("Enhances")); + } + std::string provides(const std::string& def = std::string()) const + { + return parseString(def, lookup("Provides")); + } + std::string conflicts(const std::string& def = std::string()) const + { + return parseString(def, lookup("Conflicts")); + } + std::string filename(const std::string& def = std::string()) const + { + return parseString(def, lookup("Filename")); + } + size_t packageSize(size_t def = 0) const + { + return parseSize(def, lookup("Size")); + } + std::string md5sum(const std::string& def = std::string()) const + { + return parseString(def, lookup("MD5sum")); + } + std::string sha1(const std::string& def = std::string()) const + { + return parseString(def, lookup("SHA1")); + } + std::string sha256(const std::string& def = std::string()) const + { + return parseString(def, lookup("SHA256")); + } + std::string description(const std::string& def = std::string()) const + { + return parseString(def, lookup("Description")); + } + std::string shortDescription(const std::string& def = std::string()) const + { + return parseShortDescription(def, lookup("Description")); + } + std::string longDescription(const std::string& def = std::string()) const + { + return parseLongDescription(def, lookup("Description")); + } + bool buildEssential(bool def = false) const + { + return parseBool(def, lookup("Build-Essential")); + } + std::set<std::string> tag(const std::set<std::string>& def = std::set<std::string>()) const + { + return parseTags(def, lookup("Tag")); + } +}; + +} +} + +// vim:set ts=4 sw=4: +#endif diff --git a/ept/apt/packagerecord.test.h b/ept/apt/packagerecord.test.h new file mode 100644 index 0000000..657cbab --- /dev/null +++ b/ept/apt/packagerecord.test.h @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2007 Enrico Zini <enrico@enricozini.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <ept/test.h> +#include <ept/apt/packagerecord.h> + +namespace std { + ostream& operator<<(ostream& out, const set<string>& s) + { + for (set<string>::const_iterator i = s.begin(); + i != s.end(); ++i) + if (i == s.begin()) + out << *i; + else + out << ", " << *i; + return out; + } +} + +using namespace std; +using namespace ept; +using namespace ept::apt; + +struct TestAptPackagerecord { + + // Check that the supported fields are understood + Test supportedFields() + { + string record = + "Package: apt\n" + "Priority: important\n" + "Section: admin\n" + "Installed-Size: 4368\n" + "Maintainer: APT Development Team <deity@lists.debian.org>\n" + "Architecture: amd64\n" + "Source: apt\n" + "Version: 0.6.46.4-0.1\n" + "Replaces: libapt-pkg-doc (<< 0.3.7), libapt-pkg-dev (<< 0.3.7)\n" + "Provides: libapt-pkg-libc6.3-6-3.11\n" + "Depends: libc6 (>= 2.3.5-1), libgcc1 (>= 1:4.1.1-12), libstdc++6 (>= 4.1.1-12), debian-archive-keyring\n" + "Pre-Depends: debtags (maybe)\n" + "Suggests: aptitude | synaptic | gnome-apt | wajig, dpkg-dev, apt-doc, bzip2\n" + "Recommends: debtags (maybe)\n" + "Enhances: debian\n" + "Conflicts: marameo\n" + "Filename: pool/main/a/apt/apt_0.6.46.4-0.1_amd64.deb\n" + "Size: 1436478\n" + "MD5sum: 1776421f80d6300c77a608e77a9f4a15\n" + "SHA1: 1bd7337d2df56d267632cf72ac930c0a4895898f\n" + "SHA256: b92442ab60046b4d0728245f39cc932f26e17db9f7933a5ec9aaa63172f51fda\n" + "Description: Advanced front-end for dpkg\n" + " This is Debian's next generation front-end for the dpkg package manager.\n" + " It provides the apt-get utility and APT dselect method that provides a\n" + " simpler, safer way to install and upgrade packages.\n" + " .\n" + " APT features complete installation ordering, multiple source capability\n" + " and several other unique features, see the Users Guide in apt-doc.\n" + "Build-Essential: yes\n" + "Tag: admin::package-management, filetransfer::ftp, filetransfer::http, hardware::storage:cd, interface::commandline, network::client, protocol::{ftp,http,ipv6}, role::program, suite::debian, use::downloading, use::searching, works-with::software:package\n"; + + PackageRecord p(record); + + assert_eq(p.size(), 24u); + + assert_eq(p.package(), "apt"); + assert_eq(p.priority(), "important"); + assert_eq(p.section(), "admin"); + assert_eq(p.installedSize(), 4368u); + assert_eq(p.maintainer(), "APT Development Team <deity@lists.debian.org>"); + assert_eq(p.architecture(), "amd64"); + assert_eq(p.source(), "apt"); + assert_eq(p.version(), "0.6.46.4-0.1"); + assert_eq(p.replaces(), "libapt-pkg-doc (<< 0.3.7), libapt-pkg-dev (<< 0.3.7)"); + assert_eq(p.provides(), "libapt-pkg-libc6.3-6-3.11"); + assert_eq(p.depends(), "libc6 (>= 2.3.5-1), libgcc1 (>= 1:4.1.1-12), libstdc++6 (>= 4.1.1-12), debian-archive-keyring"); + assert_eq(p.preDepends(), "debtags (maybe)"); + assert_eq(p.recommends(), "debtags (maybe)"); + assert_eq(p.suggests(), "aptitude | synaptic | gnome-apt | wajig, dpkg-dev, apt-doc, bzip2"); + assert_eq(p.enhances(), "debian"); + assert_eq(p.conflicts(), "marameo"); + assert_eq(p.filename(), "pool/main/a/apt/apt_0.6.46.4-0.1_amd64.deb"); + assert_eq(p.packageSize(), 1436478u); + assert_eq(p.md5sum(), "1776421f80d6300c77a608e77a9f4a15"); + assert_eq(p.sha1(), "1bd7337d2df56d267632cf72ac930c0a4895898f"); + assert_eq(p.sha256(), "b92442ab60046b4d0728245f39cc932f26e17db9f7933a5ec9aaa63172f51fda"); + assert_eq(p.description(), "Advanced front-end for dpkg\n" + " This is Debian's next generation front-end for the dpkg package manager.\n" + " It provides the apt-get utility and APT dselect method that provides a\n" + " simpler, safer way to install and upgrade packages.\n" + " .\n" + " APT features complete installation ordering, multiple source capability\n" + " and several other unique features, see the Users Guide in apt-doc."); + assert_eq(p.shortDescription(), "Advanced front-end for dpkg"); + assert_eq(p.longDescription(), + "This is Debian's next generation front-end for the dpkg package manager.\n" + " It provides the apt-get utility and APT dselect method that provides a\n" + " simpler, safer way to install and upgrade packages.\n" + " .\n" + " APT features complete installation ordering, multiple source capability\n" + " and several other unique features, see the Users Guide in apt-doc."); + assert_eq(p.buildEssential(), true); + + std::set<std::string> tags; + tags.insert("admin::package-management"); + tags.insert("filetransfer::ftp"); + tags.insert("filetransfer::http"); + tags.insert("hardware::storage:cd"); + tags.insert("interface::commandline"); + tags.insert("network::client"); + tags.insert("protocol::ftp"); + tags.insert("protocol::http"); + tags.insert("protocol::ipv6"); + tags.insert("role::program"); + tags.insert("suite::debian"); + tags.insert("use::downloading"); + tags.insert("use::searching"); + tags.insert("works-with::software:package"); + assert_eq(p.tag(), tags); + } + +}; + +// vim:set ts=4 sw=4: diff --git a/ept/apt/recordparser.cc b/ept/apt/recordparser.cc new file mode 100644 index 0000000..3562123 --- /dev/null +++ b/ept/apt/recordparser.cc @@ -0,0 +1,170 @@ +/** \file + * Parser for APT records + */ + +/* + * Copyright (C) 2007 Enrico Zini <enrico@enricozini.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <ept/apt/recordparser.h> + +#include <algorithm> +#include <cctype> + +//#include <iostream> + +using namespace std; + +namespace ept { +namespace apt { + +struct rpcompare +{ + const RecordParser& rp; + rpcompare(const RecordParser& rp) : rp(rp) {} + bool operator()(size_t a, size_t b) + { + return rp.name(a) < rp.name(b); + } +}; + +void RecordParser::scan(const std::string& str) +{ + buffer = str; + ends.clear(); + sorted.clear(); + + //cerr << "PARSE " << endl << buffer << "*****" << endl; + + // Scan the buffer, taking note of all ending offsets of the various fields + size_t pos = 0; + size_t idx = 0; + while (pos < buffer.size() - 1) + { + //cerr << "PREPOS " << pos << " left: " << buffer.substr(pos, 10) << endl; + pos = buffer.find("\n", pos); + //cerr << "POSTPOS " << pos << " left: " << (pos == string::npos ? "NONE" : buffer.substr(pos, 10)) << endl; + + // The buffer does not end with a newline + if (pos == string::npos) + { + //cerr << "ENDNOTEOL" << endl; + pos = buffer.size(); + ends.push_back(pos); + sorted.push_back(idx++); + break; + } + + ++pos; + //cerr << "POSTPOSINC " << pos << " left: " << buffer.substr(pos, 10) << endl; + + // The buffer ends with a newline + if (pos == buffer.size()) + { + //cerr << "ENDEOL" << endl; + ends.push_back(pos); + sorted.push_back(idx++); + break; + } + + // Terminate parsing on double newlines + if (buffer[pos] == '\n') + { + //cerr << "ENDDOUBLENL" << endl; + ends.push_back(pos); + sorted.push_back(idx++); + break; + } + + // Mark the end of the field if it's not a continuation line + if (!isspace(buffer[pos])) + { + //cerr << "INNERFIELD" << endl; + ends.push_back(pos); + sorted.push_back(idx++); + } //else + //cerr << "CONTLINE" << endl; + } + + // Sort the sorted array + sort(sorted.begin(), sorted.end(), rpcompare(*this)); + + //for (size_t i = 0; i < ends.size(); ++i) + // cerr << ends[i] << "\t" << name(i) << "\t" << sorted[i] << "\t" << name(sorted[i]) << endl; +} + +std::string RecordParser::field(size_t idx) const +{ + if (idx >= ends.size()) + return string(); + if (idx == 0) + return buffer.substr(0, ends[0]); + else + return buffer.substr(ends[idx-1], ends[idx]-ends[idx-1]); +} + +std::string RecordParser::name(size_t idx) const +{ + string res = field(idx); + size_t pos = res.find(":"); + if (pos == string::npos) + return res; + return res.substr(0, pos); +} + +std::string RecordParser::lookup(size_t idx) const +{ + string res = field(idx); + size_t pos = res.find(":"); + if (pos == string::npos) + return res; + // Skip initial whitespace after the : + for (++pos; pos < res.size() && isspace(res[pos]); ++pos) + ; + res = res.substr(pos); + // Trim spaces at the end + while (!res.empty() && isspace(res[res.size() - 1])) + res.resize(res.size() - 1); + return res; +} + +size_t RecordParser::index(const std::string& str) const +{ + int begin, end; + + /* Binary search */ + begin = -1, end = size(); + while (end - begin > 1) + { + int cur = (end + begin) / 2; + //cerr << "Test " << cur << " " << str << " < " << name(cur) << endl; + if (name(sorted[cur]) > str) + end = cur; + else + begin = cur; + } + + if (begin == -1 || name(sorted[begin]) != str) + return size(); + else + return sorted[begin]; +} + +} +} + +// vim:set ts=4 sw=4: diff --git a/ept/apt/recordparser.h b/ept/apt/recordparser.h new file mode 100644 index 0000000..cbaa466 --- /dev/null +++ b/ept/apt/recordparser.h @@ -0,0 +1,95 @@ +#ifndef EPT_APT_RECORDPARSER_H +#define EPT_APT_RECORDPARSER_H + +/** \file + * Parser for APT records + */ + +/* + * Copyright (C) 2007 Enrico Zini <enrico@enricozini.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <vector> +#include <string> + +namespace ept { +namespace apt { + +/** + * Access the fields of a package record contained inside a std::string. + * + * Implementation note: this implementation should take advantage of + * std::string sharing buffer space among them. + */ +class RecordParser +{ + /// Buffer containing the whole record + std::string buffer; + + /// End offsets of the various fields in the record + std::vector<size_t> ends; + + /// Indexes on the ends vector, sorted by field name + std::vector<size_t> sorted; + +public: + RecordParser() {} + RecordParser(const std::string& str) { scan(str); } + + /// Index a new record + void scan(const std::string& str); + + /** + * Get the index of the field with the given name. + * + * size() is returned if not found + */ + size_t index(const std::string& str) const; + + /// Return the field by its index + std::string field(size_t idx) const; + + /// Return the name of a field by its index + std::string name(size_t idx) const; + + /// Return the content of a field by its index + std::string lookup(size_t idx) const; + + /// Return the content of a field by its name + std::string lookup(const std::string& name) const { return lookup(index(name)); } + + /// Return the content of a field by its index + std::string operator[](size_t idx) const { return lookup(idx); } + + /// Return the content of a field by its name + std::string operator[](const std::string& name) const { return lookup(name); } + + /// Return the entire record + const std::string& record() const { return buffer; } + + /// Return the entire record + std::string record() { return buffer; } + + /// Return the number of fields in the record + size_t size() const { return ends.size(); } +}; + +} +} + +// vim:set ts=4 sw=4: +#endif diff --git a/ept/apt/recordparser.test.h b/ept/apt/recordparser.test.h new file mode 100644 index 0000000..629008f --- /dev/null +++ b/ept/apt/recordparser.test.h @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2007 Enrico Zini <enrico@enricozini.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <ept/test.h> +#include <ept/apt/recordparser.h> + +//#include <iostream> + +using namespace std; +using namespace ept; +using namespace ept::apt; + +struct TestAptRecordparser { + std::string record; + TestAptRecordparser() + { + record = + "A:\n" + "D: da de di do du\n" + "B: b\n" + "C: c \n" + "Desc: this is the beginning\n" + " this is the continuation\n" + " this is the end\n"; + } + + // Check that the fields are identified and broken up correctly + Test parsing() + { + RecordParser p(record); + + assert_eq(p.record(), record); + assert_eq(p.size(), 5u); + } + + Test fieldTuples() + { + RecordParser p(record); + assert_eq(p.field(0), "A:\n"); + assert_eq(p.field(1), "D: da de di do du\n"); + assert_eq(p.field(2), "B: b\n"); + assert_eq(p.field(3), "C: c \n"); + assert_eq(p.field(4), "Desc: this is the beginning\n this is the continuation\n this is the end\n"); + } + + Test fieldKeys() + { + RecordParser p(record); + assert_eq(p.name(0), "A"); + assert_eq(p.name(1), "D"); + assert_eq(p.name(2), "B"); + assert_eq(p.name(3), "C"); + assert_eq(p.name(4), "Desc"); + } + + Test fieldValues() + { + RecordParser p(record); + assert_eq(p[0], ""); + assert_eq(p[1], "da de di do du"); + assert_eq(p[2], "b"); + assert_eq(p[3], "c"); + assert_eq(p[4], "this is the beginning\n this is the continuation\n this is the end"); + } + + // Check that the field search by name finds all the fields + Test findByName() + { + RecordParser p(record); + + assert_eq(p.index("A"), 0u); + assert_eq(p.index("D"), 1u); + assert_eq(p.index("B"), 2u); + assert_eq(p.index("C"), 3u); + assert_eq(p.index("Desc"), 4u); + + assert_eq(p.name(p.index("A")), "A"); + assert_eq(p.name(p.index("B")), "B"); + assert_eq(p.name(p.index("C")), "C"); + assert_eq(p.name(p.index("D")), "D"); + assert_eq(p.name(p.index("Desc")), "Desc"); + } + + Test indexing() + { + RecordParser p(record); + assert_eq(p["A"], ""); + assert_eq(p["B"], "b"); + assert_eq(p["C"], "c"); + assert_eq(p["D"], "da de di do du"); + assert_eq(p["Desc"], "this is the beginning\n this is the continuation\n this is the end"); + } + + Test missingBehaviour() + { + RecordParser p(record); + // Missing fields give empty strings + assert_eq(p.field(100), ""); + assert_eq(p.name(100), ""); + assert_eq(p[100], ""); + assert_eq(p["Missing"], ""); + } + + // Check that scanning twice replaces the old fields + Test rescan() + { + std::string record = + "A: a\n" + "B: b\n" + "C: c\n"; + + RecordParser p(record); + assert_eq(p.size(), 3u); + assert_eq(p["A"], "a"); + assert_eq(p["B"], "b"); + assert_eq(p["C"], "c"); + + std::string record1 = + "Foo: bar\n" + "A: different\n"; + + p.scan(record1); + + //for (size_t i = 0; i < p.size(); ++i) + // cerr << ">> " << i << "==" << p.index(p.name(i)) << " " << p.name(i) << " " << p[i] << endl; + + assert_eq(p.size(), 2u); + assert_eq(p["A"], "different"); + assert_eq(p["B"], ""); + assert_eq(p["C"], ""); + assert_eq(p["Foo"], "bar"); + } + + // Real-life example + Test realLife() + { + string record = + "Package: apt\n" + "Priority: important\n" + "Section: admin\n" + "Installed-Size: 4368\n" + "Maintainer: APT Development Team <deity@lists.debian.org>\n" + "Architecture: amd64\n" + "Version: 0.6.46.4-0.1\n" + "Replaces: libapt-pkg-doc (<< 0.3.7), libapt-pkg-dev (<< 0.3.7)\n" + "Provides: libapt-pkg-libc6.3-6-3.11\n" + "Depends: libc6 (>= 2.3.5-1), libgcc1 (>= 1:4.1.1-12), libstdc++6 (>= 4.1.1-12), debian-archive-keyring\n" + "Suggests: aptitude | synaptic | gnome-apt | wajig, dpkg-dev, apt-doc, bzip2\n" + "Filename: pool/main/a/apt/apt_0.6.46.4-0.1_amd64.deb\n" + "Size: 1436478\n" + "MD5sum: 1776421f80d6300c77a608e77a9f4a15\n" + "SHA1: 1bd7337d2df56d267632cf72ac930c0a4895898f\n" + "SHA256: b92442ab60046b4d0728245f39cc932f26e17db9f7933a5ec9aaa63172f51fda\n" + "Description: Advanced front-end for dpkg\n" + " This is Debian's next generation front-end for the dpkg package manager.\n" + " It provides the apt-get utility and APT dselect method that provides a\n" + " simpler, safer way to install and upgrade packages.\n" + " .\n" + " APT features complete installation ordering, multiple source capability\n" + " and several other unique features, see the Users Guide in apt-doc.\n" + "Build-Essential: yes\n" + "Tag: admin::package-management, filetransfer::ftp, filetransfer::http, hardware::storage:cd, interface::commandline, network::client, protocol::{ftp,http,ipv6}, role::program, suite::debian, use::downloading, use::searching, works-with::software:package\n"; + RecordParser p(record); + + assert_eq(p.size(), 19u); + + string rec1; + for (size_t i = 0; i < p.size(); ++i) + rec1 += p.field(i); + assert_eq(record, rec1); + } + + // Various buffer termination patterns + Test bufferTermination() + { + std::string record = + "A: a\n" + "B: b"; + + RecordParser p(record); + assert_eq(p.size(), 2u); + assert_eq(p["A"], "a"); + assert_eq(p["B"], "b"); + } + + Test bufferTermination2() + { + std::string record = + "A: a\n" + "B: b\n\n"; + + RecordParser p(record); + assert_eq(p.size(), 2u); + assert_eq(p["A"], "a"); + assert_eq(p["B"], "b"); + } + + Test bufferTermination3() + { + std::string record = + "A: a\n" + "B: b\n\n" + "C: c\n"; + + RecordParser p(record); + assert_eq(p.size(), 2u); + assert_eq(p["A"], "a"); + assert_eq(p["B"], "b"); + } + +}; + +// vim:set ts=4 sw=4: diff --git a/ept/apt/version.cc b/ept/apt/version.cc new file mode 100644 index 0000000..2b002c1 --- /dev/null +++ b/ept/apt/version.cc @@ -0,0 +1,87 @@ +/** \file + * Provide a very lightweight Version class that represent a package with a + * version, with very cheap value copy operations. + */ + +/* + * Copyright (C) 2007 Enrico Zini <enrico@enricozini.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <ept/apt/version.h> +#include <apt-pkg/debversion.h> + +using namespace std; + +namespace ept { +namespace apt { + +std::string Version::upstreamVersion() const +{ + // Skip the epoch, if it is there + size_t start = m_version.find(':'); + if (start == string::npos) + start = 0; + else + ++start; + + // Skip everything after the trailing '-', if it is there + size_t end = m_version.rfind('-'); + if (end == string::npos) + end = m_version.size(); + + return m_version.substr(start, end-start); +} + +/* Version comparison by Debian policy */ + +bool Version::operator<=(const Version& pkg) const +{ + if (name() < pkg.name()) + return true; + if (name() == pkg.name()) + return debVS.CmpVersion(version(), pkg.version()) <= 0; + return false; +} +bool Version::operator<(const Version& pkg) const +{ + if (name() < pkg.name()) + return true; + if (name() == pkg.name()) + return debVS.CmpVersion(version(), pkg.version()) < 0; + return false; +} +bool Version::operator>=(const Version& pkg) const +{ + if (name() > pkg.name()) + return true; + if (name() == pkg.name()) + return debVS.CmpVersion(version(), pkg.version()) >= 0; + return false; +} +bool Version::operator>(const Version& pkg) const +{ + if (name() > pkg.name()) + return true; + if (name() == pkg.name()) + return debVS.CmpVersion(version(), pkg.version()) > 0; + return false; +} + +} +} + +// vim:set ts=4 sw=4: diff --git a/ept/apt/version.h b/ept/apt/version.h new file mode 100644 index 0000000..ff8a0ac --- /dev/null +++ b/ept/apt/version.h @@ -0,0 +1,94 @@ +#ifndef EPT_APT_VERSION_H +#define EPT_APT_VERSION_H + +/** \file + * Representation of a package with a version + */ + +/* + * Copyright (C) 2007 Enrico Zini <enrico@enricozini.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <string> + +namespace ept { +namespace apt { + +/** + * Lightweight Version class that represent a package with a version, with very + * cheap value copy operations. + * + * This class can be used to query package information from various information + * sources. The purpose is create a middle ground that makes sure that all + * sort of different information sources about packages are referring to the + * same package. + */ +class Version +{ +protected: + std::string m_name; + std::string m_version; + +public: + /** + * Create an invalid Version + */ + Version() {} + + /** + * Create a Version from strings + */ + Version(const std::string& name, const std::string& version) + : m_name(name), m_version(version) {} + + /** + * Return the package name + */ + std::string name() const { return m_name; } + + /** + * Return the package version, or the empty string if this is a + * versionless package. + */ + std::string version() const { return m_version; } + + /** + * Return the upstream part of the version + */ + std::string upstreamVersion() const; + + /** + * Return true if this package contains a valid value + */ + bool isValid() const { return !m_name.empty() && !m_version.empty(); } + + /** + * Comparison operators + */ + bool operator==(const Version& pkg) const { return m_name == pkg.m_name && m_version == pkg.m_version; } + bool operator!=(const Version& pkg) const { return m_name != pkg.m_name || m_version != pkg.m_version; } + bool operator<=(const Version& pkg) const; + bool operator<(const Version& pkg) const; + bool operator>=(const Version& pkg) const; + bool operator>(const Version& pkg) const; +}; + +} +} + +// vim:set ts=4 sw=4: +#endif diff --git a/ept/apt/version.test.h b/ept/apt/version.test.h new file mode 100644 index 0000000..a06a5c6 --- /dev/null +++ b/ept/apt/version.test.h @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2007 Enrico Zini <enrico@enricozini.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <ept/test.h> +#include <ept/apt/version.h> + +using namespace std; +using namespace ept::apt; + +struct TestAptVersion { + + // Basic test for invalid version + Test invalid() + { + Version test; + + assert_eq(test.name(), ""); + assert_eq(test.version(), ""); + assert_eq(test.isValid(), false); + + string p = test.name(); + + assert_eq(p, string()); + } + + // Basic test for version + Test basic() + { + Version test("test", "1.0"); + + assert_eq(test.name(), "test"); + assert_eq(test.version(), "1.0"); + assert_eq(test.isValid(), true); + + string p = test.name(); + + assert_eq(p, "test"); + + Version v(p, "1.1"); + assert_eq(v.name(), "test"); + assert_eq(v.version(), "1.1"); + assert_eq(v.isValid(), true); + } + + // Comparison semanthics + Test comparison() + { + Version test("test", "1.0"); + Version test1("test", "1.0"); + + assert(test == test1); + assert(! (test != test1)); + assert(! (test < test1)); + assert(! (test > test1)); + assert(test <= test1); + assert(test >= test1); + + + Version test2("test2", "1.0"); + + assert(test != test2); + assert(test != test2); + assert(test < test2); + assert(! (test > test2)); + assert(test <= test2); + assert(! (test >= test2)); + + + Version test3("test", "2.0"); + + assert(test != test3); + assert(test != test3); + assert(test < test3); + assert(! (test > test3)); + assert(test <= test3); + assert(! (test >= test3)); + } + + // Value-copy semanthics + Test valueCopy() + { + Version test("test", "1.0"); + Version test1 = test; + + assert(test == test1); + + Version test2; + test2 = test; + assert(test == test2); + assert(test1 == test2); + + Version test3("test", "1.0"); + assert(test == test3); + assert(test1 == test3); + assert(test2 == test3); + } + + // Extraction of upstream version + Test upstreamVersion() + { + assert_eq(Version("a", "10.0").upstreamVersion(), "10.0"); + assert_eq(Version("a", "10.0-1").upstreamVersion(), "10.0"); + assert_eq(Version("a", "10.0~foo.1-1.0").upstreamVersion(), "10.0~foo.1"); + assert_eq(Version("a", "1.0:10.0~foo.1-1.0").upstreamVersion(), "10.0~foo.1"); + } + + // Debian policy comparison semanthics + Test policyComparison() + { + assert(Version("a", "10.0") > Version("a", "2.1")); + assert(Version("a", "1:10.0") < Version("a", "2:2.1")); + assert(Version("a", "10.0-1") < Version("a", "10.0-2")); + assert(Version("a", "10.0-2") > Version("a", "10.0-1")); + assert(Version("a", "1:10.0-1") <= Version("a", "1:10.0-1")); + assert(Version("a", "1:10.0-1") >= Version("a", "1:10.0-1")); + // TODO: add more + } + +}; + +// vim:set ts=4 sw=4: |