1
// -*- mode: cpp; mode: fold -*-
3
// $Id: extract.cc,v 1.2 2001/02/20 07:03:16 jgg Exp $
4
/* ######################################################################
6
Archive Extraction Directory Stream
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.
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
24
- If the existing link/file is not a directory then it is replaced
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
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
44
##################################################################### */
46
// Include Files /*{{{*/
48
#pragma implementation "apt-pkg/extract.h"
50
#include <apt-pkg/extract.h>
51
#include <apt-pkg/error.h>
52
#include <apt-pkg/debversion.h>
61
static const char *TempExt = "dpkg-tmp";
62
//static const char *NewExt = "dpkg-new";
64
// Extract::pkgExtract - Constructor /*{{{*/
65
// ---------------------------------------------------------------------
67
pkgExtract::pkgExtract(pkgFLCache &FLCache,pkgCache::VerIterator Ver) :
68
FLCache(FLCache), Ver(Ver)
70
FLPkg = FLCache.GetPkg(Ver.ParentPkg().Name(),true);
71
if (FLPkg.end() == true)
76
// Extract::DoItem - Handle a single item from the stream /*{{{*/
77
// ---------------------------------------------------------------------
78
/* This performs the setup for the extraction.. */
79
bool pkgExtract::DoItem(Item &Itm,int &Fd)
81
char Temp[sizeof(FileName)];
83
/* Strip any leading/trailing /s from the filename, then copy it to the
84
temp buffer and re-apply the leading / We use a class variable
85
to store the new filename for use by the three extraction funcs */
86
char *End = FileName+1;
87
const char *I = Itm.Name;
88
for (; *I != 0 && *I == '/'; I++);
90
for (; *I != 0 && End < FileName + sizeof(FileName); I++, End++)
92
if (End + 20 >= FileName + sizeof(FileName))
93
return _error->Error("The path %s is too long",Itm.Name);
94
for (; End > FileName && End[-1] == '/'; End--);
98
/* Lookup the file. Nde is the file [group] we are going to write to and
99
RealNde is the actual node we are manipulating. Due to diversions
100
they may be entirely different. */
101
pkgFLCache::NodeIterator Nde = FLCache.GetNode(Itm.Name,End,0,false,false);
102
pkgFLCache::NodeIterator RealNde = Nde;
104
// See if the file is already in the file listing
105
unsigned long FileGroup = RealNde->File;
106
for (; RealNde.end() == false && FileGroup == RealNde->File; RealNde++)
107
if (RealNde.RealPackage() == FLPkg)
110
// Nope, create an entry
111
if (RealNde.end() == true)
113
RealNde = FLCache.GetNode(Itm.Name,End,FLPkg.Offset(),true,false);
114
if (RealNde.end() == true)
116
RealNde->Flags |= pkgFLCache::Node::NewFile;
119
/* Check if this entry already was unpacked. The only time this should
120
ever happen is if someone has hacked tar to support capabilities, in
121
which case this needs to be modified anyhow.. */
122
if ((RealNde->Flags & pkgFLCache::Node::Unpacked) ==
123
pkgFLCache::Node::Unpacked)
124
return _error->Error("Unpacking %s more than once",Itm.Name);
126
if (Nde.end() == true)
129
/* Consider a diverted file - We are not permitted to divert directories,
130
but everything else is fair game (including conf files!) */
131
if ((Nde->Flags & pkgFLCache::Node::Diversion) != 0)
133
if (Itm.Type == Item::Directory)
134
return _error->Error("The directory %s is diverted",Itm.Name);
136
/* A package overwriting a diversion target is just the same as
137
overwriting a normally owned file and is checked for below in
138
the overwrites mechanism */
140
/* If this package is trying to overwrite the target of a diversion,
141
that is never, ever permitted */
142
pkgFLCache::DiverIterator Div = Nde.Diversion();
143
if (Div.DivertTo() == Nde)
144
return _error->Error("The package is trying to write to the "
145
"diversion target %s/%s",Nde.DirN(),Nde.File());
147
// See if it is us and we are following it in the right direction
148
if (Div->OwnerPkg != FLPkg.Offset() && Div.DivertFrom() == Nde)
150
Nde = Div.DivertTo();
151
End = FileName + snprintf(FileName,sizeof(FileName)-20,"%s/%s",
152
Nde.DirN(),Nde.File());
154
return _error->Error("The diversion path is too long");
158
// Deal with symlinks and conf files
159
if ((RealNde->Flags & pkgFLCache::Node::NewConfFile) ==
160
pkgFLCache::Node::NewConfFile)
162
string Res = flNoLink(Itm.Name);
163
if (Res.length() > sizeof(FileName))
164
return _error->Error("The path %s is too long",Res.c_str());
166
clog << "Followed conf file from " << FileName << " to " << Res << endl;
167
Itm.Name = strcpy(FileName,Res.c_str());
170
/* Get information about the existing file, and attempt to restore
171
a backup if it does not exist */
172
struct stat LExisting;
174
if (lstat(Itm.Name,&LExisting) != 0)
178
return _error->Errno("stat","Failed to stat %s",Itm.Name);
180
// See if we can recover the backup file
181
if (Nde.end() == false)
183
snprintf(Temp,sizeof(Temp),"%s.%s",Itm.Name,TempExt);
184
if (rename(Temp,Itm.Name) != 0 && errno != ENOENT)
185
return _error->Errno("rename","Failed to rename %s to %s",
187
if (stat(Itm.Name,&LExisting) != 0)
190
return _error->Errno("stat","Failed to stat %s",Itm.Name);
199
/* If the file is a link we need to stat its destination, get the
200
existing file modes */
201
struct stat Existing = LExisting;
202
if (EValid == true && S_ISLNK(Existing.st_mode))
204
if (stat(Itm.Name,&Existing) != 0)
207
return _error->Errno("stat","Failed to stat %s",Itm.Name);
208
Existing = LExisting;
212
// We pretend a non-existing file looks like it is a normal file
214
Existing.st_mode = S_IFREG;
216
/* Okay, at this point 'Existing' is the stat information for the
217
real non-link file */
219
/* The only way this can be a no-op is if a directory is being
220
replaced by a directory or by a link */
221
if (S_ISDIR(Existing.st_mode) != 0 &&
222
(Itm.Type == Item::Directory || Itm.Type == Item::SymbolicLink))
225
/* Non-Directory being replaced by non-directory. We check for over
227
if (Nde.end() == false)
229
if (HandleOverwrites(Nde) == false)
233
/* Directory being replaced by a non-directory - this needs to see if
234
the package is the owner and then see if the directory would be
235
empty after the package is removed [ie no user files will be
237
if (S_ISDIR(Existing.st_mode) != 0)
239
if (CheckDirReplace(Itm.Name) == false)
240
return _error->Error("The directory %s is being replaced by a non-directory",Itm.Name);
244
clog << "Extract " << string(Itm.Name,End) << endl;
246
return _error->Error("Done");*/
251
// Extract::Finished - Sequence finished, erase the temp files /*{{{*/
252
// ---------------------------------------------------------------------
254
bool pkgExtract::Finished()
259
// Extract::Aborted - Sequence aborted, undo all our unpacking /*{{{*/
260
// ---------------------------------------------------------------------
261
/* This undoes everything that was done by all calls to the DoItem method
262
and restores the File Listing cache to its original form. It bases its
263
actions on the flags value for each node in the cache. */
264
bool pkgExtract::Aborted()
267
clog << "Aborted, backing out" << endl;
269
pkgFLCache::NodeIterator Files = FLPkg.Files();
270
map_ptrloc *Last = &FLPkg->Files;
272
/* Loop over all files, restore those that have been unpacked from their
274
while (Files.end() == false)
276
// Locate the hash bucket for the node and locate its group head
277
pkgFLCache::NodeIterator Nde(FLCache,FLCache.HashNode(Files));
278
for (; Nde.end() == false && Files->File != Nde->File; Nde++);
279
if (Nde.end() == true)
280
return _error->Error("Failed to locate node in its hash bucket");
282
if (snprintf(FileName,sizeof(FileName)-20,"%s/%s",
283
Nde.DirN(),Nde.File()) <= 0)
284
return _error->Error("The path is too long");
286
// Deal with diversions
287
if ((Nde->Flags & pkgFLCache::Node::Diversion) != 0)
289
pkgFLCache::DiverIterator Div = Nde.Diversion();
291
// See if it is us and we are following it in the right direction
292
if (Div->OwnerPkg != FLPkg.Offset() && Div.DivertFrom() == Nde)
294
Nde = Div.DivertTo();
295
if (snprintf(FileName,sizeof(FileName)-20,"%s/%s",
296
Nde.DirN(),Nde.File()) <= 0)
297
return _error->Error("The diversion path is too long");
301
// Deal with overwrites+replaces
302
for (; Nde.end() == false && Files->File == Nde->File; Nde++)
304
if ((Nde->Flags & pkgFLCache::Node::Replaced) ==
305
pkgFLCache::Node::Replaced)
308
clog << "De-replaced " << FileName << " from " << Nde.RealPackage()->Name << endl;
309
Nde->Flags &= ~pkgFLCache::Node::Replaced;
313
// Undo the change in the filesystem
315
clog << "Backing out " << FileName;
318
if ((Files->Flags & pkgFLCache::Node::NewFile) ==
319
pkgFLCache::Node::NewFile)
322
clog << " [new node]" << endl;
323
pkgFLCache::Node *Tmp = Files;
325
*Last = Tmp->NextPkg;
328
FLCache.DropNode(Tmp - FLCache.NodeP);
335
Last = &Files->NextPkg;
343
// Extract::Fail - Extraction of a file Failed /*{{{*/
344
// ---------------------------------------------------------------------
346
bool pkgExtract::Fail(Item &Itm,int Fd)
348
return pkgDirStream::Fail(Itm,Fd);
351
// Extract::FinishedFile - Finished a file /*{{{*/
352
// ---------------------------------------------------------------------
354
bool pkgExtract::FinishedFile(Item &Itm,int Fd)
356
return pkgDirStream::FinishedFile(Itm,Fd);
359
// Extract::HandleOverwrites - See if a replaces covers this overwrite /*{{{*/
360
// ---------------------------------------------------------------------
361
/* Check if the file is in a package that is being replaced by this
362
package or if the file is being overwritten. Note that if the file
363
is really a directory but it has been erased from the filesystem
364
this will fail with an overwrite message. This is a limitation of the
365
dpkg file information format.
367
XX If a new package installs and another package replaces files in this
368
package what should we do? */
369
bool pkgExtract::HandleOverwrites(pkgFLCache::NodeIterator Nde,
372
pkgFLCache::NodeIterator TmpNde = Nde;
373
unsigned long DiverOwner = 0;
374
unsigned long FileGroup = Nde->File;
375
const char *FirstOwner = 0;
376
for (; Nde.end() == false && FileGroup == Nde->File; Nde++)
378
if ((Nde->Flags & pkgFLCache::Node::Diversion) != 0)
380
/* Store the diversion owner if this is the forward direction
382
if (DiverCheck == true)
383
DiverOwner = Nde.Diversion()->OwnerPkg;
387
pkgFLCache::PkgIterator FPkg(FLCache,Nde.RealPackage());
388
if (FPkg.end() == true || FPkg == FLPkg)
391
/* This tests trips when we are checking a diversion to see
392
if something has already been diverted by this diversion */
393
if (FPkg.Offset() == DiverOwner)
395
FirstOwner = FPkg.Name();
397
// Now see if this package matches one in a replace depends
398
pkgCache::DepIterator Dep = Ver.DependsList();
400
for (; Dep.end() == false; Dep++)
402
if (Dep->Type != pkgCache::Dep::Replaces)
405
// Does the replaces apply to this package?
406
if (strcmp(Dep.TargetPkg().Name(),FPkg.Name()) != 0)
409
/* Check the version for match. I do not think CurrentVer can be
410
0 if we are here.. */
411
pkgCache::PkgIterator Pkg = Dep.TargetPkg();
412
if (Pkg->CurrentVer == 0)
414
_error->Warning("Overwrite package match with no version for %s",Pkg.Name());
419
if (debVS.CheckDep(Pkg.CurrentVer().VerStr(),Dep->CompareOp,Dep.TargetVer()) == true)
422
clog << "Replaced file " << Nde.DirN() << '/' << Nde.File() << " from " << Pkg.Name() << endl;
423
Nde->Flags |= pkgFLCache::Node::Replaced;
431
return _error->Error("File %s/%s overwrites the one in the package %s",
432
Nde.DirN(),Nde.File(),FPkg.Name());
435
/* If this is a diversion we might have to recurse to process
436
the other side of it */
437
if ((TmpNde->Flags & pkgFLCache::Node::Diversion) != 0)
439
pkgFLCache::DiverIterator Div = TmpNde.Diversion();
440
if (Div.DivertTo() == TmpNde)
441
return HandleOverwrites(Div.DivertFrom(),true);
447
// Extract::CheckDirReplace - See if this directory can be erased /*{{{*/
448
// ---------------------------------------------------------------------
449
/* If this directory is owned by a single package and that package is
450
replacing it with something non-directoryish then dpkg allows this.
451
We increase the requirement to be that the directory is non-empty after
452
the package is removed */
453
bool pkgExtract::CheckDirReplace(string Dir,unsigned int Depth)
459
if (Dir[Dir.size() - 1] != '/')
462
DIR *D = opendir(Dir.c_str());
464
return _error->Errno("opendir","Unable to read %s",Dir.c_str());
467
for (struct dirent *Dent = readdir(D); Dent != 0; Dent = readdir(D))
470
if (strcmp(Dent->d_name,".") == 0 ||
471
strcmp(Dent->d_name,"..") == 0)
475
File = Dir + Dent->d_name;
476
pkgFLCache::NodeIterator Nde = FLCache.GetNode(File.begin(),
477
File.end(),0,false,false);
479
// The file is not owned by this package
480
if (Nde.end() != false || Nde.RealPackage() != FLPkg)
486
// See if it is a directory
488
if (lstat(File.c_str(),&St) != 0)
491
return _error->Errno("lstat","Unable to stat %s",File.c_str());
494
// Recurse down directories
495
if (S_ISDIR(St.st_mode) != 0)
497
if (CheckDirReplace(File,Depth + 1) == false)