]> git.saurik.com Git - apt.git/blame - apt-inst/extract.cc
Change verbose logging output of apt-ftparchive to go t...
[apt.git] / apt-inst / extract.cc
CommitLineData
b2e465d6
AL
1// -*- mode: cpp; mode: fold -*-
2// Description /*{{{*/
90f057fd 3// $Id: extract.cc,v 1.4 2002/03/26 07:38:57 jgg Exp $
b2e465d6
AL
4/* ######################################################################
5
6 Archive Extraction Directory Stream
7
8 Extraction for each file is a bit of an involved process. Each object
9 undergoes an atomic backup, overwrite, erase sequence. First the
10 object is unpacked to '.dpkg.new' then the original is hardlinked to
11 '.dpkg.tmp' and finally the new object is renamed to overwrite the old
12 one. From an external perspective the file never ceased to exist.
13 After the archive has been sucessfully unpacked the .dpkg.tmp files
14 are erased. A failure causes all the .dpkg.tmp files to be restored.
15
16 Decisions about unpacking go like this:
17 - Store the original filename in the file listing
18 - Resolve any diversions that would effect this file, all checks
19 below apply to the diverted name, not the real one.
20 - Resolve any symlinked configuration files.
21 - If the existing file does not exist then .dpkg-tmp is checked for.
22 [Note, this is reduced to only check if a file was expected to be
23 there]
24 - If the existing link/file is not a directory then it is replaced
25 irregardless
26 - If the existing link/directory is being replaced by a directory then
27 absolutely nothing happens.
28 - If the existing link/directory is being replaced by a link then
29 absolutely nothing happens.
30 - If the existing link/directory is being replaced by a non-directory
31 then this will abort if the package is not the sole owner of the
32 directory. [Note, this is changed to not happen if the directory
33 non-empty - that is, it only includes files that are part of this
34 package - prevents removing user files accidentally.]
35 - If the non-directory exists in the listing database and it
36 does not belong to the current package then an overwrite condition
37 is invoked.
38
39 As we unpack we record the file list differences in the FL cache. If
40 we need to unroll the the FL cache knows which files have been unpacked
41 and can undo. When we need to erase then it knows which files have not
42 been unpacked.
43
44 ##################################################################### */
45 /*}}}*/
46// Include Files /*{{{*/
47#ifdef __GNUG__
48#pragma implementation "apt-pkg/extract.h"
49#endif
50#include <apt-pkg/extract.h>
51#include <apt-pkg/error.h>
52#include <apt-pkg/debversion.h>
53
54#include <sys/stat.h>
55#include <stdio.h>
56#include <unistd.h>
57#include <errno.h>
58#include <dirent.h>
90f057fd 59#include <iostream>
b2e465d6
AL
60 /*}}}*/
61
62static const char *TempExt = "dpkg-tmp";
63//static const char *NewExt = "dpkg-new";
64
65// Extract::pkgExtract - Constructor /*{{{*/
66// ---------------------------------------------------------------------
67/* */
68pkgExtract::pkgExtract(pkgFLCache &FLCache,pkgCache::VerIterator Ver) :
69 FLCache(FLCache), Ver(Ver)
70{
71 FLPkg = FLCache.GetPkg(Ver.ParentPkg().Name(),true);
72 if (FLPkg.end() == true)
73 return;
74 Debug = true;
75}
76 /*}}}*/
77// Extract::DoItem - Handle a single item from the stream /*{{{*/
78// ---------------------------------------------------------------------
79/* This performs the setup for the extraction.. */
80bool pkgExtract::DoItem(Item &Itm,int &Fd)
81{
82 char Temp[sizeof(FileName)];
83
84 /* Strip any leading/trailing /s from the filename, then copy it to the
85 temp buffer and re-apply the leading / We use a class variable
86 to store the new filename for use by the three extraction funcs */
87 char *End = FileName+1;
88 const char *I = Itm.Name;
89 for (; *I != 0 && *I == '/'; I++);
90 *FileName = '/';
91 for (; *I != 0 && End < FileName + sizeof(FileName); I++, End++)
92 *End = *I;
93 if (End + 20 >= FileName + sizeof(FileName))
94 return _error->Error("The path %s is too long",Itm.Name);
95 for (; End > FileName && End[-1] == '/'; End--);
96 *End = 0;
97 Itm.Name = FileName;
98
99 /* Lookup the file. Nde is the file [group] we are going to write to and
100 RealNde is the actual node we are manipulating. Due to diversions
101 they may be entirely different. */
102 pkgFLCache::NodeIterator Nde = FLCache.GetNode(Itm.Name,End,0,false,false);
103 pkgFLCache::NodeIterator RealNde = Nde;
104
105 // See if the file is already in the file listing
106 unsigned long FileGroup = RealNde->File;
107 for (; RealNde.end() == false && FileGroup == RealNde->File; RealNde++)
108 if (RealNde.RealPackage() == FLPkg)
109 break;
110
111 // Nope, create an entry
112 if (RealNde.end() == true)
113 {
114 RealNde = FLCache.GetNode(Itm.Name,End,FLPkg.Offset(),true,false);
115 if (RealNde.end() == true)
116 return false;
117 RealNde->Flags |= pkgFLCache::Node::NewFile;
118 }
119
120 /* Check if this entry already was unpacked. The only time this should
121 ever happen is if someone has hacked tar to support capabilities, in
122 which case this needs to be modified anyhow.. */
123 if ((RealNde->Flags & pkgFLCache::Node::Unpacked) ==
124 pkgFLCache::Node::Unpacked)
125 return _error->Error("Unpacking %s more than once",Itm.Name);
126
127 if (Nde.end() == true)
128 Nde = RealNde;
129
130 /* Consider a diverted file - We are not permitted to divert directories,
131 but everything else is fair game (including conf files!) */
132 if ((Nde->Flags & pkgFLCache::Node::Diversion) != 0)
133 {
134 if (Itm.Type == Item::Directory)
135 return _error->Error("The directory %s is diverted",Itm.Name);
136
137 /* A package overwriting a diversion target is just the same as
138 overwriting a normally owned file and is checked for below in
139 the overwrites mechanism */
140
141 /* If this package is trying to overwrite the target of a diversion,
142 that is never, ever permitted */
143 pkgFLCache::DiverIterator Div = Nde.Diversion();
144 if (Div.DivertTo() == Nde)
145 return _error->Error("The package is trying to write to the "
146 "diversion target %s/%s",Nde.DirN(),Nde.File());
147
148 // See if it is us and we are following it in the right direction
149 if (Div->OwnerPkg != FLPkg.Offset() && Div.DivertFrom() == Nde)
150 {
151 Nde = Div.DivertTo();
152 End = FileName + snprintf(FileName,sizeof(FileName)-20,"%s/%s",
153 Nde.DirN(),Nde.File());
154 if (End <= FileName)
155 return _error->Error("The diversion path is too long");
156 }
157 }
158
159 // Deal with symlinks and conf files
160 if ((RealNde->Flags & pkgFLCache::Node::NewConfFile) ==
161 pkgFLCache::Node::NewConfFile)
162 {
163 string Res = flNoLink(Itm.Name);
164 if (Res.length() > sizeof(FileName))
165 return _error->Error("The path %s is too long",Res.c_str());
166 if (Debug == true)
167 clog << "Followed conf file from " << FileName << " to " << Res << endl;
168 Itm.Name = strcpy(FileName,Res.c_str());
169 }
170
171 /* Get information about the existing file, and attempt to restore
172 a backup if it does not exist */
173 struct stat LExisting;
174 bool EValid = false;
175 if (lstat(Itm.Name,&LExisting) != 0)
176 {
177 // This is bad news.
178 if (errno != ENOENT)
179 return _error->Errno("stat","Failed to stat %s",Itm.Name);
180
181 // See if we can recover the backup file
182 if (Nde.end() == false)
183 {
184 snprintf(Temp,sizeof(Temp),"%s.%s",Itm.Name,TempExt);
185 if (rename(Temp,Itm.Name) != 0 && errno != ENOENT)
186 return _error->Errno("rename","Failed to rename %s to %s",
187 Temp,Itm.Name);
188 if (stat(Itm.Name,&LExisting) != 0)
189 {
190 if (errno != ENOENT)
191 return _error->Errno("stat","Failed to stat %s",Itm.Name);
192 }
193 else
194 EValid = true;
195 }
196 }
197 else
198 EValid = true;
199
200 /* If the file is a link we need to stat its destination, get the
201 existing file modes */
202 struct stat Existing = LExisting;
203 if (EValid == true && S_ISLNK(Existing.st_mode))
204 {
205 if (stat(Itm.Name,&Existing) != 0)
206 {
207 if (errno != ENOENT)
208 return _error->Errno("stat","Failed to stat %s",Itm.Name);
209 Existing = LExisting;
210 }
211 }
212
213 // We pretend a non-existing file looks like it is a normal file
214 if (EValid == false)
215 Existing.st_mode = S_IFREG;
216
217 /* Okay, at this point 'Existing' is the stat information for the
218 real non-link file */
219
220 /* The only way this can be a no-op is if a directory is being
221 replaced by a directory or by a link */
222 if (S_ISDIR(Existing.st_mode) != 0 &&
223 (Itm.Type == Item::Directory || Itm.Type == Item::SymbolicLink))
224 return true;
225
226 /* Non-Directory being replaced by non-directory. We check for over
227 writes here. */
228 if (Nde.end() == false)
229 {
230 if (HandleOverwrites(Nde) == false)
231 return false;
232 }
233
234 /* Directory being replaced by a non-directory - this needs to see if
235 the package is the owner and then see if the directory would be
236 empty after the package is removed [ie no user files will be
237 erased] */
238 if (S_ISDIR(Existing.st_mode) != 0)
239 {
240 if (CheckDirReplace(Itm.Name) == false)
241 return _error->Error("The directory %s is being replaced by a non-directory",Itm.Name);
242 }
243
244 if (Debug == true)
245 clog << "Extract " << string(Itm.Name,End) << endl;
246/* if (Count != 0)
247 return _error->Error("Done");*/
248
249 return true;
250}
251 /*}}}*/
252// Extract::Finished - Sequence finished, erase the temp files /*{{{*/
253// ---------------------------------------------------------------------
254/* */
255bool pkgExtract::Finished()
256{
257 return true;
258}
259 /*}}}*/
260// Extract::Aborted - Sequence aborted, undo all our unpacking /*{{{*/
261// ---------------------------------------------------------------------
262/* This undoes everything that was done by all calls to the DoItem method
263 and restores the File Listing cache to its original form. It bases its
264 actions on the flags value for each node in the cache. */
265bool pkgExtract::Aborted()
266{
267 if (Debug == true)
268 clog << "Aborted, backing out" << endl;
269
270 pkgFLCache::NodeIterator Files = FLPkg.Files();
271 map_ptrloc *Last = &FLPkg->Files;
272
273 /* Loop over all files, restore those that have been unpacked from their
274 dpkg-tmp entires */
275 while (Files.end() == false)
276 {
277 // Locate the hash bucket for the node and locate its group head
278 pkgFLCache::NodeIterator Nde(FLCache,FLCache.HashNode(Files));
279 for (; Nde.end() == false && Files->File != Nde->File; Nde++);
280 if (Nde.end() == true)
281 return _error->Error("Failed to locate node in its hash bucket");
282
283 if (snprintf(FileName,sizeof(FileName)-20,"%s/%s",
284 Nde.DirN(),Nde.File()) <= 0)
285 return _error->Error("The path is too long");
286
287 // Deal with diversions
288 if ((Nde->Flags & pkgFLCache::Node::Diversion) != 0)
289 {
290 pkgFLCache::DiverIterator Div = Nde.Diversion();
291
292 // See if it is us and we are following it in the right direction
293 if (Div->OwnerPkg != FLPkg.Offset() && Div.DivertFrom() == Nde)
294 {
295 Nde = Div.DivertTo();
296 if (snprintf(FileName,sizeof(FileName)-20,"%s/%s",
297 Nde.DirN(),Nde.File()) <= 0)
298 return _error->Error("The diversion path is too long");
299 }
300 }
301
302 // Deal with overwrites+replaces
303 for (; Nde.end() == false && Files->File == Nde->File; Nde++)
304 {
305 if ((Nde->Flags & pkgFLCache::Node::Replaced) ==
306 pkgFLCache::Node::Replaced)
307 {
308 if (Debug == true)
309 clog << "De-replaced " << FileName << " from " << Nde.RealPackage()->Name << endl;
310 Nde->Flags &= ~pkgFLCache::Node::Replaced;
311 }
312 }
313
314 // Undo the change in the filesystem
315 if (Debug == true)
316 clog << "Backing out " << FileName;
317
318 // Remove a new node
319 if ((Files->Flags & pkgFLCache::Node::NewFile) ==
320 pkgFLCache::Node::NewFile)
321 {
322 if (Debug == true)
323 clog << " [new node]" << endl;
324 pkgFLCache::Node *Tmp = Files;
325 Files++;
326 *Last = Tmp->NextPkg;
327 Tmp->NextPkg = 0;
328
329 FLCache.DropNode(Tmp - FLCache.NodeP);
330 }
331 else
332 {
333 if (Debug == true)
334 clog << endl;
335
336 Last = &Files->NextPkg;
337 Files++;
338 }
339 }
340
341 return true;
342}
343 /*}}}*/
344// Extract::Fail - Extraction of a file Failed /*{{{*/
345// ---------------------------------------------------------------------
346/* */
347bool pkgExtract::Fail(Item &Itm,int Fd)
348{
349 return pkgDirStream::Fail(Itm,Fd);
350}
351 /*}}}*/
352// Extract::FinishedFile - Finished a file /*{{{*/
353// ---------------------------------------------------------------------
354/* */
355bool pkgExtract::FinishedFile(Item &Itm,int Fd)
356{
357 return pkgDirStream::FinishedFile(Itm,Fd);
358}
359 /*}}}*/
360// Extract::HandleOverwrites - See if a replaces covers this overwrite /*{{{*/
361// ---------------------------------------------------------------------
362/* Check if the file is in a package that is being replaced by this
363 package or if the file is being overwritten. Note that if the file
364 is really a directory but it has been erased from the filesystem
365 this will fail with an overwrite message. This is a limitation of the
366 dpkg file information format.
367
368 XX If a new package installs and another package replaces files in this
369 package what should we do? */
370bool pkgExtract::HandleOverwrites(pkgFLCache::NodeIterator Nde,
371 bool DiverCheck)
372{
373 pkgFLCache::NodeIterator TmpNde = Nde;
374 unsigned long DiverOwner = 0;
375 unsigned long FileGroup = Nde->File;
376 const char *FirstOwner = 0;
377 for (; Nde.end() == false && FileGroup == Nde->File; Nde++)
378 {
379 if ((Nde->Flags & pkgFLCache::Node::Diversion) != 0)
380 {
381 /* Store the diversion owner if this is the forward direction
382 of the diversion */
383 if (DiverCheck == true)
384 DiverOwner = Nde.Diversion()->OwnerPkg;
385 continue;
386 }
387
388 pkgFLCache::PkgIterator FPkg(FLCache,Nde.RealPackage());
389 if (FPkg.end() == true || FPkg == FLPkg)
390 continue;
391
392 /* This tests trips when we are checking a diversion to see
393 if something has already been diverted by this diversion */
394 if (FPkg.Offset() == DiverOwner)
395 continue;
396 FirstOwner = FPkg.Name();
397
398 // Now see if this package matches one in a replace depends
399 pkgCache::DepIterator Dep = Ver.DependsList();
400 bool Ok = false;
401 for (; Dep.end() == false; Dep++)
402 {
403 if (Dep->Type != pkgCache::Dep::Replaces)
404 continue;
405
406 // Does the replaces apply to this package?
407 if (strcmp(Dep.TargetPkg().Name(),FPkg.Name()) != 0)
408 continue;
409
410 /* Check the version for match. I do not think CurrentVer can be
411 0 if we are here.. */
412 pkgCache::PkgIterator Pkg = Dep.TargetPkg();
413 if (Pkg->CurrentVer == 0)
414 {
415 _error->Warning("Overwrite package match with no version for %s",Pkg.Name());
416 continue;
417 }
418
419 // Replaces is met
420 if (debVS.CheckDep(Pkg.CurrentVer().VerStr(),Dep->CompareOp,Dep.TargetVer()) == true)
421 {
422 if (Debug == true)
423 clog << "Replaced file " << Nde.DirN() << '/' << Nde.File() << " from " << Pkg.Name() << endl;
424 Nde->Flags |= pkgFLCache::Node::Replaced;
425 Ok = true;
426 break;
427 }
428 }
429
430 // Negative Hit
431 if (Ok == false)
432 return _error->Error("File %s/%s overwrites the one in the package %s",
433 Nde.DirN(),Nde.File(),FPkg.Name());
434 }
435
436 /* If this is a diversion we might have to recurse to process
437 the other side of it */
438 if ((TmpNde->Flags & pkgFLCache::Node::Diversion) != 0)
439 {
440 pkgFLCache::DiverIterator Div = TmpNde.Diversion();
441 if (Div.DivertTo() == TmpNde)
442 return HandleOverwrites(Div.DivertFrom(),true);
443 }
444
445 return true;
446}
447 /*}}}*/
448// Extract::CheckDirReplace - See if this directory can be erased /*{{{*/
449// ---------------------------------------------------------------------
450/* If this directory is owned by a single package and that package is
451 replacing it with something non-directoryish then dpkg allows this.
452 We increase the requirement to be that the directory is non-empty after
453 the package is removed */
454bool pkgExtract::CheckDirReplace(string Dir,unsigned int Depth)
455{
456 // Looping?
457 if (Depth > 40)
458 return false;
459
460 if (Dir[Dir.size() - 1] != '/')
461 Dir += '/';
462
463 DIR *D = opendir(Dir.c_str());
464 if (D == 0)
465 return _error->Errno("opendir","Unable to read %s",Dir.c_str());
466
467 string File;
468 for (struct dirent *Dent = readdir(D); Dent != 0; Dent = readdir(D))
469 {
470 // Skip some files
471 if (strcmp(Dent->d_name,".") == 0 ||
472 strcmp(Dent->d_name,"..") == 0)
473 continue;
474
475 // Look up the node
476 File = Dir + Dent->d_name;
18255546
AL
477 pkgFLCache::NodeIterator Nde = FLCache.GetNode(File.c_str(),
478 File.c_str() + File.length(),0,false,false);
b2e465d6
AL
479
480 // The file is not owned by this package
481 if (Nde.end() != false || Nde.RealPackage() != FLPkg)
482 {
483 closedir(D);
484 return false;
485 }
486
487 // See if it is a directory
488 struct stat St;
489 if (lstat(File.c_str(),&St) != 0)
490 {
491 closedir(D);
492 return _error->Errno("lstat","Unable to stat %s",File.c_str());
493 }
494
495 // Recurse down directories
496 if (S_ISDIR(St.st_mode) != 0)
497 {
498 if (CheckDirReplace(File,Depth + 1) == false)
499 {
500 closedir(D);
501 return false;
502 }
503 }
504 }
505
506 // No conflicts
507 closedir(D);
508 return true;
509}
510 /*}}}*/