]> git.saurik.com Git - apt.git/blob - apt-inst/extract.cc
cd8edb27aaab462b52b6061529ab23edcb5584d0
[apt.git] / apt-inst / extract.cc
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 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 #include <apt-pkg/extract.h>
48 #include <apt-pkg/error.h>
49 #include <apt-pkg/debversion.h>
50
51 #include <sys/stat.h>
52 #include <stdio.h>
53 #include <unistd.h>
54 #include <errno.h>
55 #include <dirent.h>
56 #include <iostream>
57 #include <apti18n.h>
58 /*}}}*/
59 using namespace std;
60
61 static const char *TempExt = "dpkg-tmp";
62 //static const char *NewExt = "dpkg-new";
63
64 // Extract::pkgExtract - Constructor /*{{{*/
65 // ---------------------------------------------------------------------
66 /* */
67 pkgExtract::pkgExtract(pkgFLCache &FLCache,pkgCache::VerIterator Ver) :
68 FLCache(FLCache), Ver(Ver)
69 {
70 FLPkg = FLCache.GetPkg(Ver.ParentPkg().Name(),true);
71 if (FLPkg.end() == true)
72 return;
73 Debug = true;
74 }
75 /*}}}*/
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)
80 {
81 char Temp[sizeof(FileName)];
82
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++);
89 *FileName = '/';
90 for (; *I != 0 && End < FileName + sizeof(FileName); I++, End++)
91 *End = *I;
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--);
95 *End = 0;
96 Itm.Name = FileName;
97
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;
103
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)
108 break;
109
110 // Nope, create an entry
111 if (RealNde.end() == true)
112 {
113 RealNde = FLCache.GetNode(Itm.Name,End,FLPkg.Offset(),true,false);
114 if (RealNde.end() == true)
115 return false;
116 RealNde->Flags |= pkgFLCache::Node::NewFile;
117 }
118
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);
125
126 if (Nde.end() == true)
127 Nde = RealNde;
128
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)
132 {
133 if (Itm.Type == Item::Directory)
134 return _error->Error(_("The directory %s is diverted"),Itm.Name);
135
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 */
139
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());
146
147 // See if it is us and we are following it in the right direction
148 if (Div->OwnerPkg != FLPkg.Offset() && Div.DivertFrom() == Nde)
149 {
150 Nde = Div.DivertTo();
151 End = FileName + snprintf(FileName,sizeof(FileName)-20,"%s/%s",
152 Nde.DirN(),Nde.File());
153 if (End <= FileName)
154 return _error->Error(_("The diversion path is too long"));
155 }
156 }
157
158 // Deal with symlinks and conf files
159 if ((RealNde->Flags & pkgFLCache::Node::NewConfFile) ==
160 pkgFLCache::Node::NewConfFile)
161 {
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());
165 if (Debug == true)
166 clog << "Followed conf file from " << FileName << " to " << Res << endl;
167 Itm.Name = strcpy(FileName,Res.c_str());
168 }
169
170 /* Get information about the existing file, and attempt to restore
171 a backup if it does not exist */
172 struct stat LExisting;
173 bool EValid = false;
174 if (lstat(Itm.Name,&LExisting) != 0)
175 {
176 // This is bad news.
177 if (errno != ENOENT)
178 return _error->Errno("stat",_("Failed to stat %s"),Itm.Name);
179
180 // See if we can recover the backup file
181 if (Nde.end() == false)
182 {
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"),
186 Temp,Itm.Name);
187 if (stat(Itm.Name,&LExisting) != 0)
188 {
189 if (errno != ENOENT)
190 return _error->Errno("stat",_("Failed to stat %s"),Itm.Name);
191 }
192 else
193 EValid = true;
194 }
195 }
196 else
197 EValid = true;
198
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))
203 {
204 if (stat(Itm.Name,&Existing) != 0)
205 {
206 if (errno != ENOENT)
207 return _error->Errno("stat",_("Failed to stat %s"),Itm.Name);
208 Existing = LExisting;
209 }
210 }
211
212 // We pretend a non-existing file looks like it is a normal file
213 if (EValid == false)
214 Existing.st_mode = S_IFREG;
215
216 /* Okay, at this point 'Existing' is the stat information for the
217 real non-link file */
218
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))
223 return true;
224
225 /* Non-Directory being replaced by non-directory. We check for over
226 writes here. */
227 if (Nde.end() == false)
228 {
229 if (HandleOverwrites(Nde) == false)
230 return false;
231 }
232
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
236 erased] */
237 if (S_ISDIR(Existing.st_mode) != 0)
238 {
239 if (CheckDirReplace(Itm.Name) == false)
240 return _error->Error(_("The directory %s is being replaced by a non-directory"),Itm.Name);
241 }
242
243 if (Debug == true)
244 clog << "Extract " << string(Itm.Name,End) << endl;
245 /* if (Count != 0)
246 return _error->Error(_("Done"));*/
247
248 return true;
249 }
250 /*}}}*/
251 // Extract::Finished - Sequence finished, erase the temp files /*{{{*/
252 // ---------------------------------------------------------------------
253 /* */
254 bool pkgExtract::Finished()
255 {
256 return true;
257 }
258 /*}}}*/
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()
265 {
266 if (Debug == true)
267 clog << "Aborted, backing out" << endl;
268
269 pkgFLCache::NodeIterator Files = FLPkg.Files();
270 map_ptrloc *Last = &FLPkg->Files;
271
272 /* Loop over all files, restore those that have been unpacked from their
273 dpkg-tmp entires */
274 while (Files.end() == false)
275 {
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"));
281
282 if (snprintf(FileName,sizeof(FileName)-20,"%s/%s",
283 Nde.DirN(),Nde.File()) <= 0)
284 return _error->Error(_("The path is too long"));
285
286 // Deal with diversions
287 if ((Nde->Flags & pkgFLCache::Node::Diversion) != 0)
288 {
289 pkgFLCache::DiverIterator Div = Nde.Diversion();
290
291 // See if it is us and we are following it in the right direction
292 if (Div->OwnerPkg != FLPkg.Offset() && Div.DivertFrom() == Nde)
293 {
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"));
298 }
299 }
300
301 // Deal with overwrites+replaces
302 for (; Nde.end() == false && Files->File == Nde->File; Nde++)
303 {
304 if ((Nde->Flags & pkgFLCache::Node::Replaced) ==
305 pkgFLCache::Node::Replaced)
306 {
307 if (Debug == true)
308 clog << "De-replaced " << FileName << " from " << Nde.RealPackage()->Name << endl;
309 Nde->Flags &= ~pkgFLCache::Node::Replaced;
310 }
311 }
312
313 // Undo the change in the filesystem
314 if (Debug == true)
315 clog << "Backing out " << FileName;
316
317 // Remove a new node
318 if ((Files->Flags & pkgFLCache::Node::NewFile) ==
319 pkgFLCache::Node::NewFile)
320 {
321 if (Debug == true)
322 clog << " [new node]" << endl;
323 pkgFLCache::Node *Tmp = Files;
324 Files++;
325 *Last = Tmp->NextPkg;
326 Tmp->NextPkg = 0;
327
328 FLCache.DropNode(Tmp - FLCache.NodeP);
329 }
330 else
331 {
332 if (Debug == true)
333 clog << endl;
334
335 Last = &Files->NextPkg;
336 Files++;
337 }
338 }
339
340 return true;
341 }
342 /*}}}*/
343 // Extract::Fail - Extraction of a file Failed /*{{{*/
344 // ---------------------------------------------------------------------
345 /* */
346 bool pkgExtract::Fail(Item &Itm,int Fd)
347 {
348 return pkgDirStream::Fail(Itm,Fd);
349 }
350 /*}}}*/
351 // Extract::FinishedFile - Finished a file /*{{{*/
352 // ---------------------------------------------------------------------
353 /* */
354 bool pkgExtract::FinishedFile(Item &Itm,int Fd)
355 {
356 return pkgDirStream::FinishedFile(Itm,Fd);
357 }
358 /*}}}*/
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.
366
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,
370 bool DiverCheck)
371 {
372 pkgFLCache::NodeIterator TmpNde = Nde;
373 unsigned long DiverOwner = 0;
374 unsigned long FileGroup = Nde->File;
375 for (; Nde.end() == false && FileGroup == Nde->File; Nde++)
376 {
377 if ((Nde->Flags & pkgFLCache::Node::Diversion) != 0)
378 {
379 /* Store the diversion owner if this is the forward direction
380 of the diversion */
381 if (DiverCheck == true)
382 DiverOwner = Nde.Diversion()->OwnerPkg;
383 continue;
384 }
385
386 pkgFLCache::PkgIterator FPkg(FLCache,Nde.RealPackage());
387 if (FPkg.end() == true || FPkg == FLPkg)
388 continue;
389
390 /* This tests trips when we are checking a diversion to see
391 if something has already been diverted by this diversion */
392 if (FPkg.Offset() == DiverOwner)
393 continue;
394
395 // Now see if this package matches one in a replace depends
396 pkgCache::DepIterator Dep = Ver.DependsList();
397 bool Ok = false;
398 for (; Dep.end() == false; Dep++)
399 {
400 if (Dep->Type != pkgCache::Dep::Replaces)
401 continue;
402
403 // Does the replaces apply to this package?
404 if (strcmp(Dep.TargetPkg().Name(),FPkg.Name()) != 0)
405 continue;
406
407 /* Check the version for match. I do not think CurrentVer can be
408 0 if we are here.. */
409 pkgCache::PkgIterator Pkg = Dep.TargetPkg();
410 if (Pkg->CurrentVer == 0)
411 {
412 _error->Warning(_("Overwrite package match with no version for %s"),Pkg.Name());
413 continue;
414 }
415
416 // Replaces is met
417 if (debVS.CheckDep(Pkg.CurrentVer().VerStr(),Dep->CompareOp,Dep.TargetVer()) == true)
418 {
419 if (Debug == true)
420 clog << "Replaced file " << Nde.DirN() << '/' << Nde.File() << " from " << Pkg.Name() << endl;
421 Nde->Flags |= pkgFLCache::Node::Replaced;
422 Ok = true;
423 break;
424 }
425 }
426
427 // Negative Hit
428 if (Ok == false)
429 return _error->Error(_("File %s/%s overwrites the one in the package %s"),
430 Nde.DirN(),Nde.File(),FPkg.Name());
431 }
432
433 /* If this is a diversion we might have to recurse to process
434 the other side of it */
435 if ((TmpNde->Flags & pkgFLCache::Node::Diversion) != 0)
436 {
437 pkgFLCache::DiverIterator Div = TmpNde.Diversion();
438 if (Div.DivertTo() == TmpNde)
439 return HandleOverwrites(Div.DivertFrom(),true);
440 }
441
442 return true;
443 }
444 /*}}}*/
445 // Extract::CheckDirReplace - See if this directory can be erased /*{{{*/
446 // ---------------------------------------------------------------------
447 /* If this directory is owned by a single package and that package is
448 replacing it with something non-directoryish then dpkg allows this.
449 We increase the requirement to be that the directory is non-empty after
450 the package is removed */
451 bool pkgExtract::CheckDirReplace(string Dir,unsigned int Depth)
452 {
453 // Looping?
454 if (Depth > 40)
455 return false;
456
457 if (Dir[Dir.size() - 1] != '/')
458 Dir += '/';
459
460 DIR *D = opendir(Dir.c_str());
461 if (D == 0)
462 return _error->Errno("opendir",_("Unable to read %s"),Dir.c_str());
463
464 string File;
465 for (struct dirent *Dent = readdir(D); Dent != 0; Dent = readdir(D))
466 {
467 // Skip some files
468 if (strcmp(Dent->d_name,".") == 0 ||
469 strcmp(Dent->d_name,"..") == 0)
470 continue;
471
472 // Look up the node
473 File = Dir + Dent->d_name;
474 pkgFLCache::NodeIterator Nde = FLCache.GetNode(File.c_str(),
475 File.c_str() + File.length(),0,false,false);
476
477 // The file is not owned by this package
478 if (Nde.end() != false || Nde.RealPackage() != FLPkg)
479 {
480 closedir(D);
481 return false;
482 }
483
484 // See if it is a directory
485 struct stat St;
486 if (lstat(File.c_str(),&St) != 0)
487 {
488 closedir(D);
489 return _error->Errno("lstat",_("Unable to stat %s"),File.c_str());
490 }
491
492 // Recurse down directories
493 if (S_ISDIR(St.st_mode) != 0)
494 {
495 if (CheckDirReplace(File,Depth + 1) == false)
496 {
497 closedir(D);
498 return false;
499 }
500 }
501 }
502
503 // No conflicts
504 closedir(D);
505 return true;
506 }
507 /*}}}*/