]> git.saurik.com Git - apt.git/blame - apt-inst/deb/dpkgdb.cc
merge with debian/experimental
[apt.git] / apt-inst / deb / dpkgdb.cc
CommitLineData
b2e465d6
AL
1// -*- mode: cpp; mode: fold -*-
2// Description /*{{{*/
7db98ffc 3// $Id: dpkgdb.cc,v 1.7.2.1 2004/01/16 18:58:50 mdz Exp $
b2e465d6
AL
4/* ######################################################################
5
6 DPKGv1 Database Implemenation
7
8 This class provides parsers and other implementations for the DPKGv1
9 database. It reads the diversion file, the list files and the status
10 file to build both the list of currently installed files and the
11 currently installed package list.
12
13 ##################################################################### */
14 /*}}}*/
15// Include Files /*{{{*/
ea542140
DK
16#include<config.h>
17
b2e465d6
AL
18#include <apt-pkg/dpkgdb.h>
19#include <apt-pkg/configuration.h>
20#include <apt-pkg/error.h>
21#include <apt-pkg/progress.h>
22#include <apt-pkg/tagfile.h>
23#include <apt-pkg/strutl.h>
24
25#include <stdio.h>
26#include <errno.h>
27#include <sys/stat.h>
28#include <sys/mman.h>
29#include <fcntl.h>
30#include <unistd.h>
18255546 31#include <ctype.h>
90f057fd 32#include <iostream>
d77559ac 33#include <apti18n.h>
b2e465d6 34 /*}}}*/
584e4558 35using namespace std;
b2e465d6
AL
36
37// EraseDir - Erase A Directory /*{{{*/
38// ---------------------------------------------------------------------
39/* This is necessary to create a new empty sub directory. The caller should
40 invoke mkdir after this with the proper permissions and check for
41 error. Maybe stick this in fileutils */
42static bool EraseDir(const char *Dir)
43{
44 // First we try a simple RM
45 if (rmdir(Dir) == 0 ||
46 errno == ENOENT)
47 return true;
48
49 // A file? Easy enough..
50 if (errno == ENOTDIR)
51 {
52 if (unlink(Dir) != 0)
05eb7df0 53 return _error->Errno("unlink",_("Failed to remove %s"),Dir);
b2e465d6
AL
54 return true;
55 }
56
57 // Should not happen
58 if (errno != ENOTEMPTY)
05eb7df0 59 return _error->Errno("rmdir",_("Failed to remove %s"),Dir);
b2e465d6
AL
60
61 // Purge it using rm
3826564e 62 pid_t Pid = ExecFork();
b2e465d6
AL
63
64 // Spawn the subprocess
65 if (Pid == 0)
66 {
67 execlp(_config->Find("Dir::Bin::rm","/bin/rm").c_str(),
42ab8223 68 "rm","-rf","--",Dir,(char *)NULL);
b2e465d6
AL
69 _exit(100);
70 }
71 return ExecWait(Pid,_config->Find("dir::bin::rm","/bin/rm").c_str());
72}
73 /*}}}*/
74// DpkgDB::debDpkgDB - Constructor /*{{{*/
75// ---------------------------------------------------------------------
76/* */
77debDpkgDB::debDpkgDB() : CacheMap(0), FileMap(0)
78{
79 AdminDir = flNotFile(_config->Find("Dir::State::status"));
80 DiverInode = 0;
81 DiverTime = 0;
82}
83 /*}}}*/
84// DpkgDB::~debDpkgDB - Destructor /*{{{*/
85// ---------------------------------------------------------------------
86/* */
87debDpkgDB::~debDpkgDB()
88{
89 delete Cache;
90 Cache = 0;
91 delete CacheMap;
92 CacheMap = 0;
93
94 delete FList;
95 FList = 0;
96 delete FileMap;
97 FileMap = 0;
98}
99 /*}}}*/
100// DpkgDB::InitMetaTmp - Get the temp dir for meta information /*{{{*/
101// ---------------------------------------------------------------------
102/* This creats+empties the meta temporary directory /var/lib/dpkg/tmp.ci
103 Only one package at a time can be using the returned meta directory. */
104bool debDpkgDB::InitMetaTmp(string &Dir)
105{
106 string Tmp = AdminDir + "tmp.ci/";
107 if (EraseDir(Tmp.c_str()) == false)
05eb7df0 108 return _error->Error(_("Unable to create %s"),Tmp.c_str());
b2e465d6 109 if (mkdir(Tmp.c_str(),0755) != 0)
05eb7df0 110 return _error->Errno("mkdir",_("Unable to create %s"),Tmp.c_str());
b2e465d6
AL
111
112 // Verify it is on the same filesystem as the main info directory
113 dev_t Dev;
114 struct stat St;
115 if (stat((AdminDir + "info").c_str(),&St) != 0)
05eb7df0 116 return _error->Errno("stat",_("Failed to stat %sinfo"),AdminDir.c_str());
b2e465d6
AL
117 Dev = St.st_dev;
118 if (stat(Tmp.c_str(),&St) != 0)
05eb7df0 119 return _error->Errno("stat",_("Failed to stat %s"),Tmp.c_str());
b2e465d6 120 if (Dev != St.st_dev)
05eb7df0 121 return _error->Error(_("The info and temp directories need to be on the same filesystem"));
b2e465d6
AL
122
123 // Done
124 Dir = Tmp;
125 return true;
126}
127 /*}}}*/
128// DpkgDB::ReadyPkgCache - Prepare the cache with the current status /*{{{*/
129// ---------------------------------------------------------------------
130/* This reads in the status file into an empty cache. This really needs
131 to be somehow unified with the high level APT notion of the Database
132 directory, but there is no clear way on how to do that yet. */
133bool debDpkgDB::ReadyPkgCache(OpProgress &Progress)
134{
135 if (Cache != 0)
136 {
db0db9fe 137 Progress.OverallProgress(1,1,1,_("Reading package lists"));
b2e465d6
AL
138 return true;
139 }
140
141 if (CacheMap != 0)
142 {
143 delete CacheMap;
144 CacheMap = 0;
145 }
146
ea4b220b 147 if (pkgCacheGenerator::MakeOnlyStatusCache(&Progress,&CacheMap) == false)
b2e465d6
AL
148 return false;
149 Cache->DropProgress();
150
151 return true;
152}
153 /*}}}*/
154// DpkgDB::ReadFList - Read the File Listings in /*{{{*/
155// ---------------------------------------------------------------------
156/* This reads the file listing in from the state directory. This is a
157 performance critical routine, as it needs to parse about 50k lines of
158 text spread over a hundred or more files. For an initial cold start
159 most of the time is spent in reading file inodes and so on, not
160 actually parsing. */
161bool debDpkgDB::ReadFList(OpProgress &Progress)
162{
163 // Count the number of packages we need to read information for
164 unsigned long Total = 0;
165 pkgCache &Cache = this->Cache->GetCache();
166 for (pkgCache::PkgIterator I = Cache.PkgBegin(); I.end() == false; I++)
167 {
168 // Only not installed packages have no files.
169 if (I->CurrentState == pkgCache::State::NotInstalled)
170 continue;
171 Total++;
172 }
173
174 /* Switch into the admin dir, this prevents useless lookups for the
175 path components */
176 string Cwd = SafeGetCWD();
177 if (chdir((AdminDir + "info/").c_str()) != 0)
05eb7df0 178 return _error->Errno("chdir",_("Failed to change to the admin dir %sinfo"),AdminDir.c_str());
b2e465d6
AL
179
180 // Allocate a buffer. Anything larger than this buffer will be mmaped
181 unsigned long BufSize = 32*1024;
182 char *Buffer = new char[BufSize];
183
184 // Begin Loading them
185 unsigned long Count = 0;
186 char Name[300];
187 for (pkgCache::PkgIterator I = Cache.PkgBegin(); I.end() == false; I++)
188 {
189 /* Only not installed packages have no files. ConfFile packages have
190 file lists but we don't want to read them in */
191 if (I->CurrentState == pkgCache::State::NotInstalled ||
192 I->CurrentState == pkgCache::State::ConfigFiles)
193 continue;
194
195 // Fetch a package handle to associate with the file
196 pkgFLCache::PkgIterator FlPkg = FList->GetPkg(I.Name(),0,true);
197 if (FlPkg.end() == true)
198 {
db0db9fe 199 _error->Error(_("Internal error getting a package name"));
b2e465d6
AL
200 break;
201 }
202
db0db9fe 203 Progress.OverallProgress(Count,Total,1,_("Reading file listing"));
b2e465d6
AL
204
205 // Open the list file
206 snprintf(Name,sizeof(Name),"%s.list",I.Name());
207 int Fd = open(Name,O_RDONLY);
208
209 /* Okay this is very strange and bad.. Best thing is to bail and
210 instruct the user to look into it. */
211 struct stat Stat;
212 if (Fd == -1 || fstat(Fd,&Stat) != 0)
213 {
05eb7df0 214 _error->Errno("open",_("Failed to open the list file '%sinfo/%s'. If you "
b2e465d6 215 "cannot restore this file then make it empty "
05eb7df0 216 "and immediately re-install the same version of the package!"),
b2e465d6
AL
217 AdminDir.c_str(),Name);
218 break;
219 }
220
221 // Set File to be a memory buffer containing the whole file
222 char *File;
223 if ((unsigned)Stat.st_size < BufSize)
224 {
225 if (read(Fd,Buffer,Stat.st_size) != Stat.st_size)
226 {
05eb7df0 227 _error->Errno("read",_("Failed reading the list file %sinfo/%s"),
b2e465d6
AL
228 AdminDir.c_str(),Name);
229 close(Fd);
230 break;
231 }
232 File = Buffer;
233 }
234 else
235 {
236 // Use mmap
237 File = (char *)mmap(0,Stat.st_size,PROT_READ,MAP_PRIVATE,Fd,0);
238 if (File == (char *)(-1))
239 {
05eb7df0 240 _error->Errno("mmap",_("Failed reading the list file %sinfo/%s"),
b2e465d6
AL
241 AdminDir.c_str(),Name);
242 close(Fd);
243 break;
244 }
245 }
246
247 // Parse it
248 const char *Start = File;
249 const char *End = File;
250 const char *Finish = File + Stat.st_size;
251 for (; End < Finish; End++)
252 {
253 // Not an end of line
254 if (*End != '\n' && End + 1 < Finish)
255 continue;
256
257 // Skip blank lines
258 if (End - Start > 1)
259 {
260 pkgFLCache::NodeIterator Node = FList->GetNode(Start,End,
261 FlPkg.Offset(),true,false);
262 if (Node.end() == true)
263 {
db0db9fe 264 _error->Error(_("Internal error getting a node"));
b2e465d6
AL
265 break;
266 }
267 }
268
269 // Skip past the end of line
270 for (; *End == '\n' && End < Finish; End++);
271 Start = End;
272 }
273
274 close(Fd);
275 if ((unsigned)Stat.st_size >= BufSize)
276 munmap((caddr_t)File,Stat.st_size);
277
278 // Failed
279 if (End < Finish)
280 break;
281
282 Count++;
283 }
284
285 delete [] Buffer;
286 if (chdir(Cwd.c_str()) != 0)
287 chdir("/");
288
289 return !_error->PendingError();
290}
291 /*}}}*/
292// DpkgDB::ReadDiversions - Load the diversions file /*{{{*/
293// ---------------------------------------------------------------------
294/* Read the diversion file in from disk. This is usually invoked by
295 LoadChanges before performing an operation that uses the FLCache. */
296bool debDpkgDB::ReadDiversions()
297{
298 struct stat Stat;
299 if (stat((AdminDir + "diversions").c_str(),&Stat) != 0)
300 return true;
301
302 if (_error->PendingError() == true)
303 return false;
304
305 FILE *Fd = fopen((AdminDir + "diversions").c_str(),"r");
306 if (Fd == 0)
05eb7df0 307 return _error->Errno("fopen",_("Failed to open the diversions file %sdiversions"),AdminDir.c_str());
b2e465d6
AL
308
309 FList->BeginDiverLoad();
310 while (1)
311 {
312 char From[300];
313 char To[300];
314 char Package[100];
315
316 // Read the three lines in
317 if (fgets(From,sizeof(From),Fd) == 0)
318 break;
319 if (fgets(To,sizeof(To),Fd) == 0 ||
320 fgets(Package,sizeof(Package),Fd) == 0)
321 {
05eb7df0 322 _error->Error(_("The diversion file is corrupted"));
b2e465d6
AL
323 break;
324 }
325
326 // Strip the \ns
327 unsigned long Len = strlen(From);
328 if (Len < 2 || From[Len-1] != '\n')
05eb7df0 329 _error->Error(_("Invalid line in the diversion file: %s"),From);
b2e465d6
AL
330 else
331 From[Len-1] = 0;
332 Len = strlen(To);
333 if (Len < 2 || To[Len-1] != '\n')
05eb7df0 334 _error->Error(_("Invalid line in the diversion file: %s"),To);
b2e465d6
AL
335 else
336 To[Len-1] = 0;
337 Len = strlen(Package);
338 if (Len < 2 || Package[Len-1] != '\n')
05eb7df0 339 _error->Error(_("Invalid line in the diversion file: %s"),Package);
b2e465d6
AL
340 else
341 Package[Len-1] = 0;
342
343 // Make sure the lines were parsed OK
344 if (_error->PendingError() == true)
345 break;
346
347 // Fetch a package
348 if (strcmp(Package,":") == 0)
349 Package[0] = 0;
350 pkgFLCache::PkgIterator FlPkg = FList->GetPkg(Package,0,true);
351 if (FlPkg.end() == true)
352 {
db0db9fe 353 _error->Error(_("Internal error getting a package name"));
b2e465d6
AL
354 break;
355 }
356
357 // Install the diversion
358 if (FList->AddDiversion(FlPkg,From,To) == false)
359 {
db0db9fe 360 _error->Error(_("Internal error adding a diversion"));
b2e465d6
AL
361 break;
362 }
363 }
364 if (_error->PendingError() == false)
365 FList->FinishDiverLoad();
366
367 DiverInode = Stat.st_ino;
368 DiverTime = Stat.st_mtime;
369
370 fclose(Fd);
371 return !_error->PendingError();
372}
373 /*}}}*/
374// DpkgDB::ReadFileList - Read the file listing /*{{{*/
375// ---------------------------------------------------------------------
376/* Read in the file listing. The file listing is created from three
377 sources, *.list, Conffile sections and the Diversion table. */
378bool debDpkgDB::ReadyFileList(OpProgress &Progress)
379{
380 if (Cache == 0)
6804503b 381 return _error->Error(_("The pkg cache must be initialized first"));
b2e465d6
AL
382 if (FList != 0)
383 {
da9ed163 384 Progress.OverallProgress(1,1,1,_("Reading file listing"));
b2e465d6
AL
385 return true;
386 }
387
388 // Create the cache and read in the file listing
389 FileMap = new DynamicMMap(MMap::Public);
390 FList = new pkgFLCache(*FileMap);
391 if (_error->PendingError() == true ||
392 ReadFList(Progress) == false ||
393 ReadConfFiles() == false ||
394 ReadDiversions() == false)
395 {
396 delete FList;
397 delete FileMap;
398 FileMap = 0;
399 FList = 0;
400 return false;
401 }
402
403 cout << "Node: " << FList->HeaderP->NodeCount << ',' << FList->HeaderP->UniqNodes << endl;
404 cout << "Dir: " << FList->HeaderP->DirCount << endl;
405 cout << "Package: " << FList->HeaderP->PackageCount << endl;
406 cout << "HashSize: " << FList->HeaderP->HashSize << endl;
407 cout << "Size: " << FileMap->Size() << endl;
408 cout << endl;
409
410 return true;
411}
412 /*}}}*/
413// DpkgDB::ReadConfFiles - Read the conf file sections from the s-file /*{{{*/
414// ---------------------------------------------------------------------
415/* Reading the conf files is done by reparsing the status file. This is
416 actually rather fast so it is no big deal. */
417bool debDpkgDB::ReadConfFiles()
418{
419 FileFd File(_config->FindFile("Dir::State::status"),FileFd::ReadOnly);
420 pkgTagFile Tags(&File);
421 if (_error->PendingError() == true)
422 return false;
423
424 pkgTagSection Section;
425 while (1)
426 {
427 // Skip to the next section
428 unsigned long Offset = Tags.Offset();
429 if (Tags.Step(Section) == false)
430 break;
431
432 // Parse the line
433 const char *Start;
434 const char *Stop;
435 if (Section.Find("Conffiles",Start,Stop) == false)
436 continue;
437
438 const char *PkgStart;
439 const char *PkgEnd;
440 if (Section.Find("Package",PkgStart,PkgEnd) == false)
db0db9fe 441 return _error->Error(_("Failed to find a Package: header, offset %lu"),Offset);
b2e465d6
AL
442
443 // Snag a package record for it
444 pkgFLCache::PkgIterator FlPkg = FList->GetPkg(PkgStart,PkgEnd,true);
445 if (FlPkg.end() == true)
db0db9fe 446 return _error->Error(_("Internal error getting a package name"));
b2e465d6
AL
447
448 // Parse the conf file lines
449 while (1)
450 {
451 for (; isspace(*Start) != 0 && Start < Stop; Start++);
452 if (Start == Stop)
453 break;
454
455 // Split it into words
456 const char *End = Start;
457 for (; isspace(*End) == 0 && End < Stop; End++);
458 const char *StartMd5 = End;
459 for (; isspace(*StartMd5) != 0 && StartMd5 < Stop; StartMd5++);
460 const char *EndMd5 = StartMd5;
461 for (; isspace(*EndMd5) == 0 && EndMd5 < Stop; EndMd5++);
462 if (StartMd5 == EndMd5 || Start == End)
05eb7df0 463 return _error->Error(_("Bad ConfFile section in the status file. Offset %lu"),Offset);
b2e465d6
AL
464
465 // Insert a new entry
466 unsigned char MD5[16];
9deebc6a 467 if (Hex2Num(string(StartMd5,EndMd5-StartMd5),MD5,16) == false)
05eb7df0 468 return _error->Error(_("Error parsing MD5. Offset %lu"),Offset);
9deebc6a 469
b2e465d6
AL
470 if (FList->AddConfFile(Start,End,FlPkg,MD5) == false)
471 return false;
472 Start = EndMd5;
473 }
474 }
475
476 return true;
477}
478 /*}}}*/
479// DpkgDB::LoadChanges - Read in any changed state files /*{{{*/
480// ---------------------------------------------------------------------
481/* The only file in the dpkg system that can change while packages are
482 unpacking is the diversions file. */
483bool debDpkgDB::LoadChanges()
484{
485 struct stat Stat;
486 if (stat((AdminDir + "diversions").c_str(),&Stat) != 0)
487 return true;
488 if (DiverInode == Stat.st_ino && DiverTime == Stat.st_mtime)
489 return true;
490 return ReadDiversions();
491}
492 /*}}}*/