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