You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

343 lines
10 KiB

  1. // Includes /*{{{*/
  2. #include <config.h>
  3. #include <apt-pkg/cachefile.h>
  4. #include <apt-pkg/cacheset.h>
  5. #include <apt-pkg/cmndline.h>
  6. #include <apt-pkg/configuration.h>
  7. #include <apt-pkg/depcache.h>
  8. #include <apt-pkg/macros.h>
  9. #include <apt-pkg/pkgcache.h>
  10. #include <apt-pkg/pkgrecords.h>
  11. #include <apt-pkg/policy.h>
  12. #include <apt-pkg/progress.h>
  13. #include <apt-private/private-cachefile.h>
  14. #include <apt-private/private-cacheset.h>
  15. #include <apt-private/private-json-hooks.h>
  16. #include <apt-private/private-output.h>
  17. #include <apt-private/private-search.h>
  18. #include <apt-private/private-show.h>
  19. #include <iostream>
  20. #include <map>
  21. #include <sstream>
  22. #include <string>
  23. #include <utility>
  24. #include <string.h>
  25. #include <apti18n.h>
  26. /*}}}*/
  27. static bool FullTextSearch(CommandLine &CmdL) /*{{{*/
  28. {
  29. CacheFile CacheFile;
  30. CacheFile.GetDepCache();
  31. pkgCache *Cache = CacheFile.GetPkgCache();
  32. pkgDepCache::Policy *Plcy = CacheFile.GetPolicy();
  33. if (unlikely(Cache == NULL || Plcy == NULL))
  34. return false;
  35. // Make sure there is at least one argument
  36. unsigned int const NumPatterns = CmdL.FileSize() -1;
  37. if (NumPatterns < 1)
  38. return _error->Error(_("You must give at least one search pattern"));
  39. RunJsonHook("AptCli::Hooks::Search", "org.debian.apt.hooks.search.pre", CmdL.FileList, CacheFile);
  40. #define APT_FREE_PATTERNS() for (std::vector<regex_t>::iterator P = Patterns.begin(); \
  41. P != Patterns.end(); ++P) { regfree(&(*P)); }
  42. // Compile the regex pattern
  43. std::vector<regex_t> Patterns;
  44. for (unsigned int I = 0; I != NumPatterns; ++I)
  45. {
  46. regex_t pattern;
  47. if (regcomp(&pattern, CmdL.FileList[I + 1], REG_EXTENDED | REG_ICASE | REG_NOSUB) != 0)
  48. {
  49. APT_FREE_PATTERNS();
  50. return _error->Error("Regex compilation error");
  51. }
  52. Patterns.push_back(pattern);
  53. }
  54. std::map<std::string, std::string> output_map;
  55. LocalitySortedVersionSet bag;
  56. OpTextProgress progress(*_config);
  57. progress.OverallProgress(0, 100, 50, _("Sorting"));
  58. GetLocalitySortedVersionSet(CacheFile, &bag, &progress);
  59. LocalitySortedVersionSet::iterator V = bag.begin();
  60. progress.OverallProgress(50, 100, 50, _("Full Text Search"));
  61. progress.SubProgress(bag.size());
  62. pkgRecords records(CacheFile);
  63. std::string format = "${color:highlight}${Package}${color:neutral}/${Origin} ${Version} ${Architecture}${ }${apt:Status}\n";
  64. if (_config->FindB("APT::Cache::ShowFull",false) == false)
  65. format += " ${Description}\n";
  66. else
  67. format += " ${LongDescription}\n";
  68. bool const NamesOnly = _config->FindB("APT::Cache::NamesOnly", false);
  69. int Done = 0;
  70. std::vector<bool> PkgsDone(Cache->Head().PackageCount, false);
  71. for ( ;V != bag.end(); ++V)
  72. {
  73. if (Done%500 == 0)
  74. progress.Progress(Done);
  75. ++Done;
  76. // we want to list each package only once
  77. pkgCache::PkgIterator const P = V.ParentPkg();
  78. if (PkgsDone[P->ID] == true)
  79. continue;
  80. char const * const PkgName = P.Name();
  81. pkgCache::DescIterator Desc = V.TranslatedDescription();
  82. std::string LongDesc = "";
  83. if (Desc.end() == false)
  84. {
  85. pkgRecords::Parser &parser = records.Lookup(Desc.FileList());
  86. LongDesc = parser.LongDesc();
  87. }
  88. bool all_found = true;
  89. for (std::vector<regex_t>::const_iterator pattern = Patterns.begin();
  90. pattern != Patterns.end(); ++pattern)
  91. {
  92. if (regexec(&(*pattern), PkgName, 0, 0, 0) == 0)
  93. continue;
  94. else if (NamesOnly == false && regexec(&(*pattern), LongDesc.c_str(), 0, 0, 0) == 0)
  95. continue;
  96. // search patterns are AND, so one failing fails all
  97. all_found = false;
  98. break;
  99. }
  100. if (all_found == true)
  101. {
  102. PkgsDone[P->ID] = true;
  103. std::stringstream outs;
  104. ListSingleVersion(CacheFile, records, V, outs, format);
  105. output_map.insert(std::make_pair<std::string, std::string>(
  106. PkgName, outs.str()));
  107. }
  108. }
  109. APT_FREE_PATTERNS();
  110. progress.Done();
  111. // FIXME: SORT! and make sorting flexible (alphabetic, by pkg status)
  112. // output the sorted map
  113. std::map<std::string, std::string>::const_iterator K;
  114. for (K = output_map.begin(); K != output_map.end(); ++K)
  115. std::cout << (*K).second << std::endl;
  116. if (output_map.empty())
  117. RunJsonHook("AptCli::Hooks::Search", "org.debian.apt.hooks.search.fail", CmdL.FileList, CacheFile);
  118. else
  119. RunJsonHook("AptCli::Hooks::Search", "org.debian.apt.hooks.search.post", CmdL.FileList, CacheFile);
  120. return true;
  121. }
  122. /*}}}*/
  123. // LocalitySort - Sort a version list by package file locality /*{{{*/
  124. static int LocalityCompare(const void * const a, const void * const b)
  125. {
  126. pkgCache::VerFile const * const A = *static_cast<pkgCache::VerFile const * const *>(a);
  127. pkgCache::VerFile const * const B = *static_cast<pkgCache::VerFile const * const *>(b);
  128. if (A == 0 && B == 0)
  129. return 0;
  130. if (A == 0)
  131. return 1;
  132. if (B == 0)
  133. return -1;
  134. if (A->File == B->File)
  135. return A->Offset - B->Offset;
  136. return A->File - B->File;
  137. }
  138. void LocalitySort(pkgCache::VerFile ** const begin, unsigned long long const Count,size_t const Size)
  139. {
  140. qsort(begin,Count,Size,LocalityCompare);
  141. }
  142. static void LocalitySort(pkgCache::DescFile ** const begin, unsigned long long const Count,size_t const Size)
  143. {
  144. qsort(begin,Count,Size,LocalityCompare);
  145. }
  146. /*}}}*/
  147. // Search - Perform a search /*{{{*/
  148. // ---------------------------------------------------------------------
  149. /* This searches the package names and package descriptions for a pattern */
  150. struct ExDescFile
  151. {
  152. pkgCache::DescFile *Df;
  153. pkgCache::VerIterator V;
  154. map_id_t ID;
  155. ExDescFile() : Df(nullptr), ID(0) {}
  156. };
  157. static bool Search(CommandLine &CmdL)
  158. {
  159. bool const ShowFull = _config->FindB("APT::Cache::ShowFull",false);
  160. unsigned int const NumPatterns = CmdL.FileSize() -1;
  161. pkgCacheFile CacheFile;
  162. pkgCache *Cache = CacheFile.GetPkgCache();
  163. pkgDepCache::Policy *Plcy = CacheFile.GetPolicy();
  164. if (unlikely(Cache == NULL || Plcy == NULL))
  165. return false;
  166. // Make sure there is at least one argument
  167. if (NumPatterns < 1)
  168. return _error->Error(_("You must give at least one search pattern"));
  169. // Compile the regex pattern
  170. regex_t *Patterns = new regex_t[NumPatterns];
  171. memset(Patterns,0,sizeof(*Patterns)*NumPatterns);
  172. for (unsigned I = 0; I != NumPatterns; I++)
  173. {
  174. if (regcomp(&Patterns[I],CmdL.FileList[I+1],REG_EXTENDED | REG_ICASE |
  175. REG_NOSUB) != 0)
  176. {
  177. for (; I != 0; I--)
  178. regfree(&Patterns[I]);
  179. return _error->Error("Regex compilation error");
  180. }
  181. }
  182. if (_error->PendingError() == true)
  183. {
  184. for (unsigned I = 0; I != NumPatterns; I++)
  185. regfree(&Patterns[I]);
  186. return false;
  187. }
  188. size_t const descCount = Cache->HeaderP->GroupCount + 1;
  189. ExDescFile *DFList = new ExDescFile[descCount];
  190. bool *PatternMatch = new bool[descCount * NumPatterns];
  191. memset(PatternMatch,false,sizeof(*PatternMatch) * descCount * NumPatterns);
  192. // Map versions that we want to write out onto the VerList array.
  193. bool const NamesOnly = _config->FindB("APT::Cache::NamesOnly",false);
  194. for (pkgCache::GrpIterator G = Cache->GrpBegin(); G.end() == false; ++G)
  195. {
  196. size_t const PatternOffset = G->ID * NumPatterns;
  197. size_t unmatched = 0, matched = 0;
  198. for (unsigned I = 0; I < NumPatterns; ++I)
  199. {
  200. if (PatternMatch[PatternOffset + I] == true)
  201. ++matched;
  202. else if (regexec(&Patterns[I],G.Name(),0,0,0) == 0)
  203. PatternMatch[PatternOffset + I] = true;
  204. else
  205. ++unmatched;
  206. }
  207. // already dealt with this package?
  208. if (matched == NumPatterns)
  209. continue;
  210. // Doing names only, drop any that don't match..
  211. if (NamesOnly == true && unmatched == NumPatterns)
  212. continue;
  213. // Find the proper version to use
  214. pkgCache::PkgIterator P = G.FindPreferredPkg();
  215. if (P.end() == true)
  216. continue;
  217. pkgCache::VerIterator V = Plcy->GetCandidateVer(P);
  218. if (V.end() == false)
  219. {
  220. pkgCache::DescIterator const D = V.TranslatedDescription();
  221. //FIXME: packages without a description can't be found
  222. if (D.end() == true)
  223. continue;
  224. DFList[G->ID].Df = D.FileList();
  225. DFList[G->ID].V = V;
  226. DFList[G->ID].ID = G->ID;
  227. }
  228. if (unmatched == NumPatterns)
  229. continue;
  230. // Include all the packages that provide matching names too
  231. for (pkgCache::PrvIterator Prv = P.ProvidesList() ; Prv.end() == false; ++Prv)
  232. {
  233. pkgCache::VerIterator V = Plcy->GetCandidateVer(Prv.OwnerPkg());
  234. if (V.end() == true)
  235. continue;
  236. unsigned long id = Prv.OwnerPkg().Group()->ID;
  237. pkgCache::DescIterator const D = V.TranslatedDescription();
  238. //FIXME: packages without a description can't be found
  239. if (D.end() == true)
  240. continue;
  241. DFList[id].Df = D.FileList();
  242. DFList[id].V = V;
  243. DFList[id].ID = id;
  244. size_t const PrvPatternOffset = id * NumPatterns;
  245. for (unsigned I = 0; I < NumPatterns; ++I)
  246. PatternMatch[PrvPatternOffset + I] |= PatternMatch[PatternOffset + I];
  247. }
  248. }
  249. LocalitySort(&DFList->Df, Cache->HeaderP->GroupCount, sizeof(*DFList));
  250. // Create the text record parser
  251. pkgRecords Recs(*Cache);
  252. // Iterate over all the version records and check them
  253. for (ExDescFile *J = DFList; J->Df != 0; ++J)
  254. {
  255. pkgRecords::Parser &P = Recs.Lookup(pkgCache::DescFileIterator(*Cache,J->Df));
  256. size_t const PatternOffset = J->ID * NumPatterns;
  257. if (NamesOnly == false)
  258. {
  259. std::string const LongDesc = P.LongDesc();
  260. for (unsigned I = 0; I < NumPatterns; ++I)
  261. {
  262. if (PatternMatch[PatternOffset + I] == true)
  263. continue;
  264. else if (regexec(&Patterns[I],LongDesc.c_str(),0,0,0) == 0)
  265. PatternMatch[PatternOffset + I] = true;
  266. }
  267. }
  268. bool matchedAll = true;
  269. for (unsigned I = 0; I < NumPatterns; ++I)
  270. if (PatternMatch[PatternOffset + I] == false)
  271. {
  272. matchedAll = false;
  273. break;
  274. }
  275. if (matchedAll == true)
  276. {
  277. if (ShowFull == true)
  278. DisplayRecordV1(CacheFile, J->V, std::cout);
  279. else
  280. printf("%s - %s\n",P.Name().c_str(),P.ShortDesc().c_str());
  281. }
  282. }
  283. delete [] DFList;
  284. delete [] PatternMatch;
  285. for (unsigned I = 0; I != NumPatterns; I++)
  286. regfree(&Patterns[I]);
  287. delete [] Patterns;
  288. if (ferror(stdout))
  289. return _error->Error("Write to stdout failed");
  290. return true;
  291. }
  292. /*}}}*/
  293. bool DoSearch(CommandLine &CmdL) /*{{{*/
  294. {
  295. int const ShowVersion = _config->FindI("APT::Cache::Search::Version", 1);
  296. if (ShowVersion <= 1)
  297. return Search(CmdL);
  298. return FullTextSearch(CmdL);
  299. }