]> git.saurik.com Git - apt-legacy.git/blob - apt-inst/extract.cc
Initial commit.
[apt-legacy.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 #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>
59 #include <iostream>
60 #include <apti18n.h>
61 /*}}}*/
62 using namespace std;
63
64 static const char *TempExt = "dpkg-tmp";
65 //static const char *NewExt = "dpkg-new";
66
67 // Extract::pkgExtract - Constructor /*{{{*/
68 // ---------------------------------------------------------------------
69 /* */
70 pkgExtract::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.. */
82 bool 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))
96 return _error->Error(_("The path %s is too long"),Itm.Name);
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)
127 return _error->Error(_("Unpacking %s more than once"),Itm.Name);
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)
137 return _error->Error(_("The directory %s is diverted"),Itm.Name);
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)
147 return _error->Error(_("The package is trying to write to the "
148 "diversion target %s/%s"),Nde.DirN(),Nde.File());
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)
157 return _error->Error(_("The diversion path is too long"));
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))
167 return _error->Error(_("The path %s is too long"),Res.c_str());
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)
181 return _error->Errno("stat",_("Failed to stat %s"),Itm.Name);
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)
188 return _error->Errno("rename",_("Failed to rename %s to %s"),
189 Temp,Itm.Name);
190 if (stat(Itm.Name,&LExisting) != 0)
191 {
192 if (errno != ENOENT)
193 return _error->Errno("stat",_("Failed to stat %s"),Itm.Name);
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)
210 return _error->Errno("stat",_("Failed to stat %s"),Itm.Name);
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)
243 return _error->Error(_("The directory %s is being replaced by a non-directory"),Itm.Name);
244 }
245
246 if (Debug == true)
247 clog << "Extract " << string(Itm.Name,End) << endl;
248 /* if (Count != 0)
249 return _error->Error(_("Done"));*/
250
251 return true;
252 }
253 /*}}}*/
254 // Extract::Finished - Sequence finished, erase the temp files /*{{{*/
255 // ---------------------------------------------------------------------
256 /* */
257 bool 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. */
267 bool 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)
283 return _error->Error(_("Failed to locate node in its hash bucket"));
284
285 if (snprintf(FileName,sizeof(FileName)-20,"%s/%s",
286 Nde.DirN(),Nde.File()) <= 0)
287 return _error->Error(_("The path is too long"));
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)
300 return _error->Error(_("The diversion path is too long"));
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 /* */
349 bool pkgExtract::Fail(Item &Itm,int Fd)
350 {
351 return pkgDirStream::Fail(Itm,Fd);
352 }
353 /*}}}*/
354 // Extract::FinishedFile - Finished a file /*{{{*/
355 // ---------------------------------------------------------------------
356 /* */
357 bool 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? */
372 bool 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 {
417 _error->Warning(_("Overwrite package match with no version for %s"),Pkg.Name());
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)
434 return _error->Error(_("File %s/%s overwrites the one in the package %s"),
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 */
456 bool 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)
467 return _error->Errno("opendir",_("Unable to read %s"),Dir.c_str());
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;
479 pkgFLCache::NodeIterator Nde = FLCache.GetNode(File.c_str(),
480 File.c_str() + File.length(),0,false,false);
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);
494 return _error->Errno("lstat",_("Unable to stat %s"),File.c_str());
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 /*}}}*/