4 #include<apt-pkg/init.h>
5 #include<apt-pkg/error.h>
6 #include<apt-pkg/cdromutl.h>
7 #include<apt-pkg/strutl.h>
8 #include<apt-pkg/cdrom.h>
21 #include "indexcopy.h"
25 // FindPackages - Find the package files on the CDROM /*{{{*/
26 // ---------------------------------------------------------------------
27 /* We look over the cdrom for package files. This is a recursive
28 search that short circuits when it his a package file in the dir.
29 This speeds it up greatly as the majority of the size is in the
31 bool pkgCdrom::FindPackages(string CD
,
33 vector
<string
> &SList
,
34 vector
<string
> &SigList
,
35 vector
<string
> &TransList
,
36 string
&InfoDir
, pkgCdromStatus
*log
,
39 static ino_t Inodes
[9];
42 // if we have a look we "pulse" now
49 if (CD
[CD
.length()-1] != '/')
52 if (chdir(CD
.c_str()) != 0)
53 return _error
->Errno("chdir","Unable to change to %s",CD
.c_str());
55 // Look for a .disk subdirectory
57 if (stat(".disk",&Buf
) == 0)
59 if (InfoDir
.empty() == true)
60 InfoDir
= CD
+ ".disk/";
63 // Don't look into directories that have been marked to ingore.
64 if (stat(".aptignr",&Buf
) == 0)
68 /* Check _first_ for a signature file as apt-cdrom assumes that all files
69 under a Packages/Source file are in control of that file and stops
72 if (stat("Release.gpg",&Buf
) == 0)
74 SigList
.push_back(CD
);
76 /* Aha! We found some package files. We assume that everything under
77 this dir is controlled by those package files so we don't look down
79 if (stat("Packages",&Buf
) == 0 || stat("Packages.gz",&Buf
) == 0)
83 // Continue down if thorough is given
84 if (_config
->FindB("APT::CDROM::Thorough",false) == false)
87 if (stat("Sources.gz",&Buf
) == 0 || stat("Sources",&Buf
) == 0)
91 // Continue down if thorough is given
92 if (_config
->FindB("APT::CDROM::Thorough",false) == false)
96 // see if we find translatin indexes
97 if (stat("i18n",&Buf
) == 0)
100 for (struct dirent
*Dir
= readdir(D
); Dir
!= 0; Dir
= readdir(D
))
102 if(strstr(Dir
->d_name
,"Translation") != NULL
)
104 if (_config
->FindB("Debug::aptcdrom",false) == true)
105 std::clog
<< "found translations: " << Dir
->d_name
<< "\n";
106 string file
= Dir
->d_name
;
107 if(file
.substr(file
.size()-3,file
.size()) == ".gz")
108 file
= file
.substr(0,file
.size()-3);
109 TransList
.push_back(CD
+"i18n/"+ file
);
118 return _error
->Errno("opendir","Unable to read %s",CD
.c_str());
120 // Run over the directory
121 for (struct dirent
*Dir
= readdir(D
); Dir
!= 0; Dir
= readdir(D
))
124 if (strcmp(Dir
->d_name
,".") == 0 ||
125 strcmp(Dir
->d_name
,"..") == 0 ||
126 //strcmp(Dir->d_name,"source") == 0 ||
127 strcmp(Dir
->d_name
,".disk") == 0 ||
128 strcmp(Dir
->d_name
,"experimental") == 0 ||
129 strcmp(Dir
->d_name
,"binary-all") == 0 ||
130 strcmp(Dir
->d_name
,"debian-installer") == 0)
133 // See if the name is a sub directory
135 if (stat(Dir
->d_name
,&Buf
) != 0)
138 if (S_ISDIR(Buf
.st_mode
) == 0)
142 for (I
= 0; I
!= Depth
; I
++)
143 if (Inodes
[I
] == Buf
.st_ino
)
148 // Store the inodes weve seen
149 Inodes
[Depth
] = Buf
.st_ino
;
152 if (FindPackages(CD
+ Dir
->d_name
,List
,SList
,SigList
,TransList
,InfoDir
,log
,Depth
+1) == false)
155 if (chdir(CD
.c_str()) != 0)
156 return _error
->Errno("chdir","Unable to change to %s",CD
.c_str());
161 return !_error
->PendingError();
164 // Score - We compute a 'score' for a path /*{{{*/
165 // ---------------------------------------------------------------------
166 /* Paths are scored based on how close they come to what I consider
167 normal. That is ones that have 'dist' 'stable' 'testing' will score
168 higher than ones without. */
169 int pkgCdrom::Score(string Path
)
172 if (Path
.find("stable/") != string::npos
)
174 if (Path
.find("/binary-") != string::npos
)
176 if (Path
.find("testing/") != string::npos
)
178 if (Path
.find("unstable/") != string::npos
)
180 if (Path
.find("/dists/") != string::npos
)
182 if (Path
.find("/main/") != string::npos
)
184 if (Path
.find("/contrib/") != string::npos
)
186 if (Path
.find("/non-free/") != string::npos
)
188 if (Path
.find("/non-US/") != string::npos
)
190 if (Path
.find("/source/") != string::npos
)
192 if (Path
.find("/debian/") != string::npos
)
195 // check for symlinks in the patch leading to the actual file
196 // a symlink gets a big penalty
198 string statPath
= flNotFile(Path
);
199 string cdromPath
= _config
->FindDir("Acquire::cdrom::mount","/cdrom/");
200 while(statPath
!= cdromPath
&& statPath
!= "./") {
201 statPath
.resize(statPath
.size()-1); // remove the trailing '/'
202 if (lstat(statPath
.c_str(),&Buf
) == 0) {
203 if(S_ISLNK(Buf
.st_mode
)) {
208 statPath
= flNotFile(statPath
); // descent
214 // DropBinaryArch - Dump dirs with a string like /binary-<foo>/ /*{{{*/
215 // ---------------------------------------------------------------------
216 /* Here we drop everything that is not this machines arch */
217 bool pkgCdrom::DropBinaryArch(vector
<string
> &List
)
220 snprintf(S
,sizeof(S
),"/binary-%s/",
221 _config
->Find("Apt::Architecture").c_str());
223 for (unsigned int I
= 0; I
< List
.size(); I
++)
225 const char *Str
= List
[I
].c_str();
228 if ((Res
= strstr(Str
,"/binary-")) == 0)
232 if (strlen(Res
) < strlen(S
))
234 List
.erase(List
.begin() + I
);
239 // See if it is our arch
240 if (stringcmp(Res
,Res
+ strlen(S
),S
) == 0)
244 List
.erase(List
.begin() + I
);
251 // DropRepeats - Drop repeated files resulting from symlinks /*{{{*/
252 // ---------------------------------------------------------------------
253 /* Here we go and stat every file that we found and strip dup inodes. */
254 bool pkgCdrom::DropRepeats(vector
<string
> &List
,const char *Name
)
256 // Get a list of all the inodes
257 ino_t
*Inodes
= new ino_t
[List
.size()];
258 for (unsigned int I
= 0; I
!= List
.size(); I
++)
261 if (stat((List
[I
] + Name
).c_str(),&Buf
) != 0 &&
262 stat((List
[I
] + Name
+ ".gz").c_str(),&Buf
) != 0)
263 _error
->Errno("stat","Failed to stat %s%s",List
[I
].c_str(),
265 Inodes
[I
] = Buf
.st_ino
;
268 if (_error
->PendingError() == true)
272 for (unsigned int I
= 0; I
!= List
.size(); I
++)
274 for (unsigned int J
= I
+1; J
< List
.size(); J
++)
277 if (Inodes
[J
] != Inodes
[I
])
280 // We score the two paths.. and erase one
281 int ScoreA
= Score(List
[I
]);
282 int ScoreB
= Score(List
[J
]);
293 // Wipe erased entries
294 for (unsigned int I
= 0; I
< List
.size();)
296 if (List
[I
].empty() == false)
299 List
.erase(List
.begin()+I
);
305 // ReduceSourceList - Takes the path list and reduces it /*{{{*/
306 // ---------------------------------------------------------------------
307 /* This takes the list of source list expressed entires and collects
308 similar ones to form a single entry for each dist */
309 void pkgCdrom::ReduceSourcelist(string CD
,vector
<string
> &List
)
311 sort(List
.begin(),List
.end());
313 // Collect similar entries
314 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
317 string::size_type Space
= (*I
).find(' ');
318 if (Space
== string::npos
)
320 string::size_type SSpace
= (*I
).find(' ',Space
+ 1);
321 if (SSpace
== string::npos
)
324 string Word1
= string(*I
,Space
,SSpace
-Space
);
325 string Prefix
= string(*I
,0,Space
);
326 for (vector
<string
>::iterator J
= List
.begin(); J
!= I
; J
++)
329 string::size_type Space2
= (*J
).find(' ');
330 if (Space2
== string::npos
)
332 string::size_type SSpace2
= (*J
).find(' ',Space2
+ 1);
333 if (SSpace2
== string::npos
)
336 if (string(*J
,0,Space2
) != Prefix
)
338 if (string(*J
,Space2
,SSpace2
-Space2
) != Word1
)
341 *J
+= string(*I
,SSpace
);
346 // Wipe erased entries
347 for (unsigned int I
= 0; I
< List
.size();)
349 if (List
[I
].empty() == false)
352 List
.erase(List
.begin()+I
);
356 // WriteDatabase - Write the CDROM Database file /*{{{*/
357 // ---------------------------------------------------------------------
358 /* We rewrite the configuration class associated with the cdrom database. */
359 bool pkgCdrom::WriteDatabase(Configuration
&Cnf
)
361 string DFile
= _config
->FindFile("Dir::State::cdroms");
362 string NewFile
= DFile
+ ".new";
364 unlink(NewFile
.c_str());
365 ofstream
Out(NewFile
.c_str());
367 return _error
->Errno("ofstream::ofstream",
368 "Failed to open %s.new",DFile
.c_str());
370 /* Write out all of the configuration directives by walking the
371 configuration tree */
372 const Configuration::Item
*Top
= Cnf
.Tree(0);
375 // Print the config entry
376 if (Top
->Value
.empty() == false)
377 Out
<< Top
->FullTag() + " \"" << Top
->Value
<< "\";" << endl
;
385 while (Top
!= 0 && Top
->Next
== 0)
393 rename(DFile
.c_str(),string(DFile
+ '~').c_str());
394 if (rename(NewFile
.c_str(),DFile
.c_str()) != 0)
395 return _error
->Errno("rename","Failed to rename %s.new to %s",
396 DFile
.c_str(),DFile
.c_str());
401 // WriteSourceList - Write an updated sourcelist /*{{{*/
402 // ---------------------------------------------------------------------
403 /* This reads the old source list and copies it into the new one. It
404 appends the new CDROM entires just after the first block of comments.
405 This places them first in the file. It also removes any old entries
406 that were the same. */
407 bool pkgCdrom::WriteSourceList(string Name
,vector
<string
> &List
,bool Source
)
409 if (List
.size() == 0)
412 string File
= _config
->FindFile("Dir::Etc::sourcelist");
414 // Open the stream for reading
415 ifstream
F((FileExists(File
)?File
.c_str():"/dev/null"),
418 return _error
->Errno("ifstream::ifstream","Opening %s",File
.c_str());
420 string NewFile
= File
+ ".new";
421 unlink(NewFile
.c_str());
422 ofstream
Out(NewFile
.c_str());
424 return _error
->Errno("ofstream::ofstream",
425 "Failed to open %s.new",File
.c_str());
427 // Create a short uri without the path
428 string ShortURI
= "cdrom:[" + Name
+ "]/";
429 string ShortURI2
= "cdrom:" + Name
+ "/"; // For Compatibility
440 while (F
.eof() == false)
442 F
.getline(Buffer
,sizeof(Buffer
));
444 if (F
.fail() && !F
.eof())
445 return _error
->Error(_("Line %u too long in source list %s."),
446 CurLine
,File
.c_str());
447 _strtabexpand(Buffer
,sizeof(Buffer
));
451 if (Buffer
[0] == '#' || Buffer
[0] == 0)
453 Out
<< Buffer
<< endl
;
459 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
461 string::size_type Space
= (*I
).find(' ');
462 if (Space
== string::npos
)
463 return _error
->Error("Internal error");
464 Out
<< Type
<< " cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
465 " " << string(*I
,Space
+1) << endl
;
473 const char *C
= Buffer
;
474 if (ParseQuoteWord(C
,cType
) == false ||
475 ParseQuoteWord(C
,URI
) == false)
477 Out
<< Buffer
<< endl
;
481 // Emit lines like this one
482 if (cType
!= Type
|| (string(URI
,0,ShortURI
.length()) != ShortURI
&&
483 string(URI
,0,ShortURI
.length()) != ShortURI2
))
485 Out
<< Buffer
<< endl
;
490 // Just in case the file was empty
493 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
495 string::size_type Space
= (*I
).find(' ');
496 if (Space
== string::npos
)
497 return _error
->Error("Internal error");
499 Out
<< "deb cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
500 " " << string(*I
,Space
+1) << endl
;
506 rename(File
.c_str(),string(File
+ '~').c_str());
507 if (rename(NewFile
.c_str(),File
.c_str()) != 0)
508 return _error
->Errno("rename","Failed to rename %s.new to %s",
509 File
.c_str(),File
.c_str());
514 bool pkgCdrom::Ident(string
&ident
, pkgCdromStatus
*log
) /*{{{*/
519 string CDROM
= _config
->FindDir("Acquire::cdrom::mount","/cdrom/");
521 CDROM
= SafeGetCWD() + '/' + CDROM
;
525 ioprintf(msg
, _("Using CD-ROM mount point %s\nMounting CD-ROM\n"),
527 log
->Update(msg
.str());
529 if (MountCdrom(CDROM
) == false)
530 return _error
->Error("Failed to mount the cdrom.");
532 // Hash the CD to get an ID
534 log
->Update(_("Identifying.. "));
537 if (IdentCdrom(CDROM
,ident
) == false)
544 ioprintf(msg
, "[%s]\n",ident
.c_str());
545 log
->Update(msg
.str());
549 Configuration Database
;
550 string DFile
= _config
->FindFile("Dir::State::cdroms");
551 if (FileExists(DFile
) == true)
553 if (ReadConfigFile(Database
,DFile
) == false)
554 return _error
->Error("Unable to read the cdrom database %s",
559 ioprintf(msg
, _("Stored label: %s\n"),
560 Database
.Find("CD::"+ident
).c_str());
561 log
->Update(msg
.str());
564 // Unmount and finish
565 if (_config
->FindB("APT::CDROM::NoMount",false) == false) {
566 log
->Update(_("Unmounting CD-ROM...\n"), STEP_LAST
);
573 bool pkgCdrom::Add(pkgCdromStatus
*log
) /*{{{*/
578 string CDROM
= _config
->FindDir("Acquire::cdrom::mount","/cdrom/");
580 CDROM
= SafeGetCWD() + '/' + CDROM
;
583 log
->SetTotal(STEP_LAST
);
585 ioprintf(msg
, _("Using CD-ROM mount point %s\n"), CDROM
.c_str());
586 log
->Update(msg
.str(), STEP_PREPARE
);
590 Configuration Database
;
591 string DFile
= _config
->FindFile("Dir::State::cdroms");
592 if (FileExists(DFile
) == true)
594 if (ReadConfigFile(Database
,DFile
) == false)
595 return _error
->Error("Unable to read the cdrom database %s",
599 // Unmount the CD and get the user to put in the one they want
600 if (_config
->FindB("APT::CDROM::NoMount",false) == false)
603 log
->Update(_("Unmounting CD-ROM\n"), STEP_UNMOUNT
);
607 log
->Update(_("Waiting for disc...\n"), STEP_WAIT
);
608 if(!log
->ChangeCdrom()) {
614 // Mount the new CDROM
615 log
->Update(_("Mounting CD-ROM...\n"), STEP_MOUNT
);
616 if (MountCdrom(CDROM
) == false)
617 return _error
->Error("Failed to mount the cdrom.");
620 // Hash the CD to get an ID
622 log
->Update(_("Identifying.. "), STEP_IDENT
);
624 if (IdentCdrom(CDROM
,ID
) == false)
630 log
->Update("["+ID
+"]\n");
633 log
->Update(_("Scanning disc for index files..\n"),STEP_SCAN
);
635 // Get the CD structure
637 vector
<string
> SourceList
;
638 vector
<string
> SigList
;
639 vector
<string
> TransList
;
640 string StartDir
= SafeGetCWD();
642 if (FindPackages(CDROM
,List
,SourceList
, SigList
,TransList
,InfoDir
,log
) == false)
648 chdir(StartDir
.c_str());
650 if (_config
->FindB("Debug::aptcdrom",false) == true)
652 cout
<< "I found (binary):" << endl
;
653 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
655 cout
<< "I found (source):" << endl
;
656 for (vector
<string
>::iterator I
= SourceList
.begin(); I
!= SourceList
.end(); I
++)
658 cout
<< "I found (Signatures):" << endl
;
659 for (vector
<string
>::iterator I
= SigList
.begin(); I
!= SigList
.end(); I
++)
663 //log->Update(_("Cleaning package lists..."), STEP_CLEAN);
666 DropBinaryArch(List
);
667 DropRepeats(List
,"Packages");
668 DropRepeats(SourceList
,"Sources");
669 DropRepeats(SigList
,"Release.gpg");
670 DropRepeats(TransList
,"");
673 ioprintf(msg
, _("Found %zu package indexes, %zu source indexes, "
674 "%zu translation indexes and %zu signatures\n"),
675 List
.size(), SourceList
.size(), TransList
.size(),
677 log
->Update(msg
.str(), STEP_SCAN
);
680 if (List
.size() == 0 && SourceList
.size() == 0)
682 if (_config
->FindB("APT::CDROM::NoMount",false) == false)
684 return _error
->Error("Unable to locate any package files, perhaps this is not a Debian Disc");
687 // Check if the CD is in the database
689 if (Database
.Exists("CD::" + ID
) == false ||
690 _config
->FindB("APT::CDROM::Rename",false) == true)
692 // Try to use the CDs label if at all possible
693 if (InfoDir
.empty() == false &&
694 FileExists(InfoDir
+ "/info") == true)
696 ifstream
F(string(InfoDir
+ "/info").c_str());
700 if (Name
.empty() == false)
702 // Escape special characters
703 string::iterator J
= Name
.begin();
704 for (; J
!= Name
.end(); J
++)
705 if (*J
== '"' || *J
== ']' || *J
== '[')
710 ioprintf(msg
, _("Found label '%s'\n"), Name
.c_str());
711 log
->Update(msg
.str());
713 Database
.Set("CD::" + ID
+ "::Label",Name
);
717 if (_config
->FindB("APT::CDROM::Rename",false) == true ||
718 Name
.empty() == true)
722 if (_config
->FindB("APT::CDROM::NoMount",false) == false)
724 return _error
->Error("No disc name found and no way to ask for it");
728 if(!log
->AskCdromName(Name
)) {
732 cout
<< "Name: '" << Name
<< "'" << endl
;
734 if (Name
.empty() == false &&
735 Name
.find('"') == string::npos
&&
736 Name
.find('[') == string::npos
&&
737 Name
.find(']') == string::npos
)
739 log
->Update(_("That is not a valid name, try again.\n"));
744 Name
= Database
.Find("CD::" + ID
);
746 // Escape special characters
747 string::iterator J
= Name
.begin();
748 for (; J
!= Name
.end(); J
++)
749 if (*J
== '"' || *J
== ']' || *J
== '[')
752 Database
.Set("CD::" + ID
,Name
);
755 ioprintf(msg
, _("This disc is called: \n'%s'\n"), Name
.c_str());
756 log
->Update(msg
.str());
759 log
->Update(_("Copying package lists..."), STEP_COPY
);
760 // take care of the signatures and copy them if they are ok
761 // (we do this before PackageCopy as it modifies "List" and "SourceList")
762 SigVerify SignVerify
;
763 SignVerify
.CopyAndVerify(CDROM
, Name
, SigList
, List
, SourceList
);
765 // Copy the package files to the state directory
768 TranslationsCopy TransCopy
;
769 if (Copy
.CopyPackages(CDROM
,Name
,List
, log
) == false ||
770 SrcCopy
.CopyPackages(CDROM
,Name
,SourceList
, log
) == false ||
771 TransCopy
.CopyTranslations(CDROM
,Name
,TransList
, log
) == false)
774 // reduce the List so that it takes less space in sources.list
775 ReduceSourcelist(CDROM
,List
);
776 ReduceSourcelist(CDROM
,SourceList
);
778 // Write the database and sourcelist
779 if (_config
->FindB("APT::cdrom::NoAct",false) == false)
781 if (WriteDatabase(Database
) == false)
785 log
->Update(_("Writing new source list\n"), STEP_WRITE
);
787 if (WriteSourceList(Name
,List
,false) == false ||
788 WriteSourceList(Name
,SourceList
,true) == false)
792 // Print the sourcelist entries
794 log
->Update(_("Source list entries for this disc are:\n"));
796 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
798 string::size_type Space
= (*I
).find(' ');
799 if (Space
== string::npos
)
801 if (_config
->FindB("APT::CDROM::NoMount",false) == false)
803 return _error
->Error("Internal error");
808 msg
<< "deb cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
809 " " << string(*I
,Space
+1) << endl
;
810 log
->Update(msg
.str());
814 for (vector
<string
>::iterator I
= SourceList
.begin(); I
!= SourceList
.end(); I
++)
816 string::size_type Space
= (*I
).find(' ');
817 if (Space
== string::npos
)
819 if (_config
->FindB("APT::CDROM::NoMount",false) == false)
821 return _error
->Error("Internal error");
826 msg
<< "deb-src cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
827 " " << string(*I
,Space
+1) << endl
;
828 log
->Update(msg
.str());
834 // Unmount and finish
835 if (_config
->FindB("APT::CDROM::NoMount",false) == false) {
836 log
->Update(_("Unmounting CD-ROM...\n"), STEP_LAST
);