]>
git.saurik.com Git - apt.git/blob - apt-inst/extract.cc
1 // -*- mode: cpp; mode: fold -*-
3 // $Id: extract.cc,v 1.7 2004/01/07 20:39:37 mdz 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>
64 static const char *TempExt
= "dpkg-tmp";
65 //static const char *NewExt = "dpkg-new";
67 // Extract::pkgExtract - Constructor /*{{{*/
68 // ---------------------------------------------------------------------
70 pkgExtract::pkgExtract(pkgFLCache
&FLCache
,pkgCache::VerIterator Ver
) :
71 FLCache(FLCache
), Ver(Ver
)
73 FLPkg
= FLCache
.GetPkg(Ver
.ParentPkg().Name(),true);
74 if (FLPkg
.end() == true)
79 // Extract::DoItem - Handle a single item from the stream /*{{{*/
80 // ---------------------------------------------------------------------
81 /* This performs the setup for the extraction.. */
82 bool pkgExtract::DoItem(Item
&Itm
,int &Fd
)
84 char Temp
[sizeof(FileName
)];
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
++);
93 for (; *I
!= 0 && End
< FileName
+ sizeof(FileName
); I
++, End
++)
95 if (End
+ 20 >= FileName
+ sizeof(FileName
))
96 return _error
->Error(_("The path %s is too long"),Itm
.Name
);
97 for (; End
> FileName
&& End
[-1] == '/'; End
--);
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
;
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
)
113 // Nope, create an entry
114 if (RealNde
.end() == true)
116 RealNde
= FLCache
.GetNode(Itm
.Name
,End
,FLPkg
.Offset(),true,false);
117 if (RealNde
.end() == true)
119 RealNde
->Flags
|= pkgFLCache::Node::NewFile
;
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
)
127 return _error
->Error(_("Unpacking %s more than once"),Itm
.Name
);
129 if (Nde
.end() == true)
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)
136 if (Itm
.Type
== Item::Directory
)
137 return _error
->Error(_("The directory %s is diverted"),Itm
.Name
);
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 */
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
)
147 return _error
->Error(_("The package is trying to write to the "
148 "diversion target %s/%s"),Nde
.DirN(),Nde
.File());
150 // See if it is us and we are following it in the right direction
151 if (Div
->OwnerPkg
!= FLPkg
.Offset() && Div
.DivertFrom() == Nde
)
153 Nde
= Div
.DivertTo();
154 End
= FileName
+ snprintf(FileName
,sizeof(FileName
)-20,"%s/%s",
155 Nde
.DirN(),Nde
.File());
157 return _error
->Error(_("The diversion path is too long"));
161 // Deal with symlinks and conf files
162 if ((RealNde
->Flags
& pkgFLCache::Node::NewConfFile
) ==
163 pkgFLCache::Node::NewConfFile
)
165 string Res
= flNoLink(Itm
.Name
);
166 if (Res
.length() > sizeof(FileName
))
167 return _error
->Error(_("The path %s is too long"),Res
.c_str());
169 clog
<< "Followed conf file from " << FileName
<< " to " << Res
<< endl
;
170 Itm
.Name
= strcpy(FileName
,Res
.c_str());
173 /* Get information about the existing file, and attempt to restore
174 a backup if it does not exist */
175 struct stat LExisting
;
177 if (lstat(Itm
.Name
,&LExisting
) != 0)
181 return _error
->Errno("stat",_("Failed to stat %s"),Itm
.Name
);
183 // See if we can recover the backup file
184 if (Nde
.end() == false)
186 snprintf(Temp
,sizeof(Temp
),"%s.%s",Itm
.Name
,TempExt
);
187 if (rename(Temp
,Itm
.Name
) != 0 && errno
!= ENOENT
)
188 return _error
->Errno("rename",_("Failed to rename %s to %s"),
190 if (stat(Itm
.Name
,&LExisting
) != 0)
193 return _error
->Errno("stat",_("Failed to stat %s"),Itm
.Name
);
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
))
207 if (stat(Itm
.Name
,&Existing
) != 0)
210 return _error
->Errno("stat",_("Failed to stat %s"),Itm
.Name
);
211 Existing
= LExisting
;
215 // We pretend a non-existing file looks like it is a normal file
217 Existing
.st_mode
= S_IFREG
;
219 /* Okay, at this point 'Existing' is the stat information for the
220 real non-link file */
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
))
228 /* Non-Directory being replaced by non-directory. We check for over
230 if (Nde
.end() == false)
232 if (HandleOverwrites(Nde
) == false)
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
240 if (S_ISDIR(Existing
.st_mode
) != 0)
242 if (CheckDirReplace(Itm
.Name
) == false)
243 return _error
->Error(_("The directory %s is being replaced by a non-directory"),Itm
.Name
);
247 clog
<< "Extract " << string(Itm
.Name
,End
) << endl
;
249 return _error->Error(_("Done"));*/
254 // Extract::Finished - Sequence finished, erase the temp files /*{{{*/
255 // ---------------------------------------------------------------------
257 bool pkgExtract::Finished()
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. */
267 bool pkgExtract::Aborted()
270 clog
<< "Aborted, backing out" << endl
;
272 pkgFLCache::NodeIterator Files
= FLPkg
.Files();
273 map_ptrloc
*Last
= &FLPkg
->Files
;
275 /* Loop over all files, restore those that have been unpacked from their
277 while (Files
.end() == false)
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)
283 return _error
->Error(_("Failed to locate node in its hash bucket"));
285 if (snprintf(FileName
,sizeof(FileName
)-20,"%s/%s",
286 Nde
.DirN(),Nde
.File()) <= 0)
287 return _error
->Error(_("The path is too long"));
289 // Deal with diversions
290 if ((Nde
->Flags
& pkgFLCache::Node::Diversion
) != 0)
292 pkgFLCache::DiverIterator Div
= Nde
.Diversion();
294 // See if it is us and we are following it in the right direction
295 if (Div
->OwnerPkg
!= FLPkg
.Offset() && Div
.DivertFrom() == Nde
)
297 Nde
= Div
.DivertTo();
298 if (snprintf(FileName
,sizeof(FileName
)-20,"%s/%s",
299 Nde
.DirN(),Nde
.File()) <= 0)
300 return _error
->Error(_("The diversion path is too long"));
304 // Deal with overwrites+replaces
305 for (; Nde
.end() == false && Files
->File
== Nde
->File
; Nde
++)
307 if ((Nde
->Flags
& pkgFLCache::Node::Replaced
) ==
308 pkgFLCache::Node::Replaced
)
311 clog
<< "De-replaced " << FileName
<< " from " << Nde
.RealPackage()->Name
<< endl
;
312 Nde
->Flags
&= ~pkgFLCache::Node::Replaced
;
316 // Undo the change in the filesystem
318 clog
<< "Backing out " << FileName
;
321 if ((Files
->Flags
& pkgFLCache::Node::NewFile
) ==
322 pkgFLCache::Node::NewFile
)
325 clog
<< " [new node]" << endl
;
326 pkgFLCache::Node
*Tmp
= Files
;
328 *Last
= Tmp
->NextPkg
;
331 FLCache
.DropNode(Tmp
- FLCache
.NodeP
);
338 Last
= &Files
->NextPkg
;
346 // Extract::Fail - Extraction of a file Failed /*{{{*/
347 // ---------------------------------------------------------------------
349 bool pkgExtract::Fail(Item
&Itm
,int Fd
)
351 return pkgDirStream::Fail(Itm
,Fd
);
354 // Extract::FinishedFile - Finished a file /*{{{*/
355 // ---------------------------------------------------------------------
357 bool pkgExtract::FinishedFile(Item
&Itm
,int Fd
)
359 return pkgDirStream::FinishedFile(Itm
,Fd
);
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.
370 XX If a new package installs and another package replaces files in this
371 package what should we do? */
372 bool pkgExtract::HandleOverwrites(pkgFLCache::NodeIterator Nde
,
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
++)
381 if ((Nde
->Flags
& pkgFLCache::Node::Diversion
) != 0)
383 /* Store the diversion owner if this is the forward direction
385 if (DiverCheck
== true)
386 DiverOwner
= Nde
.Diversion()->OwnerPkg
;
390 pkgFLCache::PkgIterator
FPkg(FLCache
,Nde
.RealPackage());
391 if (FPkg
.end() == true || FPkg
== FLPkg
)
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
)
398 FirstOwner
= FPkg
.Name();
400 // Now see if this package matches one in a replace depends
401 pkgCache::DepIterator Dep
= Ver
.DependsList();
403 for (; Dep
.end() == false; Dep
++)
405 if (Dep
->Type
!= pkgCache::Dep::Replaces
)
408 // Does the replaces apply to this package?
409 if (strcmp(Dep
.TargetPkg().Name(),FPkg
.Name()) != 0)
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)
417 _error
->Warning(_("Overwrite package match with no version for %s"),Pkg
.Name());
422 if (debVS
.CheckDep(Pkg
.CurrentVer().VerStr(),Dep
->CompareOp
,Dep
.TargetVer()) == true)
425 clog
<< "Replaced file " << Nde
.DirN() << '/' << Nde
.File() << " from " << Pkg
.Name() << endl
;
426 Nde
->Flags
|= pkgFLCache::Node::Replaced
;
434 return _error
->Error(_("File %s/%s overwrites the one in the package %s"),
435 Nde
.DirN(),Nde
.File(),FPkg
.Name());
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)
442 pkgFLCache::DiverIterator Div
= TmpNde
.Diversion();
443 if (Div
.DivertTo() == TmpNde
)
444 return HandleOverwrites(Div
.DivertFrom(),true);
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 */
456 bool pkgExtract::CheckDirReplace(string Dir
,unsigned int Depth
)
462 if (Dir
[Dir
.size() - 1] != '/')
465 DIR *D
= opendir(Dir
.c_str());
467 return _error
->Errno("opendir",_("Unable to read %s"),Dir
.c_str());
470 for (struct dirent
*Dent
= readdir(D
); Dent
!= 0; Dent
= readdir(D
))
473 if (strcmp(Dent
->d_name
,".") == 0 ||
474 strcmp(Dent
->d_name
,"..") == 0)
478 File
= Dir
+ Dent
->d_name
;
479 pkgFLCache::NodeIterator Nde
= FLCache
.GetNode(File
.c_str(),
480 File
.c_str() + File
.length(),0,false,false);
482 // The file is not owned by this package
483 if (Nde
.end() != false || Nde
.RealPackage() != FLPkg
)
489 // See if it is a directory
491 if (lstat(File
.c_str(),&St
) != 0)
494 return _error
->Errno("lstat",_("Unable to stat %s"),File
.c_str());
497 // Recurse down directories
498 if (S_ISDIR(St
.st_mode
) != 0)
500 if (CheckDirReplace(File
,Depth
+ 1) == false)