]>
git.saurik.com Git - apt.git/blob - cmdline/apt-cdrom.cc
0c948098b2f36734f612068c5e4df9e8512e71bd
1 // -*- mode: cpp; mode: fold -*-
3 // $Id: apt-cdrom.cc,v 1.37 2001/03/13 05:23:42 jgg Exp $
4 /* ######################################################################
6 APT CDROM - Tool for handling APT's CDROM database.
8 Currently the only option is 'add' which will take the current CD
9 in the drive and add it into the database.
11 ##################################################################### */
13 // Include Files /*{{{*/
14 #include <apt-pkg/cmndline.h>
15 #include <apt-pkg/error.h>
16 #include <apt-pkg/init.h>
17 #include <apt-pkg/fileutl.h>
18 #include <apt-pkg/progress.h>
19 #include <apt-pkg/cdromutl.h>
20 #include <apt-pkg/strutl.h>
24 #include "indexcopy.h"
37 // FindPackages - Find the package files on the CDROM /*{{{*/
38 // ---------------------------------------------------------------------
39 /* We look over the cdrom for package files. This is a recursive
40 search that short circuits when it his a package file in the dir.
41 This speeds it up greatly as the majority of the size is in the
43 bool FindPackages(string CD
,vector
<string
> &List
,vector
<string
> &SList
,
44 string
&InfoDir
,unsigned int Depth
= 0)
46 static ino_t Inodes
[9];
50 if (CD
[CD
.length()-1] != '/')
53 if (chdir(CD
.c_str()) != 0)
54 return _error
->Errno("chdir","Unable to change to %s",CD
.c_str());
56 // Look for a .disk subdirectory
58 if (stat(".disk",&Buf
) == 0)
60 if (InfoDir
.empty() == true)
61 InfoDir
= CD
+ ".disk/";
64 /* Aha! We found some package files. We assume that everything under
65 this dir is controlled by those package files so we don't look down
67 if (stat("Packages",&Buf
) == 0 || stat("Packages.gz",&Buf
) == 0)
71 // Continue down if thorough is given
72 if (_config
->FindB("APT::CDROM::Thorough",false) == false)
75 if (stat("Sources.gz",&Buf
) == 0 || stat("Sources",&Buf
) == 0)
79 // Continue down if thorough is given
80 if (_config
->FindB("APT::CDROM::Thorough",false) == false)
84 DIR *D
= opendir(".");
86 return _error
->Errno("opendir","Unable to read %s",CD
.c_str());
88 // Run over the directory
89 for (struct dirent
*Dir
= readdir(D
); Dir
!= 0; Dir
= readdir(D
))
92 if (strcmp(Dir
->d_name
,".") == 0 ||
93 strcmp(Dir
->d_name
,"..") == 0 ||
94 //strcmp(Dir->d_name,"source") == 0 ||
95 strcmp(Dir
->d_name
,".disk") == 0 ||
96 strcmp(Dir
->d_name
,"experimental") == 0 ||
97 strcmp(Dir
->d_name
,"binary-all") == 0)
100 // See if the name is a sub directory
102 if (stat(Dir
->d_name
,&Buf
) != 0)
105 if (S_ISDIR(Buf
.st_mode
) == 0)
109 for (I
= 0; I
!= Depth
; I
++)
110 if (Inodes
[I
] == Buf
.st_ino
)
115 // Store the inodes weve seen
116 Inodes
[Depth
] = Buf
.st_ino
;
119 if (FindPackages(CD
+ Dir
->d_name
,List
,SList
,InfoDir
,Depth
+1) == false)
122 if (chdir(CD
.c_str()) != 0)
123 return _error
->Errno("chdir","Unable to change to %s",CD
.c_str());
128 return !_error
->PendingError();
131 // DropBinaryArch - Dump dirs with a string like /binary-<foo>/ /*{{{*/
132 // ---------------------------------------------------------------------
133 /* Here we drop everything that is not this machines arch */
134 bool DropBinaryArch(vector
<string
> &List
)
137 snprintf(S
,sizeof(S
),"/binary-%s/",
138 _config
->Find("Apt::Architecture").c_str());
140 for (unsigned int I
= 0; I
< List
.size(); I
++)
142 const char *Str
= List
[I
].c_str();
145 if ((Res
= strstr(Str
,"/binary-")) == 0)
149 if (strlen(Res
) < strlen(S
))
151 List
.erase(List
.begin() + I
);
156 // See if it is our arch
157 if (stringcmp(Res
,Res
+ strlen(S
),S
) == 0)
161 List
.erase(List
.begin() + I
);
168 // Score - We compute a 'score' for a path /*{{{*/
169 // ---------------------------------------------------------------------
170 /* Paths are scored based on how close they come to what I consider
171 normal. That is ones that have 'dist' 'stable' 'frozen' will score
172 higher than ones without. */
173 int Score(string Path
)
176 if (Path
.find("stable/") != string::npos
)
178 if (Path
.find("/binary-") != string::npos
)
180 if (Path
.find("frozen/") != string::npos
)
182 if (Path
.find("unstable/") != string::npos
)
184 if (Path
.find("/dists/") != string::npos
)
186 if (Path
.find("/main/") != string::npos
)
188 if (Path
.find("/contrib/") != string::npos
)
190 if (Path
.find("/non-free/") != string::npos
)
192 if (Path
.find("/non-US/") != string::npos
)
194 if (Path
.find("/source/") != string::npos
)
196 if (Path
.find("/debian/") != string::npos
)
201 // DropRepeats - Drop repeated files resulting from symlinks /*{{{*/
202 // ---------------------------------------------------------------------
203 /* Here we go and stat every file that we found and strip dup inodes. */
204 bool DropRepeats(vector
<string
> &List
,const char *Name
)
206 // Get a list of all the inodes
207 ino_t
*Inodes
= new ino_t
[List
.size()];
208 for (unsigned int I
= 0; I
!= List
.size(); I
++)
211 if (stat((List
[I
] + Name
).c_str(),&Buf
) != 0 &&
212 stat((List
[I
] + Name
+ ".gz").c_str(),&Buf
) != 0)
213 _error
->Errno("stat","Failed to stat %s%s",List
[I
].c_str(),
215 Inodes
[I
] = Buf
.st_ino
;
218 if (_error
->PendingError() == true)
222 for (unsigned int I
= 0; I
!= List
.size(); I
++)
224 for (unsigned int J
= I
+1; J
< List
.size(); J
++)
227 if (Inodes
[J
] != Inodes
[I
])
230 // We score the two paths.. and erase one
231 int ScoreA
= Score(List
[I
]);
232 int ScoreB
= Score(List
[J
]);
243 // Wipe erased entries
244 for (unsigned int I
= 0; I
< List
.size();)
246 if (List
[I
].empty() == false)
249 List
.erase(List
.begin()+I
);
256 // ReduceSourceList - Takes the path list and reduces it /*{{{*/
257 // ---------------------------------------------------------------------
258 /* This takes the list of source list expressed entires and collects
259 similar ones to form a single entry for each dist */
260 void ReduceSourcelist(string CD
,vector
<string
> &List
)
262 sort(List
.begin(),List
.end());
264 // Collect similar entries
265 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
268 string::size_type Space
= (*I
).find(' ');
269 if (Space
== string::npos
)
271 string::size_type SSpace
= (*I
).find(' ',Space
+ 1);
272 if (SSpace
== string::npos
)
275 string Word1
= string(*I
,Space
,SSpace
-Space
);
276 string Prefix
= string(*I
,0,Space
);
277 for (vector
<string
>::iterator J
= List
.begin(); J
!= I
; J
++)
280 string::size_type Space2
= (*J
).find(' ');
281 if (Space2
== string::npos
)
283 string::size_type SSpace2
= (*J
).find(' ',Space2
+ 1);
284 if (SSpace2
== string::npos
)
287 if (string(*J
,0,Space2
) != Prefix
)
289 if (string(*J
,Space2
,SSpace2
-Space2
) != Word1
)
292 *J
+= string(*I
,SSpace
);
297 // Wipe erased entries
298 for (unsigned int I
= 0; I
< List
.size();)
300 if (List
[I
].empty() == false)
303 List
.erase(List
.begin()+I
);
307 // WriteDatabase - Write the CDROM Database file /*{{{*/
308 // ---------------------------------------------------------------------
309 /* We rewrite the configuration class associated with the cdrom database. */
310 bool WriteDatabase(Configuration
&Cnf
)
312 string DFile
= _config
->FindFile("Dir::State::cdroms");
313 string NewFile
= DFile
+ ".new";
315 unlink(NewFile
.c_str());
316 ofstream
Out(NewFile
.c_str());
318 return _error
->Errno("ofstream::ofstream",
319 "Failed to open %s.new",DFile
.c_str());
321 /* Write out all of the configuration directives by walking the
322 configuration tree */
323 const Configuration::Item
*Top
= Cnf
.Tree(0);
326 // Print the config entry
327 if (Top
->Value
.empty() == false)
328 Out
<< Top
->FullTag() + " \"" << Top
->Value
<< "\";" << endl
;
336 while (Top
!= 0 && Top
->Next
== 0)
344 rename(DFile
.c_str(),string(DFile
+ '~').c_str());
345 if (rename(NewFile
.c_str(),DFile
.c_str()) != 0)
346 return _error
->Errno("rename","Failed to rename %s.new to %s",
347 DFile
.c_str(),DFile
.c_str());
352 // WriteSourceList - Write an updated sourcelist /*{{{*/
353 // ---------------------------------------------------------------------
354 /* This reads the old source list and copies it into the new one. It
355 appends the new CDROM entires just after the first block of comments.
356 This places them first in the file. It also removes any old entries
357 that were the same. */
358 bool WriteSourceList(string Name
,vector
<string
> &List
,bool Source
)
360 if (List
.size() == 0)
363 string File
= _config
->FindFile("Dir::Etc::sourcelist");
365 // Open the stream for reading
366 ifstream
F((FileExists(File
)?File
.c_str():"/dev/null"),
367 ios::in
| ios::nocreate
);
369 return _error
->Errno("ifstream::ifstream","Opening %s",File
.c_str());
371 string NewFile
= File
+ ".new";
372 unlink(NewFile
.c_str());
373 ofstream
Out(NewFile
.c_str());
375 return _error
->Errno("ofstream::ofstream",
376 "Failed to open %s.new",File
.c_str());
378 // Create a short uri without the path
379 string ShortURI
= "cdrom:[" + Name
+ "]/";
380 string ShortURI2
= "cdrom:" + Name
+ "/"; // For Compatibility
391 while (F
.eof() == false)
393 F
.getline(Buffer
,sizeof(Buffer
));
395 _strtabexpand(Buffer
,sizeof(Buffer
));
399 if (Buffer
[0] == '#' || Buffer
[0] == 0)
401 Out
<< Buffer
<< endl
;
407 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
409 string::size_type Space
= (*I
).find(' ');
410 if (Space
== string::npos
)
411 return _error
->Error("Internal error");
412 Out
<< Type
<< " cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
413 " " << string(*I
,Space
+1) << endl
;
421 const char *C
= Buffer
;
422 if (ParseQuoteWord(C
,cType
) == false ||
423 ParseQuoteWord(C
,URI
) == false)
425 Out
<< Buffer
<< endl
;
429 // Emit lines like this one
430 if (cType
!= Type
|| (string(URI
,0,ShortURI
.length()) != ShortURI
&&
431 string(URI
,0,ShortURI
.length()) != ShortURI2
))
433 Out
<< Buffer
<< endl
;
438 // Just in case the file was empty
441 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
443 string::size_type Space
= (*I
).find(' ');
444 if (Space
== string::npos
)
445 return _error
->Error("Internal error");
447 Out
<< "deb cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
448 " " << string(*I
,Space
+1) << endl
;
454 rename(File
.c_str(),string(File
+ '~').c_str());
455 if (rename(NewFile
.c_str(),File
.c_str()) != 0)
456 return _error
->Errno("rename","Failed to rename %s.new to %s",
457 File
.c_str(),File
.c_str());
463 // Prompt - Simple prompt /*{{{*/
464 // ---------------------------------------------------------------------
466 void Prompt(const char *Text
)
469 cout
<< Text
<< ' ' << flush
;
470 read(STDIN_FILENO
,&C
,1);
475 // PromptLine - Prompt for an input line /*{{{*/
476 // ---------------------------------------------------------------------
478 string
PromptLine(const char *Text
)
480 cout
<< Text
<< ':' << endl
;
488 // DoAdd - Add a new CDROM /*{{{*/
489 // ---------------------------------------------------------------------
490 /* This does the main add bit.. We show some status and things. The
491 sequence is to mount/umount the CD, Ident it then scan it for package
492 files and reduce that list. Then we copy over the package files and
493 verify them. Then rewrite the database files */
494 bool DoAdd(CommandLine
&)
497 string CDROM
= _config
->FindDir("Acquire::cdrom::mount","/cdrom/");
499 CDROM
= SafeGetCWD() + '/' + CDROM
;
501 cout
<< "Using CD-ROM mount point " << CDROM
<< endl
;
504 Configuration Database
;
505 string DFile
= _config
->FindFile("Dir::State::cdroms");
506 if (FileExists(DFile
) == true)
508 if (ReadConfigFile(Database
,DFile
) == false)
509 return _error
->Error("Unable to read the cdrom database %s",
513 // Unmount the CD and get the user to put in the one they want
514 if (_config
->FindB("APT::CDROM::NoMount",false) == false)
516 cout
<< "Unmounting CD-ROM" << endl
;
519 // Mount the new CDROM
520 Prompt("Please insert a Disc in the drive and press enter");
521 cout
<< "Mounting CD-ROM" << endl
;
522 if (MountCdrom(CDROM
) == false)
523 return _error
->Error("Failed to mount the cdrom.");
526 // Hash the CD to get an ID
527 cout
<< "Identifying.. " << flush
;
529 if (IdentCdrom(CDROM
,ID
) == false)
535 cout
<< '[' << ID
<< ']' << endl
;
537 cout
<< "Scanning Disc for index files.. " << flush
;
538 // Get the CD structure
540 vector
<string
> sList
;
541 string StartDir
= SafeGetCWD();
543 if (FindPackages(CDROM
,List
,sList
,InfoDir
) == false)
549 chdir(StartDir
.c_str());
551 if (_config
->FindB("Debug::aptcdrom",false) == true)
553 cout
<< "I found (binary):" << endl
;
554 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
556 cout
<< "I found (source):" << endl
;
557 for (vector
<string
>::iterator I
= sList
.begin(); I
!= sList
.end(); I
++)
562 DropBinaryArch(List
);
563 DropRepeats(List
,"Packages");
564 DropRepeats(sList
,"Sources");
565 cout
<< "Found " << List
.size() << " package indexes and " << sList
.size() <<
566 " source indexes." << endl
;
568 if (List
.size() == 0 && sList
.size() == 0)
569 return _error
->Error("Unable to locate any package files, perhaps this is not a Debian Disc");
571 // Check if the CD is in the database
573 if (Database
.Exists("CD::" + ID
) == false ||
574 _config
->FindB("APT::CDROM::Rename",false) == true)
576 // Try to use the CDs label if at all possible
577 if (InfoDir
.empty() == false &&
578 FileExists(InfoDir
+ "/info") == true)
580 ifstream
F(string(InfoDir
+ "/info").c_str());
584 if (Name
.empty() == false)
586 // Escape special characters
587 string::iterator J
= Name
.begin();
588 for (; J
!= Name
.end(); J
++)
589 if (*J
== '"' || *J
== ']' || *J
== '[')
592 cout
<< "Found label '" << Name
<< "'" << endl
;
593 Database
.Set("CD::" + ID
+ "::Label",Name
);
597 if (_config
->FindB("APT::CDROM::Rename",false) == true ||
598 Name
.empty() == true)
600 cout
<< "Please provide a name for this Disc, such as 'Debian 2.1r1 Disk 1'";
603 Name
= PromptLine("");
604 if (Name
.empty() == false &&
605 Name
.find('"') == string::npos
&&
606 Name
.find('[') == string::npos
&&
607 Name
.find(']') == string::npos
)
609 cout
<< "That is not a valid name, try again " << endl
;
614 Name
= Database
.Find("CD::" + ID
);
616 // Escape special characters
617 string::iterator J
= Name
.begin();
618 for (; J
!= Name
.end(); J
++)
619 if (*J
== '"' || *J
== ']' || *J
== '[')
622 Database
.Set("CD::" + ID
,Name
);
623 cout
<< "This Disc is called:" << endl
<< " '" << Name
<< "'" << endl
;
625 // Copy the package files to the state directory
628 if (Copy
.CopyPackages(CDROM
,Name
,List
) == false ||
629 SrcCopy
.CopyPackages(CDROM
,Name
,sList
) == false)
632 ReduceSourcelist(CDROM
,List
);
633 ReduceSourcelist(CDROM
,sList
);
635 // Write the database and sourcelist
636 if (_config
->FindB("APT::cdrom::NoAct",false) == false)
638 if (WriteDatabase(Database
) == false)
641 cout
<< "Writing new source list" << endl
;
642 if (WriteSourceList(Name
,List
,false) == false ||
643 WriteSourceList(Name
,sList
,true) == false)
647 // Print the sourcelist entries
648 cout
<< "Source List entries for this Disc are:" << endl
;
649 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
651 string::size_type Space
= (*I
).find(' ');
652 if (Space
== string::npos
)
653 return _error
->Error("Internal error");
655 cout
<< "deb cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
656 " " << string(*I
,Space
+1) << endl
;
659 for (vector
<string
>::iterator I
= sList
.begin(); I
!= sList
.end(); I
++)
661 string::size_type Space
= (*I
).find(' ');
662 if (Space
== string::npos
)
663 return _error
->Error("Internal error");
665 cout
<< "deb-src cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
666 " " << string(*I
,Space
+1) << endl
;
669 cout
<< "Repeat this process for the rest of the CDs in your set." << endl
;
671 // Unmount and finish
672 if (_config
->FindB("APT::CDROM::NoMount",false) == false)
678 // DoIdent - Ident a CDROM /*{{{*/
679 // ---------------------------------------------------------------------
681 bool DoIdent(CommandLine
&)
684 string CDROM
= _config
->FindDir("Acquire::cdrom::mount","/cdrom/");
686 CDROM
= SafeGetCWD() + '/' + CDROM
;
688 cout
<< "Using CD-ROM mount point " << CDROM
<< endl
;
689 cout
<< "Mounting CD-ROM" << endl
;
690 if (MountCdrom(CDROM
) == false)
691 return _error
->Error("Failed to mount the cdrom.");
693 // Hash the CD to get an ID
694 cout
<< "Identifying.. " << flush
;
696 if (IdentCdrom(CDROM
,ID
) == false)
702 cout
<< '[' << ID
<< ']' << endl
;
705 Configuration Database
;
706 string DFile
= _config
->FindFile("Dir::State::cdroms");
707 if (FileExists(DFile
) == true)
709 if (ReadConfigFile(Database
,DFile
) == false)
710 return _error
->Error("Unable to read the cdrom database %s",
713 cout
<< "Stored Label: '" << Database
.Find("CD::" + ID
) << "'" << endl
;
718 // ShowHelp - Show the help screen /*{{{*/
719 // ---------------------------------------------------------------------
723 ioprintf(cout
,_("%s %s for %s %s compiled on %s %s\n"),PACKAGE
,VERSION
,
724 COMMON_OS
,COMMON_CPU
,__DATE__
,__TIME__
);
725 if (_config
->FindB("version") == true)
729 "Usage: apt-cdrom [options] command\n"
731 "apt-cdrom is a tool to add CDROM's to APT's source list. The\n"
732 "CDROM mount point and device information is taken from apt.conf\n"
736 " add - Add a CDROM\n"
737 " ident - Report the identity of a CDROM\n"
740 " -h This help text\n"
741 " -d CD-ROM mount point\n"
742 " -r Rename a recognized CD-ROM\n"
744 " -f Fast mode, don't check package files\n"
745 " -a Thorough scan mode\n"
746 " -c=? Read this configuration file\n"
747 " -o=? Set an arbitary configuration option, eg -o dir::cache=/tmp\n"
753 int main(int argc
,const char *argv
[])
755 CommandLine::Args Args
[] = {
756 {'h',"help","help",0},
757 {'v',"version","version",0},
758 {'d',"cdrom","Acquire::cdrom::mount",CommandLine::HasArg
},
759 {'r',"rename","APT::CDROM::Rename",0},
760 {'m',"no-mount","APT::CDROM::NoMount",0},
761 {'f',"fast","APT::CDROM::Fast",0},
762 {'n',"just-print","APT::CDROM::NoAct",0},
763 {'n',"recon","APT::CDROM::NoAct",0},
764 {'n',"no-act","APT::CDROM::NoAct",0},
765 {'a',"thorough","APT::CDROM::Thorough",0},
766 {'c',"config-file",0,CommandLine::ConfigFile
},
767 {'o',"option",0,CommandLine::ArbItem
},
769 CommandLine::Dispatch Cmds
[] = {
774 // Parse the command line and initialize the package library
775 CommandLine
CmdL(Args
,_config
);
776 if (pkgInitConfig(*_config
) == false ||
777 CmdL
.Parse(argc
,argv
) == false ||
778 pkgInitSystem(*_config
,_system
) == false)
780 _error
->DumpErrors();
784 // See if the help should be shown
785 if (_config
->FindB("help") == true || _config
->FindB("version") == true ||
786 CmdL
.FileSize() == 0)
789 // Deal with stdout not being a tty
790 if (ttyname(STDOUT_FILENO
) == 0 && _config
->FindI("quiet",0) < 1)
791 _config
->Set("quiet","1");
793 // Match the operation
794 CmdL
.DispatchArg(Cmds
);
796 // Print any errors or warnings found during parsing
797 if (_error
->empty() == false)
799 bool Errors
= _error
->PendingError();
800 _error
->DumpErrors();
801 return Errors
== true?100:0;