Browse Source

Move CD-ROM handling backend into libapt-pkg
- apt-cdrom.cc seperated into frontend (cmdline/apt-cdrom.cc and library
apt-pkg/cdrom.{cc,h}) (Ubuntu #5668)

Patches applied:

* michael.vogt@ubuntu.com--2005/apt--auth-cdrom--0--base-0
tag of apt@packages.debian.org/apt--main--0--patch-51

* michael.vogt@ubuntu.com--2005/apt--auth-cdrom--0--patch-1
* added support for signed cdroms

* michael.vogt@ubuntu.com--2005/apt--auth-cdrom--0--patch-2
* merged with apt--main, seperated cmdline/apt-cdrom.cc into a library (apt-pkg/cdrom.{cc,h})

* michael.vogt@ubuntu.com--2005/apt--auth-cdrom--0--patch-3
* cleaned up the cmdline/apt-cdrom.cc code

Matt Zimmerman 21 years ago
parent
commit
a75c6a6e17
11 changed files with 1206 additions and 711 deletions
  1. 776 0
      apt-pkg/cdrom.cc
  2. 71 0
      apt-pkg/cdrom.h
  3. 164 44
      cmdline/indexcopy.cc
  4. 15 1
      cmdline/indexcopy.h
  5. 11 0
      apt-pkg/indexrecords.cc
  6. 3 1
      apt-pkg/indexrecords.h
  7. 3 3
      apt-pkg/makefile
  8. 55 648
      cmdline/apt-cdrom.cc
  9. 1 1
      cmdline/makefile
  10. 10 6
      debian/changelog
  11. 97 7
      po/apt-all.pot

+ 776 - 0
apt-pkg/cdrom.cc

@@ -0,0 +1,776 @@
+/*
+ */
+
+#ifdef __GNUG__
+#pragma implementation "apt-pkg/cdrom.h"
+#endif
+#include<apt-pkg/init.h>
+#include<apt-pkg/error.h>
+#include<apt-pkg/cdromutl.h>
+#include<apt-pkg/strutl.h>
+#include<apt-pkg/cdrom.h>
+#include<sstream>
+#include<fstream>
+#include<config.h>
+#include<apti18n.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <stdio.h>
+
+
+#include "indexcopy.h"
+
+using namespace std;
+
+// FindPackages - Find the package files on the CDROM			/*{{{*/
+// ---------------------------------------------------------------------
+/* We look over the cdrom for package files. This is a recursive
+   search that short circuits when it his a package file in the dir.
+   This speeds it up greatly as the majority of the size is in the
+   binary-* sub dirs. */
+bool pkgCdrom::FindPackages(string CD,vector<string> &List,
+			    vector<string> &SList, vector<string> &SigList,
+			    string &InfoDir, pkgCdromStatus *log,
+			    unsigned int Depth)
+{
+   static ino_t Inodes[9];
+
+   // if we have a look we "pulse" now
+   if(log)
+      log->Update();
+
+   if (Depth >= 7)
+      return true;
+
+   if (CD[CD.length()-1] != '/')
+      CD += '/';   
+
+   if (chdir(CD.c_str()) != 0)
+      return _error->Errno("chdir","Unable to change to %s",CD.c_str());
+
+   // Look for a .disk subdirectory
+   struct stat Buf;
+   if (stat(".disk",&Buf) == 0)
+   {
+      if (InfoDir.empty() == true)
+	 InfoDir = CD + ".disk/";
+   }
+
+   // Don't look into directories that have been marked to ingore.
+   if (stat(".aptignr",&Buf) == 0)
+      return true;
+
+
+   /* Check _first_ for a signature file as apt-cdrom assumes that all files
+      under a Packages/Source file are in control of that file and stops 
+      the scanning
+   */
+   if (stat("Release.gpg",&Buf) == 0)
+   {
+      SigList.push_back(CD);
+   }
+   /* Aha! We found some package files. We assume that everything under 
+      this dir is controlled by those package files so we don't look down
+      anymore */
+   if (stat("Packages",&Buf) == 0 || stat("Packages.gz",&Buf) == 0)
+   {
+      List.push_back(CD);
+      
+      // Continue down if thorough is given
+      if (_config->FindB("APT::CDROM::Thorough",false) == false)
+	 return true;
+   }
+   if (stat("Sources.gz",&Buf) == 0 || stat("Sources",&Buf) == 0)
+   {
+      SList.push_back(CD);
+      
+      // Continue down if thorough is given
+      if (_config->FindB("APT::CDROM::Thorough",false) == false)
+	 return true;
+   }
+   
+   DIR *D = opendir(".");
+   if (D == 0)
+      return _error->Errno("opendir","Unable to read %s",CD.c_str());
+   
+   // Run over the directory
+   for (struct dirent *Dir = readdir(D); Dir != 0; Dir = readdir(D))
+   {
+      // Skip some files..
+      if (strcmp(Dir->d_name,".") == 0 ||
+	  strcmp(Dir->d_name,"..") == 0 ||
+	  //strcmp(Dir->d_name,"source") == 0 ||
+	  strcmp(Dir->d_name,".disk") == 0 ||
+	  strcmp(Dir->d_name,"experimental") == 0 ||
+	  strcmp(Dir->d_name,"binary-all") == 0 ||
+          strcmp(Dir->d_name,"debian-installer") == 0)
+	 continue;
+
+      // See if the name is a sub directory
+      struct stat Buf;
+      if (stat(Dir->d_name,&Buf) != 0)
+	 continue;      
+      
+      if (S_ISDIR(Buf.st_mode) == 0)
+	 continue;
+      
+      unsigned int I;
+      for (I = 0; I != Depth; I++)
+	 if (Inodes[I] == Buf.st_ino)
+	    break;
+      if (I != Depth)
+	 continue;
+      
+      // Store the inodes weve seen
+      Inodes[Depth] = Buf.st_ino;
+
+      // Descend
+      if (FindPackages(CD + Dir->d_name,List,SList,SigList,InfoDir,log,Depth+1) == false)
+	 break;
+
+      if (chdir(CD.c_str()) != 0)
+	 return _error->Errno("chdir","Unable to change to %s",CD.c_str());
+   };
+
+   closedir(D);
+   
+   return !_error->PendingError();
+}
+
+// Score - We compute a 'score' for a path				/*{{{*/
+// ---------------------------------------------------------------------
+/* Paths are scored based on how close they come to what I consider
+   normal. That is ones that have 'dist' 'stable' 'testing' will score
+   higher than ones without. */
+int pkgCdrom::Score(string Path)
+{
+   int Res = 0;
+   if (Path.find("stable/") != string::npos)
+      Res += 29;
+   if (Path.find("/binary-") != string::npos)
+      Res += 20;
+   if (Path.find("testing/") != string::npos)
+      Res += 28;
+   if (Path.find("unstable/") != string::npos)
+      Res += 27;
+   if (Path.find("/dists/") != string::npos)
+      Res += 40;
+   if (Path.find("/main/") != string::npos)
+      Res += 20;
+   if (Path.find("/contrib/") != string::npos)
+      Res += 20;
+   if (Path.find("/non-free/") != string::npos)
+      Res += 20;
+   if (Path.find("/non-US/") != string::npos)
+      Res += 20;
+   if (Path.find("/source/") != string::npos)
+      Res += 10;
+   if (Path.find("/debian/") != string::npos)
+      Res -= 10;
+   return Res;
+}
+
+									/*}}}*/
+// DropBinaryArch - Dump dirs with a string like /binary-<foo>/		/*{{{*/
+// ---------------------------------------------------------------------
+/* Here we drop everything that is not this machines arch */
+bool pkgCdrom::DropBinaryArch(vector<string> &List)
+{
+   char S[300];
+   snprintf(S,sizeof(S),"/binary-%s/",
+	    _config->Find("Apt::Architecture").c_str());
+   
+   for (unsigned int I = 0; I < List.size(); I++)
+   {
+      const char *Str = List[I].c_str();
+      
+      const char *Res;
+      if ((Res = strstr(Str,"/binary-")) == 0)
+	 continue;
+
+      // Weird, remove it.
+      if (strlen(Res) < strlen(S))
+      {
+	 List.erase(List.begin() + I);
+	 I--;
+	 continue;
+      }
+	  
+      // See if it is our arch
+      if (stringcmp(Res,Res + strlen(S),S) == 0)
+	 continue;
+      
+      // Erase it
+      List.erase(List.begin() + I);
+      I--;
+   }
+   
+   return true;
+}
+
+
+// DropRepeats - Drop repeated files resulting from symlinks		/*{{{*/
+// ---------------------------------------------------------------------
+/* Here we go and stat every file that we found and strip dup inodes. */
+bool pkgCdrom::DropRepeats(vector<string> &List,const char *Name)
+{
+   // Get a list of all the inodes
+   ino_t *Inodes = new ino_t[List.size()];
+   for (unsigned int I = 0; I != List.size(); I++)
+   {
+      struct stat Buf;
+      if (stat((List[I] + Name).c_str(),&Buf) != 0 &&
+	  stat((List[I] + Name + ".gz").c_str(),&Buf) != 0)
+	 _error->Errno("stat","Failed to stat %s%s",List[I].c_str(),
+		       Name);
+      Inodes[I] = Buf.st_ino;
+   }
+   
+   if (_error->PendingError() == true)
+      return false;
+   
+   // Look for dups
+   for (unsigned int I = 0; I != List.size(); I++)
+   {
+      for (unsigned int J = I+1; J < List.size(); J++)
+      {
+	 // No match
+	 if (Inodes[J] != Inodes[I])
+	    continue;
+	 
+	 // We score the two paths.. and erase one
+	 int ScoreA = Score(List[I]);
+	 int ScoreB = Score(List[J]);
+	 if (ScoreA < ScoreB)
+	 {
+	    List[I] = string();
+	    break;
+	 }
+	 
+	 List[J] = string();
+      }
+   }  
+ 
+   // Wipe erased entries
+   for (unsigned int I = 0; I < List.size();)
+   {
+      if (List[I].empty() == false)
+	 I++;
+      else
+	 List.erase(List.begin()+I);
+   }
+   
+   return true;
+}
+									/*}}}*/
+
+// ReduceSourceList - Takes the path list and reduces it		/*{{{*/
+// ---------------------------------------------------------------------
+/* This takes the list of source list expressed entires and collects
+   similar ones to form a single entry for each dist */
+void pkgCdrom::ReduceSourcelist(string CD,vector<string> &List)
+{
+   sort(List.begin(),List.end());
+   
+   // Collect similar entries
+   for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
+   {
+      // Find a space..
+      string::size_type Space = (*I).find(' ');
+      if (Space == string::npos)
+	 continue;
+      string::size_type SSpace = (*I).find(' ',Space + 1);
+      if (SSpace == string::npos)
+	 continue;
+
+      string Word1 = string(*I,Space,SSpace-Space);
+      string Prefix = string(*I,0,Space);
+      for (vector<string>::iterator J = List.begin(); J != I; J++)
+      {
+	 // Find a space..
+	 string::size_type Space2 = (*J).find(' ');
+	 if (Space2 == string::npos)
+	    continue;
+	 string::size_type SSpace2 = (*J).find(' ',Space2 + 1);
+	 if (SSpace2 == string::npos)
+	    continue;
+	 
+	 if (string(*J,0,Space2) != Prefix)
+	    continue;
+	 if (string(*J,Space2,SSpace2-Space2) != Word1)
+	    continue;
+	 
+	 *J += string(*I,SSpace);
+	 *I = string();
+      }
+   }   
+
+   // Wipe erased entries
+   for (unsigned int I = 0; I < List.size();)
+   {
+      if (List[I].empty() == false)
+	 I++;
+      else
+	 List.erase(List.begin()+I);
+   }
+}
+									/*}}}*/
+// WriteDatabase - Write the CDROM Database file			/*{{{*/
+// ---------------------------------------------------------------------
+/* We rewrite the configuration class associated with the cdrom database. */
+bool pkgCdrom::WriteDatabase(Configuration &Cnf)
+{
+   string DFile = _config->FindFile("Dir::State::cdroms");
+   string NewFile = DFile + ".new";
+   
+   unlink(NewFile.c_str());
+   ofstream Out(NewFile.c_str());
+   if (!Out)
+      return _error->Errno("ofstream::ofstream",
+			   "Failed to open %s.new",DFile.c_str());
+   
+   /* Write out all of the configuration directives by walking the
+      configuration tree */
+   const Configuration::Item *Top = Cnf.Tree(0);
+   for (; Top != 0;)
+   {
+      // Print the config entry
+      if (Top->Value.empty() == false)
+	 Out <<  Top->FullTag() + " \"" << Top->Value << "\";" << endl;
+      
+      if (Top->Child != 0)
+      {
+	 Top = Top->Child;
+	 continue;
+      }
+      
+      while (Top != 0 && Top->Next == 0)
+	 Top = Top->Parent;
+      if (Top != 0)
+	 Top = Top->Next;
+   }   
+
+   Out.close();
+   
+   rename(DFile.c_str(),string(DFile + '~').c_str());
+   if (rename(NewFile.c_str(),DFile.c_str()) != 0)
+      return _error->Errno("rename","Failed to rename %s.new to %s",
+			   DFile.c_str(),DFile.c_str());
+
+   return true;
+}
+									/*}}}*/
+// WriteSourceList - Write an updated sourcelist			/*{{{*/
+// ---------------------------------------------------------------------
+/* This reads the old source list and copies it into the new one. It 
+   appends the new CDROM entires just after the first block of comments.
+   This places them first in the file. It also removes any old entries
+   that were the same. */
+bool pkgCdrom::WriteSourceList(string Name,vector<string> &List,bool Source)
+{
+   if (List.size() == 0)
+      return true;
+
+   string File = _config->FindFile("Dir::Etc::sourcelist");
+
+   // Open the stream for reading
+   ifstream F((FileExists(File)?File.c_str():"/dev/null"),
+	      ios::in );
+   if (!F != 0)
+      return _error->Errno("ifstream::ifstream","Opening %s",File.c_str());
+
+   string NewFile = File + ".new";
+   unlink(NewFile.c_str());
+   ofstream Out(NewFile.c_str());
+   if (!Out)
+      return _error->Errno("ofstream::ofstream",
+			   "Failed to open %s.new",File.c_str());
+
+   // Create a short uri without the path
+   string ShortURI = "cdrom:[" + Name + "]/";   
+   string ShortURI2 = "cdrom:" + Name + "/";     // For Compatibility
+
+   string Type;
+   if (Source == true)
+      Type = "deb-src";
+   else
+      Type = "deb";
+   
+   char Buffer[300];
+   int CurLine = 0;
+   bool First = true;
+   while (F.eof() == false)
+   {      
+      F.getline(Buffer,sizeof(Buffer));
+      CurLine++;
+      _strtabexpand(Buffer,sizeof(Buffer));
+      _strstrip(Buffer);
+            
+      // Comment or blank
+      if (Buffer[0] == '#' || Buffer[0] == 0)
+      {
+	 Out << Buffer << endl;
+	 continue;
+      }
+
+      if (First == true)
+      {
+	 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
+	 {
+	    string::size_type Space = (*I).find(' ');
+	    if (Space == string::npos)
+	       return _error->Error("Internal error");
+	    Out << Type << " cdrom:[" << Name << "]/" << string(*I,0,Space) <<
+	       " " << string(*I,Space+1) << endl;
+	 }
+      }
+      First = false;
+      
+      // Grok it
+      string cType;
+      string URI;
+      const char *C = Buffer;
+      if (ParseQuoteWord(C,cType) == false ||
+	  ParseQuoteWord(C,URI) == false)
+      {
+	 Out << Buffer << endl;
+	 continue;
+      }
+
+      // Emit lines like this one
+      if (cType != Type || (string(URI,0,ShortURI.length()) != ShortURI &&
+	  string(URI,0,ShortURI.length()) != ShortURI2))
+      {
+	 Out << Buffer << endl;
+	 continue;
+      }      
+   }
+   
+   // Just in case the file was empty
+   if (First == true)
+   {
+      for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
+      {
+	 string::size_type Space = (*I).find(' ');
+	 if (Space == string::npos)
+	    return _error->Error("Internal error");
+	 
+	 Out << "deb cdrom:[" << Name << "]/" << string(*I,0,Space) << 
+	    " " << string(*I,Space+1) << endl;
+      }
+   }
+   
+   Out.close();
+
+   rename(File.c_str(),string(File + '~').c_str());
+   if (rename(NewFile.c_str(),File.c_str()) != 0)
+      return _error->Errno("rename","Failed to rename %s.new to %s",
+			   File.c_str(),File.c_str());
+   
+   return true;
+}
+
+
+bool pkgCdrom::Ident(string &ident, pkgCdromStatus *log)
+{
+   stringstream msg;
+
+   // Startup
+   string CDROM = _config->FindDir("Acquire::cdrom::mount","/cdrom/");
+   if (CDROM[0] == '.')
+      CDROM= SafeGetCWD() + '/' + CDROM;
+
+   if(log) {
+      msg.str("");
+      ioprintf(msg, _("Using CD-ROM mount point %s\nMounting CD-ROM\n"),
+		      CDROM.c_str());
+      log->Update(msg.str());
+   }
+   if (MountCdrom(CDROM) == false)
+      return _error->Error("Failed to mount the cdrom.");
+
+   // Hash the CD to get an ID
+   if(log) 
+      log->Update(_("Identifying.. "));
+   
+
+   if (IdentCdrom(CDROM,ident) == false)
+   {
+      ident = "";
+      return false;
+   }
+
+   msg.str("");
+   ioprintf(msg, "[%s]\n",ident.c_str());
+   log->Update(msg.str());
+
+
+   // Read the database
+   Configuration Database;
+   string DFile = _config->FindFile("Dir::State::cdroms");
+   if (FileExists(DFile) == true)
+   {
+      if (ReadConfigFile(Database,DFile) == false)
+	 return _error->Error("Unable to read the cdrom database %s",
+			      DFile.c_str());
+   }
+   if(log) {
+      msg.str("");
+      ioprintf(msg, _("Stored Label: %s \n"),
+	       Database.Find("CD::"+ident).c_str());
+      log->Update(msg.str());
+   }
+   return true;
+}
+
+
+bool pkgCdrom::Add(pkgCdromStatus *log)
+{
+   stringstream msg;
+
+   // Startup
+   string CDROM = _config->FindDir("Acquire::cdrom::mount","/cdrom/");
+   if (CDROM[0] == '.')
+      CDROM= SafeGetCWD() + '/' + CDROM;
+   
+   if(log) {
+      log->SetTotal(STEP_LAST);
+      msg.str("");
+      ioprintf(msg, _("Using CD-ROM mount point %s\n"), CDROM.c_str());
+      log->Update(msg.str(), STEP_PREPARE);
+   }
+
+   // Read the database
+   Configuration Database;
+   string DFile = _config->FindFile("Dir::State::cdroms");
+   if (FileExists(DFile) == true)
+   {
+      if (ReadConfigFile(Database,DFile) == false)
+	 return _error->Error("Unable to read the cdrom database %s",
+			      DFile.c_str());
+   }
+   
+   // Unmount the CD and get the user to put in the one they want
+   if (_config->FindB("APT::CDROM::NoMount",false) == false)
+   {
+      if(log)
+	 log->Update(_("Unmounting CD-ROM\n"), STEP_UNMOUNT);
+      UnmountCdrom(CDROM);
+
+      if(log) {
+	 log->Update(_("Waiting for disc...\n"), STEP_WAIT);
+	 if(!log->ChangeCdrom()) {
+	    // user aborted
+	    return false; 
+	 }
+      }
+
+      // Mount the new CDROM
+      log->Update(_("Mounting CD-ROM...\n"), STEP_MOUNT);
+      if (MountCdrom(CDROM) == false)
+	 return _error->Error("Failed to mount the cdrom.");
+   }
+   
+   // Hash the CD to get an ID
+   if(log)
+      log->Update(_("Identifying.. "), STEP_IDENT);
+   string ID;
+   if (IdentCdrom(CDROM,ID) == false)
+   {
+      log->Update("\n");
+      return false;
+   }
+   if(log) 
+      log->Update("["+ID+"]\n");
+
+   if(log) 
+      log->Update(_("Scanning Disc for index files..\n"),STEP_SCAN);
+   
+   // Get the CD structure
+   vector<string> List;
+   vector<string> SourceList;
+   vector<string> SigList;
+   string StartDir = SafeGetCWD();
+   string InfoDir;
+   if (FindPackages(CDROM,List,SourceList, SigList,InfoDir,log) == false)
+   {
+      log->Update("\n");
+      return false;
+   }
+
+   chdir(StartDir.c_str());
+
+   if (_config->FindB("Debug::aptcdrom",false) == true)
+   {
+      cout << "I found (binary):" << endl;
+      for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
+	 cout << *I << endl;
+      cout << "I found (source):" << endl;
+      for (vector<string>::iterator I = SourceList.begin(); I != SourceList.end(); I++)
+	 cout << *I << endl;
+      cout << "I found (Signatures):" << endl;
+      for (vector<string>::iterator I = SigList.begin(); I != SigList.end(); I++)
+	 cout << *I << endl;
+   }   
+
+   //log->Update(_("Cleaning package lists..."), STEP_CLEAN);
+
+   // Fix up the list
+   DropBinaryArch(List);
+   DropRepeats(List,"Packages");
+   DropRepeats(SourceList,"Sources");
+   DropRepeats(SigList,"Release.gpg");
+   if(log) {
+      msg.str("");
+      ioprintf(msg, _("Found %i package indexes, %i source indexes and "
+		      "%i signatures\n"), 
+	       List.size(), SourceList.size(), SigList.size());
+      log->Update(msg.str(), STEP_SCAN);
+   }
+
+   if (List.size() == 0 && SourceList.size() == 0)
+      return _error->Error("Unable to locate any package files, perhaps this is not a Debian Disc");
+
+   // Check if the CD is in the database
+   string Name;
+   if (Database.Exists("CD::" + ID) == false ||
+       _config->FindB("APT::CDROM::Rename",false) == true)
+   {
+      // Try to use the CDs label if at all possible
+      if (InfoDir.empty() == false &&
+	  FileExists(InfoDir + "/info") == true)
+      {
+	 ifstream F(string(InfoDir + "/info").c_str());
+	 if (!F == 0)
+	    getline(F,Name);
+
+	 if (Name.empty() == false)
+	 {
+	    // Escape special characters
+	    string::iterator J = Name.begin();
+	    for (; J != Name.end(); J++)
+	       if (*J == '"' || *J == ']' || *J == '[')
+		  *J = '_';
+	    
+	    if(log) {
+	       msg.str("");
+	       ioprintf(msg, "Found label '%s'\n", Name.c_str());
+	       log->Update(msg.str());
+	    }
+	    Database.Set("CD::" + ID + "::Label",Name);
+	 }	 
+      }
+      
+      if (_config->FindB("APT::CDROM::Rename",false) == true ||
+	  Name.empty() == true)
+      {
+	 if(!log) 
+	    return _error->Error("No disc name found and no way to ask for it");
+
+	 while(true) {
+	    if(!log->AskCdromName(Name)) {
+	       // user canceld
+	       return false; 
+	    }
+	    cout << "Name: '" << Name << "'" << endl;
+
+	    if (Name.empty() == false &&
+		Name.find('"') == string::npos &&
+		Name.find('[') == string::npos &&
+		Name.find(']') == string::npos)
+	       break;
+	    log->Update(_("That is not a valid name, try again.\n"));
+	 }
+      }      
+   }
+   else
+      Name = Database.Find("CD::" + ID);
+
+   // Escape special characters
+   string::iterator J = Name.begin();
+   for (; J != Name.end(); J++)
+      if (*J == '"' || *J == ']' || *J == '[')
+	 *J = '_';
+   
+   Database.Set("CD::" + ID,Name);
+   if(log) {
+      msg.str("");
+      ioprintf(msg, _("This Disc is called: \n'%s'\n"), Name.c_str());
+      log->Update(msg.str());
+   }
+
+   log->Update(_("Copying package lists..."), STEP_COPY);
+   // take care of the signatures and copy them if they are ok
+   // (we do this before PackageCopy as it modifies "List" and "SourceList")
+   SigVerify SignVerify;
+   SignVerify.CopyAndVerify(CDROM, Name, SigList, List, SourceList);
+   
+   // Copy the package files to the state directory
+   PackageCopy Copy;
+   SourceCopy SrcCopy;
+   if (Copy.CopyPackages(CDROM,Name,List, log) == false ||
+       SrcCopy.CopyPackages(CDROM,Name,SourceList, log) == false)
+      return false;
+
+   // reduce the List so that it takes less space in sources.list
+   ReduceSourcelist(CDROM,List);
+   ReduceSourcelist(CDROM,SourceList);
+
+   // Write the database and sourcelist
+   if (_config->FindB("APT::cdrom::NoAct",false) == false)
+   {
+      if (WriteDatabase(Database) == false)
+	 return false;
+      
+      if(log) {
+	 log->Update(_("Writing new source list\n"), STEP_WRITE);
+      }
+      if (WriteSourceList(Name,List,false) == false ||
+	  WriteSourceList(Name,SourceList,true) == false)
+	 return false;
+   }
+
+   // Print the sourcelist entries
+   if(log) 
+      log->Update(_("Source List entries for this Disc are:\n"));
+
+   for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
+   {
+      string::size_type Space = (*I).find(' ');
+      if (Space == string::npos)
+	 return _error->Error("Internal error");
+
+      if(log) {
+	 msg.str("");
+	 msg << "deb cdrom:[" << Name << "]/" << string(*I,0,Space) << 
+	    " " << string(*I,Space+1) << endl;
+	 log->Update(msg.str());
+      }
+   }
+
+   for (vector<string>::iterator I = SourceList.begin(); I != SourceList.end(); I++)
+   {
+      string::size_type Space = (*I).find(' ');
+      if (Space == string::npos)
+	 return _error->Error("Internal error");
+
+      if(log) {
+	 msg.str("");
+	 msg << "deb-src cdrom:[" << Name << "]/" << string(*I,0,Space) << 
+	    " " << string(*I,Space+1) << endl;
+	 log->Update(msg.str());
+      }
+   }
+
+   
+
+   // Unmount and finish
+   if (_config->FindB("APT::CDROM::NoMount",false) == false) {
+      log->Update(_("Unmounting CD-ROM..."), STEP_LAST);
+      UnmountCdrom(CDROM);
+   }
+
+   return true;
+}

+ 71 - 0
apt-pkg/cdrom.h

@@ -0,0 +1,71 @@
+#ifndef PKGLIB_CDROM_H
+#define PKGLIB_CDROM_H
+
+#include<apt-pkg/init.h>
+#include<string>
+#include<vector>
+
+#ifdef __GNUG__
+#pragma interface "apt-pkg/cdrom.h"
+#endif
+
+using namespace std;
+
+class pkgCdromStatus
+{
+ protected:
+   int totalSteps;
+
+ public:
+   pkgCdromStatus() {};
+   virtual ~pkgCdromStatus() {};
+
+   // total steps
+   virtual void SetTotal(int total) { totalSteps = total; };
+   // update steps, will be called regularly as a "pulse"
+   virtual void Update(string text="", int current=0) = 0;
+   
+   // ask for cdrom insert
+   virtual bool ChangeCdrom() = 0;
+   // ask for cdrom name
+   virtual bool AskCdromName(string &Name) = 0;
+   // Progress indicator for the Index rewriter
+   virtual OpProgress* GetOpProgress() {return NULL; };
+};
+
+class pkgCdrom 
+{
+ protected:
+   enum {
+      STEP_PREPARE = 1,
+      STEP_UNMOUNT,
+      STEP_WAIT,
+      STEP_MOUNT,
+      STEP_IDENT,
+      STEP_SCAN,
+      STEP_COPY,
+      STEP_WRITE,
+      STEP_UNMOUNT3,
+      STEP_LAST
+   };
+
+
+   bool FindPackages(string CD,vector<string> &List,
+		     vector<string> &SList, vector<string> &SigList,
+		     string &InfoDir, pkgCdromStatus *log,
+		     unsigned int Depth = 0);
+   bool DropBinaryArch(vector<string> &List);
+   bool DropRepeats(vector<string> &List,const char *Name);
+   void ReduceSourcelist(string CD,vector<string> &List);
+   bool WriteDatabase(Configuration &Cnf);
+   bool WriteSourceList(string Name,vector<string> &List,bool Source);
+   int Score(string Path);
+
+ public:
+   bool Ident(string &ident, pkgCdromStatus *log);
+   bool Add(pkgCdromStatus *log);
+};
+
+
+
+#endif

+ 164 - 44
cmdline/indexcopy.cc

@@ -18,8 +18,13 @@
 #include <apt-pkg/fileutl.h>
 #include <apt-pkg/configuration.h>
 #include <apt-pkg/tagfile.h>
+#include <apt-pkg/indexrecords.h>
+#include <apt-pkg/md5.h>
+#include <apt-pkg/cdrom.h>
+#include <apti18n.h>
 
 #include <iostream>
+#include <sstream>
 #include <unistd.h>
 #include <sys/stat.h>
 #include <stdio.h>
@@ -30,12 +35,15 @@ using namespace std;
 // IndexCopy::CopyPackages - Copy the package files from the CD		/*{{{*/
 // ---------------------------------------------------------------------
 /* */
-bool IndexCopy::CopyPackages(string CDROM,string Name,vector<string> &List)
+bool IndexCopy::CopyPackages(string CDROM,string Name,vector<string> &List,
+			     pkgCdromStatus *log)
 {
+   OpProgress *Progress = NULL;
    if (List.size() == 0)
       return true;
    
-   OpTextProgress Progress;
+   if(log) 
+      Progress = log->GetOpProgress();
    
    bool NoStat = _config->FindB("APT::CDROM::Fast",false);
    bool Debug = _config->FindB("Debug::aptcdrom",false);
@@ -130,11 +138,13 @@ bool IndexCopy::CopyPackages(string CDROM,string Name,vector<string> &List)
 	 return _error->Errno("fdopen","Failed to reopen fd");
       
       // Setup the progress meter
-      Progress.OverallProgress(CurrentSize,TotalSize,FileSize,
-			       string("Reading ") + Type() + " Indexes");
+      if(Progress)
+	 Progress->OverallProgress(CurrentSize,TotalSize,FileSize,
+				   string("Reading ") + Type() + " Indexes");
 
       // Parse
-      Progress.SubProgress(Pkg.Size());
+      if(Progress)
+	 Progress->SubProgress(Pkg.Size());
       pkgTagSection Section;
       this->Section = &Section;
       string Prefix;
@@ -142,7 +152,8 @@ bool IndexCopy::CopyPackages(string CDROM,string Name,vector<string> &List)
       unsigned long Chop = 0;
       while (Parser.Step(Section) == true)
       {
-	 Progress.Progress(Parser.Offset());
+	 if(Progress)
+	    Progress->Progress(Parser.Offset());
 	 string File;
 	 unsigned long Size;
 	 if (GetFile(File,Size) == false)
@@ -230,35 +241,8 @@ bool IndexCopy::CopyPackages(string CDROM,string Name,vector<string> &List)
 	 FinalF += URItoFileName(S);
 	 if (rename(TargetF.c_str(),FinalF.c_str()) != 0)
 	    return _error->Errno("rename","Failed to rename");
-
-	 // Copy the release file
-	 snprintf(S,sizeof(S),"cdrom:[%s]/%sRelease",Name.c_str(),
-		  (*I).c_str() + CDROM.length());
-	 string TargetF = _config->FindDir("Dir::State::lists") + "partial/";
-	 TargetF += URItoFileName(S);
-	 if (FileExists(*I + "Release") == true)
-	 {
-	    FileFd Target(TargetF,FileFd::WriteEmpty);
-	    FileFd Rel(*I + "Release",FileFd::ReadOnly);
-	    if (_error->PendingError() == true)
-	       return false;
-	    
-	    if (CopyFile(Rel,Target) == false)
-	       return false;
-	 }
-	 else
-	 {
-	    // Empty release file
-	    FileFd Target(TargetF,FileFd::WriteEmpty);	    
-	 }	 
-
-	 // Rename the release file
-	 FinalF = _config->FindDir("Dir::State::lists");
-	 FinalF += URItoFileName(S);
-	 if (rename(TargetF.c_str(),FinalF.c_str()) != 0)
-	    return _error->Errno("rename","Failed to rename");
       }
-      
+	 
       /* Mangle the source to be in the proper notation with
        	 prefix dist [component] */ 
       *I = string(*I,Prefix.length());
@@ -267,23 +251,30 @@ bool IndexCopy::CopyPackages(string CDROM,string Name,vector<string> &List)
       
       CurrentSize += FileSize;
    }   
-   Progress.Done();
+   if(Progress)
+      Progress->Done();
    
    // Some stats
-   cout << "Wrote " << Packages << " records" ;
-   if (NotFound != 0)
-      cout << " with " << NotFound << " missing files";
-   if (NotFound != 0 && WrongSize != 0)
-      cout << " and"; 
-   if (WrongSize != 0)
-      cout << " with " << WrongSize << " mismatched files";
-   cout << '.' << endl;
+   if(log) {
+      stringstream msg;
+      if(NotFound == 0 && WrongSize == 0)
+	 ioprintf(msg, _("Wrote %i records.\n"), Packages);
+      else if (NotFound != 0 && WrongSize == 0)
+	 ioprintf(msg, _("Wrote %i records with %i missing files.\n"), 
+		  Packages, NotFound);
+      else if (NotFound == 0 && WrongSize != 0)
+	 ioprintf(msg, _("Wrote %i records with %i mismachted files\n"), 
+		  Packages, WrongSize);
+      if (NotFound != 0 && WrongSize != 0)
+	 ioprintf(msg, _("Wrote %i records with %i missing files and %i mismachted files\n"), Packages, NotFound, WrongSize);
+   }
    
    if (Packages == 0)
       _error->Warning("No valid records were found.");
 
    if (NotFound + WrongSize > 10)
-      cout << "Alot of entries were discarded, something may be wrong." << endl;
+      _error->Warning("Alot of entries were discarded, something may be wrong.\n");
+   
 
    return true;
 }
