summaryrefslogtreecommitdiffstats
path: root/apt-inst/extract.cc
blob: 35fa015e768f5317827606437900eaa565e367c8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
// -*- mode: cpp; mode: fold -*-
// Description								/*{{{*/
/* ######################################################################

   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 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/debversion.h>
#include <apt-pkg/dirstream.h>
#include <apt-pkg/error.h>
#include <apt-pkg/extract.h>
#include <apt-pkg/filelist.h>
#include <apt-pkg/fileutl.h>
#include <apt-pkg/mmap.h>
#include <apt-pkg/pkgcache.h>

#include <iostream>
#include <string>
#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>

#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_PURE 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;
}
									/*}}}*/