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