@@ -521,4 +512,133 @@ bool SourceCopy::RewriteEntry(FILE *Target,string File)
    fputc('\n',Target);
    return true;
 }
+
+
 									/*}}}*/
+
+bool SigVerify::Verify(string prefix, string file, indexRecords *MetaIndex)
+{
+   const indexRecords::checkSum *Record = MetaIndex->Lookup(file);
+
+   if (!Record) 
+   {
+      _error->Warning("Can't find authentication record for: %s",file.c_str());
+      return false;
+   }
+
+   MD5Summation sum;
+   FileFd Fd(prefix+file, FileFd::ReadOnly);
+   sum.AddFD(Fd.Fd(), Fd.Size());
+   Fd.Close();
+   string MD5 = (string)sum.Result();
+   
+   if (Record->MD5Hash != MD5)
+   {
+      _error->Warning("MD5 mismatch for: %s",file.c_str());
+      return false;
+   }
+
+   if(_config->FindB("Debug::aptcdrom",false)) 
+   {
+      cout << "File: " << prefix+file << endl;
+      cout << "Expected MD5sum: " << Record->MD5Hash << endl;
+      cout << "got: " << MD5 << endl << endl;
+   }
+
+   return true;
+}
+
+bool SigVerify::CopyMetaIndex(string CDROM, string CDName, 
+			      string prefix, string file)
+{
+      char S[400];
+      snprintf(S,sizeof(S),"cdrom:[%s]/%s%s",CDName.c_str(),
+	       (prefix).c_str() + CDROM.length(),file.c_str());
+      string TargetF = _config->FindDir("Dir::State::lists");
+      TargetF += URItoFileName(S);
+
+      FileFd Target;
+      FileFd Rel;
+      Target.Open(TargetF,FileFd::WriteEmpty);
+      Rel.Open(prefix + file,FileFd::ReadOnly);
+      if (_error->PendingError() == true)
+	 return false;
+      if (CopyFile(Rel,Target) == false)
+	 return false;
+   
+      return true;
+}
+
+bool SigVerify::CopyAndVerify(string CDROM,string Name,vector<string> &SigList,
+			      vector<string> PkgList,vector<string> SrcList)
+{
+   if (SigList.size() == 0)
+      return true;
+
+   bool Debug = _config->FindB("Debug::aptcdrom",false);
+
+   // Read all Release files
+   for (vector<string>::iterator I = SigList.begin(); I != SigList.end(); I++)
+   { 
+      if(Debug)
+	 cout << "Signature verify for: " << *I << endl;
+
+      indexRecords *MetaIndex = new indexRecords;
+      string prefix = *I; 
+
+      // a Release.gpg without a Release should never happen
+      if(!FileExists(*I+"Release"))
+	 continue;
+
+
+      // verify the gpg signature of "Release"
+      // gpg --verify "*I+Release.gpg", "*I+Release"
+      string gpgvpath = _config->Find("Dir::Bin::gpg", "/usr/bin/gpgv");
+      string pubringpath = _config->Find("Apt::GPGV::TrustedKeyring", "/etc/apt/trusted.gpg");
+      pid_t pid = ExecFork();
+      if(pid < 0) {
+	 _error->Error("Fork failed");
+	 return false;
+      }
+      if(pid == 0) {
+	 execlp(gpgvpath.c_str(), gpgvpath.c_str(), "--keyring", 
+		pubringpath.c_str(), string(*I+"Release.gpg").c_str(), 
+		string(*I+"Release").c_str(), NULL);
+      }
+      if(!ExecWait(pid, "gpgv")) {
+	 _error->Warning("Signature verification failed for: %s",
+			 string(*I+"Release.gpg").c_str());
+	 // something went wrong, don't copy the Release.gpg
+	 // FIXME: delete any existing gpg file?
+	 continue;
+      }
+
+      // Open the Release file and add it to the MetaIndex
+      if(!MetaIndex->Load(*I+"Release"))
+      {
+	 _error->Error(MetaIndex->ErrorText.c_str());
+	 return false;
+      }
+      
+      // go over the Indexfiles and see if they verify
+      // if so, remove them from our copy of the lists
+      vector<string> keys = MetaIndex->MetaKeys();
+      for (vector<string>::iterator I = keys.begin(); I != keys.end(); I++)
+      { 
+	 if(!Verify(prefix,*I, MetaIndex)) {
+	    // something went wrong, don't copy the Release.gpg
+	    // FIXME: delete any existing gpg file?
+	    continue;	 
+	 }
+      }
+
+      // we need a fresh one for the Release.gpg
+      delete MetaIndex;
+   
+      // everything was fine, copy the Release and Release.gpg file
+      CopyMetaIndex(CDROM, Name, prefix, "Release");
+      CopyMetaIndex(CDROM, Name, prefix, "Release.gpg");
+   }   
+
+   return true;
+}

