]>
git.saurik.com Git - apt.git/blob - apt-pkg/indexcopy.cc
1 // -*- mode: cpp; mode: fold -*-
3 // $Id: indexcopy.cc,v 1.10 2002/03/26 07:38:58 jgg Exp $
4 /* ######################################################################
6 Index Copying - Aid for copying and verifying the index files
8 This class helps apt-cache reconstruct a damaged index files.
10 ##################################################################### */
12 // Include Files /*{{{*/
15 #include <apt-pkg/error.h>
16 #include <apt-pkg/progress.h>
17 #include <apt-pkg/strutl.h>
18 #include <apt-pkg/fileutl.h>
19 #include <apt-pkg/aptconfiguration.h>
20 #include <apt-pkg/configuration.h>
21 #include <apt-pkg/tagfile.h>
22 #include <apt-pkg/indexrecords.h>
23 #include <apt-pkg/md5.h>
24 #include <apt-pkg/cdrom.h>
30 #include <sys/types.h>
35 #include "indexcopy.h"
41 // IndexCopy::CopyPackages - Copy the package files from the CD /*{{{*/
42 // ---------------------------------------------------------------------
44 bool IndexCopy::CopyPackages(string CDROM
,string Name
,vector
<string
> &List
,
47 OpProgress
*Progress
= NULL
;
48 if (List
.empty() == true)
52 Progress
= log
->GetOpProgress();
54 bool NoStat
= _config
->FindB("APT::CDROM::Fast",false);
55 bool Debug
= _config
->FindB("Debug::aptcdrom",false);
57 // Prepare the progress indicator
59 std::vector
<APT::Configuration::Compressor
> const compressor
= APT::Configuration::getCompressors();
60 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); ++I
)
64 std::string file
= std::string(*I
).append(GetFileName());
65 for (std::vector
<APT::Configuration::Compressor
>::const_iterator c
= compressor
.begin();
66 c
!= compressor
.end(); ++c
)
68 if (stat(std::string(file
+ c
->Extension
).c_str(), &Buf
) != 0)
75 return _error
->Errno("stat", "Stat failed for %s", file
.c_str());
76 TotalSize
+= Buf
.st_size
;
79 off_t CurrentSize
= 0;
80 unsigned int NotFound
= 0;
81 unsigned int WrongSize
= 0;
82 unsigned int Packages
= 0;
83 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); ++I
)
85 string OrigPath
= string(*I
,CDROM
.length());
87 // Open the package file
88 FileFd
Pkg(*I
+ GetFileName(), FileFd::ReadOnly
, FileFd::Auto
);
89 off_t
const FileSize
= Pkg
.Size();
91 pkgTagFile
Parser(&Pkg
);
92 if (_error
->PendingError() == true)
95 // Open the output file
97 snprintf(S
,sizeof(S
),"cdrom:[%s]/%s%s",Name
.c_str(),
98 (*I
).c_str() + CDROM
.length(),GetFileName());
99 string TargetF
= _config
->FindDir("Dir::State::lists") + "partial/";
100 TargetF
+= URItoFileName(S
);
102 if (_config
->FindB("APT::CDROM::NoAct",false) == true)
104 TargetF
= "/dev/null";
105 Target
.Open(TargetF
,FileFd::WriteExists
);
107 Target
.Open(TargetF
,FileFd::WriteAtomic
);
109 FILE *TargetFl
= fdopen(dup(Target
.Fd()),"w");
110 if (_error
->PendingError() == true)
113 return _error
->Errno("fdopen","Failed to reopen fd");
115 // Setup the progress meter
117 Progress
->OverallProgress(CurrentSize
,TotalSize
,FileSize
,
118 string("Reading ") + Type() + " Indexes");
122 Progress
->SubProgress(Pkg
.Size());
123 pkgTagSection Section
;
124 this->Section
= &Section
;
126 unsigned long Hits
= 0;
127 unsigned long Chop
= 0;
128 while (Parser
.Step(Section
) == true)
131 Progress
->Progress(Parser
.Offset());
133 unsigned long long Size
;
134 if (GetFile(File
,Size
) == false)
141 File
= OrigPath
+ ChopDirs(File
,Chop
);
143 // See if the file exists
144 bool Mangled
= false;
145 if (NoStat
== false || Hits
< 10)
147 // Attempt to fix broken structure
150 if (ReconstructPrefix(Prefix
,OrigPath
,CDROM
,File
) == false &&
151 ReconstructChop(Chop
,*I
,File
) == false)
154 clog
<< "Missed: " << File
<< endl
;
159 File
= OrigPath
+ ChopDirs(File
,Chop
);
164 if (stat(string(CDROM
+ Prefix
+ File
).c_str(),&Buf
) != 0 ||
167 // Attempt to fix busted symlink support for one instance
168 string OrigFile
= File
;
169 string::size_type Start
= File
.find("binary-");
170 string::size_type End
= File
.find("/",Start
+3);
171 if (Start
!= string::npos
&& End
!= string::npos
)
173 File
.replace(Start
,End
-Start
,"binary-all");
177 if (Mangled
== false ||
178 stat(string(CDROM
+ Prefix
+ File
).c_str(),&Buf
) != 0)
181 clog
<< "Missed(2): " << OrigFile
<< endl
;
188 if ((unsigned long long)Buf
.st_size
!= Size
)
191 clog
<< "Wrong Size: " << File
<< endl
;
200 if (RewriteEntry(TargetFl
,File
) == false)
209 cout
<< " Processed by using Prefix '" << Prefix
<< "' and chop " << Chop
<< endl
;
211 if (_config
->FindB("APT::CDROM::NoAct",false) == false)
213 // Move out of the partial directory
215 string FinalF
= _config
->FindDir("Dir::State::lists");
216 FinalF
+= URItoFileName(S
);
217 if (rename(TargetF
.c_str(),FinalF
.c_str()) != 0)
218 return _error
->Errno("rename","Failed to rename");
221 /* Mangle the source to be in the proper notation with
222 prefix dist [component] */
223 *I
= string(*I
,Prefix
.length());
224 ConvertToSourceList(CDROM
,*I
);
225 *I
= Prefix
+ ' ' + *I
;
227 CurrentSize
+= FileSize
;
235 if(NotFound
== 0 && WrongSize
== 0)
236 ioprintf(msg
, _("Wrote %i records.\n"), Packages
);
237 else if (NotFound
!= 0 && WrongSize
== 0)
238 ioprintf(msg
, _("Wrote %i records with %i missing files.\n"),
240 else if (NotFound
== 0 && WrongSize
!= 0)
241 ioprintf(msg
, _("Wrote %i records with %i mismatched files\n"),
242 Packages
, WrongSize
);
243 if (NotFound
!= 0 && WrongSize
!= 0)
244 ioprintf(msg
, _("Wrote %i records with %i missing files and %i mismatched files\n"), Packages
, NotFound
, WrongSize
);
248 _error
->Warning("No valid records were found.");
250 if (NotFound
+ WrongSize
> 10)
251 _error
->Warning("A lot of entries were discarded, something may be wrong.\n");
257 // IndexCopy::ChopDirs - Chop off the leading directory components /*{{{*/
258 // ---------------------------------------------------------------------
260 string
IndexCopy::ChopDirs(string Path
,unsigned int Depth
)
262 string::size_type I
= 0;
265 I
= Path
.find('/',I
+1);
268 while (I
!= string::npos
&& Depth
!= 0);
270 if (I
== string::npos
)
273 return string(Path
,I
+1);
276 // IndexCopy::ReconstructPrefix - Fix strange prefixing /*{{{*/
277 // ---------------------------------------------------------------------
278 /* This prepends dir components from the path to the package files to
279 the path to the deb until it is found */
280 bool IndexCopy::ReconstructPrefix(string
&Prefix
,string OrigPath
,string CD
,
283 bool Debug
= _config
->FindB("Debug::aptcdrom",false);
284 unsigned int Depth
= 1;
285 string MyPrefix
= Prefix
;
289 if (stat(string(CD
+ MyPrefix
+ File
).c_str(),&Buf
) != 0)
292 cout
<< "Failed, " << CD
+ MyPrefix
+ File
<< endl
;
293 if (GrabFirst(OrigPath
,MyPrefix
,Depth
++) == true)
307 // IndexCopy::ReconstructChop - Fixes bad source paths /*{{{*/
308 // ---------------------------------------------------------------------
309 /* This removes path components from the filename and prepends the location
310 of the package files until a file is found */
311 bool IndexCopy::ReconstructChop(unsigned long &Chop
,string Dir
,string File
)
313 // Attempt to reconstruct the filename
314 unsigned long Depth
= 0;
318 if (stat(string(Dir
+ File
).c_str(),&Buf
) != 0)
320 File
= ChopDirs(File
,1);
322 if (File
.empty() == false)
335 // IndexCopy::ConvertToSourceList - Convert a Path to a sourcelist /*{{{*/
336 // ---------------------------------------------------------------------
337 /* We look for things in dists/ notation and convert them to
338 <dist> <component> form otherwise it is left alone. This also strips
341 This implements a regex sort of like:
342 (.*)/dists/([^/]*)/(.*)/binary-*
344 | |-------- Distribution
345 |------------------- Path
347 It was deciced to use only a single word for dist (rather than say
348 unstable/non-us) to increase the chance that each CD gets a single
349 line in sources.list.
351 void IndexCopy::ConvertToSourceList(string CD
,string
&Path
)
354 snprintf(S
,sizeof(S
),"binary-%s",_config
->Find("Apt::Architecture").c_str());
356 // Strip the cdrom base path
357 Path
= string(Path
,CD
.length());
358 if (Path
.empty() == true)
361 // Too short to be a dists/ type
362 if (Path
.length() < strlen("dists/"))
366 if (stringcmp(Path
.c_str(),Path
.c_str()+strlen("dists/"),"dists/") != 0)
370 string::size_type Slash
= strlen("dists/");
371 string::size_type Slash2
= Path
.find('/',Slash
+ 1);
372 if (Slash2
== string::npos
|| Slash2
+ 2 >= Path
.length())
374 string Dist
= string(Path
,Slash
,Slash2
- Slash
);
376 // Isolate the component
378 for (unsigned I
= 0; I
!= 10; I
++)
380 Slash
= Path
.find('/',Slash
+1);
381 if (Slash
== string::npos
|| Slash
+ 2 >= Path
.length())
383 string Comp
= string(Path
,Slash2
+1,Slash
- Slash2
-1);
385 // Verify the trailing binary- bit
386 string::size_type BinSlash
= Path
.find('/',Slash
+ 1);
387 if (Slash
== string::npos
)
389 string Binary
= string(Path
,Slash
+1,BinSlash
- Slash
-1);
391 if (Binary
!= S
&& Binary
!= "source")
394 Path
= Dist
+ ' ' + Comp
;
399 // IndexCopy::GrabFirst - Return the first Depth path components /*{{{*/
400 // ---------------------------------------------------------------------
402 bool IndexCopy::GrabFirst(string Path
,string
&To
,unsigned int Depth
)
404 string::size_type I
= 0;
407 I
= Path
.find('/',I
+1);
410 while (I
!= string::npos
&& Depth
!= 0);
412 if (I
== string::npos
)
415 To
= string(Path
,0,I
+1);
419 // PackageCopy::GetFile - Get the file information from the section /*{{{*/
420 // ---------------------------------------------------------------------
422 bool PackageCopy::GetFile(string
&File
,unsigned long long &Size
)
424 File
= Section
->FindS("Filename");
425 Size
= Section
->FindI("Size");
426 if (File
.empty() || Size
== 0)
427 return _error
->Error("Cannot find filename or size tag");
431 // PackageCopy::RewriteEntry - Rewrite the entry with a new filename /*{{{*/
432 // ---------------------------------------------------------------------
434 bool PackageCopy::RewriteEntry(FILE *Target
,string File
)
436 TFRewriteData Changes
[] = {{"Filename",File
.c_str()},
439 if (TFRewrite(Target
,*Section
,TFRewritePackageOrder
,Changes
) == false)
445 // SourceCopy::GetFile - Get the file information from the section /*{{{*/
446 // ---------------------------------------------------------------------
448 bool SourceCopy::GetFile(string
&File
,unsigned long long &Size
)
450 string Files
= Section
->FindS("Files");
451 if (Files
.empty() == true)
454 // Stash the / terminated directory prefix
455 string Base
= Section
->FindS("Directory");
456 if (Base
.empty() == false && Base
[Base
.length()-1] != '/')
459 // Read the first file triplet
460 const char *C
= Files
.c_str();
464 // Parse each of the elements
465 if (ParseQuoteWord(C
,MD5Hash
) == false ||
466 ParseQuoteWord(C
,sSize
) == false ||
467 ParseQuoteWord(C
,File
) == false)
468 return _error
->Error("Error parsing file record");
470 // Parse the size and append the directory
471 Size
= strtoull(sSize
.c_str(), NULL
, 10);
476 // SourceCopy::RewriteEntry - Rewrite the entry with a new filename /*{{{*/
477 // ---------------------------------------------------------------------
479 bool SourceCopy::RewriteEntry(FILE *Target
,string File
)
481 string
Dir(File
,0,File
.rfind('/'));
482 TFRewriteData Changes
[] = {{"Directory",Dir
.c_str()},
485 if (TFRewrite(Target
,*Section
,TFRewriteSourceOrder
,Changes
) == false)
491 // SigVerify::Verify - Verify a files md5sum against its metaindex /*{{{*/
492 // ---------------------------------------------------------------------
494 bool SigVerify::Verify(string prefix
, string file
, indexRecords
*MetaIndex
)
496 const indexRecords::checkSum
*Record
= MetaIndex
->Lookup(file
);
498 // we skip non-existing files in the verifcation to support a cdrom
499 // with no Packages file (just a Package.gz), see LP: #255545
500 // (non-existing files are not considered a error)
501 if(!RealFileExists(prefix
+file
))
503 _error
->Warning(_("Skipping nonexistent file %s"), string(prefix
+file
).c_str());
509 _error
->Warning(_("Can't find authentication record for: %s"), file
.c_str());
513 if (!Record
->Hash
.VerifyFile(prefix
+file
))
515 _error
->Warning(_("Hash mismatch for: %s"),file
.c_str());
519 if(_config
->FindB("Debug::aptcdrom",false))
521 cout
<< "File: " << prefix
+file
<< endl
;
522 cout
<< "Expected Hash " << Record
->Hash
.toStr() << endl
;
528 bool SigVerify::CopyMetaIndex(string CDROM
, string CDName
, /*{{{*/
529 string prefix
, string file
)
532 snprintf(S
,sizeof(S
),"cdrom:[%s]/%s%s",CDName
.c_str(),
533 (prefix
).c_str() + CDROM
.length(),file
.c_str());
534 string TargetF
= _config
->FindDir("Dir::State::lists");
535 TargetF
+= URItoFileName(S
);
539 Target
.Open(TargetF
,FileFd::WriteAtomic
);
540 Rel
.Open(prefix
+ file
,FileFd::ReadOnly
);
541 if (_error
->PendingError() == true)
543 if (CopyFile(Rel
,Target
) == false)
549 bool SigVerify::CopyAndVerify(string CDROM
,string Name
,vector
<string
> &SigList
, /*{{{*/
550 vector
<string
> PkgList
,vector
<string
> SrcList
)
552 if (SigList
.empty() == true)
555 bool Debug
= _config
->FindB("Debug::aptcdrom",false);
557 // Read all Release files
558 for (vector
<string
>::iterator I
= SigList
.begin(); I
!= SigList
.end(); ++I
)
561 cout
<< "Signature verify for: " << *I
<< endl
;
563 indexRecords
*MetaIndex
= new indexRecords
;
566 string
const releasegpg
= *I
+"Release.gpg";
567 string
const release
= *I
+"Release";
568 string
const inrelease
= *I
+"InRelease";
569 bool useInRelease
= true;
571 // a Release.gpg without a Release should never happen
572 if (RealFileExists(inrelease
) == true)
574 else if(RealFileExists(release
) == false || RealFileExists(releasegpg
) == false)
580 useInRelease
= false;
582 pid_t pid
= ExecFork();
584 _error
->Error("Fork failed");
589 if (useInRelease
== true)
590 RunGPGV(inrelease
, inrelease
);
592 RunGPGV(release
, releasegpg
);
595 if(!ExecWait(pid
, "gpgv")) {
596 _error
->Warning("Signature verification failed for: %s",
597 (useInRelease
? inrelease
.c_str() : releasegpg
.c_str()));
598 // something went wrong, don't copy the Release.gpg
599 // FIXME: delete any existing gpg file?
603 // Open the Release file and add it to the MetaIndex
604 if(!MetaIndex
->Load(release
))
606 _error
->Error("%s",MetaIndex
->ErrorText
.c_str());
610 // go over the Indexfiles and see if they verify
611 // if so, remove them from our copy of the lists
612 vector
<string
> keys
= MetaIndex
->MetaKeys();
613 for (vector
<string
>::iterator I
= keys
.begin(); I
!= keys
.end(); ++I
)
615 if(!Verify(prefix
,*I
, MetaIndex
)) {
616 // something went wrong, don't copy the Release.gpg
617 // FIXME: delete any existing gpg file?
623 // we need a fresh one for the Release.gpg
626 // everything was fine, copy the Release and Release.gpg file
627 if (useInRelease
== true)
628 CopyMetaIndex(CDROM
, Name
, prefix
, "InRelease");
631 CopyMetaIndex(CDROM
, Name
, prefix
, "Release");
632 CopyMetaIndex(CDROM
, Name
, prefix
, "Release.gpg");
639 // SigVerify::RunGPGV - returns the command needed for verify /*{{{*/
640 // ---------------------------------------------------------------------
641 /* Generating the commandline for calling gpgv is somehow complicated as
642 we need to add multiple keyrings and user supplied options. Also, as
643 the cdrom code currently can not use the gpgv method we have two places
644 these need to be done - so the place for this method is wrong but better
645 than code duplication… */
646 bool SigVerify::RunGPGV(std::string
const &File
, std::string
const &FileGPG
,
647 int const &statusfd
, int fd
[2])
651 FILE* gpg
= fopen(File
.c_str(), "r");
653 return _error
->Errno("RunGPGV", _("Could not open file %s"), File
.c_str());
655 if (!IsPgpClearTextSignature(File
))
656 return _error
->Error(_("File %s doesn't start with a clearsigned message"), File
.c_str());
660 string
const gpgvpath
= _config
->Find("Dir::Bin::gpg", "/usr/bin/gpgv");
661 // FIXME: remove support for deprecated APT::GPGV setting
662 string
const trustedFile
= _config
->Find("APT::GPGV::TrustedKeyring", _config
->FindFile("Dir::Etc::Trusted"));
663 string
const trustedPath
= _config
->FindDir("Dir::Etc::TrustedParts");
665 bool const Debug
= _config
->FindB("Debug::Acquire::gpgv", false);
669 std::clog
<< "gpgv path: " << gpgvpath
<< std::endl
;
670 std::clog
<< "Keyring file: " << trustedFile
<< std::endl
;
671 std::clog
<< "Keyring path: " << trustedPath
<< std::endl
;
674 std::vector
<string
> keyrings
;
675 if (DirectoryExists(trustedPath
))
676 keyrings
= GetListOfFilesInDir(trustedPath
, "gpg", false, true);
677 if (RealFileExists(trustedFile
) == true)
678 keyrings
.push_back(trustedFile
);
680 std::vector
<const char *> Args
;
683 if (keyrings
.empty() == true)
685 // TRANSLATOR: %s is the trusted keyring parts directory
686 return _error
->Error(_("No keyring installed in %s."),
687 _config
->FindDir("Dir::Etc::TrustedParts").c_str());
690 Args
.push_back(gpgvpath
.c_str());
691 Args
.push_back("--ignore-time-conflict");
695 Args
.push_back("--status-fd");
697 snprintf(fd
, sizeof(fd
), "%i", statusfd
);
701 for (vector
<string
>::const_iterator K
= keyrings
.begin();
702 K
!= keyrings
.end(); ++K
)
704 Args
.push_back("--keyring");
705 Args
.push_back(K
->c_str());
708 Configuration::Item
const *Opts
;
709 Opts
= _config
->Tree("Acquire::gpgv::Options");
713 for (; Opts
!= 0; Opts
= Opts
->Next
)
715 if (Opts
->Value
.empty() == true)
717 Args
.push_back(Opts
->Value
.c_str());
721 Args
.push_back(FileGPG
.c_str());
723 Args
.push_back(File
.c_str());
724 Args
.push_back(NULL
);
728 std::clog
<< "Preparing to exec: " << gpgvpath
;
729 for (std::vector
<const char *>::const_iterator a
= Args
.begin(); *a
!= NULL
; ++a
)
730 std::clog
<< " " << *a
;
731 std::clog
<< std::endl
;
736 int const nullfd
= open("/dev/null", O_RDONLY
);
738 // Redirect output to /dev/null; we read from the status fd
739 dup2(nullfd
, STDOUT_FILENO
);
740 dup2(nullfd
, STDERR_FILENO
);
741 // Redirect the pipe to the status fd (3)
742 dup2(fd
[1], statusfd
);
744 putenv((char *)"LANG=");
745 putenv((char *)"LC_ALL=");
746 putenv((char *)"LC_MESSAGES=");
749 execvp(gpgvpath
.c_str(), (char **) &Args
[0]);
753 bool TranslationsCopy::CopyTranslations(string CDROM
,string Name
, /*{{{*/
754 vector
<string
> &List
, pkgCdromStatus
*log
)
756 OpProgress
*Progress
= NULL
;
757 if (List
.empty() == true)
761 Progress
= log
->GetOpProgress();
763 bool Debug
= _config
->FindB("Debug::aptcdrom",false);
765 // Prepare the progress indicator
767 std::vector
<APT::Configuration::Compressor
> const compressor
= APT::Configuration::getCompressors();
768 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); ++I
)
772 std::string file
= *I
;
773 for (std::vector
<APT::Configuration::Compressor
>::const_iterator c
= compressor
.begin();
774 c
!= compressor
.end(); ++c
)
776 if (stat(std::string(file
+ c
->Extension
).c_str(), &Buf
) != 0)
783 return _error
->Errno("stat", "Stat failed for %s", file
.c_str());
784 TotalSize
+= Buf
.st_size
;
787 off_t CurrentSize
= 0;
788 unsigned int NotFound
= 0;
789 unsigned int WrongSize
= 0;
790 unsigned int Packages
= 0;
791 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); ++I
)
793 string OrigPath
= string(*I
,CDROM
.length());
795 // Open the package file
796 FileFd
Pkg(*I
, FileFd::ReadOnly
, FileFd::Auto
);
797 off_t
const FileSize
= Pkg
.Size();
799 pkgTagFile
Parser(&Pkg
);
800 if (_error
->PendingError() == true)
803 // Open the output file
805 snprintf(S
,sizeof(S
),"cdrom:[%s]/%s",Name
.c_str(),
806 (*I
).c_str() + CDROM
.length());
807 string TargetF
= _config
->FindDir("Dir::State::lists") + "partial/";
808 TargetF
+= URItoFileName(S
);
809 if (_config
->FindB("APT::CDROM::NoAct",false) == true)
810 TargetF
= "/dev/null";
811 FileFd
Target(TargetF
,FileFd::WriteAtomic
);
812 FILE *TargetFl
= fdopen(dup(Target
.Fd()),"w");
813 if (_error
->PendingError() == true)
816 return _error
->Errno("fdopen","Failed to reopen fd");
818 // Setup the progress meter
820 Progress
->OverallProgress(CurrentSize
,TotalSize
,FileSize
,
821 string("Reading Translation Indexes"));
825 Progress
->SubProgress(Pkg
.Size());
826 pkgTagSection Section
;
827 this->Section
= &Section
;
829 unsigned long Hits
= 0;
830 while (Parser
.Step(Section
) == true)
833 Progress
->Progress(Parser
.Offset());
837 Section
.GetSection(Start
,Stop
);
838 fwrite(Start
,Stop
-Start
, 1, TargetFl
);
839 fputc('\n',TargetFl
);
847 cout
<< " Processed by using Prefix '" << Prefix
<< "' and chop " << endl
;
849 if (_config
->FindB("APT::CDROM::NoAct",false) == false)
851 // Move out of the partial directory
853 string FinalF
= _config
->FindDir("Dir::State::lists");
854 FinalF
+= URItoFileName(S
);
855 if (rename(TargetF
.c_str(),FinalF
.c_str()) != 0)
856 return _error
->Errno("rename","Failed to rename");
860 CurrentSize
+= FileSize
;
868 if(NotFound
== 0 && WrongSize
== 0)
869 ioprintf(msg
, _("Wrote %i records.\n"), Packages
);
870 else if (NotFound
!= 0 && WrongSize
== 0)
871 ioprintf(msg
, _("Wrote %i records with %i missing files.\n"),
873 else if (NotFound
== 0 && WrongSize
!= 0)
874 ioprintf(msg
, _("Wrote %i records with %i mismatched files\n"),
875 Packages
, WrongSize
);
876 if (NotFound
!= 0 && WrongSize
!= 0)
877 ioprintf(msg
, _("Wrote %i records with %i missing files and %i mismatched files\n"), Packages
, NotFound
, WrongSize
);
881 _error
->Warning("No valid records were found.");
883 if (NotFound
+ WrongSize
> 10)
884 _error
->Warning("A lot of entries were discarded, something may be wrong.\n");