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