+ 15 - 1
cmdline/indexcopy.h

@@ -19,6 +19,8 @@ using std::vector;
 
 class pkgTagSection;
 class FileFd;
+class indexRecords;
+class pkgCdromStatus;
 
 class IndexCopy
 {
@@ -39,7 +41,8 @@ class IndexCopy
    
    public:
 
-   bool CopyPackages(string CDROM,string Name,vector<string> &List);
+   bool CopyPackages(string CDROM,string Name,vector<string> &List,
+		     pkgCdromStatus *log);
 };
 
 class PackageCopy : public IndexCopy
@@ -66,4 +69,15 @@ class SourceCopy : public IndexCopy
    public:
 };
 
+class SigVerify 
+{
+   bool Verify(string prefix,string file, indexRecords *records);
+   bool CopyMetaIndex(string CDROM, string CDName, 
+		      string prefix, string file);
+
+ public:
+   bool CopyAndVerify(string CDROM,string Name,vector<string> &SigList,
+		      vector<string> PkgList,vector<string> SrcList);
+};
+
 #endif

+ 11 - 0
apt-pkg/indexrecords.cc

@@ -83,6 +83,17 @@ bool indexRecords::Load(const string Filename)
    return true;
 }
 
+vector<string> indexRecords::MetaKeys()
+{
+   std::vector<std::string> keys;
+   std::map<string,checkSum *>::iterator I = Entries.begin();
+   while(I != Entries.end()) {
+      keys.push_back((*I).first);
+      ++I;
+   }
+   return keys;
+}
+
 bool indexRecords::parseSumData(const char *&Start, const char *End,
 				   string &Name, string &Hash, size_t &Size)
 {

+ 3 - 1
apt-pkg/indexrecords.h

@@ -12,6 +12,7 @@
 #include <apt-pkg/fileutl.h>
 
 #include <map>
+#include <vector>
 
 class indexRecords
 {
@@ -34,7 +35,8 @@ class indexRecords
 
    // Lookup function
    virtual const checkSum *Lookup(const string MetaKey);
-   
+   std::vector<std::string> MetaKeys();
+
    virtual bool Load(string Filename);
    string GetDist() const;
    virtual bool CheckDist(const string MaybeDist) const;

+ 3 - 3
apt-pkg/makefile

@@ -13,7 +13,7 @@ include ../buildlib/defaults.mak
 # methods/makefile - FIXME
 LIBRARY=apt-pkg
 LIBEXT=$(GLIBC_VER)$(LIBSTDCPP_VER)
-MAJOR=3.8
+MAJOR=3.9
 MINOR=0
 SLIBS=$(PTHREADLIB) $(INTLLIBS)
 APT_DOMAIN:=libapt-pkg$(MAJOR)
@@ -34,14 +34,14 @@ SOURCE+= pkgcache.cc version.cc depcache.cc \
 	 acquire-worker.cc acquire-method.cc init.cc clean.cc \
 	 srcrecords.cc cachefile.cc versionmatch.cc policy.cc \
 	 pkgsystem.cc indexfile.cc pkgcachegen.cc acquire-item.cc \
-	 indexrecords.cc vendor.cc vendorlist.cc
+	 indexrecords.cc vendor.cc vendorlist.cc cdrom.cc indexcopy.cc
 HEADERS+= algorithms.h depcache.h pkgcachegen.h cacheiterators.h \
 	  orderlist.h sourcelist.h packagemanager.h tagfile.h \
 	  init.h pkgcache.h version.h progress.h pkgrecords.h \
 	  acquire.h acquire-worker.h acquire-item.h acquire-method.h \
 	  clean.h srcrecords.h cachefile.h versionmatch.h policy.h \
 	  pkgsystem.h indexfile.h metaindex.h indexrecords.h vendor.h \
-          vendorlist.h
+          vendorlist.h cdrom.h indexcopy.h
 
 # Source code for the debian specific components
 # In theory the deb headers do not need to be exported..

+ 55 - 648
cmdline/apt-cdrom.cc

@@ -18,10 +18,13 @@
 #include <apt-pkg/progress.h>
 #include <apt-pkg/cdromutl.h>
 #include <apt-pkg/strutl.h>
+#include <apt-pkg/acquire.h>
+#include <apt-pkg/acquire-item.h>
+#include <apt-pkg/cdrom.h>
 #include <config.h>
 #include <apti18n.h>
     
-#include "indexcopy.h"
+//#include "indexcopy.h"
 
 #include <locale.h>
 #include <iostream>
@@ -37,460 +40,65 @@
 
 using namespace std;
 
-// FindPackages - Find the package files on the CDROM			/*{{{*/
-// ---------------------------------------------------------------------
-/* We look over the cdrom for package files. This is a recursive
-   search that short circuits when it his a package file in the dir.
-   This speeds it up greatly as the majority of the size is in the
-   binary-* sub dirs. */
-bool FindPackages(string CD,vector<string> &List,vector<string> &SList,
-		  string &InfoDir,unsigned int Depth = 0)
+                                                                        /*{{{*/
+class pkgCdromTextStatus : public pkgCdromStatus
 {
-   static ino_t Inodes[9];
-   if (Depth >= 7)
-      return true;
-
-   if (CD[CD.length()-1] != '/')
-      CD += '/';   
-
-   if (chdir(CD.c_str()) != 0)
-      return _error->Errno("chdir","Unable to change to %s",CD.c_str());
-
-   // Look for a .disk subdirectory
-   struct stat Buf;
-   if (stat(".disk",&Buf) == 0)
-   {
-      if (InfoDir.empty() == true)
-	 InfoDir = CD + ".disk/";
-   }
-
-   // Don't look into directories that have been marked to ingore.
-   if (stat(".aptignr",&Buf) == 0)
-      return true;
-   
-   /* Aha! We found some package files. We assume that everything under 
-      this dir is controlled by those package files so we don't look down
-      anymore */
-   if (stat("Packages",&Buf) == 0 || stat("Packages.gz",&Buf) == 0)
-   {
-      List.push_back(CD);
-      
-      // Continue down if thorough is given
-      if (_config->FindB("APT::CDROM::Thorough",false) == false)
-	 return true;
-   }
-   if (stat("Sources.gz",&Buf) == 0 || stat("Sources",&Buf) == 0)
-   {
-      SList.push_back(CD);
-      
-      // Continue down if thorough is given
-      if (_config->FindB("APT::CDROM::Thorough",false) == false)
-	 return true;
-   }
-   
-   DIR *D = opendir(".");
-   if (D == 0)
-      return _error->Errno("opendir","Unable to read %s",CD.c_str());
-   
-   // Run over the directory
-   for (struct dirent *Dir = readdir(D); Dir != 0; Dir = readdir(D))
-   {
-      // Skip some files..
-      if (strcmp(Dir->d_name,".") == 0 ||
-	  strcmp(Dir->d_name,"..") == 0 ||
-	  //strcmp(Dir->d_name,"source") == 0 ||
-	  strcmp(Dir->d_name,".disk") == 0 ||
-	  strcmp(Dir->d_name,"experimental") == 0 ||
-	  strcmp(Dir->d_name,"binary-all") == 0 ||
-          strcmp(Dir->d_name,"debian-installer") == 0)
-	 continue;
-
-      // See if the name is a sub directory
-      struct stat Buf;
-      if (stat(Dir->d_name,&Buf) != 0)
-	 continue;      
-      
-      if (S_ISDIR(Buf.st_mode) == 0)
-	 continue;
-      
-      unsigned int I;
-      for (I = 0; I != Depth; I++)
-	 if (Inodes[I] == Buf.st_ino)
-	    break;
-      if (I != Depth)
-	 continue;
-      
-      // Store the inodes weve seen
-      Inodes[Depth] = Buf.st_ino;
-
-      // Descend
-      if (FindPackages(CD + Dir->d_name,List,SList,InfoDir,Depth+1) == false)
-	 break;
-
-      if (chdir(CD.c_str()) != 0)
-	 return _error->Errno("chdir","Unable to change to %s",CD.c_str());
-   };
-
-   closedir(D);
-   
-   return !_error->PendingError();
+protected:
+   OpTextProgress Progress;
+   void Prompt(const char *Text); 
+   string PromptLine(const char *Text);
+   bool AskCdromName(string &name);
+
+public:
+   virtual void Update(string text, int current);
+   virtual bool ChangeCdrom();
+   virtual OpProgress* GetOpProgress();
 }
-									/*}}}*/
-// DropBinaryArch - Dump dirs with a string like /binary-<foo>/		/*{{{*/
-// ---------------------------------------------------------------------
-/* Here we drop everything that is not this machines arch */
-bool DropBinaryArch(vector<string> &List)
-{
-   char S[300];
-   snprintf(S,sizeof(S),"/binary-%s/",
-	    _config->Find("Apt::Architecture").c_str());
-   
-   for (unsigned int I = 0; I < List.size(); I++)
-   {
-      const char *Str = List[I].c_str();
-      
-      const char *Res;
-      if ((Res = strstr(Str,"/binary-")) == 0)
-	 continue;
 
-      // Weird, remove it.
-      if (strlen(Res) < strlen(S))
-      {
-	 List.erase(List.begin() + I);
-	 I--;
-	 continue;
-      }
-	  
-      // See if it is our arch
-      if (stringcmp(Res,Res + strlen(S),S) == 0)
-	 continue;
-      
-      // Erase it
-      List.erase(List.begin() + I);
-      I--;
-   }
-   
-   return true;
-}
-									/*}}}*/
-// Score - We compute a 'score' for a path				/*{{{*/
-// ---------------------------------------------------------------------
-/* Paths are scored based on how close they come to what I consider
-   normal. That is ones that have 'dist' 'stable' 'testing' will score
-   higher than ones without. */
-int Score(string Path)
+void pkgCdromTextStatus::Prompt(const char *Text) 
 {
-   int Res = 0;
-   if (Path.find("stable/") != string::npos)
-      Res += 29;
-   if (Path.find("/binary-") != string::npos)
-      Res += 20;
-   if (Path.find("testing/") != string::npos)
-      Res += 28;
-   if (Path.find("unstable/") != string::npos)
-      Res += 27;
-   if (Path.find("/dists/") != string::npos)
-      Res += 40;
-   if (Path.find("/main/") != string::npos)
-      Res += 20;
-   if (Path.find("/contrib/") != string::npos)
-      Res += 20;
-   if (Path.find("/non-free/") != string::npos)
-      Res += 20;
-   if (Path.find("/non-US/") != string::npos)
-      Res += 20;
-   if (Path.find("/source/") != string::npos)
-      Res += 10;
-   if (Path.find("/debian/") != string::npos)
-      Res -= 10;
-   return Res;
-}
-									/*}}}*/
-// DropRepeats - Drop repeated files resulting from symlinks		/*{{{*/
-// ---------------------------------------------------------------------
-/* Here we go and stat every file that we found and strip dup inodes. */
-bool DropRepeats(vector<string> &List,const char *Name)
-{
-   // Get a list of all the inodes
-   ino_t *Inodes = new ino_t[List.size()];
-   for (unsigned int I = 0; I != List.size(); I++)
-   {
-      struct stat Buf;
-      if (stat((List[I] + Name).c_str(),&Buf) != 0 &&
-	  stat((List[I] + Name + ".gz").c_str(),&Buf) != 0)
-	 _error->Errno("stat","Failed to stat %s%s",List[I].c_str(),
-		       Name);
-      Inodes[I] = Buf.st_ino;
-   }
-   
-   if (_error->PendingError() == true)
-      return false;
-   
-   // Look for dups
-   for (unsigned int I = 0; I != List.size(); I++)
-   {
-      for (unsigned int J = I+1; J < List.size(); J++)
-      {
-	 // No match
-	 if (Inodes[J] != Inodes[I])
-	    continue;
-	 
-	 // We score the two paths.. and erase one
-	 int ScoreA = Score(List[I]);
-	 int ScoreB = Score(List[J]);
-	 if (ScoreA < ScoreB)
-	 {
-	    List[I] = string();
-	    break;
-	 }
-	 
-	 List[J] = string();
-      }
-   }  
- 
-   // Wipe erased entries
-   for (unsigned int I = 0; I < List.size();)
-   {
-      if (List[I].empty() == false)
-	 I++;
-      else
-	 List.erase(List.begin()+I);
-   }
-   
-   return true;
+   char C;
+   cout << Text << ' ' << flush;
+   read(STDIN_FILENO,&C,1);
+   if (C != '\n')
+      cout << endl;
 }
-									/*}}}*/
 
-// ReduceSourceList - Takes the path list and reduces it		/*{{{*/
-// ---------------------------------------------------------------------
-/* This takes the list of source list expressed entires and collects
-   similar ones to form a single entry for each dist */
-void ReduceSourcelist(string CD,vector<string> &List)
+string pkgCdromTextStatus::PromptLine(const char *Text)
 {
-   sort(List.begin(),List.end());
+   cout << Text << ':' << endl;
    
-   // Collect similar entries
-   for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
-   {
-      // Find a space..
-      string::size_type Space = (*I).find(' ');
-      if (Space == string::npos)
-	 continue;
-      string::size_type SSpace = (*I).find(' ',Space + 1);
-      if (SSpace == string::npos)
-	 continue;
-
-      string Word1 = string(*I,Space,SSpace-Space);
-      string Prefix = string(*I,0,Space);
-      for (vector<string>::iterator J = List.begin(); J != I; J++)
-      {
-	 // Find a space..
-	 string::size_type Space2 = (*J).find(' ');
-	 if (Space2 == string::npos)
-	    continue;
-	 string::size_type SSpace2 = (*J).find(' ',Space2 + 1);
-	 if (SSpace2 == string::npos)
-	    continue;
-	 
-	 if (string(*J,0,Space2) != Prefix)
-	    continue;
-	 if (string(*J,Space2,SSpace2-Space2) != Word1)
-	    continue;
-	 
-	 *J += string(*I,SSpace);
-	 *I = string();
-      }
-   }   
-
-   // Wipe erased entries
-   for (unsigned int I = 0; I < List.size();)
-   {
-      if (List[I].empty() == false)
-	 I++;
-      else
-	 List.erase(List.begin()+I);
-   }
+   string Res;
+   getline(cin,Res);
+   return Res;
 }
-									/*}}}*/
-// WriteDatabase - Write the CDROM Database file			/*{{{*/
-// ---------------------------------------------------------------------
-/* We rewrite the configuration class associated with the cdrom database. */
-bool WriteDatabase(Configuration &Cnf)
-{
-   string DFile = _config->FindFile("Dir::State::cdroms");
-   string NewFile = DFile + ".new";
-   
-   unlink(NewFile.c_str());
-   ofstream Out(NewFile.c_str());
-   if (!Out)
-      return _error->Errno("ofstream::ofstream",
-			   "Failed to open %s.new",DFile.c_str());
-   
-   /* Write out all of the configuration directives by walking the
-      configuration tree */
-   const Configuration::Item *Top = Cnf.Tree(0);
-   for (; Top != 0;)
-   {
-      // Print the config entry
-      if (Top->Value.empty() == false)
-	 Out <<  Top->FullTag() + " \"" << Top->Value << "\";" << endl;
-      
-      if (Top->Child != 0)
-      {
-	 Top = Top->Child;
-	 continue;
-      }
-      
-      while (Top != 0 && Top->Next == 0)
-	 Top = Top->Parent;
-      if (Top != 0)
-	 Top = Top->Next;
-   }   
 
-   Out.close();
-   
-   rename(DFile.c_str(),string(DFile + '~').c_str());
-   if (rename(NewFile.c_str(),DFile.c_str()) != 0)
-      return _error->Errno("rename","Failed to rename %s.new to %s",
-			   DFile.c_str(),DFile.c_str());
-
-   return true;
-}
-									/*}}}*/
-// WriteSourceList - Write an updated sourcelist			/*{{{*/
-// ---------------------------------------------------------------------
-/* This reads the old source list and copies it into the new one. It 
-   appends the new CDROM entires just after the first block of comments.
-   This places them first in the file. It also removes any old entries
-   that were the same. */
-bool WriteSourceList(string Name,vector<string> &List,bool Source)
+bool pkgCdromTextStatus::AskCdromName(string &name) 
 {
-   if (List.size() == 0)
-      return true;
-
-   string File = _config->FindFile("Dir::Etc::sourcelist");
-
-   // Open the stream for reading
-   ifstream F((FileExists(File)?File.c_str():"/dev/null"),
-	      ios::in );
-   if (!F != 0)
-      return _error->Errno("ifstream::ifstream","Opening %s",File.c_str());
-
-   string NewFile = File + ".new";
-   unlink(NewFile.c_str());
-   ofstream Out(NewFile.c_str());
-   if (!Out)
-      return _error->Errno("ofstream::ofstream",
-			   "Failed to open %s.new",File.c_str());
-
-   // Create a short uri without the path
-   string ShortURI = "cdrom:[" + Name + "]/";   
-   string ShortURI2 = "cdrom:" + Name + "/";     // For Compatibility
-
-   const char *Type;
-   if (Source == true)
-      Type = "deb-src";
-   else
-      Type = "deb";
-   
-   char Buffer[300];
-   int CurLine = 0;
-   bool First = true;
-   while (F.eof() == false)
-   {      
-      F.getline(Buffer,sizeof(Buffer));
-      CurLine++;
-      _strtabexpand(Buffer,sizeof(Buffer));
-      _strstrip(Buffer);
-            
-      // Comment or blank
-      if (Buffer[0] == '#' || Buffer[0] == 0)
-      {
-	 Out << Buffer << endl;
-	 continue;
-      }
-
-      if (First == true)
-      {
-	 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
-	 {
-	    string::size_type Space = (*I).find(' ');
-	    if (Space == string::npos)
-	       return _error->Error("Internal error");
-	    Out << Type << " cdrom:[" << Name << "]/" << string(*I,0,Space) <<
-	       " " << string(*I,Space+1) << endl;
-	 }
-      }
-      First = false;
-      
-      // Grok it
-      string cType;
-      string URI;
-      const char *C = Buffer;
-      if (ParseQuoteWord(C,cType) == false ||
-	  ParseQuoteWord(C,URI) == false)
-      {
-	 Out << Buffer << endl;
-	 continue;
-      }
-
-      // Emit lines like this one
-      if (cType != Type || (string(URI,0,ShortURI.length()) != ShortURI &&
-	  string(URI,0,ShortURI.length()) != ShortURI2))
-      {
-	 Out << Buffer << endl;
-	 continue;
-      }      
-   }
-   
-   // Just in case the file was empty
-   if (First == true)
-   {
-      for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
-      {
-	 string::size_type Space = (*I).find(' ');
-	 if (Space == string::npos)
-	    return _error->Error("Internal error");
+   cout << "Please provide a name for this Disc, such as 'Debian 2.1r1 Disk 1'" << flush;
+   name = PromptLine("");
 	 
-	 Out << "deb cdrom:[" << Name << "]/" << string(*I,0,Space) << 
-	    " " << string(*I,Space+1) << endl;
-      }
-   }
-   
-   Out.close();
-
-   rename(File.c_str(),string(File + '~').c_str());
-   if (rename(NewFile.c_str(),File.c_str()) != 0)
-      return _error->Errno("rename","Failed to rename %s.new to %s",
-			   File.c_str(),File.c_str());
-   
    return true;
 }
-									/*}}}*/
+   
 
-// Prompt - Simple prompt						/*{{{*/
-// ---------------------------------------------------------------------
-/* */
-void Prompt(const char *Text)
+void pkgCdromTextStatus::Update(string text, int current) 
 {
-   char C;
-   cout << Text << ' ' << flush;
-   read(STDIN_FILENO,&C,1);
-   if (C != '\n')
-      cout << endl;
+   if(text.size() > 0)
+      cout << text << flush;
 }
-									/*}}}*/
-// PromptLine - Prompt for an input line				/*{{{*/
-// ---------------------------------------------------------------------
-/* */
-string PromptLine(const char *Text)
+
+virtual bool pkgCdromTextStatus::ChangeCdrom() 
 {
-   cout << Text << ':' << endl;
-   
-   string Res;
-   getline(cin,Res);
-   return Res;
+   Prompt("Please insert a Disc in the drive and press enter");
+   return true;
 }
+
+virtual OpProgress* pkgCdromTextStatus::GetOpProgress() 
+{ 
+   return &Progress; 
+};
+
 									/*}}}*/
 
 // DoAdd - Add a new CDROM						/*{{{*/
@@ -501,186 +109,13 @@ string PromptLine(const char *Text)
    verify them. Then rewrite the database files */
 bool DoAdd(CommandLine &)
 {
-   // Startup
-   string CDROM = _config->FindDir("Acquire::cdrom::mount","/cdrom/");
-   if (CDROM[0] == '.')
-      CDROM= SafeGetCWD() + '/' + CDROM;
-   
-   cout << "Using CD-ROM mount point " << CDROM << endl;
-      
-   // Read the database
-   Configuration Database;
-   string DFile = _config->FindFile("Dir::State::cdroms");
-   if (FileExists(DFile) == true)
-   {
-      if (ReadConfigFile(Database,DFile) == false)
-	 return _error->Error("Unable to read the cdrom database %s",
-			      DFile.c_str());
-   }
-   
-   // Unmount the CD and get the user to put in the one they want
-   if (_config->FindB("APT::CDROM::NoMount",false) == false)
-   {
-      cout << "Unmounting CD-ROM" << endl;
-      UnmountCdrom(CDROM);
-
-      // Mount the new CDROM
-      Prompt("Please insert a Disc in the drive and press enter");
-      cout << "Mounting CD-ROM" << endl;
-      if (MountCdrom(CDROM) == false)
-	 return _error->Error("Failed to mount the cdrom.");
-   }
-   
-   // Hash the CD to get an ID
-   cout << "Identifying.. " << flush;
-   string ID;
-   if (IdentCdrom(CDROM,ID) == false)
-   {
-      cout << endl;
-      return false;
-   }
-   
-   cout << '[' << ID << ']' << endl;
-
-   cout << "Scanning Disc for index files..  " << flush;
-   // Get the CD structure
-   vector<string> List;
-   vector<string> sList;
-   string StartDir = SafeGetCWD();
-   string InfoDir;
-   if (FindPackages(CDROM,List,sList,InfoDir) == false)
-   {
-      cout << endl;
-      return false;
-   }
-   
-   chdir(StartDir.c_str());
-
-   if (_config->FindB("Debug::aptcdrom",false) == true)
-   {
-      cout << "I found (binary):" << endl;
-      for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
-	 cout << *I << endl;
-      cout << "I found (source):" << endl;
-      for (vector<string>::iterator I = sList.begin(); I != sList.end(); I++)
-	 cout << *I << endl;
-   }   
-   
-   // Fix up the list
-   DropBinaryArch(List);
-   DropRepeats(List,"Packages");
-   DropRepeats(sList,"Sources");
-   cout << "Found " << List.size() << " package indexes and " << sList.size() << 
-      " source indexes." << endl;
-
-   if (List.size() == 0 && sList.size() == 0)
-      return _error->Error("Unable to locate any package files, perhaps this is not a Debian Disc");
-   
-   // Check if the CD is in the database
-   string Name;
-   if (Database.Exists("CD::" + ID) == false ||
-       _config->FindB("APT::CDROM::Rename",false) == true)
-   {
-      // Try to use the CDs label if at all possible
-      if (InfoDir.empty() == false &&
-	  FileExists(InfoDir + "/info") == true)
-      {
-	 ifstream F(string(InfoDir + "/info").c_str());
-	 if (!F == 0)
-	    getline(F,Name);
-
-	 if (Name.empty() == false)
-	 {
-	    // Escape special characters
-	    string::iterator J = Name.begin();
-	    for (; J != Name.end(); J++)
-	       if (*J == '"' || *J == ']' || *J == '[')
-		  *J = '_';
-	    
-	    cout << "Found label '" << Name << "'" << endl;
-	    Database.Set("CD::" + ID + "::Label",Name);
-	 }	 
-      }
-      
-      if (_config->FindB("APT::CDROM::Rename",false) == true ||
-	  Name.empty() == true)
-      {
-	 cout << "Please provide a name for this Disc, such as 'Debian 2.1r1 Disk 1'";
-	 while (1)
-	 {
-	    Name = PromptLine("");
-	    if (Name.empty() == false &&
-		Name.find('"') == string::npos &&
-		Name.find('[') == string::npos &&
-		Name.find(']') == string::npos)
-	       break;
-	    cout << "That is not a valid name, try again " << endl;
-	 }	 
-      }      
-   }
-   else
-      Name = Database.Find("CD::" + ID);
-
-   // Escape special characters
-   string::iterator J = Name.begin();
-   for (; J != Name.end(); J++)
-      if (*J == '"' || *J == ']' || *J == '[')
-	 *J = '_';
-   
-   Database.Set("CD::" + ID,Name);
-   cout << "This Disc is called:" << endl << " '" << Name << "'" << endl;
-   
-   // Copy the package files to the state directory
-   PackageCopy Copy;
-   SourceCopy SrcCopy;
-   if (Copy.CopyPackages(CDROM,Name,List) == false ||
-       SrcCopy.CopyPackages(CDROM,Name,sList) == false)
-      return false;
-   
-   ReduceSourcelist(CDROM,List);
-   ReduceSourcelist(CDROM,sList);
-
-   // Write the database and sourcelist
-   if (_config->FindB("APT::cdrom::NoAct",false) == false)
-   {
-      if (WriteDatabase(Database) == false)
-	 return false;
-      
-      cout << "Writing new source list" << endl;
-      if (WriteSourceList(Name,List,false) == false ||
-	  WriteSourceList(Name,sList,true) == false)
-	 return false;
-   }
-
-   // Print the sourcelist entries
-   cout << "Source List entries for this Disc are:" << endl;
-   for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
-   {
-      string::size_type Space = (*I).find(' ');
-      if (Space == string::npos)
-	 return _error->Error("Internal error");
-
-      cout << "deb cdrom:[" << Name << "]/" << string(*I,0,Space) << 
-	 " " << string(*I,Space+1) << endl;
-   }
-
-   for (vector<string>::iterator I = sList.begin(); I != sList.end(); I++)
-   {
-      string::size_type Space = (*I).find(' ');
-      if (Space == string::npos)
-	 return _error->Error("Internal error");
-
-      cout << "deb-src cdrom:[" << Name << "]/" << string(*I,0,Space) << 
-	 " " << string(*I,Space+1) << endl;
-   }
-
-   cout << "Repeat this process for the rest of the CDs in your set." << endl;
-
-   // Unmount and finish
-   if (_config->FindB("APT::CDROM::NoMount",false) == false)
-      UnmountCdrom(CDROM);
-   
-   return true;
+   bool res = false;
+   pkgCdromTextStatus log;
+   pkgCdrom cdrom;
+   res = cdrom.Add(&log);
+   if(res)
+      cout << "Repeat this process for the rest of the CDs in your set." << endl;
+   return res;
 }
 									/*}}}*/
 // DoIdent - Ident a CDROM						/*{{{*/
@@ -688,38 +123,10 @@ bool DoAdd(CommandLine &)
 /* */
 bool DoIdent(CommandLine &)
 {
-   // Startup
-   string CDROM = _config->FindDir("Acquire::cdrom::mount","/cdrom/");
-   if (CDROM[0] == '.')
-      CDROM= SafeGetCWD() + '/' + CDROM;
-   
-   cout << "Using CD-ROM mount point " << CDROM << endl;
-   cout << "Mounting CD-ROM" << endl;
-   if (MountCdrom(CDROM) == false)
-      return _error->Error("Failed to mount the cdrom.");
-   
-   // Hash the CD to get an ID
-   cout << "Identifying.. " << flush;
-   string ID;
-   if (IdentCdrom(CDROM,ID) == false)
-   {
-      cout << endl;
-      return false;
-   }
-   
-   cout << '[' << ID << ']' << endl;
-
-   // Read the database
-   Configuration Database;
-   string DFile = _config->FindFile("Dir::State::cdroms");
-   if (FileExists(DFile) == true)
-   {
-      if (ReadConfigFile(Database,DFile) == false)
-	 return _error->Error("Unable to read the cdrom database %s",
-			      DFile.c_str());
-   }
-   cout << "Stored Label: '" << Database.Find("CD::" + ID) << "'" << endl;
-   return true;
+   string ident;
+   pkgCdromTextStatus log;
+   pkgCdrom cdrom;
+   return cdrom.Ident(ident, &log);
 }
 									/*}}}*/
 

+ 1 - 1
cmdline/makefile

@@ -30,7 +30,7 @@ include $(PROGRAM_H)
 PROGRAM=apt-cdrom
 SLIBS = -lapt-pkg
 LIB_MAKES = apt-pkg/makefile
-SOURCE = apt-cdrom.cc indexcopy.cc
+SOURCE = apt-cdrom.cc 
 include $(PROGRAM_H)
 
 # The apt-sortpkgs program

+ 10 - 6
debian/changelog

@@ -1,11 +1,15 @@
 apt (0.6.31) hoary; urgency=low
 
-  * Remove debugging output from apt.cron.daily (no one noticed?)
-  * Apply patch from Anthony Towns to allow SHA1Summation to process a file
-    descriptor until EOF, rather than requiring that the length of input be
-    specified (Closes: #291338)
-  * Fix build/install of Polish offline documentation, based on patch from
-    Christian Perrier (Closes: #270404)
+  * Matt Zimmerman
+    - Remove debugging output from apt.cron.daily (no one noticed?)
+    - Apply patch from Anthony Towns to allow SHA1Summation to process a file
+      descriptor until EOF, rather than requiring that the length of input be
+      specified (Closes: #291338)
+    - Fix build/install of Polish offline documentation, based on patch from
+      Christian Perrier (Closes: #270404)
+  * Michael Vogt
+    - apt-cdrom.cc seperated into frontend (cmdline/apt-cdrom.cc and library
+      apt-pkg/cdrom.{cc,h}) (Ubuntu #5668)
 
  -- Matt Zimmerman <mdz@ubuntu.com>  Fri,  4 Feb 2005 10:23:01 -0800
 

+ 97 - 7
po/apt-all.pot

@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2004-12-20 10:20+0100\n"
+"POT-Creation-Date: 2005-01-26 18:00+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -146,7 +146,7 @@ msgstr ""
 msgid "       %4i %s\n"
 msgstr ""
 
-#: cmdline/apt-cache.cc:1651 cmdline/apt-cdrom.cc:731 cmdline/apt-config.cc:70
+#: cmdline/apt-cache.cc:1651 cmdline/apt-cdrom.cc:353 cmdline/apt-config.cc:70
 #: cmdline/apt-extracttemplates.cc:225 ftparchive/apt-ftparchive.cc:545
 #: cmdline/apt-get.cc:2313 cmdline/apt-sortpkgs.cc:144
 #, c-format
@@ -2259,31 +2259,31 @@ msgstr ""
 msgid "rename failed, %s (%s -> %s)."
 msgstr ""
 
-#: apt-pkg/acquire-item.cc:235 apt-pkg/acquire-item.cc:897
+#: apt-pkg/acquire-item.cc:235 apt-pkg/acquire-item.cc:904
 msgid "MD5Sum mismatch"
 msgstr ""
 
-#: apt-pkg/acquire-item.cc:711
+#: apt-pkg/acquire-item.cc:718
 #, c-format
 msgid ""
 "I wasn't able to locate a file for the %s package. This might mean you need "
 "to manually fix this package. (due to missing arch)"
 msgstr ""
 
-#: apt-pkg/acquire-item.cc:764
+#: apt-pkg/acquire-item.cc:771
 #, c-format
 msgid ""
 "I wasn't able to locate file for the %s package. This might mean you need to "
 "manually fix this package."
 msgstr ""
 
-#: apt-pkg/acquire-item.cc:800
+#: apt-pkg/acquire-item.cc:807
 #, c-format
 msgid ""
 "The package index files are corrupted. No Filename: field for package %s."
 msgstr ""
 
-#: apt-pkg/acquire-item.cc:887
+#: apt-pkg/acquire-item.cc:894
 msgid "Size mismatch"
 msgstr ""
 
@@ -2291,3 +2291,93 @@ msgstr ""
 #, c-format
 msgid "Vendor block %s contains no fingerprint"
 msgstr ""
+
+#: apt-pkg/cdrom.cc:487
+#, c-format
+msgid ""
+"Using CD-ROM mount point %s\n"
+"Mounting CD-ROM\n"
+msgstr ""
+
+#: apt-pkg/cdrom.cc:496 apt-pkg/cdrom.cc:578
+msgid "Identifying.. "
+msgstr ""
+
+#: apt-pkg/cdrom.cc:521
+#, c-format
+msgid "Stored Label: %s \n"
+msgstr ""
+
+#: apt-pkg/cdrom.cc:541
+#, c-format
+msgid "Using CD-ROM mount point %s\n"
+msgstr ""
+
+#: apt-pkg/cdrom.cc:559
+msgid "Unmounting CD-ROM\n"
+msgstr ""
+
+#: apt-pkg/cdrom.cc:563
+msgid "Waiting for disc...\n"
+msgstr ""
+
+#. Mount the new CDROM
+#: apt-pkg/cdrom.cc:571
+msgid "Mounting CD-ROM...\n"
+msgstr ""
+
+#: apt-pkg/cdrom.cc:589
+msgid "Scanning Disc for index files..\n"
+msgstr ""
+
+#: apt-pkg/cdrom.cc:627
+#, c-format
+msgid "Found %i package indexes, %i source indexes and %i signatures\n"
+msgstr ""
+
+#: apt-pkg/cdrom.cc:684
+msgid "That is not a valid name, try again.\n"
+msgstr ""
+
+#: apt-pkg/cdrom.cc:700
+#, c-format
+msgid ""
+"This Disc is called: \n"
+"'%s'\n"
+msgstr ""
+
+#: apt-pkg/cdrom.cc:704
+msgid "Copying package lists..."
+msgstr ""
+
+#: apt-pkg/cdrom.cc:728
+msgid "Writing new source list\n"
+msgstr ""
+
+#: apt-pkg/cdrom.cc:737
+msgid "Source List entries for this Disc are:\n"
+msgstr ""
+
+#: apt-pkg/cdrom.cc:771
+msgid "Unmounting CD-ROM..."
+msgstr ""
+
+#: apt-pkg/indexcopy.cc:261
+#, c-format
+msgid "Wrote %i records.\n"
+msgstr ""
+
+#: apt-pkg/indexcopy.cc:263
+#, c-format
+msgid "Wrote %i records with %i missing files.\n"
+msgstr ""
+
+#: apt-pkg/indexcopy.cc:266
+#, c-format
+msgid "Wrote %i records with %i mismachted files\n"
+msgstr ""
+
+#: apt-pkg/indexcopy.cc:269
+#, c-format
+msgid "Wrote %i records with %i missing files and %i mismachted files\n"
+msgstr ""