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