~mvo/apt/mvo

« back to all changes in this revision

Viewing changes to apt-inst/extract.cc

  • Committer: Arch Librarian
  • Date: 2004-09-20 16:56:32 UTC
  • Revision ID: Arch-1:apt@arch.ubuntu.com%apt--MAIN--0--patch-614
Join with aliencode
Author: jgg
Date: 2001-02-20 07:03:16 GMT
Join with aliencode

Show diffs side-by-side

added added

removed removed

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