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