From 76e2632459410dec81337edb6a9fee33c9a660f3 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 13 Apr 2024 11:59:37 +0200 Subject: Adding upstream version 2.7.12. Signed-off-by: Daniel Baumann --- cmdline/CMakeLists.txt | 59 ++ cmdline/apt-cache.cc | 1145 +++++++++++++++++++++++++++++++++++++ cmdline/apt-cdrom.cc | 235 ++++++++ cmdline/apt-config.cc | 144 +++++ cmdline/apt-dump-solver.cc | 184 ++++++ cmdline/apt-extracttemplates.cc | 336 +++++++++++ cmdline/apt-extracttemplates.h | 49 ++ cmdline/apt-get.cc | 452 +++++++++++++++ cmdline/apt-helper.cc | 334 +++++++++++ cmdline/apt-internal-planner.cc | 195 +++++++ cmdline/apt-internal-solver.cc | 214 +++++++ cmdline/apt-key.in | 832 +++++++++++++++++++++++++++ cmdline/apt-mark.cc | 476 +++++++++++++++ cmdline/apt-report-mirror-failure | 29 + cmdline/apt-sortpkgs.cc | 166 ++++++ cmdline/apt.cc | 125 ++++ 16 files changed, 4975 insertions(+) create mode 100644 cmdline/CMakeLists.txt create mode 100644 cmdline/apt-cache.cc create mode 100644 cmdline/apt-cdrom.cc create mode 100644 cmdline/apt-config.cc create mode 100644 cmdline/apt-dump-solver.cc create mode 100644 cmdline/apt-extracttemplates.cc create mode 100644 cmdline/apt-extracttemplates.h create mode 100644 cmdline/apt-get.cc create mode 100644 cmdline/apt-helper.cc create mode 100644 cmdline/apt-internal-planner.cc create mode 100644 cmdline/apt-internal-solver.cc create mode 100644 cmdline/apt-key.in create mode 100644 cmdline/apt-mark.cc create mode 100755 cmdline/apt-report-mirror-failure create mode 100644 cmdline/apt-sortpkgs.cc create mode 100644 cmdline/apt.cc (limited to 'cmdline') diff --git a/cmdline/CMakeLists.txt b/cmdline/CMakeLists.txt new file mode 100644 index 0000000..91bf9bb --- /dev/null +++ b/cmdline/CMakeLists.txt @@ -0,0 +1,59 @@ +# Create the executable tasks +add_executable(apt apt.cc) +add_executable(apt-cache apt-cache.cc) +add_executable(apt-get apt-get.cc) +add_executable(apt-mark apt-mark.cc) +add_executable(apt-config apt-config.cc) +add_executable(apt-cdrom apt-cdrom.cc) +add_executable(apt-helper apt-helper.cc) +add_executable(apt-sortpkgs apt-sortpkgs.cc) +add_executable(apt-extracttemplates apt-extracttemplates.cc) +add_executable(apt-internal-solver apt-internal-solver.cc) +add_executable(apt-dump-solver apt-dump-solver.cc) +add_executable(apt-internal-planner apt-internal-planner.cc) +add_vendor_file(OUTPUT apt-key + INPUT apt-key.in + MODE 755 + VARIABLES keyring-filename + keyring-removed-filename + keyring-master-filename + keyring-uri keyring-package) + + +# Link the executables against the libraries +target_link_libraries(apt apt-pkg apt-private) +target_link_libraries(apt-cache apt-pkg apt-private) +target_link_libraries(apt-get apt-pkg apt-private) +target_link_libraries(apt-config apt-pkg apt-private) +target_link_libraries(apt-cdrom apt-pkg apt-private) +target_link_libraries(apt-helper apt-pkg apt-private) +target_link_libraries(apt-mark apt-pkg apt-private) +target_link_libraries(apt-sortpkgs apt-pkg apt-private) +target_link_libraries(apt-extracttemplates apt-pkg apt-private) +target_link_libraries(apt-internal-solver apt-pkg apt-private) +target_link_libraries(apt-dump-solver apt-pkg apt-private) +target_link_libraries(apt-internal-planner apt-pkg apt-private) + +set_target_properties(apt-dump-solver + PROPERTIES RUNTIME_OUTPUT_DIRECTORY solvers + RUNTIME_OUTPUT_NAME dump) +set_target_properties(apt-internal-solver + PROPERTIES RUNTIME_OUTPUT_DIRECTORY solvers + RUNTIME_OUTPUT_NAME apt) +set_target_properties(apt-internal-planner + PROPERTIES RUNTIME_OUTPUT_DIRECTORY planners + RUNTIME_OUTPUT_NAME apt) + +# Install the executables +install(TARGETS apt apt-cache apt-get apt-config apt-cdrom apt-mark apt-sortpkgs + apt-extracttemplates + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +install(TARGETS apt-helper RUNTIME DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/apt/) +install(TARGETS apt-dump-solver apt-internal-solver RUNTIME DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/apt/solvers) +install(TARGETS apt-internal-planner RUNTIME DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/apt/planners) + +add_links(${CMAKE_INSTALL_LIBEXECDIR}/apt/planners ../solvers/dump planners/dump) + +# Install the not-to-be-compiled programs +INSTALL(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/apt-key DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/cmdline/apt-cache.cc b/cmdline/apt-cache.cc new file mode 100644 index 0000000..531500c --- /dev/null +++ b/cmdline/apt-cache.cc @@ -0,0 +1,1145 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + apt-cache - Manages the cache files + + apt-cache provides some functions fo manipulating the cache files. + It uses the command line interface common to all the APT tools. + + Returns 100 on failure, 0 on success. + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + /*}}}*/ + +using namespace std; + +// DumpPackage - Show a dump of a package record /*{{{*/ +// --------------------------------------------------------------------- +/* */ +static bool DumpPackage(CommandLine &CmdL) +{ + pkgCacheFile CacheFile; + APT::CacheSetHelper helper(true, GlobalError::NOTICE); + APT::PackageList pkgset = APT::PackageList::FromCommandLine(CacheFile, CmdL.FileList + 1, helper); + + for (APT::PackageList::const_iterator Pkg = pkgset.begin(); Pkg != pkgset.end(); ++Pkg) + { + cout << "Package: " << Pkg.FullName(true) << endl; + cout << "Versions: " << endl; + for (pkgCache::VerIterator Cur = Pkg.VersionList(); Cur.end() != true; ++Cur) + { + cout << Cur.VerStr(); + for (pkgCache::VerFileIterator Vf = Cur.FileList(); Vf.end() == false; ++Vf) + cout << " (" << Vf.File().FileName() << ")"; + cout << endl; + for (pkgCache::DescIterator D = Cur.DescriptionList(); D.end() == false; ++D) + { + cout << " Description Language: " << D.LanguageCode() << endl + << " File: " << D.FileList().File().FileName() << endl + << " MD5: " << D.md5() << endl; + } + cout << endl; + } + + cout << endl; + + cout << "Reverse Depends: " << endl; + for (pkgCache::DepIterator D = Pkg.RevDependsList(); D.end() != true; ++D) + { + cout << " " << D.ParentPkg().FullName(true) << ',' << D.TargetPkg().FullName(true); + if (D->Version != 0) + cout << ' ' << DeNull(D.TargetVer()) << endl; + else + cout << endl; + } + + cout << "Dependencies: " << endl; + for (pkgCache::VerIterator Cur = Pkg.VersionList(); Cur.end() != true; ++Cur) + { + cout << Cur.VerStr() << " - "; + for (pkgCache::DepIterator Dep = Cur.DependsList(); Dep.end() != true; ++Dep) + cout << Dep.TargetPkg().FullName(true) << " (" << (int)Dep->CompareOp << " " << DeNull(Dep.TargetVer()) << ") "; + cout << endl; + } + + cout << "Provides: " << endl; + for (pkgCache::VerIterator Cur = Pkg.VersionList(); Cur.end() != true; ++Cur) + { + cout << Cur.VerStr() << " - "; + for (pkgCache::PrvIterator Prv = Cur.ProvidesList(); Prv.end() != true; ++Prv) + cout << Prv.ParentPkg().FullName(true) << " (= " << (Prv->ProvideVersion == 0 ? "" : Prv.ProvideVersion()) << ") "; + cout << endl; + } + cout << "Reverse Provides: " << endl; + for (pkgCache::PrvIterator Prv = Pkg.ProvidesList(); Prv.end() != true; ++Prv) + cout << Prv.OwnerPkg().FullName(true) << " " << Prv.OwnerVer().VerStr() << " (= " << (Prv->ProvideVersion == 0 ? "" : Prv.ProvideVersion()) << ")"<< endl; + } + + return true; +} + /*}}}*/ +// ShowHashTableStats - Show stats about a hashtable /*{{{*/ +// --------------------------------------------------------------------- +/* */ +static map_pointer PackageNext(pkgCache::Package const * const P) { return P->NextPackage; } +static map_pointer GroupNext(pkgCache::Group const * const G) { return G->Next; } +template +static void ShowHashTableStats(char const *const Type, + T *StartP, + map_pointer *Hashtable, + unsigned long Size, + map_pointer (*Next)(T const *const)) +{ + // hashtable stats for the HashTable + unsigned long NumBuckets = Size; + unsigned long UsedBuckets = 0; + unsigned long UnusedBuckets = 0; + unsigned long LongestBucket = 0; + unsigned long ShortestBucket = NumBuckets; + unsigned long Entries = 0; + for (unsigned int i=0; i < NumBuckets; ++i) + { + T *P = StartP + Hashtable[i]; + if(P == 0 || P == StartP) + { + ++UnusedBuckets; + continue; + } + ++UsedBuckets; + unsigned long ThisBucketSize = 0; + for (; P != StartP; P = StartP + Next(P)) + ++ThisBucketSize; + Entries += ThisBucketSize; + LongestBucket = std::max(ThisBucketSize, LongestBucket); + ShortestBucket = std::min(ThisBucketSize, ShortestBucket); + } + cout << "Total buckets in " << Type << ": " << NumBuckets << std::endl; + cout << " Unused: " << UnusedBuckets << std::endl; + cout << " Used: " << UsedBuckets << std::endl; + cout << " Utilization: " << 100.0 * UsedBuckets/NumBuckets << "%" << std::endl; + cout << " Average entries: " << Entries/(double)UsedBuckets << std::endl; + cout << " Longest: " << LongestBucket << std::endl; + cout << " Shortest: " << ShortestBucket << std::endl; +} + /*}}}*/ +// Stats - Dump some nice statistics /*{{{*/ +// --------------------------------------------------------------------- +/* */ +static bool Stats(CommandLine &CmdL) +{ + if (CmdL.FileSize() > 1) { + _error->Error(_("%s does not take any arguments"), "apt-cache stats"); + return false; + } + + pkgCacheFile CacheFile; + pkgCache *Cache = CacheFile.GetPkgCache(); + + if (unlikely(Cache == NULL)) + return false; + + cout << _("Total package names: ") << Cache->Head().GroupCount << " (" << + SizeToStr(Cache->Head().GroupCount*Cache->Head().GroupSz) << ')' << endl + << _("Total package structures: ") << Cache->Head().PackageCount << " (" << + SizeToStr(Cache->Head().PackageCount*Cache->Head().PackageSz) << ')' << endl; + + int Normal = 0; + int Virtual = 0; + int NVirt = 0; + int DVirt = 0; + int Missing = 0; + for (pkgCache::PkgIterator I = Cache->PkgBegin(); I.end() != true; ++I) + { + if (I->VersionList != 0 && I->ProvidesList == 0) + { + Normal++; + continue; + } + + if (I->VersionList != 0 && I->ProvidesList != 0) + { + NVirt++; + continue; + } + + if (I->VersionList == 0 && I->ProvidesList != 0) + { + // Only 1 provides + if (I.ProvidesList()->NextProvides == 0) + { + DVirt++; + } + else + Virtual++; + continue; + } + if (I->VersionList == 0 && I->ProvidesList == 0) + { + Missing++; + continue; + } + } + cout << _(" Normal packages: ") << Normal << endl; + cout << _(" Pure virtual packages: ") << Virtual << endl; + cout << _(" Single virtual packages: ") << DVirt << endl; + cout << _(" Mixed virtual packages: ") << NVirt << endl; + cout << _(" Missing: ") << Missing << endl; + + cout << _("Total distinct versions: ") << Cache->Head().VersionCount << " (" << + SizeToStr(Cache->Head().VersionCount*Cache->Head().VersionSz) << ')' << endl; + cout << _("Total distinct descriptions: ") << Cache->Head().DescriptionCount << " (" << + SizeToStr(Cache->Head().DescriptionCount*Cache->Head().DescriptionSz) << ')' << endl; + cout << _("Total dependencies: ") << Cache->Head().DependsCount << "/" << Cache->Head().DependsDataCount << " (" << + SizeToStr((Cache->Head().DependsCount*Cache->Head().DependencySz) + + (Cache->Head().DependsDataCount*Cache->Head().DependencyDataSz)) << ')' << endl; + cout << _("Total ver/file relations: ") << Cache->Head().VerFileCount << " (" << + SizeToStr(Cache->Head().VerFileCount*Cache->Head().VerFileSz) << ')' << endl; + cout << _("Total Desc/File relations: ") << Cache->Head().DescFileCount << " (" << + SizeToStr(Cache->Head().DescFileCount*Cache->Head().DescFileSz) << ')' << endl; + cout << _("Total Provides mappings: ") << Cache->Head().ProvidesCount << " (" << + SizeToStr(Cache->Head().ProvidesCount*Cache->Head().ProvidesSz) << ')' << endl; + + // String list stats + std::set stritems; + for (pkgCache::GrpIterator G = Cache->GrpBegin(); G.end() == false; ++G) + stritems.insert(G->Name); + for (pkgCache::PkgIterator P = Cache->PkgBegin(); P.end() == false; ++P) + { + stritems.insert(P->Arch); + for (pkgCache::VerIterator V = P.VersionList(); V.end() == false; ++V) + { + if (V->VerStr != 0) + stritems.insert(V->VerStr); + if (V->Section != 0) + stritems.insert(V->Section); + stritems.insert(V->SourcePkgName); + stritems.insert(V->SourceVerStr); + for (pkgCache::DepIterator D = V.DependsList(); D.end() == false; ++D) + { + if (D->Version != 0) + stritems.insert(D->Version); + } + for (pkgCache::DescIterator D = V.DescriptionList(); D.end() == false; ++D) + { + stritems.insert(D->md5sum); + stritems.insert(D->language_code); + } + } + for (pkgCache::PrvIterator Prv = P.ProvidesList(); Prv.end() == false; ++Prv) + { + if (Prv->ProvideVersion != 0) + stritems.insert(Prv->ProvideVersion); + } + } + for (pkgCache::RlsFileIterator F = Cache->RlsFileBegin(); F != Cache->RlsFileEnd(); ++F) + { + stritems.insert(F->FileName); + stritems.insert(F->Archive); + stritems.insert(F->Codename); + stritems.insert(F->Version); + stritems.insert(F->Origin); + stritems.insert(F->Label); + stritems.insert(F->Site); + } + for (pkgCache::PkgFileIterator F = Cache->FileBegin(); F != Cache->FileEnd(); ++F) + { + stritems.insert(F->FileName); + stritems.insert(F->Architecture); + stritems.insert(F->Component); + stritems.insert(F->IndexType); + } + + unsigned long Size = 0; + for (std::set::const_iterator i = stritems.begin(); i != stritems.end(); ++i) + Size += strlen(Cache->StrP + *i) + 1; + cout << _("Total globbed strings: ") << stritems.size() << " (" << SizeToStr(Size) << ')' << endl; + stritems.clear(); + + unsigned long Slack = 0; + for (int I = 0; I != 7; I++) + Slack += Cache->Head().Pools[I].ItemSize*Cache->Head().Pools[I].Count; + cout << _("Total slack space: ") << SizeToStr(Slack) << endl; + + unsigned long Total = 0; +#define APT_CACHESIZE(X,Y) (Cache->Head().X * Cache->Head().Y) + Total = Slack + Size + + APT_CACHESIZE(GroupCount, GroupSz) + + APT_CACHESIZE(PackageCount, PackageSz) + + APT_CACHESIZE(VersionCount, VersionSz) + + APT_CACHESIZE(DescriptionCount, DescriptionSz) + + APT_CACHESIZE(DependsCount, DependencySz) + + APT_CACHESIZE(DependsDataCount, DependencyDataSz) + + APT_CACHESIZE(ReleaseFileCount, ReleaseFileSz) + + APT_CACHESIZE(PackageFileCount, PackageFileSz) + + APT_CACHESIZE(VerFileCount, VerFileSz) + + APT_CACHESIZE(DescFileCount, DescFileSz) + + APT_CACHESIZE(ProvidesCount, ProvidesSz) + + (2 * Cache->Head().GetHashTableSize() * sizeof(map_id_t)); + cout << _("Total space accounted for: ") << SizeToStr(Total) << endl; +#undef APT_CACHESIZE + + // hashtable stats + ShowHashTableStats("PkgHashTable", Cache->PkgP, Cache->Head().PkgHashTableP(), Cache->Head().GetHashTableSize(), PackageNext); + ShowHashTableStats("GrpHashTable", Cache->GrpP, Cache->Head().GrpHashTableP(), Cache->Head().GetHashTableSize(), GroupNext); + + return true; +} + /*}}}*/ +// Dump - show everything /*{{{*/ +// --------------------------------------------------------------------- +/* This is worthless except fer debugging things */ +static bool Dump(CommandLine &) +{ + pkgCacheFile CacheFile; + pkgCache *Cache = CacheFile.GetPkgCache(); + if (unlikely(Cache == NULL)) + return false; + + std::cout << "Using Versioning System: " << Cache->VS->Label << std::endl; + + for (pkgCache::PkgIterator P = Cache->PkgBegin(); P.end() == false; ++P) + { + std::cout << "Package: " << P.FullName(true) << std::endl; + for (pkgCache::VerIterator V = P.VersionList(); V.end() == false; ++V) + { + std::cout << " Version: " << V.VerStr() << std::endl; + std::cout << " File: " << V.FileList().File().FileName() << std::endl; + for (pkgCache::DepIterator D = V.DependsList(); D.end() == false; ++D) + std::cout << " Depends: " << D.TargetPkg().FullName(true) << ' ' << + DeNull(D.TargetVer()) << std::endl; + for (pkgCache::DescIterator D = V.DescriptionList(); D.end() == false; ++D) + { + std::cout << " Description Language: " << D.LanguageCode() << std::endl + << " File: " << D.FileList().File().FileName() << std::endl + << " MD5: " << D.md5() << std::endl; + } + } + } + + for (pkgCache::PkgFileIterator F = Cache->FileBegin(); F.end() == false; ++F) + { + std::cout << "File: " << F.FileName() << std::endl; + std::cout << " Type: " << F.IndexType() << std::endl; + std::cout << " Size: " << F->Size << std::endl; + std::cout << " ID: " << F->ID << std::endl; + std::cout << " Flags: " << F->Flags << std::endl; + std::cout << " Time: " << TimeRFC1123(F->mtime, true) << std::endl; + std::cout << " Archive: " << DeNull(F.Archive()) << std::endl; + std::cout << " Component: " << DeNull(F.Component()) << std::endl; + std::cout << " Version: " << DeNull(F.Version()) << std::endl; + std::cout << " Origin: " << DeNull(F.Origin()) << std::endl; + std::cout << " Site: " << DeNull(F.Site()) << std::endl; + std::cout << " Label: " << DeNull(F.Label()) << std::endl; + std::cout << " Architecture: " << DeNull(F.Architecture()) << std::endl; + } + + return true; +} + /*}}}*/ +// DumpAvail - Print out the available list /*{{{*/ +// --------------------------------------------------------------------- +/* This is needed to make dpkg --merge happy.. I spent a bit of time to + make this run really fast, perhaps I went a little overboard.. */ +static bool DumpAvail(CommandLine &) +{ + pkgCacheFile CacheFile; + pkgCache *Cache = CacheFile.GetPkgCache(); + if (unlikely(Cache == NULL || CacheFile.BuildPolicy() == false)) + return false; + + auto const Count = Cache->HeaderP->PackageCount+1; + pkgCache::VerFile **VFList = new pkgCache::VerFile *[Count]; + memset(VFList,0,sizeof(*VFList)*Count); + + // Map versions that we want to write out onto the VerList array. + for (pkgCache::PkgIterator P = Cache->PkgBegin(); P.end() == false; ++P) + { + if (P->VersionList == 0) + continue; + + /* Find the proper version to use. If the policy says there are no + possible selections we return the installed version, if available.. + This prevents dselect from making it obsolete. */ + pkgCache::VerIterator V = CacheFile.GetPolicy()->GetCandidateVer(P); + if (V.end() == true) + { + if (P->CurrentVer == 0) + continue; + V = P.CurrentVer(); + } + + pkgCache::VerFileIterator VF = V.FileList(); + for (; VF.end() == false ; ++VF) + if ((VF.File()->Flags & pkgCache::Flag::NotSource) == 0) + break; + + /* Okay, here we have a bit of a problem.. The policy has selected the + currently installed package - however it only exists in the + status file.. We need to write out something or dselect will mark + the package as obsolete! Thus we emit the status file entry, but + below we remove the status line to make it valid for the + available file. However! We only do this if their do exist *any* + non-source versions of the package - that way the dselect obsolete + handling works OK. */ + if (VF.end() == true) + { + for (pkgCache::VerIterator Cur = P.VersionList(); Cur.end() != true; ++Cur) + { + for (VF = Cur.FileList(); VF.end() == false; ++VF) + { + if ((VF.File()->Flags & pkgCache::Flag::NotSource) == 0) + { + VF = V.FileList(); + break; + } + } + + if (VF.end() == false) + break; + } + } + + VFList[P->ID] = VF; + } + + LocalitySort(VFList,Count,sizeof(*VFList)); + + std::vector RW; + RW.push_back(pkgTagSection::Tag::Remove("Status")); + RW.push_back(pkgTagSection::Tag::Remove("Config-Version")); + FileFd stdoutfd; + stdoutfd.OpenDescriptor(STDOUT_FILENO, FileFd::WriteOnly, false); + + // Iterate over all the package files and write them out. + char *Buffer = new char[Cache->HeaderP->MaxVerFileSize+10]; + for (pkgCache::VerFile **J = VFList; *J != 0;) + { + pkgCache::PkgFileIterator File(*Cache, Cache->PkgFileP + (*J)->File); + // FIXME: Add support for volatile/with-source files + FileFd PkgF(File.FileName(),FileFd::ReadOnly, FileFd::Extension); + if (_error->PendingError() == true) + break; + + /* Write all of the records from this package file, since we + already did locality sorting we can now just seek through the + file in read order. We apply 1 more optimization here, since often + there will be < 1 byte gaps between records (for the \n) we read that + into the next buffer and offset a bit.. */ + unsigned long Pos = 0; + for (; *J != 0; J++) + { + if (Cache->PkgFileP + (*J)->File != File) + break; + + const pkgCache::VerFile &VF = **J; + + // Read the record and then write it out again. + unsigned long Jitter = VF.Offset - Pos; + if (Jitter > 8) + { + if (PkgF.Seek(VF.Offset) == false) + break; + Jitter = 0; + } + + if (PkgF.Read(Buffer,VF.Size + Jitter) == false) + break; + Buffer[VF.Size + Jitter] = '\n'; + + // See above.. + if ((File->Flags & pkgCache::Flag::NotSource) == pkgCache::Flag::NotSource) + { + pkgTagSection Tags; + if (Tags.Scan(Buffer+Jitter,VF.Size+1) == false || + Tags.Write(stdoutfd, NULL, RW) == false || + stdoutfd.Write("\n", 1) == false) + { + _error->Error("Internal Error, Unable to parse a package record"); + break; + } + } + else + { + if (stdoutfd.Write(Buffer + Jitter, VF.Size + 1) == false) + break; + } + + Pos = VF.Offset + VF.Size; + } + + if (_error->PendingError() == true) + break; + } + + delete [] Buffer; + delete [] VFList; + return !_error->PendingError(); +} + /*}}}*/ +// xvcg - Generate a graph for xvcg /*{{{*/ +// --------------------------------------------------------------------- +// Code contributed from Junichi Uekawa on 20 June 2002. + +static bool XVcg(CommandLine &CmdL) +{ + pkgCacheFile CacheFile; + pkgCache *Cache = CacheFile.GetPkgCache(); + if (unlikely(Cache == NULL)) + return false; + + bool GivenOnly = _config->FindB("APT::Cache::GivenOnly",false); + + /* Normal packages are boxes + Pure Provides are triangles + Mixed are diamonds + rhomb are missing packages*/ + const char *Shapes[] = {"ellipse","triangle","box","rhomb"}; + + /* Initialize the list of packages to show. + 1 = To Show + 2 = To Show no recurse + 3 = Emitted no recurse + 4 = Emitted + 0 = None */ + enum States {None=0, ToShow, ToShowNR, DoneNR, Done}; + enum TheFlags {ForceNR=(1<<0)}; + auto PackageCount = Cache->Head().PackageCount; + unsigned char *Show = new unsigned char[PackageCount]; + unsigned char *Flags = new unsigned char[PackageCount]; + unsigned char *ShapeMap = new unsigned char[PackageCount]; + + // Show everything if no arguments given + if (CmdL.FileList[1] == 0) + for (decltype(PackageCount) I = 0; I != PackageCount; ++I) + Show[I] = ToShow; + else + for (decltype(PackageCount) I = 0; I != PackageCount; ++I) + Show[I] = None; + memset(Flags,0,sizeof(*Flags)*PackageCount); + + // Map the shapes + for (pkgCache::PkgIterator Pkg = Cache->PkgBegin(); Pkg.end() == false; ++Pkg) + { + if (Pkg->VersionList == 0) + { + // Missing + if (Pkg->ProvidesList == 0) + ShapeMap[Pkg->ID] = 0; + else + ShapeMap[Pkg->ID] = 1; + } + else + { + // Normal + if (Pkg->ProvidesList == 0) + ShapeMap[Pkg->ID] = 2; + else + ShapeMap[Pkg->ID] = 3; + } + } + + // Load the list of packages from the command line into the show list + APT::CacheSetHelper helper(true, GlobalError::NOTICE); + std::list mods; + mods.push_back(APT::CacheSetHelper::PkgModifier(0, ",", APT::CacheSetHelper::PkgModifier::POSTFIX)); + mods.push_back(APT::CacheSetHelper::PkgModifier(1, "^", APT::CacheSetHelper::PkgModifier::POSTFIX)); + std::map pkgsets = + APT::PackageSet::GroupedFromCommandLine(CacheFile, CmdL.FileList + 1, mods, 0, helper); + + for (APT::PackageSet::const_iterator Pkg = pkgsets[0].begin(); + Pkg != pkgsets[0].end(); ++Pkg) + Show[Pkg->ID] = ToShow; + for (APT::PackageSet::const_iterator Pkg = pkgsets[1].begin(); + Pkg != pkgsets[1].end(); ++Pkg) + { + Show[Pkg->ID] = ToShow; + Flags[Pkg->ID] |= ForceNR; + } + + // Little header + cout << "graph: { title: \"packages\"" << endl << + "xmax: 700 ymax: 700 x: 30 y: 30" << endl << + "layout_downfactor: 8" << endl; + + bool Act = true; + while (Act == true) + { + Act = false; + for (pkgCache::PkgIterator Pkg = Cache->PkgBegin(); Pkg.end() == false; ++Pkg) + { + // See we need to show this package + if (Show[Pkg->ID] == None || Show[Pkg->ID] >= DoneNR) + continue; + + //printf ("node: { title: \"%s\" label: \"%s\" }\n", Pkg.Name(), Pkg.Name()); + + // Colour as done + if (Show[Pkg->ID] == ToShowNR || (Flags[Pkg->ID] & ForceNR) == ForceNR) + { + // Pure Provides and missing packages have no deps! + if (ShapeMap[Pkg->ID] == 0 || ShapeMap[Pkg->ID] == 1) + Show[Pkg->ID] = Done; + else + Show[Pkg->ID] = DoneNR; + } + else + Show[Pkg->ID] = Done; + Act = true; + + // No deps to map out + if (Pkg->VersionList == 0 || Show[Pkg->ID] == DoneNR) + continue; + + pkgCache::VerIterator Ver = Pkg.VersionList(); + for (pkgCache::DepIterator D = Ver.DependsList(); D.end() == false; ++D) + { + // See if anything can meet this dep + // Walk along the actual package providing versions + bool Hit = false; + pkgCache::PkgIterator DPkg = D.TargetPkg(); + for (pkgCache::VerIterator I = DPkg.VersionList(); + I.end() == false && Hit == false; ++I) + { + if (Cache->VS->CheckDep(I.VerStr(),D->CompareOp,D.TargetVer()) == true) + Hit = true; + } + + // Follow all provides + for (pkgCache::PrvIterator I = DPkg.ProvidesList(); + I.end() == false && Hit == false; ++I) + { + if (Cache->VS->CheckDep(I.ProvideVersion(),D->CompareOp,D.TargetVer()) == false) + Hit = true; + } + + + // Only graph critical deps + if (D.IsCritical() == true) + { + printf ("edge: { sourcename: \"%s\" targetname: \"%s\" class: 2 ",Pkg.FullName(true).c_str(), D.TargetPkg().FullName(true).c_str() ); + + // Colour the node for recursion + if (Show[D.TargetPkg()->ID] <= DoneNR) + { + /* If a conflicts does not meet anything in the database + then show the relation but do not recurse */ + if (Hit == false && D.IsNegative() == true) + { + if (Show[D.TargetPkg()->ID] == None && + Show[D.TargetPkg()->ID] != ToShow) + Show[D.TargetPkg()->ID] = ToShowNR; + } + else + { + if (GivenOnly == true && Show[D.TargetPkg()->ID] != ToShow) + Show[D.TargetPkg()->ID] = ToShowNR; + else + Show[D.TargetPkg()->ID] = ToShow; + } + } + + // Edge colour + switch(D->Type) + { + case pkgCache::Dep::Conflicts: + printf("label: \"conflicts\" color: lightgreen }\n"); + break; + case pkgCache::Dep::DpkgBreaks: + printf("label: \"breaks\" color: lightgreen }\n"); + break; + case pkgCache::Dep::Obsoletes: + printf("label: \"obsoletes\" color: lightgreen }\n"); + break; + + case pkgCache::Dep::PreDepends: + printf("label: \"predepends\" color: blue }\n"); + break; + + default: + printf("}\n"); + break; + } + } + } + } + } + + /* Draw the box colours after the fact since we can not tell what colour + they should be until everything is finished drawing */ + for (pkgCache::PkgIterator Pkg = Cache->PkgBegin(); Pkg.end() == false; ++Pkg) + { + if (Show[Pkg->ID] < DoneNR) + continue; + + if (Show[Pkg->ID] == DoneNR) + printf("node: { title: \"%s\" label: \"%s\" color: orange shape: %s }\n", Pkg.FullName(true).c_str(), Pkg.FullName(true).c_str(), + Shapes[ShapeMap[Pkg->ID]]); + else + printf("node: { title: \"%s\" label: \"%s\" shape: %s }\n", Pkg.FullName(true).c_str(), Pkg.FullName(true).c_str(), + Shapes[ShapeMap[Pkg->ID]]); + + } + + delete[] Show; + delete[] Flags; + delete[] ShapeMap; + + printf("}\n"); + return true; +} + /*}}}*/ +// Dotty - Generate a graph for Dotty /*{{{*/ +// --------------------------------------------------------------------- +/* Dotty is the graphvis program for generating graphs. It is a fairly + simple queuing algorithm that just writes dependencies and nodes. + http://www.research.att.com/sw/tools/graphviz/ */ +static bool Dotty(CommandLine &CmdL) +{ + pkgCacheFile CacheFile; + pkgCache *Cache = CacheFile.GetPkgCache(); + if (unlikely(Cache == NULL)) + return false; + + bool GivenOnly = _config->FindB("APT::Cache::GivenOnly",false); + + /* Normal packages are boxes + Pure Provides are triangles + Mixed are diamonds + Hexagons are missing packages*/ + const char *Shapes[] = {"hexagon","triangle","box","diamond"}; + + /* Initialize the list of packages to show. + 1 = To Show + 2 = To Show no recurse + 3 = Emitted no recurse + 4 = Emitted + 0 = None */ + enum States {None=0, ToShow, ToShowNR, DoneNR, Done}; + enum TheFlags {ForceNR=(1<<0)}; + auto PackageCount = Cache->Head().PackageCount; + unsigned char *Show = new unsigned char[PackageCount]; + unsigned char *Flags = new unsigned char[PackageCount]; + unsigned char *ShapeMap = new unsigned char[PackageCount]; + + // Show everything if no arguments given + if (CmdL.FileList[1] == 0) + for (decltype(PackageCount) I = 0; I != PackageCount; ++I) + Show[I] = ToShow; + else + for (decltype(PackageCount) I = 0; I != PackageCount; ++I) + Show[I] = None; + memset(Flags,0,sizeof(*Flags)*PackageCount); + + // Map the shapes + for (pkgCache::PkgIterator Pkg = Cache->PkgBegin(); Pkg.end() == false; ++Pkg) + { + if (Pkg->VersionList == 0) + { + // Missing + if (Pkg->ProvidesList == 0) + ShapeMap[Pkg->ID] = 0; + else + ShapeMap[Pkg->ID] = 1; + } + else + { + // Normal + if (Pkg->ProvidesList == 0) + ShapeMap[Pkg->ID] = 2; + else + ShapeMap[Pkg->ID] = 3; + } + } + + // Load the list of packages from the command line into the show list + APT::CacheSetHelper helper(true, GlobalError::NOTICE); + std::list mods; + mods.push_back(APT::CacheSetHelper::PkgModifier(0, ",", APT::CacheSetHelper::PkgModifier::POSTFIX)); + mods.push_back(APT::CacheSetHelper::PkgModifier(1, "^", APT::CacheSetHelper::PkgModifier::POSTFIX)); + std::map pkgsets = + APT::PackageSet::GroupedFromCommandLine(CacheFile, CmdL.FileList + 1, mods, 0, helper); + + for (APT::PackageSet::const_iterator Pkg = pkgsets[0].begin(); + Pkg != pkgsets[0].end(); ++Pkg) + Show[Pkg->ID] = ToShow; + for (APT::PackageSet::const_iterator Pkg = pkgsets[1].begin(); + Pkg != pkgsets[1].end(); ++Pkg) + { + Show[Pkg->ID] = ToShow; + Flags[Pkg->ID] |= ForceNR; + } + + // Little header + printf("digraph packages {\n"); + printf("concentrate=true;\n"); + printf("size=\"30,40\";\n"); + + bool Act = true; + while (Act == true) + { + Act = false; + for (pkgCache::PkgIterator Pkg = Cache->PkgBegin(); Pkg.end() == false; ++Pkg) + { + // See we need to show this package + if (Show[Pkg->ID] == None || Show[Pkg->ID] >= DoneNR) + continue; + + // Colour as done + if (Show[Pkg->ID] == ToShowNR || (Flags[Pkg->ID] & ForceNR) == ForceNR) + { + // Pure Provides and missing packages have no deps! + if (ShapeMap[Pkg->ID] == 0 || ShapeMap[Pkg->ID] == 1) + Show[Pkg->ID] = Done; + else + Show[Pkg->ID] = DoneNR; + } + else + Show[Pkg->ID] = Done; + Act = true; + + // No deps to map out + if (Pkg->VersionList == 0 || Show[Pkg->ID] == DoneNR) + continue; + + pkgCache::VerIterator Ver = Pkg.VersionList(); + for (pkgCache::DepIterator D = Ver.DependsList(); D.end() == false; ++D) + { + // See if anything can meet this dep + // Walk along the actual package providing versions + bool Hit = false; + pkgCache::PkgIterator DPkg = D.TargetPkg(); + for (pkgCache::VerIterator I = DPkg.VersionList(); + I.end() == false && Hit == false; ++I) + { + if (Cache->VS->CheckDep(I.VerStr(),D->CompareOp,D.TargetVer()) == true) + Hit = true; + } + + // Follow all provides + for (pkgCache::PrvIterator I = DPkg.ProvidesList(); + I.end() == false && Hit == false; ++I) + { + if (Cache->VS->CheckDep(I.ProvideVersion(),D->CompareOp,D.TargetVer()) == false) + Hit = true; + } + + // Only graph critical deps + if (D.IsCritical() == true) + { + printf("\"%s\" -> \"%s\"",Pkg.FullName(true).c_str(),D.TargetPkg().FullName(true).c_str()); + + // Colour the node for recursion + if (Show[D.TargetPkg()->ID] <= DoneNR) + { + /* If a conflicts does not meet anything in the database + then show the relation but do not recurse */ + if (Hit == false && D.IsNegative() == true) + { + if (Show[D.TargetPkg()->ID] == None && + Show[D.TargetPkg()->ID] != ToShow) + Show[D.TargetPkg()->ID] = ToShowNR; + } + else + { + if (GivenOnly == true && Show[D.TargetPkg()->ID] != ToShow) + Show[D.TargetPkg()->ID] = ToShowNR; + else + Show[D.TargetPkg()->ID] = ToShow; + } + } + + // Edge colour + switch(D->Type) + { + case pkgCache::Dep::Conflicts: + case pkgCache::Dep::Obsoletes: + case pkgCache::Dep::DpkgBreaks: + printf("[color=springgreen];\n"); + break; + + case pkgCache::Dep::PreDepends: + printf("[color=blue];\n"); + break; + + default: + printf(";\n"); + break; + } + } + } + } + } + + /* Draw the box colours after the fact since we can not tell what colour + they should be until everything is finished drawing */ + for (pkgCache::PkgIterator Pkg = Cache->PkgBegin(); Pkg.end() == false; ++Pkg) + { + if (Show[Pkg->ID] < DoneNR) + continue; + + // Orange box for early recursion stoppage + if (Show[Pkg->ID] == DoneNR) + printf("\"%s\" [color=orange,shape=%s];\n",Pkg.FullName(true).c_str(), + Shapes[ShapeMap[Pkg->ID]]); + else + printf("\"%s\" [shape=%s];\n",Pkg.FullName(true).c_str(), + Shapes[ShapeMap[Pkg->ID]]); + } + + printf("}\n"); + delete[] Show; + delete[] Flags; + delete[] ShapeMap; + return true; +} + /*}}}*/ +/* ShowAuto - show automatically installed packages (sorted) {{{*/ +static bool ShowAuto(CommandLine &) +{ + pkgCacheFile CacheFile; + pkgCache *Cache = CacheFile.GetPkgCache(); + pkgDepCache *DepCache = CacheFile.GetDepCache(); + if (unlikely(Cache == NULL || DepCache == NULL)) + return false; + + std::vector packages; + packages.reserve(Cache->HeaderP->PackageCount / 3); + + for (pkgCache::PkgIterator P = Cache->PkgBegin(); P.end() == false; ++P) + if ((*DepCache)[P].Flags & pkgCache::Flag::Auto) + packages.push_back(P.Name()); + + std::sort(packages.begin(), packages.end()); + + for (vector::iterator I = packages.begin(); I != packages.end(); ++I) + cout << *I << "\n"; + + _error->Notice(_("This command is deprecated. Please use 'apt-mark showauto' instead.")); + return true; +} + /*}}}*/ +// ShowPkgNames - Show package names /*{{{*/ +// --------------------------------------------------------------------- +/* This does a prefix match on the first argument */ +static bool ShowPkgNames(CommandLine &CmdL) +{ + pkgCacheFile CacheFile; + if (unlikely(CacheFile.BuildCaches(NULL, false) == false)) + return false; + pkgCache::GrpIterator I = CacheFile.GetPkgCache()->GrpBegin(); + bool const All = _config->FindB("APT::Cache::AllNames", false); + + if (CmdL.FileList[1] != 0) + { + for (;I.end() != true; ++I) + { + if (All == false && (I.PackageList().end() || I.PackageList()->VersionList == 0)) + continue; + if (strncmp(I.Name(),CmdL.FileList[1],strlen(CmdL.FileList[1])) == 0) + cout << I.Name() << endl; + } + + return true; + } + + // Show all pkgs + for (;I.end() != true; ++I) + { + if (All == false && (I.PackageList().end() || I.PackageList()->VersionList == 0)) + continue; + cout << I.Name() << endl; + } + + return true; +} + /*}}}*/ +// Madison - Look a bit like katie's madison /*{{{*/ +// --------------------------------------------------------------------- +/* */ +static bool Madison(CommandLine &CmdL) +{ + pkgCacheFile CacheFile; + pkgSourceList *SrcList = CacheFile.GetSourceList(); + + if (SrcList == 0) + return false; + + // Create the src text record parsers and ignore errors about missing + // deb-src lines that are generated from pkgSrcRecords::pkgSrcRecords + pkgSrcRecords SrcRecs(*SrcList); + if (_error->PendingError() == true) + _error->Discard(); + + APT::CacheSetHelper helper(true, GlobalError::NOTICE); + for (const char **I = CmdL.FileList + 1; *I != 0; I++) + { + _error->PushToStack(); + APT::PackageList pkgset = APT::PackageList::FromString(CacheFile, *I, helper); + for (APT::PackageList::const_iterator Pkg = pkgset.begin(); Pkg != pkgset.end(); ++Pkg) + { + for (pkgCache::VerIterator V = Pkg.VersionList(); V.end() == false; ++V) + { + for (pkgCache::VerFileIterator VF = V.FileList(); VF.end() == false; ++VF) + { +// This might be nice, but wouldn't uniquely identify the source -mdz +// if (VF.File().Archive() != 0) +// { +// cout << setw(10) << Pkg.Name() << " | " << setw(10) << V.VerStr() << " | " +// << VF.File().Archive() << endl; +// } + + // Locate the associated index files so we can derive a description + for (pkgSourceList::const_iterator S = SrcList->begin(); S != SrcList->end(); ++S) + { + vector *Indexes = (*S)->GetIndexFiles(); + for (vector::const_iterator IF = Indexes->begin(); + IF != Indexes->end(); ++IF) + { + if ((*IF)->FindInCache(*(VF.File().Cache())) == VF.File()) + { + cout << setw(10) << Pkg.FullName(true) << " | " << setw(10) << V.VerStr() << " | " + << (*IF)->Describe(true) << endl; + } + } + } + } + } + } + + SrcRecs.Restart(); + pkgSrcRecords::Parser *SrcParser; + bool foundSomething = false; + while ((SrcParser = SrcRecs.Find(*I, false)) != 0) + { + foundSomething = true; + // Maybe support Release info here too eventually + cout << setw(10) << SrcParser->Package() << " | " + << setw(10) << SrcParser->Version() << " | " + << SrcParser->Index().Describe(true) << endl; + } + if (foundSomething == true) + _error->RevertToStack(); + else + _error->MergeWithStack(); + } + + return true; +} + /*}}}*/ +// GenCaches - Call the main cache generator /*{{{*/ +// --------------------------------------------------------------------- +/* */ +static bool GenCaches(CommandLine &) +{ + OpTextProgress Progress(*_config); + + pkgCacheFile CacheFile; + return CacheFile.BuildCaches(&Progress, true); +} + /*}}}*/ +static bool ShowHelp(CommandLine &) /*{{{*/ +{ + std::cout << + _("Usage: apt-cache [options] command\n" + " apt-cache [options] show pkg1 [pkg2 ...]\n" + "\n" + "apt-cache queries and displays available information about installed\n" + "and installable packages. It works exclusively on the data acquired\n" + "into the local cache via the 'update' command of e.g. apt-get. The\n" + "displayed information may therefore be outdated if the last update was\n" + "too long ago, but in exchange apt-cache works independently of the\n" + "availability of the configured sources (e.g. offline).\n"); + return true; +} + /*}}}*/ +static std::vector GetCommands() /*{{{*/ +{ + return { + {"gencaches",&GenCaches, nullptr}, + {"showsrc",&ShowSrcPackage, _("Show source records")}, + {"showpkg",&DumpPackage, nullptr}, + {"stats",&Stats, nullptr}, + {"dump",&Dump, nullptr}, + {"dumpavail",&DumpAvail, nullptr}, + {"unmet",&UnMet, nullptr}, + {"search",&DoSearch, _("Search the package list for a regex pattern")}, + {"depends",&Depends, _("Show raw dependency information for a package")}, + {"rdepends",&RDepends, _("Show reverse dependency information for a package")}, + {"dotty",&Dotty, nullptr}, + {"xvcg",&XVcg, nullptr}, + {"show",&ShowPackage, _("Show a readable record for the package")}, + {"pkgnames",&ShowPkgNames, _("List the names of all packages in the system")}, + {"showauto",&ShowAuto, nullptr}, + {"policy",&Policy, _("Show policy settings")}, + {"madison",&Madison, nullptr}, + {nullptr, nullptr, nullptr} + }; +} + /*}}}*/ +int main(int argc,const char *argv[]) /*{{{*/ +{ + // Parse the command line and initialize the package library + CommandLine CmdL; + auto const Cmds = ParseCommandLine(CmdL, APT_CMD::APT_CACHE, &_config, &_system, argc, argv, &ShowHelp, &GetCommands); + + InitOutput(); + + if (_config->Exists("APT::Cache::Generate") == true) + _config->Set("pkgCacheFile::Generate", _config->FindB("APT::Cache::Generate", true)); + + return DispatchCommandLine(CmdL, Cmds); +} + /*}}}*/ diff --git a/cmdline/apt-cdrom.cc b/cmdline/apt-cdrom.cc new file mode 100644 index 0000000..94202d9 --- /dev/null +++ b/cmdline/apt-cdrom.cc @@ -0,0 +1,235 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + APT CDROM - Tool for handling APT's CDROM database. + + Currently the only option is 'add' which will take the current CD + in the drive and add it into the database. + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + /*}}}*/ + +using namespace std; + +class pkgCdromTextStatus : public pkgCdromStatus /*{{{*/ +{ +protected: + OpTextProgress Progress; + void Prompt(const char *Text); + string PromptLine(const char *Text); + bool AskCdromName(string &name) APT_OVERRIDE; + +public: + virtual void Update(string text, int current) APT_OVERRIDE; + virtual bool ChangeCdrom() APT_OVERRIDE; + virtual OpProgress* GetOpProgress() APT_OVERRIDE; +}; + +void pkgCdromTextStatus::Prompt(const char *Text) +{ + char C; + cout << Text << ' ' << flush; + if (read(STDIN_FILENO,&C,1) < 0) + _error->Errno("pkgCdromTextStatus::Prompt", + "Failed to read from standard input (not a terminal?)"); + if (C != '\n') + cout << endl; +} + +string pkgCdromTextStatus::PromptLine(const char *Text) +{ + cout << Text << ':' << endl; + + string Res; + getline(cin,Res); + return Res; +} + +bool pkgCdromTextStatus::AskCdromName(string &name) +{ + cout << _("Please provide a name for this Disc, such as 'Debian 5.0.3 Disk 1'") << flush; + name = PromptLine(""); + + return true; +} + + +void pkgCdromTextStatus::Update(string text, int /*current*/) +{ + if(text.size() > 0) + cout << text << flush; +} + +bool pkgCdromTextStatus::ChangeCdrom() +{ + Prompt(_("Please insert a Disc in the drive and press [Enter]")); + return true; +} + +APT_PURE OpProgress* pkgCdromTextStatus::GetOpProgress() +{ + return &Progress; +} + /*}}}*/ +// AddOrIdent - Add or Ident a CDROM /*{{{*/ +static bool AddOrIdent(bool const Add) +{ + pkgUdevCdromDevices UdevCdroms; + pkgCdromTextStatus log; + pkgCdrom cdrom; + + bool oneSuccessful = false; + bool AutoDetect = _config->FindB("Acquire::cdrom::AutoDetect", true); + if (AutoDetect == true) + { + bool const Debug = _config->FindB("Debug::Acquire::cdrom", false); + std::string const CDMount = _config->Find("Acquire::cdrom::mount"); + bool const NoMount = _config->FindB("APT::CDROM::NoMount", false); + if (NoMount == false) + _config->Set("APT::CDROM::NoMount", true); + + vector const v = UdevCdroms.Scan(); + for (std::vector::const_iterator cd = v.begin(); cd != v.end(); ++cd) + { + if (Debug) + clog << "Looking at device:" + << "\tDeviveName: '" << cd->DeviceName << "'" + << "\tIsMounted: '" << cd->Mounted << "'" + << "\tMountPoint: '" << cd->MountPath << "'" + << endl; + + std::string AptMountPoint; + if (cd->Mounted) + _config->Set("Acquire::cdrom::mount", cd->MountPath); + else if (NoMount == true) + continue; + else + { + AptMountPoint = _config->FindDir("Dir::Media::MountPath"); + if (FileExists(AptMountPoint) == false) + mkdir(AptMountPoint.c_str(), 0750); + if(MountCdrom(AptMountPoint, cd->DeviceName) == false) + { + _error->Warning(_("Failed to mount '%s' to '%s'"), cd->DeviceName.c_str(), AptMountPoint.c_str()); + continue; + } + _config->Set("Acquire::cdrom::mount", AptMountPoint); + } + + _error->PushToStack(); + if (Add == true) + oneSuccessful = cdrom.Add(&log); + else + { + std::string id; + oneSuccessful = cdrom.Ident(id, &log); + } + _error->MergeWithStack(); + + if (AptMountPoint.empty() == false) + UnmountCdrom(AptMountPoint); + } + if (NoMount == false) + _config->Set("APT::CDROM::NoMount", NoMount); + _config->Set("Acquire::cdrom::mount", CDMount); + } + + // fallback if auto-detect didn't work + if (oneSuccessful == false) + { + _error->PushToStack(); + if (Add == true) + oneSuccessful = cdrom.Add(&log); + else + { + std::string id; + oneSuccessful = cdrom.Ident(id, &log); + } + _error->MergeWithStack(); + } + + if (oneSuccessful == false) + _error->Error("%s", _("No CD-ROM could be auto-detected or found using the default mount point.\n" + "You may try the --cdrom option to set the CD-ROM mount point.\n" + "See 'man apt-cdrom' for more information about the CD-ROM auto-detection and mount point.")); + else if (Add == true) + cout << _("Repeat this process for the rest of the CDs in your set.") << endl; + + return oneSuccessful; +} + /*}}}*/ +// DoAdd - Add a new CDROM /*{{{*/ +// --------------------------------------------------------------------- +/* This does the main add bit.. We show some status and things. The + sequence is to mount/umount the CD, Ident it then scan it for package + files and reduce that list. Then we copy over the package files and + verify them. Then rewrite the database files */ +static bool DoAdd(CommandLine &) +{ + return AddOrIdent(true); +} + /*}}}*/ +// DoIdent - Ident a CDROM /*{{{*/ +static bool DoIdent(CommandLine &) +{ + return AddOrIdent(false); +} + /*}}}*/ +static bool ShowHelp(CommandLine &) /*{{{*/ +{ + std::cout << + _("Usage: apt-cdrom [options] command\n" + "\n" + "apt-cdrom is used to add CDROM's, USB flashdrives and other removable\n" + "media types as package sources to APT. The mount point and device\n" + "information is taken from apt.conf(5), udev(7) and fstab(5).\n"); + return true; +} + /*}}}*/ +static std::vector GetCommands() /*{{{*/ +{ + return { + {"add", &DoAdd, "Add a CDROM"}, + {"ident", &DoIdent, "Report the identity of a CDROM"}, + {nullptr, nullptr, nullptr} + }; +} + /*}}}*/ +int main(int argc,const char *argv[]) /*{{{*/ +{ + // Parse the command line and initialize the package library + CommandLine CmdL; + auto const Cmds = ParseCommandLine(CmdL, APT_CMD::APT_CDROM, &_config, &_system, argc, argv, &ShowHelp, &GetCommands); + + InitOutput(); + + return DispatchCommandLine(CmdL, Cmds); +} + /*}}}*/ diff --git a/cmdline/apt-config.cc b/cmdline/apt-config.cc new file mode 100644 index 0000000..6b1e385 --- /dev/null +++ b/cmdline/apt-config.cc @@ -0,0 +1,144 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + APT Config - Program to manipulate APT configuration files + + This program will parse a config file and then do something with it. + + Commands: + shell - Shell mode. After this a series of word pairs should occur. + The first is the environment var to set and the second is + the key to set it from. Use like: + eval `apt-config shell QMode apt::QMode` + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include + /*}}}*/ +using namespace std; + +// DoShell - Handle the shell command /*{{{*/ +// --------------------------------------------------------------------- +/* */ +static bool DoShell(CommandLine &CmdL) +{ + for (const char **I = CmdL.FileList + 1; *I != 0; I += 2) + { + if (I[1] == 0 || strlen(I[1]) == 0) + return _error->Error(_("Arguments not in pairs")); + + string key = I[1]; + if (key.end()[-1] == '/') // old directory format + key.append("d"); + + if (_config->ExistsAny(key.c_str())) + cout << *I << "='" << + SubstVar(_config->FindAny(key.c_str()),"'","'\\''") << '\'' << endl; + + } + + return true; +} + /*}}}*/ +// DoDump - Dump the configuration space /*{{{*/ +// --------------------------------------------------------------------- +/* */ +static bool DoDump(CommandLine &CmdL) +{ + bool const empty = _config->FindB("APT::Config::Dump::EmptyValue", true); + std::string const format = _config->Find("APT::Config::Dump::Format", "%F \"%v\";\n"); + if (CmdL.FileSize() == 1) + _config->Dump(cout, NULL, format.c_str(), empty); + else + for (const char **I = CmdL.FileList + 1; *I != 0; ++I) + _config->Dump(cout, *I, format.c_str(), empty); + return true; +} + /*}}}*/ +static bool ShowHelp(CommandLine &) /*{{{*/ +{ + std::cout << + _("Usage: apt-config [options] command\n" + "\n" + "apt-config is an interface to the configuration settings used by\n" + "all APT tools, mainly intended for debugging and shell scripting.\n"); + return true; +} + /*}}}*/ +static std::vector GetCommands() /*{{{*/ +{ + return { + {"shell", &DoShell, _("get configuration values via shell evaluation")}, + {"dump", &DoDump, _("show the active configuration setting")}, + {nullptr, nullptr, nullptr} + }; +} + /*}}}*/ +int main(int argc,const char *argv[]) /*{{{*/ +{ + // Parse the command line and initialize the package library + CommandLine CmdL; + auto const Cmds = ParseCommandLine(CmdL, APT_CMD::APT_CONFIG, &_config, &_system, argc, argv, &ShowHelp, &GetCommands); + + std::vector const langs = APT::Configuration::getLanguages(true); + _config->Clear("Acquire::Languages"); + for (std::vector::const_iterator l = langs.begin(); l != langs.end(); ++l) + _config->Set("Acquire::Languages::", *l); + + std::vector const archs = APT::Configuration::getArchitectures(); + _config->Clear("APT::Architectures"); + for (std::vector::const_iterator a = archs.begin(); a != archs.end(); ++a) + _config->Set("APT::Architectures::", *a); + + string const conf = "APT::Compressor::"; + std::map CompressorNames; + for (auto && key : _config->FindVector("APT::Compressor", "", true)) + { + auto const comp = conf + key + "::Name"; + CompressorNames.emplace(_config->Find(comp, key), key); + } + std::vector const compressors = APT::Configuration::getCompressors(); + _config->Clear("APT::Compressor"); + for (std::vector::const_iterator c = compressors.begin(); c != compressors.end(); ++c) + { + auto const n = CompressorNames.find(c->Name); + string comp = conf + (n == CompressorNames.end() ? c->Name : n->second) + "::"; + _config->Set(comp + "Name", c->Name); + _config->Set(comp + "Extension", c->Extension); + _config->Set(comp + "Binary", c->Binary); + _config->Set((comp + "Cost").c_str(), c->Cost); + for (std::vector::const_iterator a = c->CompressArgs.begin(); a != c->CompressArgs.end(); ++a) + _config->Set(comp + "CompressArg::", *a); + for (std::vector::const_iterator a = c->UncompressArgs.begin(); a != c->UncompressArgs.end(); ++a) + _config->Set(comp + "UncompressArg::", *a); + } + + std::vector const profiles = APT::Configuration::getBuildProfiles(); + _config->Clear("APT::Build-Profiles"); + for (std::vector::const_iterator p = profiles.begin(); p != profiles.end(); ++p) + _config->Set("APT::Build-Profiles::", *p); + + return DispatchCommandLine(CmdL, Cmds); +} + /*}}}*/ diff --git a/cmdline/apt-dump-solver.cc b/cmdline/apt-dump-solver.cc new file mode 100644 index 0000000..3e59035 --- /dev/null +++ b/cmdline/apt-dump-solver.cc @@ -0,0 +1,184 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ##################################################################### + + dummy solver to get quickly a scenario file out of APT + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include + /*}}}*/ + +static bool ShowHelp(CommandLine &) /*{{{*/ +{ + std::cout << + _("Usage: apt-dump-solver\n" + "\n" + "apt-dump-solver is an interface to store an EDSP scenario in\n" + "a file and optionally forwards it to another solver.\n"); + return true; +} + /*}}}*/ +static std::vector GetCommands() /*{{{*/ +{ + return {}; +} + /*}}}*/ +static int WriteError(char const * const uid, std::ostringstream &out, FileFd &stdoutfd, pid_t const &Solver)/*{{{*/ +{ + _error->DumpErrors(out); + // ensure the solver isn't printing into "our" error message, too + if (Solver != 0) + ExecWait(Solver, "dump", true); + EDSP::WriteError(uid, out.str(), stdoutfd); + return 0; +} + /*}}}*/ +int main(int argc,const char *argv[]) /*{{{*/ +{ + CommandLine CmdL; + ParseCommandLine(CmdL, APT_CMD::APT_DUMP_SOLVER, &_config, nullptr, argc, argv, &ShowHelp, &GetCommands); + _config->Clear("Dir::Log"); + + bool const is_forwarding_dumper = (CmdL.FileSize() != 0); + + FileFd stdoutfd; + if (stdoutfd.OpenDescriptor(STDOUT_FILENO, FileFd::WriteOnly | FileFd::BufferedWrite, true) == false) + return 252; + + FileFd dump; + char const * const filename = is_forwarding_dumper ? CmdL.FileList[0] : getenv("APT_EDSP_DUMP_FILENAME"); + if (filename == nullptr || strlen(filename) == 0) + { + if (is_forwarding_dumper == false) + { + EDSP::WriteError("ERR_NO_FILENAME", "You have to set the environment variable APT_EDSP_DUMP_FILENAME\n" + "to a valid filename to store the dump of EDSP solver input in.\n" + "For example with: export APT_EDSP_DUMP_FILENAME=/tmp/dump.edsp", stdoutfd); + return 0; + } + } + else + { + // ignore errors here as logging isn't really critical + _error->PushToStack(); + if (dump.Open(filename, FileFd::WriteOnly | FileFd::Exclusive | FileFd::Create, FileFd::Extension, 0644) == false && + is_forwarding_dumper == false) + { + _error->MergeWithStack(); + std::ostringstream out; + out << "Writing EDSP solver input to file '" << filename << "' failed as it couldn't be created!\n"; + return WriteError("ERR_CREATE_FILE", out, stdoutfd, 0); + } + _error->RevertToStack(); + } + + pid_t Solver = 0; + FileFd forward; + if (is_forwarding_dumper) + { + signal(SIGPIPE, SIG_IGN); + int external[] = {-1, -1}; + if (pipe(external) != 0) + return 250; + for (int i = 0; i < 2; ++i) + SetCloseExec(external[i], true); + + Solver = ExecFork(); + if (Solver == 0) { + _config->Set("APT::Sandbox::User", _config->Find("APT::Solver::RunAsUser", _config->Find("APT::Sandbox::User"))); + DropPrivileges(); + dup2(external[0], STDIN_FILENO); + execv(CmdL.FileList[1], const_cast(CmdL.FileList + 1)); + std::cerr << "Failed to execute '" << CmdL.FileList[1] << "'!" << std::endl; + _exit(100); + } + close(external[0]); + + if (WaitFd(external[1], true, 5) == false) + return 251; + + if (forward.OpenDescriptor(external[1], FileFd::WriteOnly | FileFd::BufferedWrite, true) == false) + return 252; + } + + DropPrivileges(); + + FileFd input; + if (input.OpenDescriptor(STDIN_FILENO, FileFd::ReadOnly) == false) + { + std::ostringstream out; + out << "Writing EDSP solver input to file '" << filename << "' failed as stdin couldn't be opened!\n"; + return WriteError("ERR_READ_ERROR", out, stdoutfd, Solver); + } + + std::unique_ptr Buf(new char[APT_BUFFER_SIZE]); + unsigned long long ToRead = 0; + do { + if (input.Read(Buf.get(), APT_BUFFER_SIZE, &ToRead) == false) + { + std::ostringstream out; + out << "Writing EDSP solver input to file '" << filename << "' failed as reading from stdin failed!\n"; + return WriteError("ERR_READ_ERROR", out, stdoutfd, Solver); + } + if (ToRead == 0) + break; + if (forward.IsOpen() && forward.Failed() == false && forward.Write(Buf.get(),ToRead) == false) + forward.Close(); + if (dump.IsOpen() && dump.Failed() == false && dump.Write(Buf.get(),ToRead) == false) + dump.Close(); + } while (true); + input.Close(); + forward.Close(); + dump.Close(); + + if (is_forwarding_dumper) + { + // Wait and collect the error code + int Status; + while (waitpid(Solver, &Status, 0) != Solver) + { + if (errno == EINTR) + continue; + + std::ostringstream out; + ioprintf(out, _("Waited for %s but it wasn't there"), CmdL.FileList[1]); + return WriteError("ERR_FORWARD", out, stdoutfd, 0); + } + if (WIFEXITED(Status)) + return WEXITSTATUS(Status); + else + return 255; + } + else if (_error->PendingError()) + { + std::ostringstream out; + out << "Writing EDSP solver input to file '" << filename << "' failed due to write errors!\n"; + return WriteError("ERR_WRITE_ERROR", out, stdoutfd, Solver); + } + else + EDSP::WriteError("ERR_JUST_DUMPING", "I am too dumb, i can just dump!\nPlease use one of my friends instead!", stdoutfd); + return 0; +} diff --git a/cmdline/apt-extracttemplates.cc b/cmdline/apt-extracttemplates.cc new file mode 100644 index 0000000..a578fa8 --- /dev/null +++ b/cmdline/apt-extracttemplates.cc @@ -0,0 +1,336 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + APT Extract Templates - Program to extract debconf config and template + files + + This is a simple program to extract config and template information + from Debian packages. It can be used to speed up the preconfiguration + process for debconf-enabled packages + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "apt-extracttemplates.h" + +#include + /*}}}*/ + +using namespace std; + +pkgCache *DebFile::Cache = 0; + +// DebFile::DebFile - Construct the DebFile object /*{{{*/ +// --------------------------------------------------------------------- +/* */ +DebFile::DebFile(const char *debfile) + : File(debfile, FileFd::ReadOnly), Control(NULL), ControlLen(0), + DepOp(0), PreDepOp(0), Config(0), Template(0), Which(None) +{ +} + /*}}}*/ +// DebFile::~DebFile - Destruct the DebFile object /*{{{*/ +// --------------------------------------------------------------------- +/* */ +DebFile::~DebFile() +{ + delete [] Control; + delete [] Config; + delete [] Template; +} + /*}}}*/ +// DebFile::GetInstalledVer - Find out the installed version of a pkg /*{{{*/ +// --------------------------------------------------------------------- +/* */ +string DebFile::GetInstalledVer(const string &package) +{ + pkgCache::PkgIterator Pkg = Cache->FindPkg(package); + if (Pkg.end() == false) + { + pkgCache::VerIterator V = Pkg.CurrentVer(); + if (V.end() == false) + { + return V.VerStr(); + } + } + + return string(); +} + /*}}}*/ +// DebFile::Go - Start extracting a debian package /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool DebFile::Go() +{ + debDebFile Deb(File); + + return Deb.ExtractTarMember(*this, "control.tar"); +} + /*}}}*/ +// DebFile::DoItem examine element in package and mark /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool DebFile::DoItem(Item &I, int &Fd) +{ + if (strcmp(I.Name, "control") == 0) + { + delete [] Control; + Control = new char[I.Size+3]; + Control[I.Size] = '\n'; + Control[I.Size + 1] = '\n'; + Control[I.Size + 2] = '\0'; + Which = IsControl; + ControlLen = I.Size + 3; + // make it call the Process method below. this is so evil + Fd = -2; + } + else if (strcmp(I.Name, "config") == 0) + { + delete [] Config; + Config = new char[I.Size+1]; + Config[I.Size] = 0; + Which = IsConfig; + Fd = -2; + } + else if (strcmp(I.Name, "templates") == 0) + { + delete [] Template; + Template = new char[I.Size+1]; + Template[I.Size] = 0; + Which = IsTemplate; + Fd = -2; + } + else + { + // Ignore it + Fd = -1; + } + return true; +} + /*}}}*/ +// DebFile::Process examine element in package and copy /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool DebFile::Process(Item &/*I*/, const unsigned char *data, + unsigned long long size, unsigned long long pos) +{ + switch (Which) + { + case IsControl: + memcpy(Control + pos, data, size); + break; + case IsConfig: + memcpy(Config + pos, data, size); + break; + case IsTemplate: + memcpy(Template + pos, data, size); + break; + default: /* throw it away */ ; + } + return true; +} + /*}}}*/ +// DebFile::ParseInfo - Parse control file for dependency info /*{{{*/ +// --------------------------------------------------------------------- +/* */ +bool DebFile::ParseInfo() +{ + if (Control == NULL) return false; + + pkgTagSection Section; + if (Section.Scan(Control, ControlLen) == false) + return false; + + Package = Section.Find(pkgTagSection::Key::Package).to_string(); + Version = GetInstalledVer(Package); + + const char *Start, *Stop; + if (Section.Find(pkgTagSection::Key::Depends, Start, Stop)) + { + while (1) + { + string P, V; + unsigned int Op; + Start = debListParser::ParseDepends(Start, Stop, P, V, Op); + if (Start == 0) return false; + if (P == "debconf") + { + DepVer = V; + DepOp = Op; + break; + } + if (Start == Stop) break; + } + } + + if (Section.Find(pkgTagSection::Key::Pre_Depends, Start, Stop)) + { + while (1) + { + string P, V; + unsigned int Op; + Start = debListParser::ParseDepends(Start, Stop, P, V, Op); + if (Start == 0) return false; + if (P == "debconf") + { + PreDepVer = V; + PreDepOp = Op; + break; + } + if (Start == Stop) break; + } + } + + return true; +} + /*}}}*/ +static bool ShowHelp(CommandLine &) /*{{{*/ +{ + cout << + _("Usage: apt-extracttemplates file1 [file2 ...]\n" + "\n" + "apt-extracttemplates is used to extract config and template files\n" + "from debian packages. It is used mainly by debconf(1) to prompt for\n" + "configuration questions before installation of packages.\n"); + return true; +} + /*}}}*/ +// WriteFile - write the contents of the passed string to a file /*{{{*/ +// --------------------------------------------------------------------- +/* */ +static string WriteFile(const char *package, const char *prefix, const char *data) +{ + FileFd f; + std::string tplname; + strprintf(tplname, "%s.%s", package, prefix); + GetTempFile(tplname, false, &f); + if (data != nullptr) + f.Write(data, strlen(data)); + return f.Name(); +} + /*}}}*/ +// WriteConfig - write out the config data from a debian package file /*{{{*/ +// --------------------------------------------------------------------- +/* */ +static void WriteConfig(const DebFile &file) +{ + string templatefile = WriteFile(file.Package.c_str(), "template", file.Template); + string configscript = WriteFile(file.Package.c_str(), "config", file.Config); + + if (templatefile.empty() == true || configscript.empty() == true) + return; + cout << file.Package << " " << file.Version << " " + << templatefile << " " << configscript << endl; +} + /*}}}*/ +// InitCache - initialize the package cache /*{{{*/ +// --------------------------------------------------------------------- +/* */ +static bool Go(CommandLine &CmdL) +{ + // Initialize the apt cache + MMap *Map = 0; + pkgSourceList List; + List.ReadMainList(); + pkgCacheGenerator::MakeStatusCache(List,NULL,&Map,true); + if (Map == 0) + return false; + DebFile::Cache = new pkgCache(Map); + if (_error->PendingError() == true) + return false; + + // Find out what version of debconf is currently installed + string debconfver = DebFile::GetInstalledVer("debconf"); + if (debconfver.empty() == true) + return _error->Error( _("Cannot get debconf version. Is debconf installed?")); + + auto const tmpdir = _config->Find("APT::ExtractTemplates::TempDir"); + if (tmpdir.empty() == false) + setenv("TMPDIR", tmpdir.c_str(), 1); + + // Process each package passsed in + for (unsigned int I = 0; I != CmdL.FileSize(); I++) + { + // Will pick up the errors later.. + DebFile file(CmdL.FileList[I]); + if (file.Go() == false) + { + _error->Error("Prior errors apply to %s",CmdL.FileList[I]); + continue; + } + + // Does the package have templates? + if (file.Template != 0 && file.ParseInfo() == true) + { + // Check to make sure debconf dependencies are + // satisfied + // cout << "Check " << file.DepVer << ',' << debconfver << endl; + if (file.DepVer != "" && + DebFile::Cache->VS->CheckDep(debconfver.c_str(), + file.DepOp,file.DepVer.c_str() + ) == false) + continue; + if (file.PreDepVer != "" && + DebFile::Cache->VS->CheckDep(debconfver.c_str(), + file.PreDepOp,file.PreDepVer.c_str() + ) == false) + continue; + + WriteConfig(file); + } + } + + + delete Map; + delete DebFile::Cache; + + return !_error->PendingError(); +} + /*}}}*/ +static std::vector GetCommands() /*{{{*/ +{ + return { + {nullptr, nullptr, nullptr} + }; +} + /*}}}*/ +int main(int argc, const char **argv) /*{{{*/ +{ + CommandLine CmdL; + ParseCommandLine(CmdL, APT_CMD::APT_EXTRACTTEMPLATES, &_config, &_system, argc, argv, &ShowHelp, &GetCommands); + + Go(CmdL); + + return DispatchCommandLine(CmdL, {}); +} + /*}}}*/ diff --git a/cmdline/apt-extracttemplates.h b/cmdline/apt-extracttemplates.h new file mode 100644 index 0000000..bbd926e --- /dev/null +++ b/cmdline/apt-extracttemplates.h @@ -0,0 +1,49 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + apt-extracttemplate - tool to extract template and config data + + ##################################################################### */ + /*}}}*/ +#ifndef _APTEXTRACTTEMPLATE_H_ +#define _APTEXTRACTTEMPLATE_H_ + +#include +#include + +#include + +class pkgCache; + +class DebFile : public pkgDirStream +{ + FileFd File; + char *Control; + unsigned long ControlLen; + +public: + explicit DebFile(const char *FileName); + ~DebFile(); + bool DoItem(Item &I, int &fd) APT_OVERRIDE; + bool Process(pkgDirStream::Item &I, const unsigned char *data, + unsigned long long size, unsigned long long pos) APT_OVERRIDE; + + bool Go(); + bool ParseInfo(); + + static std::string GetInstalledVer(const std::string &package); + + std::string Package; + std::string Version; + std::string DepVer, PreDepVer; + unsigned int DepOp, PreDepOp; + + char *Config; + char *Template; + + static pkgCache *Cache; + enum { None, IsControl, IsConfig, IsTemplate } Which; +}; + +#endif diff --git a/cmdline/apt-get.cc b/cmdline/apt-get.cc new file mode 100644 index 0000000..2828ad6 --- /dev/null +++ b/cmdline/apt-get.cc @@ -0,0 +1,452 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + apt-get - Cover for dpkg + + This is an allout cover for dpkg implementing a safer front end. It is + based largely on libapt-pkg. + + The syntax is different, + apt-get [opt] command [things] + Where command is: + update - Resyncronize the package files from their sources + upgrade - Smart-Download the newest versions of all packages + dselect-upgrade - Follows dselect's changes to the Status: field + and installs new and removes old packages + dist-upgrade - Powerful upgrader designed to handle the issues with + a new distribution. + install - Download and install a given package (by name, not by .deb) + check - Update the package cache and check for broken packages + clean - Erase the .debs downloaded to /var/cache/apt/archives and + the partial dir too + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + /*}}}*/ + +using namespace std; + +/* mark packages as automatically/manually installed. {{{*/ +static bool DoMarkAuto(CommandLine &CmdL) +{ + bool Action = true; + int AutoMarkChanged = 0; + OpTextProgress progress; + CacheFile Cache; + if (Cache.Open() == false) + return false; + + if (strcasecmp(CmdL.FileList[0],"markauto") == 0) + Action = true; + else if (strcasecmp(CmdL.FileList[0],"unmarkauto") == 0) + Action = false; + + for (const char **I = CmdL.FileList + 1; *I != 0; I++) + { + const char *S = *I; + // Locate the package + pkgCache::PkgIterator Pkg = Cache->FindPkg(S); + if (Pkg.end() == true) { + return _error->Error(_("Couldn't find package %s"),S); + } + else + { + if (!Action) + ioprintf(c1out,_("%s set to manually installed.\n"), Pkg.Name()); + else + ioprintf(c1out,_("%s set to automatically installed.\n"), + Pkg.Name()); + + Cache->MarkAuto(Pkg,Action); + AutoMarkChanged++; + } + } + + _error->Notice(_("This command is deprecated. Please use 'apt-mark auto' and 'apt-mark manual' instead.")); + + if (AutoMarkChanged && ! _config->FindB("APT::Get::Simulate",false)) + return Cache->writeStateFile(NULL); + return false; +} + /*}}}*/ +// DoDSelectUpgrade - Do an upgrade by following dselects selections /*{{{*/ +// --------------------------------------------------------------------- +/* Follows dselect's selections */ +static bool DoDSelectUpgrade(CommandLine &) +{ + CacheFile Cache; + if (Cache.OpenForInstall() == false || Cache.CheckDeps() == false) + return false; + + pkgDepCache::ActionGroup group(Cache); + + // Install everything with the install flag set + for (pkgCache::PkgIterator I = Cache->PkgBegin(); I.end() != true; ++I) + { + /* Install the package only if it is a new install, the autoupgrader + will deal with the rest */ + if (I->SelectedState == pkgCache::State::Install) + Cache->MarkInstall(I,false); + } + + /* Now install their deps too, if we do this above then order of + the status file is significant for | groups */ + for (pkgCache::PkgIterator I = Cache->PkgBegin(); I.end() != true; ++I) + { + /* Install the package only if it is a new install, the autoupgrader + will deal with the rest */ + if (I->SelectedState == pkgCache::State::Install) + Cache->MarkInstall(I,true); + } + + // Apply erasures now, they override everything else. + for (pkgCache::PkgIterator I = Cache->PkgBegin(); I.end() != true; ++I) + { + // Remove packages + if (I->SelectedState == pkgCache::State::DeInstall || + I->SelectedState == pkgCache::State::Purge) + Cache->MarkDelete(I,I->SelectedState == pkgCache::State::Purge); + } + + /* Resolve any problems that dselect created, allupgrade cannot handle + such things. We do so quite aggressively too.. */ + if (Cache->BrokenCount() != 0) + { + pkgProblemResolver Fix(Cache); + + // Hold back held packages. + if (_config->FindB("APT::Ignore-Hold",false) == false) + { + for (pkgCache::PkgIterator I = Cache->PkgBegin(); I.end() == false; ++I) + { + if (I->SelectedState == pkgCache::State::Hold) + { + Fix.Protect(I); + Cache->MarkKeep(I); + } + } + } + + if (Fix.Resolve() == false) + { + ShowBroken(c1out,Cache,false); + return _error->Error(_("Internal error, problem resolver broke stuff")); + } + } + + // Now upgrade everything + if (APT::Upgrade::Upgrade(Cache, APT::Upgrade::FORBID_REMOVE_PACKAGES | APT::Upgrade::FORBID_INSTALL_NEW_PACKAGES) == false) + { + ShowBroken(c1out,Cache,false); + return _error->Error(_("Internal error, problem resolver broke stuff")); + } + + APT::PackageVector HeldBackPackages; + for (pkgCache::PkgIterator Pkg = Cache->PkgBegin(); not Pkg.end(); ++Pkg) + if (Pkg->CurrentVer != 0 && Cache[Pkg].Upgradable() && not Cache[Pkg].Upgrade() && not Cache[Pkg].Delete()) + HeldBackPackages.push_back(Pkg); + + return InstallPackages(Cache, HeldBackPackages, false); +} + /*}}}*/ +// DoCheck - Perform the check operation /*{{{*/ +// --------------------------------------------------------------------- +/* Opening automatically checks the system, this command is mostly used + for debugging */ +static bool DoCheck(CommandLine &) +{ + CacheFile Cache; + Cache.Open(); + Cache.CheckDeps(); + + return true; +} + /*}}}*/ +// DoIndexTargets - Lists all IndexTargets /*{{{*/ +static std::string format_key(std::string key) +{ + // deb822 is case-insensitive, but the human eye prefers candy + std::transform(key.begin(), key.end(), key.begin(), ::tolower); + key[0] = ::toupper(key[0]); + size_t found = key.find("_uri"); + if (found != std::string::npos) + key.replace(found, 4, "-URI"); + while ((found = key.find('_')) != std::string::npos) + { + key[found] = '-'; + key[found + 1] = ::toupper(key[found + 1]); + } + return key; +} +static bool DoIndexTargets(CommandLine &CmdL) +{ + pkgCacheFile CacheFile; + pkgSourceList *SrcList = CacheFile.GetSourceList(); + pkgCache *Cache = CacheFile.GetPkgCache(); + + if (SrcList == nullptr || Cache == nullptr) + return false; + + std::string const Format = _config->Find("APT::Get::IndexTargets::Format"); + bool const ReleaseInfo = _config->FindB("APT::Get::IndexTargets::ReleaseInfo", true); + bool Filtered = CmdL.FileSize() > 1; + for (pkgSourceList::const_iterator S = SrcList->begin(); S != SrcList->end(); ++S) + { + std::vector const targets = (*S)->GetIndexTargets(); + std::map AddOptions; + if (ReleaseInfo) + { + AddOptions.insert(std::make_pair("TRUSTED", ((*S)->IsTrusted() ? "yes" : "no"))); + pkgCache::RlsFileIterator const RlsFile = (*S)->FindInCache(*Cache, false); + if (RlsFile.end()) + continue; +#define APT_RELEASE(X,Y) if (RlsFile.Y() != NULL) AddOptions.insert(std::make_pair(X, RlsFile.Y())) + APT_RELEASE("CODENAME", Codename); + APT_RELEASE("SUITE", Archive); + APT_RELEASE("VERSION", Version); + APT_RELEASE("ORIGIN", Origin); + APT_RELEASE("LABEL", Label); +#undef APT_RELEASE + } + + for (std::vector::const_iterator T = targets.begin(); T != targets.end(); ++T) + { + std::string filename = T->Option(ReleaseInfo ? IndexTarget::EXISTING_FILENAME : IndexTarget::FILENAME); + if (filename.empty()) + continue; + + std::ostringstream stanza; + if (Filtered || Format.empty()) + { + stanza << "MetaKey: " << T->MetaKey << "\n" + << "ShortDesc: " << T->ShortDesc << "\n" + << "Description: " << T->Description << "\n" + << "URI: " << T->URI << "\n" + << "Filename: " << filename << "\n" + << "Optional: " << (T->IsOptional ? "yes" : "no") << "\n" + << "KeepCompressed: " << (T->KeepCompressed ? "yes" : "no") << "\n"; + for (std::map::const_iterator O = AddOptions.begin(); O != AddOptions.end(); ++O) + stanza << format_key(O->first) << ": " << O->second << "\n"; + for (std::map::const_iterator O = T->Options.begin(); O != T->Options.end(); ++O) + { + if (O->first == "PDIFFS") + stanza << "PDiffs: " << O->second << "\n"; + else if (O->first == "COMPRESSIONTYPES") + stanza << "CompressionTypes: " << O->second << "\n"; + else if (O->first == "KEEPCOMPRESSEDAS") + stanza << "KeepCompressedAs: " << O->second << "\n"; + else if (O->first == "DEFAULTENABLED") + stanza << "DefaultEnabled: " << O->second << "\n"; + else + stanza << format_key(O->first) << ": " << O->second << "\n"; + } + stanza << "\n"; + + if (Filtered) + { + // that is a bit crude, but good enough for now + bool found = true; + std::string haystack = std::string("\n") + stanza.str() + "\n"; + std::transform(haystack.begin(), haystack.end(), haystack.begin(), ::tolower); + size_t const filesize = CmdL.FileSize() - 1; + for (size_t i = 0; i != filesize; ++i) + { + std::string needle = std::string("\n") + CmdL.FileList[i + 1] + "\n"; + std::transform(needle.begin(), needle.end(), needle.begin(), ::tolower); + if (haystack.find(needle) != std::string::npos) + continue; + found = false; + break; + } + if (found == false) + continue; + } + } + + if (Format.empty()) + cout << stanza.str(); + else + { + std::string out = SubstVar(Format, "$(FILENAME)", filename); + out = T->Format(out); + for (std::map::const_iterator O = AddOptions.begin(); O != AddOptions.end(); ++O) + out = SubstVar(out, std::string("$(") + O->first + ")", O->second); + cout << out << std::endl; + } + } + } + + return true; +} + /*}}}*/ +static bool ShowHelp(CommandLine &) /*{{{*/ +{ + if (_config->FindB("version") == true) + { + cout << _("Supported modules:") << endl; + + for (unsigned I = 0; I != pkgVersioningSystem::GlobalListLen; I++) + { + pkgVersioningSystem *VS = pkgVersioningSystem::GlobalList[I]; + if (_system != 0 && _system->VS == VS) + cout << '*'; + else + cout << ' '; + cout << "Ver: " << VS->Label << endl; + + /* Print out all the packaging systems that will work with + this VS */ + for (unsigned J = 0; J != pkgSystem::GlobalListLen; J++) + { + pkgSystem *Sys = pkgSystem::GlobalList[J]; + if (_system == Sys) + cout << '*'; + else + cout << ' '; + if (Sys->VS->TestCompatibility(*VS) == true) + cout << "Pkg: " << Sys->Label << " (Priority " << Sys->Score(*_config) << ")" << endl; + } + } + + for (unsigned I = 0; I != pkgSourceList::Type::GlobalListLen; I++) + { + pkgSourceList::Type *Type = pkgSourceList::Type::GlobalList[I]; + cout << " S.L: '" << Type->Name << "' " << Type->Label << endl; + } + + for (unsigned I = 0; I != pkgIndexFile::Type::GlobalListLen; I++) + { + pkgIndexFile::Type *Type = pkgIndexFile::Type::GlobalList[I]; + cout << " Idx: " << Type->Label << endl; + } + + return true; + } + + std::cout << + _("Usage: apt-get [options] command\n" + " apt-get [options] install|remove pkg1 [pkg2 ...]\n" + " apt-get [options] source pkg1 [pkg2 ...]\n" + "\n" + "apt-get is a command line interface for retrieval of packages\n" + "and information about them from authenticated sources and\n" + "for installation, upgrade and removal of packages together\n" + "with their dependencies.\n"); + return true; +} + /*}}}*/ +static std::vector GetCommands() /*{{{*/ +{ + // advanced commands are left undocumented on purpose + return { + {"update", &DoUpdate, _("Retrieve new lists of packages")}, + {"upgrade", &DoUpgrade, _("Perform an upgrade")}, + {"install", &DoInstall, _("Install new packages (pkg is libc6 not libc6.deb)")}, + {"reinstall", &DoInstall, _("Reinstall packages (pkg is libc6 not libc6.deb)")}, + {"remove", &DoInstall, _("Remove packages")}, + {"purge", &DoInstall, _("Remove packages and config files")}, + {"autoremove", &DoInstall, _("Remove automatically all unused packages")}, + {"auto-remove", &DoInstall, nullptr}, + {"autopurge",&DoInstall, nullptr}, + {"markauto", &DoMarkAuto, nullptr}, + {"unmarkauto", &DoMarkAuto, nullptr}, + {"dist-upgrade", &DoDistUpgrade, _("Distribution upgrade, see apt-get(8)")}, + {"full-upgrade", &DoDistUpgrade, nullptr}, + {"dselect-upgrade", &DoDSelectUpgrade, _("Follow dselect selections")}, + {"build-dep", &DoBuildDep, _("Configure build-dependencies for source packages")}, + {"satisfy", &DoBuildDep, _("Satisfy dependency strings")}, + {"clean", &DoClean, _("Erase downloaded archive files")}, + {"autoclean", &DoAutoClean, _("Erase old downloaded archive files")}, + {"auto-clean", &DoAutoClean, nullptr}, + {"distclean", &DoDistClean, nullptr}, + {"dist-clean", &DoDistClean, nullptr}, + {"check", &DoCheck, _("Verify that there are no broken dependencies")}, + {"source", &DoSource, _("Download source archives")}, + {"download", &DoDownload, _("Download the binary package into the current directory")}, + {"changelog", &DoChangelog, _("Download and display the changelog for the given package")}, + {"indextargets", &DoIndexTargets, nullptr}, + {"moo", &DoMoo, nullptr}, + {nullptr, nullptr, nullptr} + }; +} + /*}}}*/ +int main(int argc,const char *argv[]) /*{{{*/ +{ + // Parse the command line and initialize the package library + CommandLine CmdL; + auto const Cmds = ParseCommandLine(CmdL, APT_CMD::APT_GET, &_config, &_system, argc, argv, &ShowHelp, &GetCommands); + + InitSignals(); + InitOutput(); + + CheckIfSimulateMode(CmdL); + + return DispatchCommandLine(CmdL, Cmds); +} + /*}}}*/ diff --git a/cmdline/apt-helper.cc b/cmdline/apt-helper.cc new file mode 100644 index 0000000..f48cb29 --- /dev/null +++ b/cmdline/apt-helper.cc @@ -0,0 +1,334 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ##################################################################### + apt-helper - cmdline helpers + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + /*}}}*/ + +static bool DoAutoDetectProxy(CommandLine &CmdL) /*{{{*/ +{ + if (CmdL.FileSize() != 2) + return _error->Error(_("Need one URL as argument")); + URI ServerURL(CmdL.FileList[1]); + if (AutoDetectProxy(ServerURL) == false) + return false; + std::string SpecificProxy = _config->Find("Acquire::"+ServerURL.Access+"::Proxy::" + ServerURL.Host); + ioprintf(std::cout, "Using proxy '%s' for URL '%s'\n", + SpecificProxy.c_str(), std::string(ServerURL).c_str()); + + return true; +} + /*}}}*/ +static bool DoDownloadFile(CommandLine &CmdL) /*{{{*/ +{ + if (CmdL.FileSize() <= 2) + return _error->Error(_("Must specify at least one pair url/filename")); + + aptAcquireWithTextStatus Fetcher; + size_t fileind = 0; + std::vector targetfiles; + while (fileind + 2 <= CmdL.FileSize()) + { + std::string download_uri = CmdL.FileList[fileind + 1]; + std::string targetfile = CmdL.FileList[fileind + 2]; + HashStringList hashes; + + fileind += 2; + + // An empty string counts as a hash for compatibility reasons + if (CmdL.FileSize() > fileind + 1 && *CmdL.FileList[fileind + 1] == '\0') + fileind++; + + /* Let's start looking for hashes */ + for (auto i = fileind + 1; CmdL.FileSize() > i; i++) + { + bool isAHash = false; + + for (auto HashP = HashString::SupportedHashes(); *HashP != nullptr; HashP++) + { + if (APT::String::Startswith(CmdL.FileList[i], *HashP)) + isAHash = true; + } + + if (!isAHash) + break; + + hashes.push_back(HashString(CmdL.FileList[i])); + fileind++; + } + + // we use download_uri as descr and targetfile as short-descr + new pkgAcqFile(&Fetcher, download_uri, hashes, 0, download_uri, targetfile, + "dest-dir-ignored", targetfile); + targetfiles.push_back(targetfile); + } + + bool Failed = false; + if (AcquireRun(Fetcher, 0, &Failed, NULL) == false || Failed == true) + return _error->Error(_("Download Failed")); + if (targetfiles.empty() == false) + for (std::vector::const_iterator f = targetfiles.begin(); f != targetfiles.end(); ++f) + if (FileExists(*f) == false) + return _error->Error(_("Download Failed")); + + return true; +} + /*}}}*/ +static bool DoSrvLookup(CommandLine &CmdL) /*{{{*/ +{ + if (CmdL.FileSize() <= 1) + return _error->Error("Must specify at least one SRV record"); + + for(size_t i = 1; CmdL.FileList[i] != NULL; ++i) + { + std::vector srv_records; + std::string const name = CmdL.FileList[i]; + c0out << "# Target\tPriority\tWeight\tPort # for " << name << std::endl; + size_t const found = name.find(":"); + if (found != std::string::npos) + { + std::string const host = name.substr(0, found); + size_t const port = atoi(name.c_str() + found + 1); + if(GetSrvRecords(host, port, srv_records) == false) + _error->Error(_("GetSrvRec failed for %s"), name.c_str()); + } + else if(GetSrvRecords(name, srv_records) == false) + _error->Error(_("GetSrvRec failed for %s"), name.c_str()); + + for (SrvRec const &I : srv_records) + ioprintf(c1out, "%s\t%d\t%d\t%d\n", I.target.c_str(), I.priority, I.weight, I.port); + } + return true; +} + /*}}}*/ +static const APT::Configuration::Compressor *FindCompressor(std::vector const &compressors, std::string const &name) /*{{{*/ +{ + APT::Configuration::Compressor const * compressor = NULL; + for (auto const & c : compressors) + { + if (compressor != NULL && c.Cost >= compressor->Cost) + continue; + if (c.Name == name || c.Extension == name || (!c.Extension.empty() && c.Extension.substr(1) == name)) + compressor = &c; + } + + return compressor; +} + /*}}}*/ +static bool DoCatFile(CommandLine &CmdL) /*{{{*/ +{ + FileFd fd; + FileFd out; + std::string const compressorName = _config->Find("Apt-Helper::Cat-File::Compress", ""); + + if (compressorName.empty() == false) + { + + auto const compressors = APT::Configuration::getCompressors(); + auto const compressor = FindCompressor(compressors, compressorName); + + if (compressor == NULL) + return _error->Error("Could not find compressor: %s", compressorName.c_str()); + + if (out.OpenDescriptor(STDOUT_FILENO, FileFd::WriteOnly, *compressor) == false) + return false; + } else + { + if (out.OpenDescriptor(STDOUT_FILENO, FileFd::WriteOnly) == false) + return false; + } + + if (CmdL.FileSize() <= 1) + { + if (fd.OpenDescriptor(STDIN_FILENO, FileFd::ReadOnly) == false) + return false; + if (CopyFile(fd, out) == false) + return false; + return true; + } + + for(size_t i = 1; CmdL.FileList[i] != NULL; ++i) + { + std::string const name = CmdL.FileList[i]; + + if (name != "-") + { + if (fd.Open(name, FileFd::ReadOnly, FileFd::Extension) == false) + return false; + } + else + { + if (fd.OpenDescriptor(STDIN_FILENO, FileFd::ReadOnly) == false) + return false; + } + + if (CopyFile(fd, out) == false) + return false; + } + return true; +} + /*}}}*/ + +static pid_t ExecuteProcess(const char *Args[]) /*{{{*/ +{ + pid_t pid = ExecFork(); + if (pid == 0) + { + execvp(Args[0], (char **)Args); + _exit(100); + } + return pid; +} + +static bool ServiceIsActive(const char *service) +{ + const char *argv[] = {"systemctl", "is-active", "-q", service, nullptr}; + pid_t pid = ExecuteProcess(argv); + return ExecWait(pid, "systemctl is-active", true); +} + +static bool DoWaitOnline(CommandLine &) +{ + // Also add services to After= in .service + static const char *WaitingTasks[][6] = { + {"systemd-networkd.service", "/lib/systemd/systemd-networkd-wait-online", "-q", "--timeout=30", nullptr}, + {"NetworkManager.service", "nm-online", "-q", "--timeout", "30", nullptr}, + {"connman.service", "connmand-wait-online", "--timeout=30", nullptr}, + }; + + for (const char **task : WaitingTasks) + { + if (ServiceIsActive(task[0])) + { + pid_t pid = ExecuteProcess(task + 1); + + ExecWait(pid, task[1]); + } + } + + return _error->PendingError() == false; +} + /*}}}*/ +static bool DropPrivsAndRun(CommandLine &CmdL) /*{{{*/ +{ + if (CmdL.FileSize() < 2) + return _error->Error("No command given to run without privileges"); + if (DropPrivileges() == false) + return _error->Error("Dropping Privileges failed, not executing '%s'", CmdL.FileList[1]); + + std::vector Args; + Args.reserve(CmdL.FileSize() + 1); + for (auto a = CmdL.FileList + 1; *a != nullptr; ++a) + Args.push_back(*a); + Args.push_back(nullptr); + auto const pid = ExecuteProcess(Args.data()); + return ExecWait(pid, CmdL.FileList[1]); +} + /*}}}*/ +static bool AnalyzePattern(CommandLine &CmdL) /*{{{*/ +{ + if (CmdL.FileSize() != 2) + return _error->Error("Expect one argument, a pattern"); + + try + { + auto top = APT::Internal::PatternTreeParser(CmdL.FileList[1]).parseTop(); + top->render(std::cout) << "\n"; + } + catch (APT::Internal::PatternTreeParser::Error &e) + { + std::stringstream ss; + ss << "input:" << e.location.start << "-" << e.location.end << ": error: " << e.message << "\n"; + ss << CmdL.FileList[1] << "\n"; + for (size_t i = 0; i < e.location.start; i++) + ss << " "; + for (size_t i = e.location.start; i < e.location.end; i++) + ss << "^"; + + ss << "\n"; + + _error->Error("%s", ss.str().c_str()); + return false; + } + + return true; +} + /*}}}*/ +static bool DoQuoteString(CommandLine &CmdL) /*{{{*/ +{ + if (CmdL.FileSize() != 3) + return _error->Error("Expect two arguments, a string to quote and a string of additional characters to quote"); + std::cout << QuoteString(CmdL.FileList[1], CmdL.FileList[2]) << '\n'; + return true; +} + /*}}}*/ +static bool ShowHelp(CommandLine &) /*{{{*/ +{ + std::cout << + _("Usage: apt-helper [options] command\n" + " apt-helper [options] cat-file file ...\n" + " apt-helper [options] download-file uri target-path\n" + "\n" + "apt-helper bundles a variety of commands for shell scripts to use\n" + "e.g. the same proxy configuration or acquire system as APT would.\n"); + return true; +} + /*}}}*/ +static std::vector GetCommands() /*{{{*/ +{ + return { + {"download-file", &DoDownloadFile, _("download the given uri to the target-path")}, + {"srv-lookup", &DoSrvLookup, _("lookup a SRV record (e.g. _http._tcp.ftp.debian.org)")}, + {"cat-file", &DoCatFile, _("concatenate files, with automatic decompression")}, + {"auto-detect-proxy", &DoAutoDetectProxy, _("detect proxy using apt.conf")}, + {"wait-online", &DoWaitOnline, _("wait for system to be online")}, + {"drop-privs", &DropPrivsAndRun, _("drop privileges before running given command")}, + {"analyze-pattern", &AnalyzePattern, _("analyse a pattern")}, + {"analyse-pattern", &AnalyzePattern, nullptr}, + {"quote-string", &DoQuoteString, nullptr}, + {nullptr, nullptr, nullptr}}; +} + /*}}}*/ +int main(int argc,const char *argv[]) /*{{{*/ +{ + CommandLine CmdL; + auto const Cmds = ParseCommandLine(CmdL, APT_CMD::APT_HELPER, &_config, &_system, argc, argv, &ShowHelp, &GetCommands); + + InitOutput(); + + return DispatchCommandLine(CmdL, Cmds); +} + /*}}}*/ diff --git a/cmdline/apt-internal-planner.cc b/cmdline/apt-internal-planner.cc new file mode 100644 index 0000000..c77997a --- /dev/null +++ b/cmdline/apt-internal-planner.cc @@ -0,0 +1,195 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ##################################################################### + + cover around the internal solver to be able to run it like an external + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + /*}}}*/ + +static bool ShowHelp(CommandLine &) /*{{{*/ +{ + std::cout << + _("Usage: apt-internal-planner\n" + "\n" + "apt-internal-planner is an interface to use the current internal\n" + "installation planner for the APT family like an external one,\n" + "for debugging or the like.\n"); + return true; +} + /*}}}*/ +APT_NORETURN static void DIE(std::string const &message) { /*{{{*/ + std::cerr << "ERROR: " << message << std::endl; + _error->DumpErrors(std::cerr); + exit(EXIT_FAILURE); +} + /*}}}*/ +static std::vector GetCommands() /*{{{*/ +{ + return {}; +} + /*}}}*/ +class PMOutput: public pkgPackageManager /*{{{*/ +{ + FileFd &output; + bool const Debug; + +protected: + virtual bool Install(PkgIterator Pkg,std::string) APT_OVERRIDE + { + //std::cerr << "INSTALL: " << APT::PrettyPkg(&Cache, Pkg) << std::endl; + return EDSP::WriteSolutionStanza(output, "Unpack", Cache[Pkg].InstVerIter(Cache)); + } + virtual bool Configure(PkgIterator Pkg) APT_OVERRIDE + { + //std::cerr << "CONFIGURE: " << APT::PrettyPkg(&Cache, Pkg) << " " << std::endl; + return EDSP::WriteSolutionStanza(output, "Configure", Cache[Pkg].InstVerIter(Cache)); + } + virtual bool Remove(PkgIterator Pkg,bool) APT_OVERRIDE + { + //std::cerr << "REMOVE: " << APT::PrettyPkg(&Cache, Pkg) << " " << std::endl; + return EDSP::WriteSolutionStanza(output, "Remove", Pkg.CurrentVer()); + } +public: + PMOutput(pkgDepCache *Cache, FileFd &file) : pkgPackageManager(Cache), output(file), + Debug(_config->FindB("Debug::EDSP::WriteSolution", false)) + {} + + bool ApplyRequest(std::list> const &actions) + { + for (auto && a: actions) + { + auto const Pkg = Cache.FindPkg(a.first); + if (unlikely(Pkg.end() == true)) + continue; + switch (a.second) + { + case EIPP::PKG_ACTION::NOOP: + break; + case EIPP::PKG_ACTION::INSTALL: + case EIPP::PKG_ACTION::REINSTALL: + FileNames[Pkg->ID] = "EIPP"; + break; + case EIPP::PKG_ACTION::REMOVE: + break; + } + } + return true; + } +}; + /*}}}*/ +int main(int argc,const char *argv[]) /*{{{*/ +{ + // we really don't need anything + DropPrivileges(); + + CommandLine CmdL; + ParseCommandLine(CmdL, APT_CMD::APT_INTERNAL_PLANNER, &_config, NULL, argc, argv, &ShowHelp, &GetCommands); + + // Deal with stdout not being a tty + if (!isatty(STDOUT_FILENO) && _config->FindI("quiet", -1) == -1) + _config->Set("quiet","1"); + + if (_config->FindI("quiet", 0) < 1) + _config->Set("Debug::EIPP::WriteSolution", true); + + _config->Set("APT::System", "Debian APT planner interface"); + _config->Set("APT::Planner", "internal"); + _config->Set("eipp::scenario", "/nonexistent/stdin"); + FileFd output; + if (output.OpenDescriptor(STDOUT_FILENO, FileFd::WriteOnly | FileFd::BufferedWrite, true) == false) + DIE("stdout couldn't be opened"); + int const input = STDIN_FILENO; + SetNonBlock(input, false); + + EDSP::WriteProgress(0, "Start up planner…", output); + + if (pkgInitSystem(*_config,_system) == false) + DIE("System could not be initialized!"); + + EDSP::WriteProgress(1, "Read request…", output); + + if (WaitFd(input, false, 5) == false) + DIE("WAIT timed out in the planner"); + + std::list> actions; + unsigned int flags; + if (EIPP::ReadRequest(input, actions, flags) == false) + DIE("Parsing the request failed!"); + _config->Set("APT::Immediate-Configure", (flags & EIPP::Request::NO_IMMEDIATE_CONFIGURATION) == 0); + _config->Set("APT::Immediate-Configure-All", (flags & EIPP::Request::IMMEDIATE_CONFIGURATION_ALL) != 0); + _config->Set("APT::Force-LoopBreak", (flags & EIPP::Request::ALLOW_TEMPORARY_REMOVE_OF_ESSENTIALS) != 0); + + EDSP::WriteProgress(5, "Read scenario…", output); + + pkgCacheFile CacheFile; + if (CacheFile.Open(NULL, false) == false) + DIE("Failed to open CacheFile!"); + + EDSP::WriteProgress(50, "Apply request on scenario…", output); + + if (EIPP::ApplyRequest(actions, CacheFile) == false) + DIE("Failed to apply request to depcache!"); + + EDSP::WriteProgress(60, "Call orderinstall on current scenario…", output); + + //_config->Set("Debug::pkgOrderList", true); + //_config->Set("Debug::pkgPackageManager", true); + PMOutput PM(CacheFile, output); + if (PM.ApplyRequest(actions) == false) + DIE("Failed to apply request to packagemanager!"); + pkgPackageManager::OrderResult const Res = PM.DoInstallPreFork(); + std::ostringstream broken; + switch (Res) + { + case pkgPackageManager::Completed: + EDSP::WriteProgress(100, "Done", output); + break; + case pkgPackageManager::Incomplete: + broken << "Planner could only incompletely plan an installation order!" << std::endl; + _error->DumpErrors(broken, GlobalError::DEBUG); + EDSP::WriteError("pm-incomplete", broken.str(), output); + break; + case pkgPackageManager::Failed: + broken << "Planner failed to find an installation order!" << std::endl; + _error->DumpErrors(broken, GlobalError::DEBUG); + EDSP::WriteError("pm-failed", broken.str(), output); + break; + } + + return DispatchCommandLine(CmdL, {}); +} + /*}}}*/ diff --git a/cmdline/apt-internal-solver.cc b/cmdline/apt-internal-solver.cc new file mode 100644 index 0000000..094ed18 --- /dev/null +++ b/cmdline/apt-internal-solver.cc @@ -0,0 +1,214 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ##################################################################### + + cover around the internal solver to be able to run it like an external + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + /*}}}*/ + +static bool ShowHelp(CommandLine &) /*{{{*/ +{ + std::cout << + _("Usage: apt-internal-solver\n" + "\n" + "apt-internal-solver is an interface to use the current internal\n" + "resolver for the APT family like an external one, for debugging or\n" + "the like.\n"); + return true; +} + /*}}}*/ +APT_NORETURN static void DIE(std::string const &message) { /*{{{*/ + std::cerr << "ERROR: " << message << std::endl; + _error->DumpErrors(std::cerr); + exit(EXIT_FAILURE); +} + /*}}}*/ +static std::vector GetCommands() /*{{{*/ +{ + return {}; +} + /*}}}*/ +static bool WriteSolution(pkgDepCache &Cache, FileFd &output) /*{{{*/ +{ + bool Okay = output.Failed() == false; + for (pkgCache::PkgIterator Pkg = Cache.PkgBegin(); Pkg.end() == false && likely(Okay); ++Pkg) + { + if (Cache[Pkg].Delete() == true) + Okay &= EDSP::WriteSolutionStanza(output, "Remove", Pkg.CurrentVer()); + else if (Cache[Pkg].NewInstall() == true || Cache[Pkg].Upgrade() == true) + Okay &= EDSP::WriteSolutionStanza(output, "Install", Cache.GetCandidateVersion(Pkg)); + else if (Cache[Pkg].Garbage == true) + Okay &= EDSP::WriteSolutionStanza(output, "Autoremove", Pkg.CurrentVer()); + } + return Okay; +} + /*}}}*/ +int main(int argc,const char *argv[]) /*{{{*/ +{ + // we really don't need anything + DropPrivileges(); + + CommandLine CmdL; + ParseCommandLine(CmdL, APT_CMD::APT_INTERNAL_SOLVER, &_config, NULL, argc, argv, &ShowHelp, &GetCommands); + + if (CmdL.FileList[0] != 0 && strcmp(CmdL.FileList[0], "scenario") == 0) + { + if (pkgInitSystem(*_config,_system) == false) { + std::cerr << "System could not be initialized!" << std::endl; + return 1; + } + pkgCacheFile CacheFile; + CacheFile.Open(NULL, false); + APT::PackageSet pkgset = APT::PackageSet::FromCommandLine(CacheFile, CmdL.FileList + 1); + FileFd output; + if (output.OpenDescriptor(STDOUT_FILENO, FileFd::WriteOnly | FileFd::BufferedWrite, true) == false) + return 2; + if (pkgset.empty() == true) + EDSP::WriteScenario(CacheFile, output); + else + { + std::vector pkgvec(CacheFile->Head().PackageCount, false); + for (auto const &p: pkgset) + pkgvec[p->ID] = true; + EDSP::WriteLimitedScenario(CacheFile, output, pkgvec); + } + output.Close(); + _error->DumpErrors(std::cerr); + return 0; + } + + // Deal with stdout not being a tty + if (!isatty(STDOUT_FILENO) && _config->FindI("quiet", -1) == -1) + _config->Set("quiet","1"); + + if (_config->FindI("quiet", 0) < 1) + _config->Set("Debug::EDSP::WriteSolution", true); + + _config->Set("APT::System", "Debian APT solver interface"); + _config->Set("APT::Solver", "internal"); + _config->Set("edsp::scenario", "/nonexistent/stdin"); + _config->Clear("Dir::Log"); + FileFd output; + if (output.OpenDescriptor(STDOUT_FILENO, FileFd::WriteOnly | FileFd::BufferedWrite, true) == false) + DIE("stdout couldn't be opened"); + int const input = STDIN_FILENO; + SetNonBlock(input, false); + + EDSP::WriteProgress(0, "Start up solver…", output); + + if (pkgInitSystem(*_config,_system) == false) + DIE("System could not be initialized!"); + + EDSP::WriteProgress(1, "Read request…", output); + + if (WaitFd(input, false, 5) == false) + DIE("WAIT timed out in the resolver"); + + std::list install, remove; + unsigned int flags; + if (EDSP::ReadRequest(input, install, remove, flags) == false) + DIE("Parsing the request failed!"); + + EDSP::WriteProgress(5, "Read scenario…", output); + + pkgCacheFile CacheFile; + CacheFile.InhibitActionGroups(true); + if (CacheFile.Open(NULL, false) == false) + DIE("Failed to open CacheFile!"); + + EDSP::WriteProgress(50, "Apply request on scenario…", output); + + if (EDSP::ApplyRequest(install, remove, CacheFile) == false) + DIE("Failed to apply request to depcache!"); + + pkgProblemResolver Fix(CacheFile); + for (std::list::const_iterator i = remove.begin(); + i != remove.end(); ++i) { + pkgCache::PkgIterator P = CacheFile->FindPkg(*i); + Fix.Clear(P); + Fix.Protect(P); + Fix.Remove(P); + } + + for (std::list::const_iterator i = install.begin(); + i != install.end(); ++i) { + pkgCache::PkgIterator P = CacheFile->FindPkg(*i); + Fix.Clear(P); + Fix.Protect(P); + } + + for (std::list::const_iterator i = install.begin(); + i != install.end(); ++i) + CacheFile->MarkInstall(CacheFile->FindPkg(*i), true); + + EDSP::WriteProgress(60, "Call problemresolver on current scenario…", output); + + std::string failure; + if (flags & EDSP::Request::UPGRADE_ALL) { + int upgrade_flags = APT::Upgrade::ALLOW_EVERYTHING; + if (flags & EDSP::Request::FORBID_NEW_INSTALL) + upgrade_flags |= APT::Upgrade::FORBID_INSTALL_NEW_PACKAGES; + if (flags & EDSP::Request::FORBID_REMOVE) + upgrade_flags |= APT::Upgrade::FORBID_REMOVE_PACKAGES; + + if (APT::Upgrade::Upgrade(CacheFile, upgrade_flags)) + ; + else if (upgrade_flags == APT::Upgrade::ALLOW_EVERYTHING) + failure = "ERR_UNSOLVABLE_FULL_UPGRADE"; + else + failure = "ERR_UNSOLVABLE_UPGRADE"; + } else if (Fix.Resolve() == false) + failure = "ERR_UNSOLVABLE"; + + if (failure.empty() == false) { + std::ostringstream broken; + ShowBroken(broken, CacheFile, false); + EDSP::WriteError(failure.c_str(), broken.str(), output); + return 0; + } + + EDSP::WriteProgress(95, "Write solution…", output); + + CacheFile->MarkAndSweep(); + if (WriteSolution(CacheFile, output) == false) + DIE("Failed to output the solution!"); + + EDSP::WriteProgress(100, "Done", output); + + return DispatchCommandLine(CmdL, {}); +} + /*}}}*/ diff --git a/cmdline/apt-key.in b/cmdline/apt-key.in new file mode 100644 index 0000000..4f3e9c8 --- /dev/null +++ b/cmdline/apt-key.in @@ -0,0 +1,832 @@ +#!/bin/sh + +set -e +unset GREP_OPTIONS GPGHOMEDIR CURRENTTRAP +export IFS="$(printf "\n\b")" + +MASTER_KEYRING='&keyring-master-filename;' +eval "$(apt-config shell MASTER_KEYRING APT::Key::MasterKeyring)" +ARCHIVE_KEYRING='&keyring-filename;' +eval "$(apt-config shell ARCHIVE_KEYRING APT::Key::ArchiveKeyring)" +REMOVED_KEYS='&keyring-removed-filename;' +eval "$(apt-config shell REMOVED_KEYS APT::Key::RemovedKeys)" +ARCHIVE_KEYRING_URI='&keyring-uri;' +eval "$(apt-config shell ARCHIVE_KEYRING_URI APT::Key::ArchiveKeyringURI)" + +aptkey_echo() { echo "$@"; } + +find_gpgv_status_fd() { + while [ -n "$1" ]; do + if [ "$1" = '--status-fd' ]; then + shift + echo "$1" + break + fi + shift + done +} +GPGSTATUSFD="$(find_gpgv_status_fd "$@")" + +apt_warn() { + if [ -z "$GPGHOMEDIR" ]; then + echo >&2 'W:' "$@" + else + echo 'W:' "$@" > "${GPGHOMEDIR}/aptwarnings.log" + fi + if [ -n "$GPGSTATUSFD" ]; then + echo >&${GPGSTATUSFD} '[APTKEY:] WARNING' "$@" + fi +} +apt_error() { + if [ -z "$GPGHOMEDIR" ]; then + echo >&2 'E:' "$@" + else + echo 'E:' "$@" > "${GPGHOMEDIR}/aptwarnings.log" + fi + if [ -n "$GPGSTATUSFD" ]; then + echo >&${GPGSTATUSFD} '[APTKEY:] ERROR' "$@" + fi +} + +cleanup_gpg_home() { + if [ -z "$GPGHOMEDIR" ]; then return; fi + if [ -s "$GPGHOMEDIR/aptwarnings.log" ]; then + cat >&2 "$GPGHOMEDIR/aptwarnings.log" + fi + if command_available 'gpgconf'; then + GNUPGHOME="${GPGHOMEDIR}" gpgconf --kill all >/dev/null 2>&1 || true + fi + rm -rf "$GPGHOMEDIR" +} + +# gpg needs (in different versions more or less) files to function correctly, +# so we give it its own homedir and generate some valid content for it later on +create_gpg_home() { + # for cases in which we want to cache a homedir due to expensive setup + if [ -n "$GPGHOMEDIR" ]; then + return + fi + if [ -n "$TMPDIR" ]; then + # tmpdir is a directory and current user has rwx access to it + # same tests as in apt-pkg/contrib/fileutl.cc GetTempDir() + if [ ! -d "$TMPDIR" ] || [ ! -r "$TMPDIR" ] || [ ! -w "$TMPDIR" ] || [ ! -x "$TMPDIR" ]; then + unset TMPDIR + fi + fi + GPGHOMEDIR="$(mktemp --directory --tmpdir 'apt-key-gpghome.XXXXXXXXXX')" + CURRENTTRAP="${CURRENTTRAP} cleanup_gpg_home;" + trap "${CURRENTTRAP}" 0 HUP INT QUIT ILL ABRT FPE SEGV PIPE TERM + if [ -z "$GPGHOMEDIR" ]; then + apt_error "Could not create temporary gpg home directory in $TMPDIR (wrong permissions?)" + exit 28 + fi + chmod 700 "$GPGHOMEDIR" +} + +requires_root() { + if [ "$(id -u)" -ne 0 ]; then + apt_error "This command can only be used by root." + exit 1 + fi +} + +command_available() { + if [ -x "$1" ]; then return 0; fi + command -v "$1" >/dev/null # required by policy, see #747320 +} + +escape_shell() { + echo "$@" | sed -e "s#'#'\"'\"'#g" +} + +get_fingerprints_of_keyring() { + aptkey_execute "$GPG_SH" --keyring "$1" --with-colons --fingerprint | while read publine; do + # search for a public key + if [ "${publine%%:*}" != 'pub' ]; then continue; fi + # search for the associated fingerprint (should be the very next line) + while read fprline; do + if [ "${fprline%%:*}" = 'sub' ]; then break; # should never happen + elif [ "${fprline%%:*}" != 'fpr' ]; then continue; fi + echo "$fprline" | cut -d':' -f 10 + done + # order in the keyring shouldn't be important + done | sort +} + +add_keys_with_verify_against_master_keyring() { + ADD_KEYRING="$1" + MASTER="$2" + + if [ ! -f "$ADD_KEYRING" ]; then + apt_error "Keyring '$ADD_KEYRING' to be added not found" + return + fi + if [ ! -f "$MASTER" ]; then + apt_error "Master-Keyring '$MASTER' not found" + return + fi + + # when adding new keys, make sure that the archive-master-keyring + # is honored. so: + # all keys that are exported must have a valid signature + # from a key in the $distro-master-keyring + add_keys="$(get_fingerprints_of_keyring "$ADD_KEYRING")" + all_add_keys="$(aptkey_execute "$GPG_SH" --keyring "$ADD_KEYRING" --with-colons --list-keys | grep ^[ps]ub | cut -d: -f5)" + master_keys="$(aptkey_execute "$GPG_SH" --keyring "$MASTER" --with-colons --list-keys | grep ^pub | cut -d: -f5)" + + # ensure there are no colisions LP: #857472 + for all_add_key in $all_add_keys; do + for master_key in $master_keys; do + if [ "$all_add_key" = "$master_key" ]; then + echo >&2 "Keyid collision for '$all_add_key' detected, operation aborted" + return 1 + fi + done + done + + for add_key in $add_keys; do + # export the add keyring one-by-one + local TMP_KEYRING="${GPGHOMEDIR}/tmp-keyring.gpg" + aptkey_execute "$GPG_SH" --batch --yes --keyring "$ADD_KEYRING" --output "$TMP_KEYRING" --export "$add_key" + if ! aptkey_execute "$GPG_SH" --batch --yes --keyring "$TMP_KEYRING" --import "$MASTER" > "${GPGHOMEDIR}/gpgoutput.log" 2>&1; then + cat >&2 "${GPGHOMEDIR}/gpgoutput.log" + false + fi + # check if signed with the master key and only add in this case + ADDED=0 + for master_key in $master_keys; do + if aptkey_execute "$GPG_SH" --keyring "$TMP_KEYRING" --check-sigs --with-colons "$add_key" \ + | grep '^sig:!:' | cut -d: -f5 | grep -q "$master_key"; then + aptkey_execute "$GPG_SH" --batch --yes --keyring "$ADD_KEYRING" --export "$add_key" \ + | aptkey_execute "$GPG" --batch --yes --import + ADDED=1 + fi + done + if [ $ADDED = 0 ]; then + echo >&2 "Key '$add_key' not added. It is not signed with a master key" + fi + rm -f "${TMP_KEYRING}" + done +} + +# update the current archive signing keyring from a network URI +# the archive-keyring keys needs to be signed with the master key +# (otherwise it does not make sense from a security POV) +net_update() { + local APT_DIR='/' + eval $(apt-config shell APT_DIR Dir) + + # Disabled for now as code is insecure (LP: #1013639 (and 857472, 1013128)) + APT_KEY_NET_UPDATE_ENABLED="" + eval $(apt-config shell APT_KEY_NET_UPDATE_ENABLED APT::Key::Net-Update-Enabled) + if [ -z "$APT_KEY_NET_UPDATE_ENABLED" ]; then + exit 1 + fi + + if [ -z "$ARCHIVE_KEYRING_URI" ]; then + apt_error 'Your distribution is not supported in net-update as no uri for the archive-keyring is set' + exit 1 + fi + # in theory we would need to depend on wget for this, but this feature + # isn't usable in debian anyway as we have no keyring uri nor a master key + if ! command_available 'wget'; then + apt_error 'wget is required for a network-based update, but it is not installed' + exit 1 + fi + if [ ! -d "${APT_DIR}/var/lib/apt/keyrings" ]; then + mkdir -p "${APT_DIR}/var/lib/apt/keyrings" + fi + keyring="${APT_DIR}/var/lib/apt/keyrings/$(basename "$ARCHIVE_KEYRING_URI")" + old_mtime=0 + if [ -e $keyring ]; then + old_mtime=$(stat -c %Y "$keyring") + fi + (cd "${APT_DIR}/var/lib/apt/keyrings"; wget --timeout=90 -q -N "$ARCHIVE_KEYRING_URI") + if [ ! -e "$keyring" ]; then + return + fi + new_mtime=$(stat -c %Y "$keyring") + if [ $new_mtime -ne $old_mtime ]; then + aptkey_echo "Checking for new archive signing keys now" + add_keys_with_verify_against_master_keyring "$keyring" "$MASTER_KEYRING" + fi +} + +update() { + if [ -z "$APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE" ]; then + echo >&2 "Warning: 'apt-key update' is deprecated and should not be used anymore!" + if [ -z "$ARCHIVE_KEYRING" ]; then + echo >&2 "Note: In your distribution this command is a no-op and can therefore be removed safely." + exit 0 + fi + fi + if [ ! -f "$ARCHIVE_KEYRING" ]; then + apt_error "Can't find the archive-keyring (Is the &keyring-package; package installed?)" + exit 1 + fi + + # add new keys from the package; + + # we do not use add_keys_with_verify_against_master_keyring here, + # because "update" is run on regular package updates. A + # attacker might as well replace the master-archive-keyring file + # in the package and add his own keys. so this check wouldn't + # add any security. we *need* this check on net-update though + import_keyring_into_keyring "$ARCHIVE_KEYRING" '' && cat "${GPGHOMEDIR}/gpgoutput.log" + + if [ -r "$REMOVED_KEYS" ]; then + # remove no-longer supported/used keys + get_fingerprints_of_keyring "$(dearmor_filename "$REMOVED_KEYS")" | while read key; do + foreach_keyring_do 'remove_key_from_keyring' "$key" + done + else + apt_warn "Removed keys keyring '$REMOVED_KEYS' missing or not readable" + fi +} + +remove_key_from_keyring() { + local KEYRINGFILE="$1" + shift + # non-existent keyrings have by definition no keys + if [ ! -e "$KEYRINGFILE" ]; then + return + fi + + local FINGERPRINTS="${GPGHOMEDIR}/keyringfile.keylst" + local DEARMOR="$(dearmor_filename "$KEYRINGFILE")" + get_fingerprints_of_keyring "$DEARMOR" > "$FINGERPRINTS" + + for KEY in "$@"; do + # strip leading 0x, if present: + KEY="$(echo "${KEY#0x}" | tr -d ' ')" + + # check if the key is in this keyring + if ! grep -iq "^[0-9A-F]*${KEY}$" "$FINGERPRINTS"; then + continue + fi + if [ ! -w "$KEYRINGFILE" ]; then + apt_warn "Key ${KEY} is in keyring ${KEYRINGFILE}, but can't be removed as it is read only." + continue + fi + # check if it is the only key in the keyring and if so remove the keyring altogether + if [ '1' = "$(uniq "$FINGERPRINTS" | wc -l)" ]; then + mv -f "$KEYRINGFILE" "${KEYRINGFILE}~" # behave like gpg + return + fi + # we can't just modify pointed to files as these might be in /usr or something + local REALTARGET + if [ -L "$DEARMOR" ]; then + REALTARGET="$(readlink -f "$DEARMOR")" + mv -f "$DEARMOR" "${DEARMOR}.dpkg-tmp" + cp -a "$REALTARGET" "$DEARMOR" + fi + # delete the key from the keyring + aptkey_execute "$GPG_SH" --keyring "$DEARMOR" --batch --delete-keys --yes "$KEY" + if [ -n "$REALTARGET" ]; then + # the real backup is the old link, not the copy we made + mv -f "${DEARMOR}.dpkg-tmp" "${DEARMOR}~" + fi + if [ "$DEARMOR" != "$KEYRINGFILE" ]; then + mv -f "$KEYRINGFILE" "${KEYRINGFILE}~" + create_new_keyring "$KEYRINGFILE" + aptkey_execute "$GPG_SH" --keyring "$DEARMOR" --armor --export > "$KEYRINGFILE" + fi + get_fingerprints_of_keyring "$DEARMOR" > "$FINGERPRINTS" + done +} + +accessible_file_exists() { + if ! test -s "$1"; then + return 1 + fi + if test -r "$1"; then + return 0 + fi + apt_warn "The key(s) in the keyring $1 are ignored as the file is not readable by user '$USER' executing apt-key." + return 1 +} + +is_supported_keyring() { + # empty files are always supported + if ! test -s "$1"; then + return 0 + fi + local FILEEXT="${1##*.}" + if [ "$FILEEXT" = 'gpg' ]; then + # 0x98, 0x99 and 0xC6 via octal as hex isn't supported by dashs printf + if printf '\231' | cmp -s -n 1 - "$1"; then + true + elif printf '\230' | cmp -s -n 1 - "$1"; then + true + elif printf '\306' | cmp -s -n 1 - "$1"; then + true + else + apt_warn "The key(s) in the keyring $1 are ignored as the file has an unsupported filetype." + return 1 + fi + elif [ "$FILEEXT" = 'asc' ]; then + true #dearmor_filename will deal with them + else + # most callers ignore unsupported extensions silently + apt_warn "The key(s) in the keyring $1 are ignored as the file has an unsupported filename extension." + return 1 + fi + return 0 +} + +foreach_keyring_do() { + local ACTION="$1" + shift + # if a --keyring was given, just work on this one + if [ -n "$FORCED_KEYRING" ]; then + $ACTION "$FORCED_KEYRING" "$@" + else + # otherwise all known keyrings are up for inspection + if accessible_file_exists "$TRUSTEDFILE" && is_supported_keyring "$TRUSTEDFILE"; then + $ACTION "$TRUSTEDFILE" "$@" + fi + local TRUSTEDPARTS="/etc/apt/trusted.gpg.d" + eval "$(apt-config shell TRUSTEDPARTS Dir::Etc::TrustedParts/d)" + if [ -d "$TRUSTEDPARTS" ]; then + TRUSTEDPARTS="$(readlink -f "$TRUSTEDPARTS")" + local TRUSTEDPARTSLIST="$(cd /; find "$TRUSTEDPARTS" -mindepth 1 -maxdepth 1 \( -name '*.gpg' -o -name '*.asc' \))" + for trusted in $(echo "$TRUSTEDPARTSLIST" | sort); do + if accessible_file_exists "$trusted" && is_supported_keyring "$trusted"; then + $ACTION "$trusted" "$@" + fi + done + fi + fi +} + +list_keys_in_keyring() { + local KEYRINGFILE="$1" + shift + # fingerprint and co will fail if key isn't in this keyring + aptkey_execute "$GPG_SH" --keyring "$(dearmor_filename "$KEYRINGFILE")" "$@" > "${GPGHOMEDIR}/gpgoutput.log" 2> "${GPGHOMEDIR}/gpgoutput.err" || true + if [ ! -s "${GPGHOMEDIR}/gpgoutput.log" ]; then + return + fi + # we fake gpg header here to refer to the real asc file rather than a temp file + if [ "${KEYRINGFILE##*.}" = 'asc' ]; then + if expr match "$(sed -n '2p' "${GPGHOMEDIR}/gpgoutput.log")" '^-\+$' >/dev/null 2>&1; then + echo "$KEYRINGFILE" + echo "$KEYRINGFILE" | sed 's#[^-]#-#g' + sed '1,2d' "${GPGHOMEDIR}/gpgoutput.log" || true + else + cat "${GPGHOMEDIR}/gpgoutput.log" + fi + else + cat "${GPGHOMEDIR}/gpgoutput.log" + fi + if [ -s "${GPGHOMEDIR}/gpgoutput.err" ]; then + cat >&2 "${GPGHOMEDIR}/gpgoutput.err" + fi +} + +export_key_from_to() { + local FROM="$1" + local TO="$2" + shift 2 + if ! aptkey_execute "$GPG_SH" --keyring "$(dearmor_filename "$FROM")" --export "$@" > "$TO" 2> "${GPGHOMEDIR}/gpgoutput.log"; then + cat >&2 "${GPGHOMEDIR}/gpgoutput.log" + false + else + chmod 0644 -- "$TO" + fi +} + +import_keyring_into_keyring() { + local FROM="${1:-${GPGHOMEDIR}/pubring.gpg}" + local TO="${2:-${GPGHOMEDIR}/pubring.gpg}" + shift 2 + rm -f "${GPGHOMEDIR}/gpgoutput.log" + # the idea is simple: We take keys from one keyring and copy it to another + # we do this with so many checks in between to ensure that WE control the + # creation, so we know that the (potentially) created $TO keyring is a + # simple keyring rather than a keybox as gpg2 would create it which in turn + # can't be read by gpgv. + # BEWARE: This is designed more in the way to work with the current + # callers, than to have a well defined it would be easy to add new callers to. + if [ ! -s "$TO" ]; then + if [ -s "$FROM" ]; then + if [ -z "$2" ]; then + local OPTS + if [ "${TO##*.}" = 'asc' ]; then + OPTS='--armor' + fi + export_key_from_to "$(dearmor_filename "$FROM")" "$TO" $OPTS ${1:+"$1"} + else + create_new_keyring "$TO" + fi + else + create_new_keyring "$TO" + fi + elif [ -s "$FROM" ]; then + local EXPORTLIMIT="$1" + if [ -n "$1$2" ]; then shift; fi + local DEARMORTO="$(dearmor_filename "$TO")" + if ! aptkey_execute "$GPG_SH" --keyring "$(dearmor_filename "$FROM")" --export ${EXPORTLIMIT:+"$EXPORTLIMIT"} \ + | aptkey_execute "$GPG_SH" --keyring "$DEARMORTO" --batch --import "$@" > "${GPGHOMEDIR}/gpgoutput.log" 2>&1; then + cat >&2 "${GPGHOMEDIR}/gpgoutput.log" + false + fi + if [ "$DEARMORTO" != "$TO" ]; then + export_key_from_to "$DEARMORTO" "${DEARMORTO}.asc" --armor + if ! cmp -s "$TO" "${DEARMORTO}.asc" 2>/dev/null; then + cp -a "$TO" "${TO}~" + mv -f "${DEARMORTO}.asc" "$TO" + fi + fi + fi +} + +dearmor_keyring() { + # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=831409#67 + # The awk script is more complex through to skip surrounding garbage and + # to support multiple keys in one file (old gpgs generate version headers + # which get printed with the original and hence result in garbage input for base64 + awk '{ gsub(/\r/,"") } +/^-----BEGIN/{ x = 1; } +/^$/{ if (x == 1) { x = 2; }; } +/^[^=-]/{ if (x == 2) { print $0; }; } +/^-----END/{ x = 0; }' | base64 -d +} +dearmor_filename() { + if [ "${1##*.}" = 'asc' ]; then + local trusted="${GPGHOMEDIR}/${1##*/}.gpg" + if [ -s "$1" ]; then + dearmor_keyring < "$1" > "$trusted" + fi + echo "$trusted" + elif [ "${1##*.}" = 'gpg' ]; then + echo "$1" + elif [ "$(head -n 1 "$1" 2>/dev/null)" = '-----BEGIN PGP PUBLIC KEY BLOCK-----' ]; then + local trusted="${GPGHOMEDIR}/${1##*/}.gpg" + dearmor_keyring < "$1" > "$trusted" + echo "$trusted" + else + echo "$1" + fi +} +catfile() { + cat "$(dearmor_filename "$1")" >> "$2" +} + +merge_all_trusted_keyrings_into_pubring() { + # does the same as: + # foreach_keyring_do 'import_keys_from_keyring' "${GPGHOMEDIR}/pubring.gpg" + # but without using gpg, just cat and find + local PUBRING="$(readlink -f "${GPGHOMEDIR}")/pubring.gpg" + rm -f "$PUBRING" + touch "$PUBRING" + foreach_keyring_do 'catfile' "$PUBRING" +} + +import_keys_from_keyring() { + import_keyring_into_keyring "$1" "$2" +} + +merge_keys_into_keyrings() { + import_keyring_into_keyring "$2" "$1" '' --import-options 'merge-only' +} + +merge_back_changes() { + if [ -n "$FORCED_KEYRING" ]; then + # if the keyring was forced merge is already done + if [ "$FORCED_KEYRING" != "$TRUSTEDFILE" ]; then + mv -f "$FORCED_KEYRING" "${FORCED_KEYRING}~" + export_key_from_to "$TRUSTEDFILE" "$FORCED_KEYRING" --armor + fi + return + fi + if [ -s "${GPGHOMEDIR}/pubring.gpg" ]; then + # merge all updated keys + foreach_keyring_do 'merge_keys_into_keyrings' "${GPGHOMEDIR}/pubring.gpg" + fi + # look for keys which were added or removed + get_fingerprints_of_keyring "${GPGHOMEDIR}/pubring.orig.gpg" > "${GPGHOMEDIR}/pubring.orig.keylst" + get_fingerprints_of_keyring "${GPGHOMEDIR}/pubring.gpg" > "${GPGHOMEDIR}/pubring.keylst" + comm -3 "${GPGHOMEDIR}/pubring.keylst" "${GPGHOMEDIR}/pubring.orig.keylst" > "${GPGHOMEDIR}/pubring.diff" + # key isn't part of new keyring, so remove + cut -f 2 "${GPGHOMEDIR}/pubring.diff" | while read key; do + if [ -z "$key" ]; then continue; fi + foreach_keyring_do 'remove_key_from_keyring' "$key" + done + # key is only part of new keyring, so we need to import it + cut -f 1 "${GPGHOMEDIR}/pubring.diff" | while read key; do + if [ -z "$key" ]; then continue; fi + import_keyring_into_keyring '' "$TRUSTEDFILE" "$key" + done +} + +setup_merged_keyring() { + if [ -n "$FORCED_KEYID" ]; then + merge_all_trusted_keyrings_into_pubring + FORCED_KEYRING="${GPGHOMEDIR}/forcedkeyid.gpg" + TRUSTEDFILE="${FORCED_KEYRING}" + echo "#!/bin/sh +exec sh '($(escape_shell "${GPG}")' --keyring '$(escape_shell "${TRUSTEDFILE}")' \"\$@\"" > "${GPGHOMEDIR}/gpg.1.sh" + GPG="${GPGHOMEDIR}/gpg.1.sh" + # ignore error as this "just" means we haven't found the forced keyid and the keyring will be empty + import_keyring_into_keyring '' "$TRUSTEDFILE" "$FORCED_KEYID" || true + elif [ -z "$FORCED_KEYRING" ]; then + merge_all_trusted_keyrings_into_pubring + if [ -r "${GPGHOMEDIR}/pubring.gpg" ]; then + cp -a "${GPGHOMEDIR}/pubring.gpg" "${GPGHOMEDIR}/pubring.orig.gpg" + else + touch "${GPGHOMEDIR}/pubring.gpg" "${GPGHOMEDIR}/pubring.orig.gpg" + fi + echo "#!/bin/sh +exec sh '$(escape_shell "${GPG}")' --keyring '$(escape_shell "${GPGHOMEDIR}/pubring.gpg")' \"\$@\"" > "${GPGHOMEDIR}/gpg.1.sh" + GPG="${GPGHOMEDIR}/gpg.1.sh" + else + TRUSTEDFILE="$(dearmor_filename "$FORCED_KEYRING")" + create_new_keyring "$TRUSTEDFILE" + echo "#!/bin/sh +exec sh '$(escape_shell "${GPG}")' --keyring '$(escape_shell "${TRUSTEDFILE}")' \"\$@\"" > "${GPGHOMEDIR}/gpg.1.sh" + GPG="${GPGHOMEDIR}/gpg.1.sh" + fi +} + +create_new_keyring() { + # gpg defaults to mode 0600 for new keyrings. Create one with 0644 instead. + if ! [ -e "$1" ]; then + if [ -w "$(dirname "$1")" ]; then + touch -- "$1" + chmod 0644 -- "$1" + fi + fi +} + +aptkey_execute() { sh "$@"; } + +usage() { + echo "Usage: apt-key [--keyring file] [command] [arguments]" + echo + echo "Manage apt's list of trusted keys" + echo + echo " apt-key add - add the key contained in ('-' for stdin)" + echo " apt-key del - remove the key " + echo " apt-key export - output the key " + echo " apt-key exportall - output all trusted keys" + echo " apt-key update - update keys using the keyring package" + echo " apt-key net-update - update keys using the network" + echo " apt-key list - list keys" + echo " apt-key finger - list fingerprints" + echo " apt-key adv - pass advanced options to gpg (download key)" + echo + echo "If no specific keyring file is given the command applies to all keyring files." +} + +while [ -n "$1" ]; do + case "$1" in + --keyring) + shift + if [ -z "$FORCED_KEYRING" -o "$FORCED_KEYRING" = '/dev/null' ]; then + TRUSTEDFILE="$1" + FORCED_KEYRING="$1" + elif [ "$TRUSTEDFILE" = "$FORCED_KEYRING" ]; then + create_gpg_home + FORCED_KEYRING="${GPGHOMEDIR}/mergedkeyrings.gpg" + echo -n '' > "$FORCED_KEYRING" + chmod 0644 -- "$FORCED_KEYRING" + catfile "$TRUSTEDFILE" "$FORCED_KEYRING" + catfile "$1" "$FORCED_KEYRING" + else + catfile "$1" "$FORCED_KEYRING" + fi + ;; + --keyid) + shift + if [ -n "$FORCED_KEYID" ]; then + apt_error 'Specifying --keyid multiple times is not supported' + exit 1 + fi + FORCED_KEYID="$1" + ;; + --secret-keyring) + shift + FORCED_SECRET_KEYRING="$1" + ;; + --readonly) + merge_back_changes() { true; } + create_new_keyring() { if [ ! -r "$FORCED_KEYRING" ]; then TRUSTEDFILE='/dev/null'; FORCED_KEYRING="$TRUSTEDFILE"; fi; } + ;; + --fakeroot) + requires_root() { true; } + ;; + --quiet) + aptkey_echo() { true; } + ;; + --debug1) + # some cmds like finger redirect stderr to /dev/null … + aptkey_execute() { echo 'EXEC:' "$@"; sh "$@"; } + ;; + --debug2) + # … other more complicated ones pipe gpg into gpg. + aptkey_execute() { echo >&2 'EXEC:' "$@"; sh "$@"; } + ;; + --homedir) + # force usage of a specific homedir instead of creating a temporary + shift + GPGHOMEDIR="$1" + ;; + --*) + echo >&2 "Unknown option: $1" + usage + exit 1;; + *) + break;; + esac + shift +done + +if [ -z "$TRUSTEDFILE" ]; then + TRUSTEDFILE="/etc/apt/trusted.gpg" + eval $(apt-config shell TRUSTEDFILE Apt::GPGV::TrustedKeyring) + eval $(apt-config shell TRUSTEDFILE Dir::Etc::Trusted/f) + if [ "$APT_KEY_NO_LEGACY_KEYRING" ]; then + TRUSTEDFILE="/dev/null" + fi +fi + +command="$1" +if [ -z "$command" ]; then + usage + exit 1 +fi +shift + +prepare_gpg_home() { + # crude detection if we are called from a maintainerscript where the + # package depends on gnupg or not. We accept recommends here as + # well as the script hopefully uses apt-key optionally then like e.g. + # debian-archive-keyring for (upgrade) cleanup did + if [ -n "$DPKG_MAINTSCRIPT_PACKAGE" ] && [ -z "$APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE" ]; then + if ! dpkg-query --show --showformat '${Pre-Depends}${Depends}${Recommends}\n' "$DPKG_MAINTSCRIPT_PACKAGE" 2>/dev/null | grep -E -q 'gpg|gnupg'; then + cat >&2 < "${GPGHOMEDIR}/gpg.0.sh" + GPG_SH="${GPGHOMEDIR}/gpg.0.sh" + GPG="$GPG_SH" + + # create the trustdb with an (empty) dummy keyring + # older gpgs required it, newer gpgs even warn that it isn't needed, + # but require it nonetheless for some commands, so we just play safe + # here for the foreseeable future and create a dummy one + touch "${GPGHOMEDIR}/empty.gpg" + if ! "$GPG_EXE" --ignore-time-conflict --no-options --no-default-keyring \ + --homedir "$GPGHOMEDIR" --quiet --check-trustdb --keyring "${GPGHOMEDIR}/empty.gpg" >"${GPGHOMEDIR}/gpgoutput.log" 2>&1; then + cat >&2 "${GPGHOMEDIR}/gpgoutput.log" + false + fi + + # We don't usually need a secret keyring, of course, but + # for advanced operations, we might really need a secret keyring after all + if [ -n "$FORCED_SECRET_KEYRING" ] && [ -r "$FORCED_SECRET_KEYRING" ]; then + if ! aptkey_execute "$GPG" -v --batch --import "$FORCED_SECRET_KEYRING" >"${GPGHOMEDIR}/gpgoutput.log" 2>&1; then + # already imported keys cause gpg1 to fail for some reason… ignore this error + if ! grep -q 'already in secret keyring' "${GPGHOMEDIR}/gpgoutput.log"; then + cat >&2 "${GPGHOMEDIR}/gpgoutput.log" + false + fi + fi + else + # and then, there are older versions of gpg which panic and implode + # if there isn't one available - and writeable for imports + # and even if not output is littered with the creation of a secring, + # so lets call import once to have it create what it wants in silence + echo -n | aptkey_execute "$GPG" --batch --import >/dev/null 2>&1 || true + fi +} + +warn_on_script_usage() { + if [ -n "$APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE" ]; then + return + fi + # (Maintainer) scripts should not be using apt-key + if [ -n "$DPKG_MAINTSCRIPT_PACKAGE" ]; then + echo >&2 "Warning: apt-key should not be used in scripts (called from $DPKG_MAINTSCRIPT_NAME maintainerscript of the package ${DPKG_MAINTSCRIPT_PACKAGE})" + fi + + echo >&2 "Warning: apt-key is deprecated. Manage keyring files in trusted.gpg.d instead (see apt-key(8))." +} + +warn_outside_maintscript() { + # In del, we want to warn in interactive use, but not inside maintainer + # scripts, so as to give people a chance to migrate keyrings. + # + # FIXME: We should always warn starting in 2022. + if [ -z "$DPKG_MAINTSCRIPT_PACKAGE" ]; then + echo >&2 "Warning: apt-key is deprecated. Manage keyring files in trusted.gpg.d instead (see apt-key(8))." + fi +} + +if [ "$command" != 'help' ] && [ "$command" != 'verify' ]; then + prepare_gpg_home +fi + +case "$command" in + add) + warn_on_script_usage + requires_root + setup_merged_keyring + aptkey_execute "$GPG" --quiet --batch --import "$@" + merge_back_changes + aptkey_echo "OK" + ;; + del|rm|remove) + # no script warning here as removing 'add' usage needs 'del' for cleanup + warn_outside_maintscript + requires_root + foreach_keyring_do 'remove_key_from_keyring' "$@" + aptkey_echo "OK" + ;; + update) + warn_on_script_usage + requires_root + setup_merged_keyring + update + merge_back_changes + ;; + net-update) + warn_on_script_usage + requires_root + setup_merged_keyring + net_update + merge_back_changes + ;; + list|finger*) + warn_on_script_usage + foreach_keyring_do 'list_keys_in_keyring' --fingerprint "$@" + ;; + export|exportall) + warn_on_script_usage + merge_all_trusted_keyrings_into_pubring + aptkey_execute "$GPG_SH" --keyring "${GPGHOMEDIR}/pubring.gpg" --armor --export "$@" + ;; + adv*) + warn_on_script_usage + setup_merged_keyring + aptkey_echo "Executing: $GPG" "$@" + aptkey_execute "$GPG" "$@" + merge_back_changes + ;; + verify) + GPGV='' + eval $(apt-config shell GPGV Apt::Key::gpgvcommand) + if [ -n "$GPGV" ] && command_available "$GPGV"; then true; + elif command_available 'gpgv'; then GPGV='gpgv'; + elif command_available 'gpgv2'; then GPGV='gpgv2'; + elif command_available 'gpgv1'; then GPGV='gpgv1'; + else + apt_error 'gpgv, gpgv2 or gpgv1 required for verification, but neither seems installed' + exit 29 + fi + # for a forced keyid we need gpg --export, so full wrapping required + if [ -n "$FORCED_KEYID" ]; then + prepare_gpg_home + else + create_gpg_home + fi + setup_merged_keyring + if [ -n "$FORCED_KEYRING" ]; then + "$GPGV" --homedir "${GPGHOMEDIR}" --keyring "$(dearmor_filename "${FORCED_KEYRING}")" --ignore-time-conflict "$@" + else + "$GPGV" --homedir "${GPGHOMEDIR}" --keyring "${GPGHOMEDIR}/pubring.gpg" --ignore-time-conflict "$@" + fi + ;; + help) + usage + ;; + *) + usage + exit 1 + ;; +esac diff --git a/cmdline/apt-mark.cc b/cmdline/apt-mark.cc new file mode 100644 index 0000000..33716bb --- /dev/null +++ b/cmdline/apt-mark.cc @@ -0,0 +1,476 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ##################################################################### + apt-mark - show and change auto-installed bit information + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + /*}}}*/ +using namespace std; + +/* DoAuto - mark packages as automatically/manually installed {{{*/ +static bool DoAuto(CommandLine &CmdL) +{ + pkgCacheFile CacheFile; + CacheFile.InhibitActionGroups(true); + pkgDepCache * const DepCache = CacheFile.GetDepCache(); + if (unlikely(DepCache == nullptr)) + return false; + + APT::PackageList pkgset = APT::PackageList::FromCommandLine(CacheFile, CmdL.FileList + 1); + if (pkgset.empty() == true) + return _error->Error(_("No packages found")); + + bool MarkAuto = strcasecmp(CmdL.FileList[0],"auto") == 0; + + vector PackagesMarked; + + for (APT::PackageList::const_iterator Pkg = pkgset.begin(); Pkg != pkgset.end(); ++Pkg) + { + if (Pkg->CurrentVer == 0) + { + ioprintf(c1out,_("%s can not be marked as it is not installed.\n"), Pkg.FullName(true).c_str()); + continue; + } + else if ((((*DepCache)[Pkg].Flags & pkgCache::Flag::Auto) == pkgCache::Flag::Auto) == MarkAuto) + { + if (MarkAuto == false) + ioprintf(c1out,_("%s was already set to manually installed.\n"), Pkg.FullName(true).c_str()); + else + ioprintf(c1out,_("%s was already set to automatically installed.\n"), Pkg.FullName(true).c_str()); + continue; + } + + PackagesMarked.push_back(Pkg.FullName(true)); + DepCache->MarkAuto(Pkg, MarkAuto); + } + + bool MarkWritten = false; + bool IsSimulation = _config->FindB("APT::Mark::Simulate", false); + if (PackagesMarked.size() > 0 && !IsSimulation) { + MarkWritten = DepCache->writeStateFile(NULL); + if(!MarkWritten) { + return MarkWritten; + } + } + + if(IsSimulation || MarkWritten) { + for (vector::const_iterator I = PackagesMarked.begin(); I != PackagesMarked.end(); ++I) { + if (MarkAuto == false) + ioprintf(c1out,_("%s set to manually installed.\n"), (*I).c_str()); + else + ioprintf(c1out,_("%s set to automatically installed.\n"), (*I).c_str()); + } + } + return true; +} + /*}}}*/ +/* DoMarkAuto - mark packages as automatically/manually installed {{{*/ +/* Does the same as DoAuto but tries to do it exactly the same why as + the python implementation did it so it can be a drop-in replacement */ +static bool DoMarkAuto(CommandLine &CmdL) +{ + pkgCacheFile CacheFile; + CacheFile.InhibitActionGroups(true); + pkgDepCache * const DepCache = CacheFile.GetDepCache(); + if (unlikely(DepCache == nullptr)) + return false; + + APT::PackageList pkgset = APT::PackageList::FromCommandLine(CacheFile, CmdL.FileList + 1); + if (pkgset.empty() == true) + return _error->Error(_("No packages found")); + + bool const MarkAuto = strcasecmp(CmdL.FileList[0],"markauto") == 0; + bool const Verbose = _config->FindB("APT::MarkAuto::Verbose", false); + int AutoMarkChanged = 0; + + for (APT::PackageList::const_iterator Pkg = pkgset.begin(); Pkg != pkgset.end(); ++Pkg) + { + if (Pkg->CurrentVer == 0 || + (((*DepCache)[Pkg].Flags & pkgCache::Flag::Auto) == pkgCache::Flag::Auto) == MarkAuto) + continue; + + if (Verbose == true) + ioprintf(c1out, "changing %s to %d\n", Pkg.Name(), (MarkAuto == false) ? 0 : 1); + + DepCache->MarkAuto(Pkg, MarkAuto); + ++AutoMarkChanged; + } + if (AutoMarkChanged > 0 && _config->FindB("APT::Mark::Simulate", false) == false) + return DepCache->writeStateFile(NULL); + + _error->Notice(_("This command is deprecated. Please use 'apt-mark auto' and 'apt-mark manual' instead.")); + + return true; +} + /*}}}*/ +// helper for Install-Recommends-Sections and Never-MarkAuto-Sections /*{{{*/ +// FIXME: Copied verbatim from apt-pkg/depcache.cc +static bool ConfigValueInSubTree(const char* SubTree, std::string_view const needle) +{ + if (needle.empty()) + return false; + Configuration::Item const *Opts = _config->Tree(SubTree); + if (Opts != nullptr && Opts->Child != nullptr) + { + Opts = Opts->Child; + for (; Opts != nullptr; Opts = Opts->Next) + { + if (Opts->Value.empty()) + continue; + if (needle == Opts->Value) + return true; + } + } + return false; +} +static bool SectionInSubTree(char const * const SubTree, std::string_view Needle) +{ + if (ConfigValueInSubTree(SubTree, Needle)) + return true; + auto const sub = Needle.rfind('/'); + if (sub == std::string_view::npos) + { + std::string special{"/"}; + special.append(Needle); + return ConfigValueInSubTree(SubTree, special); + } + return ConfigValueInSubTree(SubTree, Needle.substr(sub + 1)); +} + /*}}}*/ +/* DoMinimize - minimize manually installed {{{*/ +/* Traverses dependencies of meta packages and marks them as manually + * installed. */ +static bool DoMinimize(CommandLine &CmdL) +{ + + pkgCacheFile CacheFile; + pkgDepCache *const DepCache = CacheFile.GetDepCache(); + if (unlikely(DepCache == nullptr)) + return false; + + if (CmdL.FileList[1] != nullptr) + return _error->Error(_("%s does not take any arguments"), "apt-mark minimize-manual"); + + auto Debug = _config->FindB("Debug::AptMark::Minimize", false); + auto is_root = [&DepCache](pkgCache::PkgIterator const &pkg) { + auto ver = pkg.CurrentVer(); + return ver.end() == false && ((*DepCache)[pkg].Flags & pkgCache::Flag::Auto) == 0 && + ver->Section != 0 && + SectionInSubTree("APT::Never-MarkAuto-Sections", ver.Section()); + }; + + APT::PackageSet roots; + for (auto Pkg = DepCache->PkgBegin(); Pkg.end() == false; ++Pkg) + { + if (is_root(Pkg)) + { + if (Debug) + std::clog << "Found root " << Pkg.Name() << std::endl; + roots.insert(Pkg); + } + } + + APT::PackageSet workset(roots); + APT::PackageSet seen; + APT::PackageSet changed; + + pkgDepCache::ActionGroup group(*DepCache); + + while (workset.empty() == false) + { + if (Debug) + std::clog << "Iteration\n"; + + APT::PackageSet workset2; + for (auto &Pkg : workset) + { + if (seen.find(Pkg) != seen.end()) + continue; + + seen.insert(Pkg); + + if (Debug) + std::cerr << " Visiting " << Pkg.FullName(true) << "\n"; + if (roots.find(Pkg) == roots.end() && ((*DepCache)[Pkg].Flags & pkgCache::Flag::Auto) == 0) + { + DepCache->MarkAuto(Pkg, true); + changed.insert(Pkg); + } + + // Visit dependencies, add them to next working set + for (auto Dep = Pkg.CurrentVer().DependsList(); !Dep.end(); ++Dep) + { + if (DepCache->IsImportantDep(Dep) == false) + continue; + std::unique_ptr targets(Dep.AllTargets()); + for (int i = 0; targets[i] != nullptr; i++) + { + pkgCache::VerIterator Tgt(*DepCache, targets[i]); + if (Tgt.ParentPkg()->CurrentVer != 0 && Tgt.ParentPkg().CurrentVer()->ID == Tgt->ID) + workset2.insert(Tgt.ParentPkg()); + } + } + } + + workset = std::move(workset2); + } + + if (changed.empty()) { + cout << _("No changes necessary") << endl; + return true; + } + + ShowList(c1out, _("The following packages will be marked as automatically installed:"), changed, + [](const pkgCache::PkgIterator &) { return true; }, + &PrettyFullName, + &PrettyFullName); + + if (_config->FindB("APT::Mark::Simulate", false) == false) + { + if (YnPrompt(_("Do you want to continue?"), false) == false) + return true; + + return DepCache->writeStateFile(NULL); + } + + return true; +} + /*}}}*/ + +/* ShowAuto - show automatically installed packages (sorted) {{{*/ +static bool ShowAuto(CommandLine &CmdL) +{ + pkgCacheFile CacheFile; + CacheFile.InhibitActionGroups(true); + pkgDepCache * const DepCache = CacheFile.GetDepCache(); + if (unlikely(DepCache == nullptr)) + return false; + + std::vector packages; + + bool const ShowAuto = strcasecmp(CmdL.FileList[0],"showauto") == 0; + + if (CmdL.FileList[1] == 0) + { + packages.reserve(DepCache->Head().PackageCount / 3); + for (pkgCache::PkgIterator P = DepCache->PkgBegin(); P.end() == false; ++P) + if (P->CurrentVer != 0 && + (((*DepCache)[P].Flags & pkgCache::Flag::Auto) == pkgCache::Flag::Auto) == ShowAuto) + packages.push_back(P.FullName(true)); + } + else + { + APT::CacheSetHelper helper(false); // do not show errors + APT::PackageSet pkgset = APT::PackageSet::FromCommandLine(CacheFile, CmdL.FileList + 1, helper); + packages.reserve(pkgset.size()); + for (APT::PackageSet::const_iterator P = pkgset.begin(); P != pkgset.end(); ++P) + if (P->CurrentVer != 0 && + (((*DepCache)[P].Flags & pkgCache::Flag::Auto) == pkgCache::Flag::Auto) == ShowAuto) + packages.push_back(P.FullName(true)); + } + + std::sort(packages.begin(), packages.end()); + + for (vector::const_iterator I = packages.begin(); I != packages.end(); ++I) + std::cout << *I << std::endl; + + return true; +} + /*}}}*/ +// DoSelection - wrapping around dpkg selections /*{{{*/ +static bool DoSelection(CommandLine &CmdL) +{ + pkgCacheFile CacheFile; + CacheFile.InhibitActionGroups(true); + pkgCache * const Cache = CacheFile.GetPkgCache(); + if (unlikely(Cache == nullptr)) + return false; + + APT::VersionVector pkgset = APT::VersionVector::FromCommandLine(CacheFile, CmdL.FileList + 1, APT::CacheSetHelper::INSTCAND); + if (pkgset.empty() == true) + return _error->Error(_("No packages found")); + + APT::StateChanges marks; + if (strcasecmp(CmdL.FileList[0], "hold") == 0 || strcasecmp(CmdL.FileList[0], "unhold") == 0) + { + auto const part = std::stable_partition(pkgset.begin(), pkgset.end(), + [](pkgCache::VerIterator const &V) { return V.ParentPkg()->SelectedState == pkgCache::State::Hold; }); + + bool const MarkHold = strcasecmp(CmdL.FileList[0],"hold") == 0; + auto const doneBegin = MarkHold ? pkgset.begin() : part; + auto const doneEnd = MarkHold ? part : pkgset.end(); + std::for_each(doneBegin, doneEnd, [&MarkHold](pkgCache::VerIterator const &V) { + if (MarkHold == true) + ioprintf(c1out, _("%s was already set on hold.\n"), V.ParentPkg().FullName(true).c_str()); + else + ioprintf(c1out, _("%s was already not on hold.\n"), V.ParentPkg().FullName(true).c_str()); + }); + + if (doneBegin == pkgset.begin() && doneEnd == pkgset.end()) + return true; + + auto const changeBegin = MarkHold ? part : pkgset.begin(); + auto const changeEnd = MarkHold ? pkgset.end() : part; + std::move(changeBegin, changeEnd, std::back_inserter(MarkHold ? marks.Hold() : marks.Unhold())); + } + else + { + // FIXME: Maybe show a message for unchanged states here as well? + if (strcasecmp(CmdL.FileList[0], "purge") == 0) + std::swap(marks.Purge(), pkgset); + else if (strcasecmp(CmdL.FileList[0], "deinstall") == 0 || strcasecmp(CmdL.FileList[0], "remove") == 0) + std::swap(marks.Remove(), pkgset); + else //if (strcasecmp(CmdL.FileList[0], "install") == 0) + std::swap(marks.Install(), pkgset); + } + pkgset.clear(); + + bool success = true; + if (_config->FindB("APT::Mark::Simulate", false) == false) + { + success = marks.Save(); + if (success == false) + _error->Error(_("Executing dpkg failed. Are you root?")); + } + for (auto Ver : marks.Hold()) + ioprintf(c1out,_("%s set on hold.\n"), Ver.ParentPkg().FullName(true).c_str()); + for (auto Ver : marks.Unhold()) + ioprintf(c1out,_("Canceled hold on %s.\n"), Ver.ParentPkg().FullName(true).c_str()); + for (auto Ver : marks.Purge()) + ioprintf(c1out,_("Selected %s for purge.\n"), Ver.ParentPkg().FullName(true).c_str()); + for (auto Ver : marks.Remove()) + ioprintf(c1out,_("Selected %s for removal.\n"), Ver.ParentPkg().FullName(true).c_str()); + for (auto Ver : marks.Install()) + ioprintf(c1out,_("Selected %s for installation.\n"), Ver.ParentPkg().FullName(true).c_str()); + return success; +} + /*}}}*/ +static bool ShowSelection(CommandLine &CmdL) /*{{{*/ +{ + pkgCacheFile CacheFile; + pkgCache * const Cache = CacheFile.GetPkgCache(); + if (unlikely(Cache == nullptr)) + return false; + + pkgCache::State::PkgSelectedState selector; + if (strncasecmp(CmdL.FileList[0], "showpurge", strlen("showpurge")) == 0) + selector = pkgCache::State::Purge; + else if (strncasecmp(CmdL.FileList[0], "showdeinstall", strlen("showdeinstall")) == 0 || + strncasecmp(CmdL.FileList[0], "showremove", strlen("showremove")) == 0) + selector = pkgCache::State::DeInstall; + else if (strncasecmp(CmdL.FileList[0], "showhold", strlen("showhold")) == 0 || strncasecmp(CmdL.FileList[0], "showheld", strlen("showheld")) == 0) + selector = pkgCache::State::Hold; + else //if (strcasecmp(CmdL.FileList[0], "showinstall", strlen("showinstall")) == 0) + selector = pkgCache::State::Install; + + std::vector packages; + + if (CmdL.FileList[1] == 0) + { + packages.reserve(50); // how many holds are realistic? I hope just a few… + for (pkgCache::PkgIterator P = Cache->PkgBegin(); P.end() == false; ++P) + if (P->SelectedState == selector) + packages.push_back(P.FullName(true)); + } + else + { + APT::CacheSetHelper helper(false); // do not show errors + APT::PackageSet pkgset = APT::PackageSet::FromCommandLine(CacheFile, CmdL.FileList + 1, helper); + packages.reserve(pkgset.size()); + for (APT::PackageSet::const_iterator P = pkgset.begin(); P != pkgset.end(); ++P) + if (P->SelectedState == selector) + packages.push_back(P.FullName(true)); + } + + std::sort(packages.begin(), packages.end()); + + for (vector::const_iterator I = packages.begin(); I != packages.end(); ++I) + std::cout << *I << std::endl; + + return true; +} + /*}}}*/ +static bool ShowHelp(CommandLine &) /*{{{*/ +{ + std::cout << + _("Usage: apt-mark [options] {auto|manual} pkg1 [pkg2 ...]\n" + "\n" + "apt-mark is a simple command line interface for marking packages\n" + "as manually or automatically installed. It can also be used to\n" + "manipulate the dpkg(1) selection states of packages, and to list\n" + "all packages with or without a certain marking.\n"); + return true; +} + /*}}}*/ +static std::vector GetCommands() /*{{{*/ +{ + return { + {"auto",&DoAuto, _("Mark the given packages as automatically installed")}, + {"manual",&DoAuto, _("Mark the given packages as manually installed")}, + {"minimize-manual", &DoMinimize, _("Mark all dependencies of meta packages as automatically installed.")}, + {"hold",&DoSelection, _("Mark a package as held back")}, + {"unhold",&DoSelection, _("Unset a package set as held back")}, + {"install",&DoSelection, nullptr}, + {"remove",&DoSelection, nullptr}, // dpkg uses deinstall, but we use remove everywhere else + {"deinstall",&DoSelection, nullptr}, + {"purge",&DoSelection, nullptr}, + {"showauto",&ShowAuto, _("Print the list of automatically installed packages")}, + {"showmanual",&ShowAuto, _("Print the list of manually installed packages")}, + {"showhold",&ShowSelection, _("Print the list of packages on hold")}, {"showholds",&ShowSelection, nullptr}, {"showheld",&ShowSelection, nullptr}, + {"showinstall",&ShowSelection, nullptr}, {"showinstalls",&ShowSelection, nullptr}, + {"showdeinstall",&ShowSelection, nullptr}, {"showdeinstalls",&ShowSelection, nullptr}, + {"showremove",&ShowSelection, nullptr}, {"showremoves",&ShowSelection, nullptr}, + {"showpurge",&ShowSelection, nullptr}, {"showpurges",&ShowSelection, nullptr}, + // obsolete commands for compatibility + {"markauto", &DoMarkAuto, nullptr}, + {"unmarkauto", &DoMarkAuto, nullptr}, + {nullptr, nullptr, nullptr} + }; +} + /*}}}*/ +int main(int argc,const char *argv[]) /*{{{*/ +{ + CommandLine CmdL; + auto const Cmds = ParseCommandLine(CmdL, APT_CMD::APT_MARK, &_config, &_system, argc, argv, &ShowHelp, &GetCommands); + + InitOutput(); + + return DispatchCommandLine(CmdL, Cmds); +} + /*}}}*/ diff --git a/cmdline/apt-report-mirror-failure b/cmdline/apt-report-mirror-failure new file mode 100755 index 0000000..7c39064 --- /dev/null +++ b/cmdline/apt-report-mirror-failure @@ -0,0 +1,29 @@ +#!/usr/bin/python +# +# This is a stub that is meant to support failure reporting of +# mirrors to a central database +# +# its currently not used + +import sys +import urllib +import apt_pkg + +apt_pkg.init() +url = apt_pkg.Config.find("Acquire::Mirror::ReportFailures", "") + #"http://people.ubuntu.com:9000/mirror-failure") + #"http://localhost:9000/mirror-failure") +if not url: + sys.exit(0) + +print "Reporting mirror failure to '%s'" % url + +data = {} +data['mirror'] = sys.argv[1] +data['failurl'] = sys.argv[2] +data['error'] = sys.argv[3] +f = urllib.urlopen(url, urllib.urlencode(data)) +f.read() +f.close() + + diff --git a/cmdline/apt-sortpkgs.cc b/cmdline/apt-sortpkgs.cc new file mode 100644 index 0000000..acbb283 --- /dev/null +++ b/cmdline/apt-sortpkgs.cc @@ -0,0 +1,166 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + APT Sort Packages - Program to sort Package and Source files + + This program is quite simple, it just sorts the package files by + package and sorts the fields inside by the internal APT sort order. + Input is taken from a named file and sent to stdout. + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + /*}}}*/ + +using namespace std; + +struct PkgName /*{{{*/ +{ + string Name; + string Ver; + string Arch; + unsigned long Offset; + unsigned long Length; + + inline int Compare3(const PkgName &x) const + { + int A = stringcasecmp(Name,x.Name); + if (A == 0) + { + A = stringcasecmp(Ver,x.Ver); + if (A == 0) + A = stringcasecmp(Arch,x.Arch); + } + return A; + } + + bool operator <(const PkgName &x) const {return Compare3(x) < 0;}; + bool operator >(const PkgName &x) const {return Compare3(x) > 0;}; + bool operator ==(const PkgName &x) const {return Compare3(x) == 0;}; +}; + /*}}}*/ +// DoIt - Sort a single file /*{{{*/ +// --------------------------------------------------------------------- +/* */ +static bool DoIt(string InFile) +{ + FileFd Fd(InFile,FileFd::ReadOnly); + pkgTagFile Tags(&Fd); + if (_error->PendingError() == true) + return false; + + // Parse. + vector List; + pkgTagSection Section; + unsigned long Largest = 0; + unsigned long Offset = Tags.Offset(); + bool Source = _config->FindB("APT::SortPkgs::Source",false); + while (Tags.Step(Section) == true) + { + PkgName Tmp; + + /* Fetch the name, auto-detecting if this is a source file or a + package file */ + Tmp.Name = Section.Find(pkgTagSection::Key::Package).to_string(); + Tmp.Ver = Section.Find(pkgTagSection::Key::Version).to_string(); + Tmp.Arch = Section.Find(pkgTagSection::Key::Architecture).to_string(); + + if (Tmp.Name.empty() == true) + return _error->Error(_("Unknown package record!")); + + Tmp.Offset = Offset; + Tmp.Length = Section.size(); + if (Largest < Tmp.Length) + Largest = Tmp.Length; + + List.push_back(Tmp); + + Offset = Tags.Offset(); + } + if (_error->PendingError() == true) + return false; + + // Sort it + sort(List.begin(),List.end()); + + const char **Order = TFRewritePackageOrder; + if (Source == true) + Order = TFRewriteSourceOrder; + + // Emit + FileFd stdoutfd; + stdoutfd.OpenDescriptor(STDOUT_FILENO, FileFd::WriteOnly, false); + auto const Buffer = std::unique_ptr(new unsigned char[Largest+1]); + for (vector::iterator I = List.begin(); I != List.end(); ++I) + { + // Read in the Record. + if (Fd.Seek(I->Offset) == false || Fd.Read(Buffer.get(),I->Length) == false) + return false; + + Buffer[I->Length] = '\n'; + if (Section.Scan((char *)Buffer.get(),I->Length+1) == false) + return _error->Error("Internal error, failed to scan buffer"); + + // Sort the section + if (Section.Write(stdoutfd, Order) == false || stdoutfd.Write("\n", 1) == false) + return _error->Error("Internal error, failed to sort fields"); + } + return true; +} + /*}}}*/ +static bool ShowHelp(CommandLine &) /*{{{*/ +{ + std::cout << + _("Usage: apt-sortpkgs [options] file1 [file2 ...]\n" + "\n" + "apt-sortpkgs is a simple tool to sort package information files.\n" + "By default it sorts by binary package information, but the -s option\n" + "can be used to switch to source package ordering instead.\n"); + return true; +} + /*}}}*/ +static std::vector GetCommands() /*{{{*/ +{ + return { + {nullptr, nullptr, nullptr} + }; +} + /*}}}*/ +int main(int argc,const char *argv[]) /*{{{*/ +{ + CommandLine CmdL; + ParseCommandLine(CmdL, APT_CMD::APT_SORTPKG, &_config, &_system, argc, argv, &ShowHelp, &GetCommands); + + // Match the operation + for (unsigned int I = 0; I != CmdL.FileSize(); I++) + if (DoIt(CmdL.FileList[I]) == false) + break; + + return DispatchCommandLine(CmdL, {}); +} + /*}}}*/ diff --git a/cmdline/apt.cc b/cmdline/apt.cc new file mode 100644 index 0000000..ef58bce --- /dev/null +++ b/cmdline/apt.cc @@ -0,0 +1,125 @@ +// -*- mode: cpp; mode: fold -*- +// Description /*{{{*/ +/* ###################################################################### + + apt - CLI UI for apt + + Returns 100 on failure, 0 on success. + + ##################################################################### */ + /*}}}*/ +// Include Files /*{{{*/ +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + /*}}}*/ + +static bool ShowHelp(CommandLine &) /*{{{*/ +{ + std::cout << + _("Usage: apt [options] command\n" + "\n" + "apt is a commandline package manager and provides commands for\n" + "searching and managing as well as querying information about packages.\n" + "It provides the same functionality as the specialized APT tools,\n" + "like apt-get and apt-cache, but enables options more suitable for\n" + "interactive use by default.\n"); + return true; +} + /*}}}*/ +static std::vector GetCommands() /*{{{*/ +{ + // advanced commands are left undocumented on purpose + return { + // query + {"list", &DoList, _("list packages based on package names")}, + {"search", &DoSearch, _("search in package descriptions")}, + {"show", &ShowPackage, _("show package details")}, + + // package stuff + {"install", &DoInstall, _("install packages")}, + {"reinstall", &DoInstall, _("reinstall packages")}, + {"remove", &DoInstall, _("remove packages")}, + {"autoremove", &DoInstall, _("automatically remove all unused packages")}, + {"auto-remove", &DoInstall, nullptr}, + {"autopurge",&DoInstall, nullptr}, + {"purge", &DoInstall, nullptr}, + + // system wide stuff + {"update", &DoUpdate, _("update list of available packages")}, + {"upgrade", &DoUpgrade, _("upgrade the system by installing/upgrading packages")}, + {"full-upgrade", &DoDistUpgrade, _("upgrade the system by removing/installing/upgrading packages")}, + + // misc + {"edit-sources", &EditSources, _("edit the source information file")}, + {"moo", &DoMoo, nullptr}, + {"satisfy", &DoBuildDep, _("satisfy dependency strings")}, + + // for compat with muscle memory + {"dist-upgrade", &DoDistUpgrade, nullptr}, + {"showsrc",&ShowSrcPackage, nullptr}, + {"depends",&Depends, nullptr}, + {"rdepends",&RDepends, nullptr}, + {"policy",&Policy, nullptr}, + {"build-dep", &DoBuildDep,nullptr}, + {"clean", &DoClean, nullptr}, + {"distclean", &DoDistClean, nullptr}, + {"dist-clean", &DoDistClean, nullptr}, + {"autoclean", &DoAutoClean, nullptr}, + {"auto-clean", &DoAutoClean, nullptr}, + {"source", &DoSource, nullptr}, + {"download", &DoDownload, nullptr}, + {"changelog", &DoChangelog, nullptr}, + {"info", &ShowPackage, nullptr}, + + {nullptr, nullptr, nullptr} + }; +} + /*}}}*/ +int main(int argc, const char *argv[]) /*{{{*/ +{ + CommandLine CmdL; + auto const Cmds = ParseCommandLine(CmdL, APT_CMD::APT, &_config, &_system, argc, argv, &ShowHelp, &GetCommands); + + int const quiet = _config->FindI("quiet", 0); + if (quiet == 2) + { + _config->CndSet("quiet::NoProgress", true); + _config->Set("quiet", 1); + } + + InitSignals(); + InitOutput(); + + CheckIfCalledByScript(argc, argv); + CheckIfSimulateMode(CmdL); + + return DispatchCommandLine(CmdL, Cmds); +} + /*}}}*/ -- cgit v1.2.3