]>
git.saurik.com Git - apt.git/blob - cmdline/apt-cdrom.cc
1 // -*- mode: cpp; mode: fold -*-
3 // $Id: apt-cdrom.cc,v 1.40 2001/08/18 22:23:38 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"
39 // FindPackages - Find the package files on the CDROM /*{{{*/
40 // ---------------------------------------------------------------------
41 /* We look over the cdrom for package files. This is a recursive
42 search that short circuits when it his a package file in the dir.
43 This speeds it up greatly as the majority of the size is in the
45 bool FindPackages(string CD
,vector
<string
> &List
,vector
<string
> &SList
,
46 string
&InfoDir
,unsigned int Depth
= 0)
48 static ino_t Inodes
[9];
52 if (CD
[CD
.length()-1] != '/')
55 if (chdir(CD
.c_str()) != 0)
56 return _error
->Errno("chdir","Unable to change to %s",CD
.c_str());
58 // Look for a .disk subdirectory
60 if (stat(".disk",&Buf
) == 0)
62 if (InfoDir
.empty() == true)
63 InfoDir
= CD
+ ".disk/";
66 // Don't look into directories that have been marked to ingore.
67 if (stat(".aptignr",&Buf
) == 0)
70 /* Aha! We found some package files. We assume that everything under
71 this dir is controlled by those package files so we don't look down
73 if (stat("Packages",&Buf
) == 0 || stat("Packages.gz",&Buf
) == 0)
77 // Continue down if thorough is given
78 if (_config
->FindB("APT::CDROM::Thorough",false) == false)
81 if (stat("Sources.gz",&Buf
) == 0 || stat("Sources",&Buf
) == 0)
85 // Continue down if thorough is given
86 if (_config
->FindB("APT::CDROM::Thorough",false) == false)
90 DIR *D
= opendir(".");
92 return _error
->Errno("opendir","Unable to read %s",CD
.c_str());
94 // Run over the directory
95 for (struct dirent
*Dir
= readdir(D
); Dir
!= 0; Dir
= readdir(D
))
98 if (strcmp(Dir
->d_name
,".") == 0 ||
99 strcmp(Dir
->d_name
,"..") == 0 ||
100 //strcmp(Dir->d_name,"source") == 0 ||
101 strcmp(Dir
->d_name
,".disk") == 0 ||
102 strcmp(Dir
->d_name
,"experimental") == 0 ||
103 strcmp(Dir
->d_name
,"binary-all") == 0)
106 // See if the name is a sub directory
108 if (stat(Dir
->d_name
,&Buf
) != 0)
111 if (S_ISDIR(Buf
.st_mode
) == 0)
115 for (I
= 0; I
!= Depth
; I
++)
116 if (Inodes
[I
] == Buf
.st_ino
)
121 // Store the inodes weve seen
122 Inodes
[Depth
] = Buf
.st_ino
;
125 if (FindPackages(CD
+ Dir
->d_name
,List
,SList
,InfoDir
,Depth
+1) == false)
128 if (chdir(CD
.c_str()) != 0)
129 return _error
->Errno("chdir","Unable to change to %s",CD
.c_str());
134 return !_error
->PendingError();
137 // DropBinaryArch - Dump dirs with a string like /binary-<foo>/ /*{{{*/
138 // ---------------------------------------------------------------------
139 /* Here we drop everything that is not this machines arch */
140 bool DropBinaryArch(vector
<string
> &List
)
143 snprintf(S
,sizeof(S
),"/binary-%s/",
144 _config
->Find("Apt::Architecture").c_str());
146 for (unsigned int I
= 0; I
< List
.size(); I
++)
148 const char *Str
= List
[I
].c_str();
151 if ((Res
= strstr(Str
,"/binary-")) == 0)
155 if (strlen(Res
) < strlen(S
))
157 List
.erase(List
.begin() + I
);
162 // See if it is our arch
163 if (stringcmp(Res
,Res
+ strlen(S
),S
) == 0)
167 List
.erase(List
.begin() + I
);
174 // Score - We compute a 'score' for a path /*{{{*/
175 // ---------------------------------------------------------------------
176 /* Paths are scored based on how close they come to what I consider
177 normal. That is ones that have 'dist' 'stable' 'frozen' will score
178 higher than ones without. */
179 int Score(string Path
)
182 if (Path
.find("stable/") != string::npos
)
184 if (Path
.find("/binary-") != string::npos
)
186 if (Path
.find("frozen/") != string::npos
)
188 if (Path
.find("unstable/") != string::npos
)
190 if (Path
.find("/dists/") != string::npos
)
192 if (Path
.find("/main/") != string::npos
)
194 if (Path
.find("/contrib/") != string::npos
)
196 if (Path
.find("/non-free/") != string::npos
)
198 if (Path
.find("/non-US/") != string::npos
)
200 if (Path
.find("/source/") != string::npos
)
202 if (Path
.find("/debian/") != string::npos
)
207 // DropRepeats - Drop repeated files resulting from symlinks /*{{{*/
208 // ---------------------------------------------------------------------
209 /* Here we go and stat every file that we found and strip dup inodes. */
210 bool DropRepeats(vector
<string
> &List
,const char *Name
)
212 // Get a list of all the inodes
213 ino_t
*Inodes
= new ino_t
[List
.size()];
214 for (unsigned int I
= 0; I
!= List
.size(); I
++)
217 if (stat((List
[I
] + Name
).c_str(),&Buf
) != 0 &&
218 stat((List
[I
] + Name
+ ".gz").c_str(),&Buf
) != 0)
219 _error
->Errno("stat","Failed to stat %s%s",List
[I
].c_str(),
221 Inodes
[I
] = Buf
.st_ino
;
224 if (_error
->PendingError() == true)
228 for (unsigned int I
= 0; I
!= List
.size(); I
++)
230 for (unsigned int J
= I
+1; J
< List
.size(); J
++)
233 if (Inodes
[J
] != Inodes
[I
])
236 // We score the two paths.. and erase one
237 int ScoreA
= Score(List
[I
]);
238 int ScoreB
= Score(List
[J
]);
249 // Wipe erased entries
250 for (unsigned int I
= 0; I
< List
.size();)
252 if (List
[I
].empty() == false)
255 List
.erase(List
.begin()+I
);
262 // ReduceSourceList - Takes the path list and reduces it /*{{{*/
263 // ---------------------------------------------------------------------
264 /* This takes the list of source list expressed entires and collects
265 similar ones to form a single entry for each dist */
266 void ReduceSourcelist(string CD
,vector
<string
> &List
)
268 sort(List
.begin(),List
.end());
270 // Collect similar entries
271 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
274 string::size_type Space
= (*I
).find(' ');
275 if (Space
== string::npos
)
277 string::size_type SSpace
= (*I
).find(' ',Space
+ 1);
278 if (SSpace
== string::npos
)
281 string Word1
= string(*I
,Space
,SSpace
-Space
);
282 string Prefix
= string(*I
,0,Space
);
283 for (vector
<string
>::iterator J
= List
.begin(); J
!= I
; J
++)
286 string::size_type Space2
= (*J
).find(' ');
287 if (Space2
== string::npos
)
289 string::size_type SSpace2
= (*J
).find(' ',Space2
+ 1);
290 if (SSpace2
== string::npos
)
293 if (string(*J
,0,Space2
) != Prefix
)
295 if (string(*J
,Space2
,SSpace2
-Space2
) != Word1
)
298 *J
+= string(*I
,SSpace
);
303 // Wipe erased entries
304 for (unsigned int I
= 0; I
< List
.size();)
306 if (List
[I
].empty() == false)
309 List
.erase(List
.begin()+I
);
313 // WriteDatabase - Write the CDROM Database file /*{{{*/
314 // ---------------------------------------------------------------------
315 /* We rewrite the configuration class associated with the cdrom database. */
316 bool WriteDatabase(Configuration
&Cnf
)
318 string DFile
= _config
->FindFile("Dir::State::cdroms");
319 string NewFile
= DFile
+ ".new";
321 unlink(NewFile
.c_str());
322 ofstream
Out(NewFile
.c_str());
324 return _error
->Errno("ofstream::ofstream",
325 "Failed to open %s.new",DFile
.c_str());
327 /* Write out all of the configuration directives by walking the
328 configuration tree */
329 const Configuration::Item
*Top
= Cnf
.Tree(0);
332 // Print the config entry
333 if (Top
->Value
.empty() == false)
334 Out
<< Top
->FullTag() + " \"" << Top
->Value
<< "\";" << endl
;
342 while (Top
!= 0 && Top
->Next
== 0)
350 rename(DFile
.c_str(),string(DFile
+ '~').c_str());
351 if (rename(NewFile
.c_str(),DFile
.c_str()) != 0)
352 return _error
->Errno("rename","Failed to rename %s.new to %s",
353 DFile
.c_str(),DFile
.c_str());
358 // WriteSourceList - Write an updated sourcelist /*{{{*/
359 // ---------------------------------------------------------------------
360 /* This reads the old source list and copies it into the new one. It
361 appends the new CDROM entires just after the first block of comments.
362 This places them first in the file. It also removes any old entries
363 that were the same. */
364 bool WriteSourceList(string Name
,vector
<string
> &List
,bool Source
)
366 if (List
.size() == 0)
369 string File
= _config
->FindFile("Dir::Etc::sourcelist");
371 // Open the stream for reading
372 ifstream
F((FileExists(File
)?File
.c_str():"/dev/null"),
375 return _error
->Errno("ifstream::ifstream","Opening %s",File
.c_str());
377 string NewFile
= File
+ ".new";
378 unlink(NewFile
.c_str());
379 ofstream
Out(NewFile
.c_str());
381 return _error
->Errno("ofstream::ofstream",
382 "Failed to open %s.new",File
.c_str());
384 // Create a short uri without the path
385 string ShortURI
= "cdrom:[" + Name
+ "]/";
386 string ShortURI2
= "cdrom:" + Name
+ "/"; // For Compatibility
397 while (F
.eof() == false)
399 F
.getline(Buffer
,sizeof(Buffer
));
401 _strtabexpand(Buffer
,sizeof(Buffer
));
405 if (Buffer
[0] == '#' || Buffer
[0] == 0)
407 Out
<< Buffer
<< endl
;
413 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
415 string::size_type Space
= (*I
).find(' ');
416 if (Space
== string::npos
)
417 return _error
->Error("Internal error");
418 Out
<< Type
<< " cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
419 " " << string(*I
,Space
+1) << endl
;
427 const char *C
= Buffer
;
428 if (ParseQuoteWord(C
,cType
) == false ||
429 ParseQuoteWord(C
,URI
) == false)
431 Out
<< Buffer
<< endl
;
435 // Emit lines like this one
436 if (cType
!= Type
|| (string(URI
,0,ShortURI
.length()) != ShortURI
&&
437 string(URI
,0,ShortURI
.length()) != ShortURI2
))
439 Out
<< Buffer
<< endl
;
444 // Just in case the file was empty
447 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
449 string::size_type Space
= (*I
).find(' ');
450 if (Space
== string::npos
)
451 return _error
->Error("Internal error");
453 Out
<< "deb cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
454 " " << string(*I
,Space
+1) << endl
;
460 rename(File
.c_str(),string(File
+ '~').c_str());
461 if (rename(NewFile
.c_str(),File
.c_str()) != 0)
462 return _error
->Errno("rename","Failed to rename %s.new to %s",
463 File
.c_str(),File
.c_str());
469 // Prompt - Simple prompt /*{{{*/
470 // ---------------------------------------------------------------------
472 void Prompt(const char *Text
)
475 cout
<< Text
<< ' ' << flush
;
476 read(STDIN_FILENO
,&C
,1);
481 // PromptLine - Prompt for an input line /*{{{*/
482 // ---------------------------------------------------------------------
484 string
PromptLine(const char *Text
)
486 cout
<< Text
<< ':' << endl
;
494 // DoAdd - Add a new CDROM /*{{{*/
495 // ---------------------------------------------------------------------
496 /* This does the main add bit.. We show some status and things. The
497 sequence is to mount/umount the CD, Ident it then scan it for package
498 files and reduce that list. Then we copy over the package files and
499 verify them. Then rewrite the database files */
500 bool DoAdd(CommandLine
&)
503 string CDROM
= _config
->FindDir("Acquire::cdrom::mount","/cdrom/");
505 CDROM
= SafeGetCWD() + '/' + CDROM
;
507 cout
<< "Using CD-ROM mount point " << CDROM
<< endl
;
510 Configuration Database
;
511 string DFile
= _config
->FindFile("Dir::State::cdroms");
512 if (FileExists(DFile
) == true)
514 if (ReadConfigFile(Database
,DFile
) == false)
515 return _error
->Error("Unable to read the cdrom database %s",
519 // Unmount the CD and get the user to put in the one they want
520 if (_config
->FindB("APT::CDROM::NoMount",false) == false)
522 cout
<< "Unmounting CD-ROM" << endl
;
525 // Mount the new CDROM
526 Prompt("Please insert a Disc in the drive and press enter");
527 cout
<< "Mounting CD-ROM" << endl
;
528 if (MountCdrom(CDROM
) == false)
529 return _error
->Error("Failed to mount the cdrom.");
532 // Hash the CD to get an ID
533 cout
<< "Identifying.. " << flush
;
535 if (IdentCdrom(CDROM
,ID
) == false)
541 cout
<< '[' << ID
<< ']' << endl
;
543 cout
<< "Scanning Disc for index files.. " << flush
;
544 // Get the CD structure
546 vector
<string
> sList
;
547 string StartDir
= SafeGetCWD();
549 if (FindPackages(CDROM
,List
,sList
,InfoDir
) == false)
555 chdir(StartDir
.c_str());
557 if (_config
->FindB("Debug::aptcdrom",false) == true)
559 cout
<< "I found (binary):" << endl
;
560 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
562 cout
<< "I found (source):" << endl
;
563 for (vector
<string
>::iterator I
= sList
.begin(); I
!= sList
.end(); I
++)
568 DropBinaryArch(List
);
569 DropRepeats(List
,"Packages");
570 DropRepeats(sList
,"Sources");
571 cout
<< "Found " << List
.size() << " package indexes and " << sList
.size() <<
572 " source indexes." << endl
;
574 if (List
.size() == 0 && sList
.size() == 0)
575 return _error
->Error("Unable to locate any package files, perhaps this is not a Debian Disc");
577 // Check if the CD is in the database
579 if (Database
.Exists("CD::" + ID
) == false ||
580 _config
->FindB("APT::CDROM::Rename",false) == true)
582 // Try to use the CDs label if at all possible
583 if (InfoDir
.empty() == false &&
584 FileExists(InfoDir
+ "/info") == true)
586 ifstream
F(string(InfoDir
+ "/info").c_str());
590 if (Name
.empty() == false)
592 // Escape special characters
593 string::iterator J
= Name
.begin();
594 for (; J
!= Name
.end(); J
++)
595 if (*J
== '"' || *J
== ']' || *J
== '[')
598 cout
<< "Found label '" << Name
<< "'" << endl
;
599 Database
.Set("CD::" + ID
+ "::Label",Name
);
603 if (_config
->FindB("APT::CDROM::Rename",false) == true ||
604 Name
.empty() == true)
606 cout
<< "Please provide a name for this Disc, such as 'Debian 2.1r1 Disk 1'";
609 Name
= PromptLine("");
610 if (Name
.empty() == false &&
611 Name
.find('"') == string::npos
&&
612 Name
.find('[') == string::npos
&&
613 Name
.find(']') == string::npos
)
615 cout
<< "That is not a valid name, try again " << endl
;
620 Name
= Database
.Find("CD::" + ID
);
622 // Escape special characters
623 string::iterator J
= Name
.begin();
624 for (; J
!= Name
.end(); J
++)
625 if (*J
== '"' || *J
== ']' || *J
== '[')
628 Database
.Set("CD::" + ID
,Name
);
629 cout
<< "This Disc is called:" << endl
<< " '" << Name
<< "'" << endl
;
631 // Copy the package files to the state directory
634 if (Copy
.CopyPackages(CDROM
,Name
,List
) == false ||
635 SrcCopy
.CopyPackages(CDROM
,Name
,sList
) == false)
638 ReduceSourcelist(CDROM
,List
);
639 ReduceSourcelist(CDROM
,sList
);
641 // Write the database and sourcelist
642 if (_config
->FindB("APT::cdrom::NoAct",false) == false)
644 if (WriteDatabase(Database
) == false)
647 cout
<< "Writing new source list" << endl
;
648 if (WriteSourceList(Name
,List
,false) == false ||
649 WriteSourceList(Name
,sList
,true) == false)
653 // Print the sourcelist entries
654 cout
<< "Source List entries for this Disc are:" << endl
;
655 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
657 string::size_type Space
= (*I
).find(' ');
658 if (Space
== string::npos
)
659 return _error
->Error("Internal error");
661 cout
<< "deb cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
662 " " << string(*I
,Space
+1) << endl
;
665 for (vector
<string
>::iterator I
= sList
.begin(); I
!= sList
.end(); I
++)
667 string::size_type Space
= (*I
).find(' ');
668 if (Space
== string::npos
)
669 return _error
->Error("Internal error");
671 cout
<< "deb-src cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
672 " " << string(*I
,Space
+1) << endl
;
675 cout
<< "Repeat this process for the rest of the CDs in your set." << endl
;
677 // Unmount and finish
678 if (_config
->FindB("APT::CDROM::NoMount",false) == false)
684 // DoIdent - Ident a CDROM /*{{{*/
685 // ---------------------------------------------------------------------
687 bool DoIdent(CommandLine
&)
690 string CDROM
= _config
->FindDir("Acquire::cdrom::mount","/cdrom/");
692 CDROM
= SafeGetCWD() + '/' + CDROM
;
694 cout
<< "Using CD-ROM mount point " << CDROM
<< endl
;
695 cout
<< "Mounting CD-ROM" << endl
;
696 if (MountCdrom(CDROM
) == false)
697 return _error
->Error("Failed to mount the cdrom.");
699 // Hash the CD to get an ID
700 cout
<< "Identifying.. " << flush
;
702 if (IdentCdrom(CDROM
,ID
) == false)
708 cout
<< '[' << ID
<< ']' << endl
;
711 Configuration Database
;
712 string DFile
= _config
->FindFile("Dir::State::cdroms");
713 if (FileExists(DFile
) == true)
715 if (ReadConfigFile(Database
,DFile
) == false)
716 return _error
->Error("Unable to read the cdrom database %s",
719 cout
<< "Stored Label: '" << Database
.Find("CD::" + ID
) << "'" << endl
;
724 // ShowHelp - Show the help screen /*{{{*/
725 // ---------------------------------------------------------------------
729 ioprintf(cout
,_("%s %s for %s %s compiled on %s %s\n"),PACKAGE
,VERSION
,
730 COMMON_OS
,COMMON_CPU
,__DATE__
,__TIME__
);
731 if (_config
->FindB("version") == true)
735 "Usage: apt-cdrom [options] command\n"
737 "apt-cdrom is a tool to add CDROM's to APT's source list. The\n"
738 "CDROM mount point and device information is taken from apt.conf\n"
742 " add - Add a CDROM\n"
743 " ident - Report the identity of a CDROM\n"
746 " -h This help text\n"
747 " -d CD-ROM mount point\n"
748 " -r Rename a recognized CD-ROM\n"
750 " -f Fast mode, don't check package files\n"
751 " -a Thorough scan mode\n"
752 " -c=? Read this configuration file\n"
753 " -o=? Set an arbitary configuration option, eg -o dir::cache=/tmp\n"
759 int main(int argc
,const char *argv
[])
761 CommandLine::Args Args
[] = {
762 {'h',"help","help",0},
763 {'v',"version","version",0},
764 {'d',"cdrom","Acquire::cdrom::mount",CommandLine::HasArg
},
765 {'r',"rename","APT::CDROM::Rename",0},
766 {'m',"no-mount","APT::CDROM::NoMount",0},
767 {'f',"fast","APT::CDROM::Fast",0},
768 {'n',"just-print","APT::CDROM::NoAct",0},
769 {'n',"recon","APT::CDROM::NoAct",0},
770 {'n',"no-act","APT::CDROM::NoAct",0},
771 {'a',"thorough","APT::CDROM::Thorough",0},
772 {'c',"config-file",0,CommandLine::ConfigFile
},
773 {'o',"option",0,CommandLine::ArbItem
},
775 CommandLine::Dispatch Cmds
[] = {
780 // Parse the command line and initialize the package library
781 CommandLine
CmdL(Args
,_config
);
782 if (pkgInitConfig(*_config
) == false ||
783 CmdL
.Parse(argc
,argv
) == false ||
784 pkgInitSystem(*_config
,_system
) == false)
786 _error
->DumpErrors();
790 // See if the help should be shown
791 if (_config
->FindB("help") == true || _config
->FindB("version") == true ||
792 CmdL
.FileSize() == 0)
795 // Deal with stdout not being a tty
796 if (ttyname(STDOUT_FILENO
) == 0 && _config
->FindI("quiet",0) < 1)
797 _config
->Set("quiet","1");
799 // Match the operation
800 CmdL
.DispatchArg(Cmds
);
802 // Print any errors or warnings found during parsing
803 if (_error
->empty() == false)
805 bool Errors
= _error
->PendingError();
806 _error
->DumpErrors();
807 return Errors
== true?100:0;