]> git.saurik.com Git - apt.git/blame - ftparchive/cachedb.cc
Merge remote-tracking branch 'mvo/feature/hash-stats' into debian/experimental
[apt.git] / ftparchive / cachedb.cc
CommitLineData
b2e465d6
AL
1// -*- mode: cpp; mode: fold -*-
2// Description /*{{{*/
c9569a1e 3// $Id: cachedb.cc,v 1.7 2004/05/08 19:41:01 mdz Exp $
b2e465d6
AL
4/* ######################################################################
5
6 CacheDB
7
8 Simple uniform interface to a cache database.
9
10 ##################################################################### */
11 /*}}}*/
12// Include Files /*{{{*/
ea542140 13#include <config.h>
b2e465d6
AL
14
15#include <apt-pkg/error.h>
16#include <apt-pkg/md5.h>
cde41ae8 17#include <apt-pkg/sha1.h>
84a0890e 18#include <apt-pkg/sha2.h>
b2e465d6
AL
19#include <apt-pkg/strutl.h>
20#include <apt-pkg/configuration.h>
472ff00e 21#include <apt-pkg/fileutl.h>
453b82a3 22#include <apt-pkg/debfile.h>
ce928105 23#include <apt-pkg/gpgv.h>
a311fb96 24#include <apt-pkg/hashes.h>
a00a9b44 25
b2e465d6 26#include <netinet/in.h> // htonl, etc
453b82a3
DK
27#include <ctype.h>
28#include <stddef.h>
29#include <sys/stat.h>
a311fb96 30#include <strings.h>
ea542140 31
ea542140 32#include "cachedb.h"
a00a9b44
DK
33
34#include <apti18n.h>
b2e465d6
AL
35 /*}}}*/
36
21ea1dbb
MV
37CacheDB::CacheDB(std::string const &DB)
38 : Dbp(0), Fd(NULL), DebFile(0)
39{
40 TmpKey[0]='\0';
41 ReadyDB(DB);
42};
43
44CacheDB::~CacheDB()
45{
46 ReadyDB();
47 delete DebFile;
48};
49
b2e465d6
AL
50// CacheDB::ReadyDB - Ready the DB2 /*{{{*/
51// ---------------------------------------------------------------------
52/* This opens the DB2 file for caching package information */
8f3ba4e8 53bool CacheDB::ReadyDB(std::string const &DB)
b2e465d6 54{
c9569a1e
AL
55 int err;
56
b2e465d6
AL
57 ReadOnly = _config->FindB("APT::FTPArchive::ReadOnlyDB",false);
58
59 // Close the old DB
60 if (Dbp != 0)
61 Dbp->close(Dbp,0);
62
63 /* Check if the DB was disabled while running and deal with a
64 corrupted DB */
65 if (DBFailed() == true)
66 {
dc738e7a 67 _error->Warning(_("DB was corrupted, file renamed to %s.old"),DBFile.c_str());
b2e465d6
AL
68 rename(DBFile.c_str(),(DBFile+".old").c_str());
69 }
70
71 DBLoaded = false;
72 Dbp = 0;
8f3ba4e8 73 DBFile = std::string();
b2e465d6
AL
74
75 if (DB.empty())
76 return true;
c9569a1e
AL
77
78 db_create(&Dbp, NULL, 0);
cde41ae8 79 if ((err = Dbp->open(Dbp, NULL, DB.c_str(), NULL, DB_BTREE,
b2e465d6 80 (ReadOnly?DB_RDONLY:DB_CREATE),
c9569a1e 81 0644)) != 0)
b2e465d6 82 {
c9569a1e
AL
83 if (err == DB_OLD_VERSION)
84 {
85 _error->Warning(_("DB is old, attempting to upgrade %s"),DBFile.c_str());
86 err = Dbp->upgrade(Dbp, DB.c_str(), 0);
87 if (!err)
88 err = Dbp->open(Dbp, NULL, DB.c_str(), NULL, DB_HASH,
89 (ReadOnly?DB_RDONLY:DB_CREATE), 0644);
90
91 }
eb2bc4f2
MV
92 // the database format has changed from DB_HASH to DB_BTREE in
93 // apt 0.6.44
94 if (err == EINVAL)
95 {
c6474fb6 96 _error->Error(_("DB format is invalid. If you upgraded from an older version of apt, please remove and re-create the database."));
eb2bc4f2 97 }
c9569a1e
AL
98 if (err)
99 {
100 Dbp = 0;
101 return _error->Error(_("Unable to open DB file %s: %s"),DB.c_str(), db_strerror(err));
102 }
b2e465d6 103 }
243b2a38 104
b2e465d6
AL
105 DBFile = DB;
106 DBLoaded = true;
107 return true;
108}
109 /*}}}*/
c6474fb6 110// CacheDB::OpenFile - Open the file /*{{{*/
b2e465d6 111// ---------------------------------------------------------------------
cde41ae8
MV
112/* */
113bool CacheDB::OpenFile()
114{
acea28d0
MV
115 // always close existing file first
116 CloseFile();
215b0faf
MV
117
118 // open a new file
119 Fd = new FileFd(FileName,FileFd::ReadOnly);
120 if (_error->PendingError() == true)
121 {
122 CloseFile();
123 return false;
124 }
125 return true;
cde41ae8
MV
126}
127 /*}}}*/
215b0faf 128// CacheDB::CloseFile - Close the file /*{{{*/
ce928105
MV
129void CacheDB::CloseFile()
130{
215b0faf
MV
131 if(Fd != NULL)
132 {
133 delete Fd;
134 Fd = NULL;
135 }
ce928105 136}
215b0faf
MV
137 /*}}}*/
138// CacheDB::OpenDebFile - Open a debfile /*{{{*/
ce928105
MV
139bool CacheDB::OpenDebFile()
140{
acea28d0
MV
141 // always close existing file first
142 CloseDebFile();
215b0faf
MV
143
144 // first open the fd, then pass it to the debDebFile
145 if(OpenFile() == false)
146 return false;
ce928105
MV
147 DebFile = new debDebFile(*Fd);
148 if (_error->PendingError() == true)
149 return false;
150 return true;
151}
215b0faf
MV
152 /*}}}*/
153// CacheDB::CloseDebFile - Close a debfile again /*{{{*/
ce928105
MV
154void CacheDB::CloseDebFile()
155{
215b0faf 156 CloseFile();
ce928105 157
215b0faf
MV
158 if(DebFile != NULL)
159 {
160 delete DebFile;
161 DebFile = NULL;
162 }
163}
164 /*}}}*/
cde41ae8
MV
165// CacheDB::GetFileStat - Get stats from the file /*{{{*/
166// ---------------------------------------------------------------------
167/* This gets the size from the database if it's there. If we need
168 * to look at the file, also get the mtime from the file. */
ff574e76 169bool CacheDB::GetFileStat(bool const &doStat)
cde41ae8 170{
215b0faf
MV
171 if ((CurStat.Flags & FlSize) == FlSize && doStat == false)
172 return true;
173
174 /* Get it from the file. */
175 if (OpenFile() == false)
176 return false;
177
178 // Stat the file
179 struct stat St;
180 if (fstat(Fd->Fd(),&St) != 0)
181 {
182 CloseFile();
183 return _error->Errno("fstat",
184 _("Failed to stat %s"),FileName.c_str());
185 }
186 CurStat.FileSize = St.st_size;
187 CurStat.mtime = htonl(St.st_mtime);
188 CurStat.Flags |= FlSize;
189
243b2a38
MV
190 return true;
191}
192 /*}}}*/
193// CacheDB::GetCurStatCompatOldFormat /*{{{*/
194// ---------------------------------------------------------------------
195/* Read the old (32bit FileSize) StateStore format from disk */
196bool CacheDB::GetCurStatCompatOldFormat()
197{
198 InitQueryStats();
199 Data.data = &CurStatOldFormat;
200 Data.flags = DB_DBT_USERMEM;
201 Data.ulen = sizeof(CurStatOldFormat);
202 if (Get() == false)
203 {
204 CurStat.Flags = 0;
205 } else {
206 CurStat.Flags = CurStatOldFormat.Flags;
207 CurStat.mtime = CurStatOldFormat.mtime;
208 CurStat.FileSize = CurStatOldFormat.FileSize;
209 memcpy(CurStat.MD5, CurStatOldFormat.MD5, sizeof(CurStat.MD5));
210 memcpy(CurStat.SHA1, CurStatOldFormat.SHA1, sizeof(CurStat.SHA1));
211 memcpy(CurStat.SHA256, CurStatOldFormat.SHA256, sizeof(CurStat.SHA256));
212 }
213 return true;
214}
215 /*}}}*/
216// CacheDB::GetCurStatCompatOldFormat /*{{{*/
217// ---------------------------------------------------------------------
218/* Read the new (64bit FileSize) StateStore format from disk */
219bool CacheDB::GetCurStatCompatNewFormat()
220{
221 InitQueryStats();
222 Data.data = &CurStat;
223 Data.flags = DB_DBT_USERMEM;
224 Data.ulen = sizeof(CurStat);
225 if (Get() == false)
226 {
227 CurStat.Flags = 0;
228 }
215b0faf 229 return true;
cde41ae8
MV
230}
231 /*}}}*/
232// CacheDB::GetCurStat - Set the CurStat variable. /*{{{*/
233// ---------------------------------------------------------------------
234/* Sets the CurStat variable. Either to 0 if no database is used
235 * or to the value in the database if one is used */
236bool CacheDB::GetCurStat()
b2e465d6 237{
b2e465d6
AL
238 memset(&CurStat,0,sizeof(CurStat));
239
215b0faf
MV
240 if (DBLoaded)
241 {
243b2a38 242 // do a first query to just get the size of the data on disk
37497bd5 243 InitQueryStats();
215b0faf 244 Data.data = &CurStat;
215b0faf 245 Data.flags = DB_DBT_USERMEM;
243b2a38
MV
246 Data.ulen = 0;
247 Get();
248
249 if (Data.size == 0)
b2e465d6 250 {
243b2a38
MV
251 // nothing needs to be done, we just have not data for this deb
252 }
253 // check if the record is written in the old format (32bit filesize)
254 else if(Data.size == sizeof(CurStatOldFormat))
255 {
256 GetCurStatCompatOldFormat();
257 }
258 else if(Data.size == sizeof(CurStat))
259 {
260 GetCurStatCompatNewFormat();
261 } else {
262 return _error->Error("Cache record size mismatch (%ul)", Data.size);
263 }
264
215b0faf
MV
265 CurStat.Flags = ntohl(CurStat.Flags);
266 CurStat.FileSize = ntohl(CurStat.FileSize);
b2e465d6 267 }
215b0faf 268 return true;
cde41ae8
MV
269}
270 /*}}}*/
271// CacheDB::GetFileInfo - Get all the info about the file /*{{{*/
272// ---------------------------------------------------------------------
a311fb96
DK
273bool CacheDB::GetFileInfo(std::string const &FileName, bool const &DoControl, bool const &DoContents,
274 bool const &GenContentsOnly, bool const DoSource, unsigned int const DoHashes,
9a961efc 275 bool const &checkMtime)
cde41ae8 276{
ce928105 277 this->FileName = FileName;
cde41ae8 278
ce928105 279 if (GetCurStat() == false)
ce928105 280 return false;
b2e465d6 281 OldStat = CurStat;
ff574e76 282
ce928105 283 if (GetFileStat(checkMtime) == false)
a311fb96 284 return false;
cde41ae8 285
215b0faf
MV
286 /* if mtime changed, update CurStat from disk */
287 if (checkMtime == true && OldStat.mtime != CurStat.mtime)
288 CurStat.Flags = FlSize;
289
290 Stats.Bytes += CurStat.FileSize;
a311fb96 291 ++Stats.Packages;
215b0faf
MV
292
293 if ((DoControl && LoadControl() == false)
a311fb96
DK
294 || (DoContents && LoadContents(GenContentsOnly) == false)
295 || (DoSource && LoadSource() == false)
296 || (DoHashes != 0 && GetHashes(false, DoHashes) == false)
297 )
215b0faf 298 {
a311fb96 299 return false;
215b0faf 300 }
a311fb96
DK
301
302 return true;
ce928105
MV
303}
304 /*}}}*/
a311fb96 305bool CacheDB::LoadSource() /*{{{*/
ce928105
MV
306{
307 // Try to read the control information out of the DB.
308 if ((CurStat.Flags & FlSource) == FlSource)
309 {
310 // Lookup the control information
37497bd5 311 InitQuerySource();
ce928105 312 if (Get() == true && Dsc.TakeDsc(Data.data, Data.size) == true)
53ba4e2c 313 {
ce928105 314 return true;
53ba4e2c 315 }
ce928105
MV
316 CurStat.Flags &= ~FlSource;
317 }
215b0faf 318 if (OpenFile() == false)
ce928105 319 return false;
ce928105 320
ce928105
MV
321 Stats.Misses++;
322 if (Dsc.Read(FileName) == false)
323 return false;
cde41ae8 324
ce928105
MV
325 if (Dsc.Data == 0)
326 return _error->Error(_("Failed to read .dsc"));
327
328 // Write back the control information
37497bd5 329 InitQuerySource();
ce928105
MV
330 if (Put(Dsc.Data, Dsc.Length) == true)
331 CurStat.Flags |= FlSource;
cde41ae8 332
b2e465d6
AL
333 return true;
334}
a311fb96 335 /*}}}*/
b2e465d6
AL
336// CacheDB::LoadControl - Load Control information /*{{{*/
337// ---------------------------------------------------------------------
338/* */
339bool CacheDB::LoadControl()
340{
341 // Try to read the control information out of the DB.
342 if ((CurStat.Flags & FlControl) == FlControl)
343 {
344 // Lookup the control information
37497bd5 345 InitQueryControl();
b2e465d6
AL
346 if (Get() == true && Control.TakeControl(Data.data,Data.size) == true)
347 return true;
348 CurStat.Flags &= ~FlControl;
349 }
350
215b0faf 351 if(OpenDebFile() == false)
cde41ae8 352 return false;
b2e465d6
AL
353
354 Stats.Misses++;
355 if (Control.Read(*DebFile) == false)
356 return false;
357
358 if (Control.Control == 0)
dc738e7a 359 return _error->Error(_("Archive has no control record"));
b2e465d6
AL
360
361 // Write back the control information
37497bd5 362 InitQueryControl();
b2e465d6
AL
363 if (Put(Control.Control,Control.Length) == true)
364 CurStat.Flags |= FlControl;
365 return true;
366}
367 /*}}}*/
368// CacheDB::LoadContents - Load the File Listing /*{{{*/
369// ---------------------------------------------------------------------
370/* */
9209ec47 371bool CacheDB::LoadContents(bool const &GenOnly)
b2e465d6
AL
372{
373 // Try to read the control information out of the DB.
374 if ((CurStat.Flags & FlContents) == FlContents)
375 {
376 if (GenOnly == true)
377 return true;
378
379 // Lookup the contents information
37497bd5 380 InitQueryContent();
b2e465d6
AL
381 if (Get() == true)
382 {
383 if (Contents.TakeContents(Data.data,Data.size) == true)
384 return true;
385 }
386
387 CurStat.Flags &= ~FlContents;
388 }
389
215b0faf 390 if(OpenDebFile() == false)
cde41ae8 391 return false;
b2e465d6 392
0a3b93fc 393 Stats.Misses++;
b2e465d6
AL
394 if (Contents.Read(*DebFile) == false)
395 return false;
396
397 // Write back the control information
37497bd5 398 InitQueryContent();
b2e465d6
AL
399 if (Put(Contents.Data,Contents.CurSize) == true)
400 CurStat.Flags |= FlContents;
401 return true;
402}
403 /*}}}*/
a311fb96 404// CacheDB::GetHashes - Get the hashs /*{{{*/
8f3ba4e8 405static std::string bytes2hex(uint8_t *bytes, size_t length) {
76ef756a 406 char buf[3];
0fffbc8c 407 std::string space;
76ef756a
MV
408
409 space.reserve(length*2 + 1);
410 for (size_t i = 0; i < length; i++) {
411 snprintf(buf, sizeof(buf), "%02x", bytes[i]);
412 space.append(buf);
413 }
414 return space;
cde41ae8
MV
415}
416
9209ec47 417static inline unsigned char xdig2num(char const &dig) {
cde41ae8
MV
418 if (isdigit(dig)) return dig - '0';
419 if ('a' <= dig && dig <= 'f') return dig - 'a' + 10;
420 if ('A' <= dig && dig <= 'F') return dig - 'A' + 10;
421 return 0;
422}
423
424static void hex2bytes(uint8_t *bytes, const char *hex, int length) {
425 while (length-- > 0) {
426 *bytes = 0;
427 if (isxdigit(hex[0]) && isxdigit(hex[1])) {
428 *bytes = xdig2num(hex[0]) * 16 + xdig2num(hex[1]);
429 hex += 2;
430 }
431 bytes++;
432 }
433}
a311fb96 434bool CacheDB::GetHashes(bool const GenOnly, unsigned int const DoHashes)
b2e465d6 435{
a311fb96
DK
436 unsigned int FlHashes = DoHashes & (Hashes::MD5SUM | Hashes::SHA1SUM | Hashes::SHA256SUM | Hashes::SHA512SUM);
437 HashesList.clear();
215b0faf 438
a311fb96 439 if (FlHashes != 0)
cde41ae8 440 {
a311fb96
DK
441 if (OpenFile() == false)
442 return false;
cde41ae8 443
a311fb96
DK
444 Hashes hashes;
445 if (Fd->Seek(0) == false || hashes.AddFD(*Fd, CurStat.FileSize, FlHashes) == false)
446 return false;
215b0faf 447
a311fb96
DK
448 HashStringList hl = hashes.GetHashStringList();
449 for (HashStringList::const_iterator hs = hl.begin(); hs != hl.end(); ++hs)
450 {
451 HashesList.push_back(*hs);
452 if (strcasecmp(hs->HashType().c_str(), "SHA512") == 0)
453 {
454 Stats.SHA512Bytes += CurStat.FileSize;
455 hex2bytes(CurStat.SHA512, hs->HashValue().data(), sizeof(CurStat.SHA512));
456 CurStat.Flags |= FlSHA512;
457 }
458 else if (strcasecmp(hs->HashType().c_str(), "SHA256") == 0)
459 {
460 Stats.SHA256Bytes += CurStat.FileSize;
461 hex2bytes(CurStat.SHA256, hs->HashValue().data(), sizeof(CurStat.SHA256));
462 CurStat.Flags |= FlSHA256;
463 }
464 else if (strcasecmp(hs->HashType().c_str(), "SHA1") == 0)
465 {
466 Stats.SHA1Bytes += CurStat.FileSize;
467 hex2bytes(CurStat.SHA1, hs->HashValue().data(), sizeof(CurStat.SHA1));
468 CurStat.Flags |= FlSHA1;
469 }
470 else if (strcasecmp(hs->HashType().c_str(), "MD5Sum") == 0)
471 {
472 Stats.MD5Bytes += CurStat.FileSize;
473 hex2bytes(CurStat.MD5, hs->HashValue().data(), sizeof(CurStat.MD5));
474 CurStat.Flags |= FlMD5;
475 }
476 else
477 return _error->Error("Got unknown unrequested hashtype %s", hs->HashType().c_str());
478 }
cde41ae8 479 }
a311fb96 480 if (GenOnly == true)
9a961efc 481 return true;
215b0faf 482
a311fb96
DK
483 return HashesList.push_back(HashString("MD5Sum", bytes2hex(CurStat.MD5, sizeof(CurStat.MD5)))) &&
484 HashesList.push_back(HashString("SHA1", bytes2hex(CurStat.SHA1, sizeof(CurStat.SHA1)))) &&
485 HashesList.push_back(HashString("SHA256", bytes2hex(CurStat.SHA256, sizeof(CurStat.SHA256)))) &&
486 HashesList.push_back(HashString("SHA512", bytes2hex(CurStat.SHA512, sizeof(CurStat.SHA512))));
9a961efc
MV
487}
488 /*}}}*/
b2e465d6
AL
489// CacheDB::Finish - Write back the cache structure /*{{{*/
490// ---------------------------------------------------------------------
491/* */
492bool CacheDB::Finish()
493{
494 // Optimize away some writes.
495 if (CurStat.Flags == OldStat.Flags &&
9bfe66dc 496 CurStat.mtime == OldStat.mtime)
b2e465d6 497 return true;
cf6bbca0 498
b2e465d6
AL
499 // Write the stat information
500 CurStat.Flags = htonl(CurStat.Flags);
cde41ae8 501 CurStat.FileSize = htonl(CurStat.FileSize);
37497bd5 502 InitQueryStats();
b2e465d6
AL
503 Put(&CurStat,sizeof(CurStat));
504 CurStat.Flags = ntohl(CurStat.Flags);
cde41ae8
MV
505 CurStat.FileSize = ntohl(CurStat.FileSize);
506
b2e465d6
AL
507 return true;
508}
509 /*}}}*/
510// CacheDB::Clean - Clean the Database /*{{{*/
511// ---------------------------------------------------------------------
512/* Tidy the database by removing files that no longer exist at all. */
513bool CacheDB::Clean()
514{
515 if (DBLoaded == false)
516 return true;
517
518 /* I'm not sure what VERSION_MINOR should be here.. 2.4.14 certainly
519 needs the lower one and 2.7.7 needs the upper.. */
b2e465d6 520 DBC *Cursor;
c9569a1e 521 if ((errno = Dbp->cursor(Dbp, NULL, &Cursor, 0)) != 0)
dc738e7a 522 return _error->Error(_("Unable to get a cursor"));
b2e465d6
AL
523
524 DBT Key;
525 DBT Data;
526 memset(&Key,0,sizeof(Key));
527 memset(&Data,0,sizeof(Data));
528 while ((errno = Cursor->c_get(Cursor,&Key,&Data,DB_NEXT)) == 0)
529 {
592b401a
MV
530 const char *Colon = (char*)memrchr(Key.data, ':', Key.size);
531 if (Colon)
b2e465d6 532 {
592b401a
MV
533 if (stringcmp(Colon + 1, (char *)Key.data+Key.size,"st") == 0 ||
534 stringcmp(Colon + 1, (char *)Key.data+Key.size,"cl") == 0 ||
53ba4e2c 535 stringcmp(Colon + 1, (char *)Key.data+Key.size,"cs") == 0 ||
592b401a 536 stringcmp(Colon + 1, (char *)Key.data+Key.size,"cn") == 0)
b2e465d6 537 {
53ba4e2c
MV
538 std::string FileName = std::string((const char *)Key.data,Colon);
539 if (FileExists(FileName) == true) {
540 continue;
541 }
b2e465d6
AL
542 }
543 }
b2e465d6
AL
544 Cursor->c_del(Cursor,0);
545 }
53ba4e2c
MV
546 int res = Dbp->compact(Dbp, NULL, NULL, NULL, NULL, DB_FREE_SPACE, NULL);
547 if (res < 0)
548 _error->Warning("compact failed with result %i", res);
549
550 if(_config->FindB("Debug::APT::FTPArchive::Clean", false) == true)
551 Dbp->stat_print(Dbp, 0);
552
b2e465d6
AL
553
554 return true;
555}
556 /*}}}*/