]> git.saurik.com Git - apt.git/blob - apt-inst/extract.cc
* debian/rules:
[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<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
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 for (; Nde.end() == false && FileGroup == Nde->File; Nde++)
379 {
380 if ((Nde->Flags & pkgFLCache::Node::Diversion) != 0)
381 {
382 /* Store the diversion owner if this is the forward direction
383 of the diversion */
384 if (DiverCheck == true)
385 DiverOwner = Nde.Diversion()->OwnerPkg;
386 continue;
387 }
388
389 pkgFLCache::PkgIterator FPkg(FLCache,Nde.RealPackage());
390 if (FPkg.end() == true || FPkg == FLPkg)
391 continue;
392
393 /* This tests trips when we are checking a diversion to see
394 if something has already been diverted by this diversion */
395 if (FPkg.Offset() == DiverOwner)
396 continue;
397
398 // Now see if this package matches one in a replace depends
399 pkgCache::DepIterator Dep = Ver.DependsList();
400 bool Ok = false;
401 for (; Dep.end() == false; Dep++)
402 {
403 if (Dep->Type != pkgCache::Dep::Replaces)
404 continue;
405
406 // Does the replaces apply to this package?
407 if (strcmp(Dep.TargetPkg().Name(),FPkg.Name()) != 0)
408 continue;
409
410 /* Check the version for match. I do not think CurrentVer can be
411 0 if we are here.. */
412 pkgCache::PkgIterator Pkg = Dep.TargetPkg();
413 if (Pkg->CurrentVer == 0)
414 {
415 _error->Warning(_("Overwrite package match with no version for %s"),Pkg.Name());
416 continue;
417 }
418
419 // Replaces is met
420 if (debVS.CheckDep(Pkg.CurrentVer().VerStr(),Dep->CompareOp,Dep.TargetVer()) == true)
421 {
422 if (Debug == true)
423 clog << "Replaced file " << Nde.DirN() << '/' << Nde.File() << " from " << Pkg.Name() << endl;
424 Nde->Flags |= pkgFLCache::Node::Replaced;
425 Ok = true;
426 break;
427 }
428 }
429
430 // Negative Hit
431 if (Ok == false)
432 return _error->Error(_("File %s/%s overwrites the one in the package %s"),
433 Nde.DirN(),Nde.File(),FPkg.Name());
434 }
435
436 /* If this is a diversion we might have to recurse to process
437 the other side of it */
438 if ((TmpNde->Flags & pkgFLCache::Node::Diversion) != 0)
439 {
440 pkgFLCache::DiverIterator Div = TmpNde.Diversion();
441 if (Div.DivertTo() == TmpNde)
442 return HandleOverwrites(Div.DivertFrom(),true);
443 }
444
445 return true;
446 }
447 /*}}}*/
448 // Extract::CheckDirReplace - See if this directory can be erased /*{{{*/
449 // ---------------------------------------------------------------------
450 /* If this directory is owned by a single package and that package is
451 replacing it with something non-directoryish then dpkg allows this.
452 We increase the requirement to be that the directory is non-empty after
453 the package is removed */
454 bool pkgExtract::CheckDirReplace(string Dir,unsigned int Depth)
455 {
456 // Looping?
457 if (Depth > 40)
458 return false;
459
460 if (Dir[Dir.size() - 1] != '/')
461 Dir += '/';
462
463 DIR *D = opendir(Dir.c_str());
464 if (D == 0)
465 return _error->Errno("opendir",_("Unable to read %s"),Dir.c_str());
466
467 string File;
468 for (struct dirent *Dent = readdir(D); Dent != 0; Dent = readdir(D))
469 {
470 // Skip some files
471 if (strcmp(Dent->d_name,".") == 0 ||
472 strcmp(Dent->d_name,"..") == 0)
473 continue;
474
475 // Look up the node
476 File = Dir + Dent->d_name;
477 pkgFLCache::NodeIterator Nde = FLCache.GetNode(File.c_str(),
478 File.c_str() + File.length(),0,false,false);
479
480 // The file is not owned by this package
481 if (Nde.end() != false || Nde.RealPackage() != FLPkg)
482 {
483 closedir(D);
484 return false;
485 }
486
487 // See if it is a directory
488 struct stat St;
489 if (lstat(File.c_str(),&St) != 0)
490 {
491 closedir(D);
492 return _error->Errno("lstat",_("Unable to stat %s"),File.c_str());
493 }
494
495 // Recurse down directories
496 if (S_ISDIR(St.st_mode) != 0)
497 {
498 if (CheckDirReplace(File,Depth + 1) == false)
499 {
500 closedir(D);
501 return false;
502 }
503 }
504 }
505
506 // No conflicts
507 closedir(D);
508 return true;
509 }
510 /*}}}*/