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