]> git.saurik.com Git - apt.git/blame - apt-inst/extract.cc
tests: don't do boundless string compares with data()
[apt.git] / apt-inst / extract.cc
CommitLineData
b2e465d6
AL
1// -*- mode: cpp; mode: fold -*-
2// Description /*{{{*/
b3d44315 3// $Id: extract.cc,v 1.6.2.1 2004/01/16 18:58:50 mdz Exp $
b2e465d6
AL
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.
1e3f4083 13 After the archive has been successfully unpacked the .dpkg.tmp files
b2e465d6
AL
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
1e3f4083 25 regardless
b2e465d6
AL
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 /*{{{*/
ea542140
DK
47#include<config.h>
48
b2e465d6
AL
49#include <apt-pkg/extract.h>
50#include <apt-pkg/error.h>
51#include <apt-pkg/debversion.h>
472ff00e 52#include <apt-pkg/fileutl.h>
453b82a3
DK
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>
b2e465d6 58
453b82a3
DK
59#include <string.h>
60#include <string>
b2e465d6
AL
61#include <sys/stat.h>
62#include <stdio.h>
b2e465d6
AL
63#include <errno.h>
64#include <dirent.h>
90f057fd 65#include <iostream>
453b82a3 66
d77559ac 67#include <apti18n.h>
b2e465d6 68 /*}}}*/
584e4558 69using namespace std;
b2e465d6
AL
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.. */
65512241 89bool pkgExtract::DoItem(Item &Itm, int &/*Fd*/)
b2e465d6 90{
b2e465d6
AL
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))
05eb7df0 101 return _error->Error(_("The path %s is too long"),Itm.Name);
b2e465d6
AL
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)
05eb7df0 132 return _error->Error(_("Unpacking %s more than once"),Itm.Name);
b2e465d6
AL
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)
05eb7df0 142 return _error->Error(_("The directory %s is diverted"),Itm.Name);
b2e465d6
AL
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)
05eb7df0
AL
152 return _error->Error(_("The package is trying to write to the "
153 "diversion target %s/%s"),Nde.DirN(),Nde.File());
b2e465d6
AL
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)
05eb7df0 162 return _error->Error(_("The diversion path is too long"));
b2e465d6
AL
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))
05eb7df0 172 return _error->Error(_("The path %s is too long"),Res.c_str());
b2e465d6
AL
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)
05eb7df0 186 return _error->Errno("stat",_("Failed to stat %s"),Itm.Name);
b2e465d6
AL
187
188 // See if we can recover the backup file
189 if (Nde.end() == false)
190 {
69c2ecbd 191 char Temp[sizeof(FileName)];
b2e465d6
AL
192 snprintf(Temp,sizeof(Temp),"%s.%s",Itm.Name,TempExt);
193 if (rename(Temp,Itm.Name) != 0 && errno != ENOENT)
05eb7df0 194 return _error->Errno("rename",_("Failed to rename %s to %s"),
b2e465d6
AL
195 Temp,Itm.Name);
196 if (stat(Itm.Name,&LExisting) != 0)
197 {
198 if (errno != ENOENT)
05eb7df0 199 return _error->Errno("stat",_("Failed to stat %s"),Itm.Name);
b2e465d6
AL
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)
05eb7df0 216 return _error->Errno("stat",_("Failed to stat %s"),Itm.Name);
b2e465d6
AL
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)
05eb7df0 249 return _error->Error(_("The directory %s is being replaced by a non-directory"),Itm.Name);
b2e465d6
AL
250 }
251
252 if (Debug == true)
253 clog << "Extract " << string(Itm.Name,End) << endl;
254/* if (Count != 0)
05eb7df0 255 return _error->Error(_("Done"));*/
b2e465d6
AL
256
257 return true;
258}
259 /*}}}*/
260// Extract::Finished - Sequence finished, erase the temp files /*{{{*/
261// ---------------------------------------------------------------------
262/* */
a02db58f 263APT_CONST bool pkgExtract::Finished()
b2e465d6
AL
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
8d89cda7 282 dpkg-tmp entries */
b2e465d6
AL
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)
05eb7df0 289 return _error->Error(_("Failed to locate node in its hash bucket"));
b2e465d6
AL
290
291 if (snprintf(FileName,sizeof(FileName)-20,"%s/%s",
292 Nde.DirN(),Nde.File()) <= 0)
05eb7df0 293 return _error->Error(_("The path is too long"));
b2e465d6
AL
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)
05eb7df0 306 return _error->Error(_("The diversion path is too long"));
b2e465d6
AL
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;
b2e465d6
AL
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;
e3d26885 403
b2e465d6
AL
404 // Now see if this package matches one in a replace depends
405 pkgCache::DepIterator Dep = Ver.DependsList();
406 bool Ok = false;
804de19f 407 for (; Dep.end() == false; ++Dep)
b2e465d6
AL
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 {
05eb7df0 421 _error->Warning(_("Overwrite package match with no version for %s"),Pkg.Name());
b2e465d6
AL
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)
05eb7df0 438 return _error->Error(_("File %s/%s overwrites the one in the package %s"),
b2e465d6
AL
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)
05eb7df0 471 return _error->Errno("opendir",_("Unable to read %s"),Dir.c_str());
b2e465d6
AL
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;
18255546
AL
483 pkgFLCache::NodeIterator Nde = FLCache.GetNode(File.c_str(),
484 File.c_str() + File.length(),0,false,false);
b2e465d6
AL
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);
05eb7df0 498 return _error->Errno("lstat",_("Unable to stat %s"),File.c_str());
b2e465d6
AL
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 /*}}}*/