123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517 |
- // -*- mode: cpp; mode: fold -*-
- // Description /*{{{*/
- // $Id: extract.cc,v 1.6.2.1 2004/01/16 18:58:50 mdz Exp $
- /* ######################################################################
- Archive Extraction Directory Stream
-
- Extraction for each file is a bit of an involved process. Each object
- undergoes an atomic backup, overwrite, erase sequence. First the
- object is unpacked to '.dpkg.new' then the original is hardlinked to
- '.dpkg.tmp' and finally the new object is renamed to overwrite the old
- one. From an external perspective the file never ceased to exist.
- After the archive has been successfully unpacked the .dpkg.tmp files
- are erased. A failure causes all the .dpkg.tmp files to be restored.
-
- Decisions about unpacking go like this:
- - Store the original filename in the file listing
- - Resolve any diversions that would effect this file, all checks
- below apply to the diverted name, not the real one.
- - Resolve any symlinked configuration files.
- - If the existing file does not exist then .dpkg-tmp is checked for.
- [Note, this is reduced to only check if a file was expected to be
- there]
- - If the existing link/file is not a directory then it is replaced
- regardless
- - If the existing link/directory is being replaced by a directory then
- absolutely nothing happens.
- - If the existing link/directory is being replaced by a link then
- absolutely nothing happens.
- - If the existing link/directory is being replaced by a non-directory
- then this will abort if the package is not the sole owner of the
- directory. [Note, this is changed to not happen if the directory
- non-empty - that is, it only includes files that are part of this
- package - prevents removing user files accidentally.]
- - If the non-directory exists in the listing database and it
- does not belong to the current package then an overwrite condition
- is invoked.
-
- As we unpack we record the file list differences in the FL cache. If
- we need to unroll the the FL cache knows which files have been unpacked
- and can undo. When we need to erase then it knows which files have not
- been unpacked.
-
- ##################################################################### */
- /*}}}*/
- // Include Files /*{{{*/
- #include<config.h>
- #include <apt-pkg/extract.h>
- #include <apt-pkg/error.h>
- #include <apt-pkg/debversion.h>
- #include <apt-pkg/fileutl.h>
- #include <apt-pkg/dirstream.h>
- #include <apt-pkg/filelist.h>
- #include <apt-pkg/mmap.h>
- #include <apt-pkg/pkgcache.h>
- #include <apt-pkg/cacheiterators.h>
- #include <string.h>
- #include <string>
- #include <sys/stat.h>
- #include <stdio.h>
- #include <errno.h>
- #include <dirent.h>
- #include <iostream>
- #include <apti18n.h>
- /*}}}*/
- using namespace std;
- static const char *TempExt = "dpkg-tmp";
- //static const char *NewExt = "dpkg-new";
- // Extract::pkgExtract - Constructor /*{{{*/
- // ---------------------------------------------------------------------
- /* */
- pkgExtract::pkgExtract(pkgFLCache &FLCache,pkgCache::VerIterator Ver) :
- FLCache(FLCache), Ver(Ver)
- {
- FLPkg = FLCache.GetPkg(Ver.ParentPkg().Name(),true);
- if (FLPkg.end() == true)
- return;
- Debug = true;
- }
- /*}}}*/
- // Extract::DoItem - Handle a single item from the stream /*{{{*/
- // ---------------------------------------------------------------------
- /* This performs the setup for the extraction.. */
- bool pkgExtract::DoItem(Item &Itm, int &/*Fd*/)
- {
- /* Strip any leading/trailing /s from the filename, then copy it to the
- temp buffer and re-apply the leading / We use a class variable
- to store the new filename for use by the three extraction funcs */
- char *End = FileName+1;
- const char *I = Itm.Name;
- for (; *I != 0 && *I == '/'; I++);
- *FileName = '/';
- for (; *I != 0 && End < FileName + sizeof(FileName); I++, End++)
- *End = *I;
- if (End + 20 >= FileName + sizeof(FileName))
- return _error->Error(_("The path %s is too long"),Itm.Name);
- for (; End > FileName && End[-1] == '/'; End--);
- *End = 0;
- Itm.Name = FileName;
-
- /* Lookup the file. Nde is the file [group] we are going to write to and
- RealNde is the actual node we are manipulating. Due to diversions
- they may be entirely different. */
- pkgFLCache::NodeIterator Nde = FLCache.GetNode(Itm.Name,End,0,false,false);
- pkgFLCache::NodeIterator RealNde = Nde;
-
- // See if the file is already in the file listing
- unsigned long FileGroup = RealNde->File;
- for (; RealNde.end() == false && FileGroup == RealNde->File; RealNde++)
- if (RealNde.RealPackage() == FLPkg)
- break;
- // Nope, create an entry
- if (RealNde.end() == true)
- {
- RealNde = FLCache.GetNode(Itm.Name,End,FLPkg.Offset(),true,false);
- if (RealNde.end() == true)
- return false;
- RealNde->Flags |= pkgFLCache::Node::NewFile;
- }
- /* Check if this entry already was unpacked. The only time this should
- ever happen is if someone has hacked tar to support capabilities, in
- which case this needs to be modified anyhow.. */
- if ((RealNde->Flags & pkgFLCache::Node::Unpacked) ==
- pkgFLCache::Node::Unpacked)
- return _error->Error(_("Unpacking %s more than once"),Itm.Name);
-
- if (Nde.end() == true)
- Nde = RealNde;
- /* Consider a diverted file - We are not permitted to divert directories,
- but everything else is fair game (including conf files!) */
- if ((Nde->Flags & pkgFLCache::Node::Diversion) != 0)
- {
- if (Itm.Type == Item::Directory)
- return _error->Error(_("The directory %s is diverted"),Itm.Name);
- /* A package overwriting a diversion target is just the same as
- overwriting a normally owned file and is checked for below in
- the overwrites mechanism */
- /* If this package is trying to overwrite the target of a diversion,
- that is never, ever permitted */
- pkgFLCache::DiverIterator Div = Nde.Diversion();
- if (Div.DivertTo() == Nde)
- return _error->Error(_("The package is trying to write to the "
- "diversion target %s/%s"),Nde.DirN(),Nde.File());
-
- // See if it is us and we are following it in the right direction
- if (Div->OwnerPkg != FLPkg.Offset() && Div.DivertFrom() == Nde)
- {
- Nde = Div.DivertTo();
- End = FileName + snprintf(FileName,sizeof(FileName)-20,"%s/%s",
- Nde.DirN(),Nde.File());
- if (End <= FileName)
- return _error->Error(_("The diversion path is too long"));
- }
- }
-
- // Deal with symlinks and conf files
- if ((RealNde->Flags & pkgFLCache::Node::NewConfFile) ==
- pkgFLCache::Node::NewConfFile)
- {
- string Res = flNoLink(Itm.Name);
- if (Res.length() > sizeof(FileName))
- return _error->Error(_("The path %s is too long"),Res.c_str());
- if (Debug == true)
- clog << "Followed conf file from " << FileName << " to " << Res << endl;
- Itm.Name = strcpy(FileName,Res.c_str());
- }
-
- /* Get information about the existing file, and attempt to restore
- a backup if it does not exist */
- struct stat LExisting;
- bool EValid = false;
- if (lstat(Itm.Name,&LExisting) != 0)
- {
- // This is bad news.
- if (errno != ENOENT)
- return _error->Errno("stat",_("Failed to stat %s"),Itm.Name);
-
- // See if we can recover the backup file
- if (Nde.end() == false)
- {
- char Temp[sizeof(FileName)];
- snprintf(Temp,sizeof(Temp),"%s.%s",Itm.Name,TempExt);
- if (rename(Temp,Itm.Name) != 0 && errno != ENOENT)
- return _error->Errno("rename",_("Failed to rename %s to %s"),
- Temp,Itm.Name);
- if (stat(Itm.Name,&LExisting) != 0)
- {
- if (errno != ENOENT)
- return _error->Errno("stat",_("Failed to stat %s"),Itm.Name);
- }
- else
- EValid = true;
- }
- }
- else
- EValid = true;
-
- /* If the file is a link we need to stat its destination, get the
- existing file modes */
- struct stat Existing = LExisting;
- if (EValid == true && S_ISLNK(Existing.st_mode))
- {
- if (stat(Itm.Name,&Existing) != 0)
- {
- if (errno != ENOENT)
- return _error->Errno("stat",_("Failed to stat %s"),Itm.Name);
- Existing = LExisting;
- }
- }
-
- // We pretend a non-existing file looks like it is a normal file
- if (EValid == false)
- Existing.st_mode = S_IFREG;
-
- /* Okay, at this point 'Existing' is the stat information for the
- real non-link file */
-
- /* The only way this can be a no-op is if a directory is being
- replaced by a directory or by a link */
- if (S_ISDIR(Existing.st_mode) != 0 &&
- (Itm.Type == Item::Directory || Itm.Type == Item::SymbolicLink))
- return true;
-
- /* Non-Directory being replaced by non-directory. We check for over
- writes here. */
- if (Nde.end() == false)
- {
- if (HandleOverwrites(Nde) == false)
- return false;
- }
-
- /* Directory being replaced by a non-directory - this needs to see if
- the package is the owner and then see if the directory would be
- empty after the package is removed [ie no user files will be
- erased] */
- if (S_ISDIR(Existing.st_mode) != 0)
- {
- if (CheckDirReplace(Itm.Name) == false)
- return _error->Error(_("The directory %s is being replaced by a non-directory"),Itm.Name);
- }
-
- if (Debug == true)
- clog << "Extract " << string(Itm.Name,End) << endl;
- /* if (Count != 0)
- return _error->Error(_("Done"));*/
-
- return true;
- }
- /*}}}*/
- // Extract::Finished - Sequence finished, erase the temp files /*{{{*/
- // ---------------------------------------------------------------------
- /* */
- APT_CONST bool pkgExtract::Finished()
- {
- return true;
- }
- /*}}}*/
- // Extract::Aborted - Sequence aborted, undo all our unpacking /*{{{*/
- // ---------------------------------------------------------------------
- /* This undoes everything that was done by all calls to the DoItem method
- and restores the File Listing cache to its original form. It bases its
- actions on the flags value for each node in the cache. */
- bool pkgExtract::Aborted()
- {
- if (Debug == true)
- clog << "Aborted, backing out" << endl;
-
- pkgFLCache::NodeIterator Files = FLPkg.Files();
- map_ptrloc *Last = &FLPkg->Files;
-
- /* Loop over all files, restore those that have been unpacked from their
- dpkg-tmp entries */
- while (Files.end() == false)
- {
- // Locate the hash bucket for the node and locate its group head
- pkgFLCache::NodeIterator Nde(FLCache,FLCache.HashNode(Files));
- for (; Nde.end() == false && Files->File != Nde->File; Nde++);
- if (Nde.end() == true)
- return _error->Error(_("Failed to locate node in its hash bucket"));
-
- if (snprintf(FileName,sizeof(FileName)-20,"%s/%s",
- Nde.DirN(),Nde.File()) <= 0)
- return _error->Error(_("The path is too long"));
-
- // Deal with diversions
- if ((Nde->Flags & pkgFLCache::Node::Diversion) != 0)
- {
- pkgFLCache::DiverIterator Div = Nde.Diversion();
-
- // See if it is us and we are following it in the right direction
- if (Div->OwnerPkg != FLPkg.Offset() && Div.DivertFrom() == Nde)
- {
- Nde = Div.DivertTo();
- if (snprintf(FileName,sizeof(FileName)-20,"%s/%s",
- Nde.DirN(),Nde.File()) <= 0)
- return _error->Error(_("The diversion path is too long"));
- }
- }
-
- // Deal with overwrites+replaces
- for (; Nde.end() == false && Files->File == Nde->File; Nde++)
- {
- if ((Nde->Flags & pkgFLCache::Node::Replaced) ==
- pkgFLCache::Node::Replaced)
- {
- if (Debug == true)
- clog << "De-replaced " << FileName << " from " << Nde.RealPackage()->Name << endl;
- Nde->Flags &= ~pkgFLCache::Node::Replaced;
- }
- }
-
- // Undo the change in the filesystem
- if (Debug == true)
- clog << "Backing out " << FileName;
-
- // Remove a new node
- if ((Files->Flags & pkgFLCache::Node::NewFile) ==
- pkgFLCache::Node::NewFile)
- {
- if (Debug == true)
- clog << " [new node]" << endl;
- pkgFLCache::Node *Tmp = Files;
- Files++;
- *Last = Tmp->NextPkg;
- Tmp->NextPkg = 0;
- FLCache.DropNode(Tmp - FLCache.NodeP);
- }
- else
- {
- if (Debug == true)
- clog << endl;
-
- Last = &Files->NextPkg;
- Files++;
- }
- }
-
- return true;
- }
- /*}}}*/
- // Extract::Fail - Extraction of a file Failed /*{{{*/
- // ---------------------------------------------------------------------
- /* */
- bool pkgExtract::Fail(Item &Itm,int Fd)
- {
- return pkgDirStream::Fail(Itm,Fd);
- }
- /*}}}*/
- // Extract::FinishedFile - Finished a file /*{{{*/
- // ---------------------------------------------------------------------
- /* */
- bool pkgExtract::FinishedFile(Item &Itm,int Fd)
- {
- return pkgDirStream::FinishedFile(Itm,Fd);
- }
- /*}}}*/
- // Extract::HandleOverwrites - See if a replaces covers this overwrite /*{{{*/
- // ---------------------------------------------------------------------
- /* Check if the file is in a package that is being replaced by this
- package or if the file is being overwritten. Note that if the file
- is really a directory but it has been erased from the filesystem
- this will fail with an overwrite message. This is a limitation of the
- dpkg file information format.
-
- XX If a new package installs and another package replaces files in this
- package what should we do? */
- bool pkgExtract::HandleOverwrites(pkgFLCache::NodeIterator Nde,
- bool DiverCheck)
- {
- pkgFLCache::NodeIterator TmpNde = Nde;
- unsigned long DiverOwner = 0;
- unsigned long FileGroup = Nde->File;
- for (; Nde.end() == false && FileGroup == Nde->File; Nde++)
- {
- if ((Nde->Flags & pkgFLCache::Node::Diversion) != 0)
- {
- /* Store the diversion owner if this is the forward direction
- of the diversion */
- if (DiverCheck == true)
- DiverOwner = Nde.Diversion()->OwnerPkg;
- continue;
- }
- pkgFLCache::PkgIterator FPkg(FLCache,Nde.RealPackage());
- if (FPkg.end() == true || FPkg == FLPkg)
- continue;
-
- /* This tests trips when we are checking a diversion to see
- if something has already been diverted by this diversion */
- if (FPkg.Offset() == DiverOwner)
- continue;
- // Now see if this package matches one in a replace depends
- pkgCache::DepIterator Dep = Ver.DependsList();
- bool Ok = false;
- for (; Dep.end() == false; ++Dep)
- {
- if (Dep->Type != pkgCache::Dep::Replaces)
- continue;
-
- // Does the replaces apply to this package?
- if (strcmp(Dep.TargetPkg().Name(),FPkg.Name()) != 0)
- continue;
-
- /* Check the version for match. I do not think CurrentVer can be
- 0 if we are here.. */
- pkgCache::PkgIterator Pkg = Dep.TargetPkg();
- if (Pkg->CurrentVer == 0)
- {
- _error->Warning(_("Overwrite package match with no version for %s"),Pkg.Name());
- continue;
- }
- // Replaces is met
- if (debVS.CheckDep(Pkg.CurrentVer().VerStr(),Dep->CompareOp,Dep.TargetVer()) == true)
- {
- if (Debug == true)
- clog << "Replaced file " << Nde.DirN() << '/' << Nde.File() << " from " << Pkg.Name() << endl;
- Nde->Flags |= pkgFLCache::Node::Replaced;
- Ok = true;
- break;
- }
- }
-
- // Negative Hit
- if (Ok == false)
- return _error->Error(_("File %s/%s overwrites the one in the package %s"),
- Nde.DirN(),Nde.File(),FPkg.Name());
- }
-
- /* If this is a diversion we might have to recurse to process
- the other side of it */
- if ((TmpNde->Flags & pkgFLCache::Node::Diversion) != 0)
- {
- pkgFLCache::DiverIterator Div = TmpNde.Diversion();
- if (Div.DivertTo() == TmpNde)
- return HandleOverwrites(Div.DivertFrom(),true);
- }
-
- return true;
- }
- /*}}}*/
- // Extract::CheckDirReplace - See if this directory can be erased /*{{{*/
- // ---------------------------------------------------------------------
- /* If this directory is owned by a single package and that package is
- replacing it with something non-directoryish then dpkg allows this.
- We increase the requirement to be that the directory is non-empty after
- the package is removed */
- bool pkgExtract::CheckDirReplace(string Dir,unsigned int Depth)
- {
- // Looping?
- if (Depth > 40)
- return false;
-
- if (Dir[Dir.size() - 1] != '/')
- Dir += '/';
-
- DIR *D = opendir(Dir.c_str());
- if (D == 0)
- return _error->Errno("opendir",_("Unable to read %s"),Dir.c_str());
- string File;
- for (struct dirent *Dent = readdir(D); Dent != 0; Dent = readdir(D))
- {
- // Skip some files
- if (strcmp(Dent->d_name,".") == 0 ||
- strcmp(Dent->d_name,"..") == 0)
- continue;
-
- // Look up the node
- File = Dir + Dent->d_name;
- pkgFLCache::NodeIterator Nde = FLCache.GetNode(File.c_str(),
- File.c_str() + File.length(),0,false,false);
- // The file is not owned by this package
- if (Nde.end() != false || Nde.RealPackage() != FLPkg)
- {
- closedir(D);
- return false;
- }
-
- // See if it is a directory
- struct stat St;
- if (lstat(File.c_str(),&St) != 0)
- {
- closedir(D);
- return _error->Errno("lstat",_("Unable to stat %s"),File.c_str());
- }
-
- // Recurse down directories
- if (S_ISDIR(St.st_mode) != 0)
- {
- if (CheckDirReplace(File,Depth + 1) == false)
- {
- closedir(D);
- return false;
- }
- }
- }
-
- // No conflicts
- closedir(D);
- return true;
- }
- /*}}}*/
|