]>
git.saurik.com Git - apt.git/blob - cmdline/apt-cdrom.cc
1 // -*- mode: cpp; mode: fold -*-
3 // $Id: apt-cdrom.cc,v 1.38 2001/05/27 04:46:43 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 /* Aha! We found some package files. We assume that everything under
67 this dir is controlled by those package files so we don't look down
69 if (stat("Packages",&Buf
) == 0 || stat("Packages.gz",&Buf
) == 0)
73 // Continue down if thorough is given
74 if (_config
->FindB("APT::CDROM::Thorough",false) == false)
77 if (stat("Sources.gz",&Buf
) == 0 || stat("Sources",&Buf
) == 0)
81 // Continue down if thorough is given
82 if (_config
->FindB("APT::CDROM::Thorough",false) == false)
86 DIR *D
= opendir(".");
88 return _error
->Errno("opendir","Unable to read %s",CD
.c_str());
90 // Run over the directory
91 for (struct dirent
*Dir
= readdir(D
); Dir
!= 0; Dir
= readdir(D
))
94 if (strcmp(Dir
->d_name
,".") == 0 ||
95 strcmp(Dir
->d_name
,"..") == 0 ||
96 //strcmp(Dir->d_name,"source") == 0 ||
97 strcmp(Dir
->d_name
,".disk") == 0 ||
98 strcmp(Dir
->d_name
,"experimental") == 0 ||
99 strcmp(Dir
->d_name
,"binary-all") == 0)
102 // See if the name is a sub directory
104 if (stat(Dir
->d_name
,&Buf
) != 0)
107 if (S_ISDIR(Buf
.st_mode
) == 0)
111 for (I
= 0; I
!= Depth
; I
++)
112 if (Inodes
[I
] == Buf
.st_ino
)
117 // Store the inodes weve seen
118 Inodes
[Depth
] = Buf
.st_ino
;
121 if (FindPackages(CD
+ Dir
->d_name
,List
,SList
,InfoDir
,Depth
+1) == false)
124 if (chdir(CD
.c_str()) != 0)
125 return _error
->Errno("chdir","Unable to change to %s",CD
.c_str());
130 return !_error
->PendingError();
133 // DropBinaryArch - Dump dirs with a string like /binary-<foo>/ /*{{{*/
134 // ---------------------------------------------------------------------
135 /* Here we drop everything that is not this machines arch */
136 bool DropBinaryArch(vector
<string
> &List
)
139 snprintf(S
,sizeof(S
),"/binary-%s/",
140 _config
->Find("Apt::Architecture").c_str());
142 for (unsigned int I
= 0; I
< List
.size(); I
++)
144 const char *Str
= List
[I
].c_str();
147 if ((Res
= strstr(Str
,"/binary-")) == 0)
151 if (strlen(Res
) < strlen(S
))
153 List
.erase(List
.begin() + I
);
158 // See if it is our arch
159 if (stringcmp(Res
,Res
+ strlen(S
),S
) == 0)
163 List
.erase(List
.begin() + I
);
170 // Score - We compute a 'score' for a path /*{{{*/
171 // ---------------------------------------------------------------------
172 /* Paths are scored based on how close they come to what I consider
173 normal. That is ones that have 'dist' 'stable' 'frozen' will score
174 higher than ones without. */
175 int Score(string Path
)
178 if (Path
.find("stable/") != string::npos
)
180 if (Path
.find("/binary-") != string::npos
)
182 if (Path
.find("frozen/") != string::npos
)
184 if (Path
.find("unstable/") != string::npos
)
186 if (Path
.find("/dists/") != string::npos
)
188 if (Path
.find("/main/") != string::npos
)
190 if (Path
.find("/contrib/") != string::npos
)
192 if (Path
.find("/non-free/") != string::npos
)
194 if (Path
.find("/non-US/") != string::npos
)
196 if (Path
.find("/source/") != string::npos
)
198 if (Path
.find("/debian/") != string::npos
)
203 // DropRepeats - Drop repeated files resulting from symlinks /*{{{*/
204 // ---------------------------------------------------------------------
205 /* Here we go and stat every file that we found and strip dup inodes. */
206 bool DropRepeats(vector
<string
> &List
,const char *Name
)
208 // Get a list of all the inodes
209 ino_t
*Inodes
= new ino_t
[List
.size()];
210 for (unsigned int I
= 0; I
!= List
.size(); I
++)
213 if (stat((List
[I
] + Name
).c_str(),&Buf
) != 0 &&
214 stat((List
[I
] + Name
+ ".gz").c_str(),&Buf
) != 0)
215 _error
->Errno("stat","Failed to stat %s%s",List
[I
].c_str(),
217 Inodes
[I
] = Buf
.st_ino
;
220 if (_error
->PendingError() == true)
224 for (unsigned int I
= 0; I
!= List
.size(); I
++)
226 for (unsigned int J
= I
+1; J
< List
.size(); J
++)
229 if (Inodes
[J
] != Inodes
[I
])
232 // We score the two paths.. and erase one
233 int ScoreA
= Score(List
[I
]);
234 int ScoreB
= Score(List
[J
]);
245 // Wipe erased entries
246 for (unsigned int I
= 0; I
< List
.size();)
248 if (List
[I
].empty() == false)
251 List
.erase(List
.begin()+I
);
258 // ReduceSourceList - Takes the path list and reduces it /*{{{*/
259 // ---------------------------------------------------------------------
260 /* This takes the list of source list expressed entires and collects
261 similar ones to form a single entry for each dist */
262 void ReduceSourcelist(string CD
,vector
<string
> &List
)
264 sort(List
.begin(),List
.end());
266 // Collect similar entries
267 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
270 string::size_type Space
= (*I
).find(' ');
271 if (Space
== string::npos
)
273 string::size_type SSpace
= (*I
).find(' ',Space
+ 1);
274 if (SSpace
== string::npos
)
277 string Word1
= string(*I
,Space
,SSpace
-Space
);
278 string Prefix
= string(*I
,0,Space
);
279 for (vector
<string
>::iterator J
= List
.begin(); J
!= I
; J
++)
282 string::size_type Space2
= (*J
).find(' ');
283 if (Space2
== string::npos
)
285 string::size_type SSpace2
= (*J
).find(' ',Space2
+ 1);
286 if (SSpace2
== string::npos
)
289 if (string(*J
,0,Space2
) != Prefix
)
291 if (string(*J
,Space2
,SSpace2
-Space2
) != Word1
)
294 *J
+= string(*I
,SSpace
);
299 // Wipe erased entries
300 for (unsigned int I
= 0; I
< List
.size();)
302 if (List
[I
].empty() == false)
305 List
.erase(List
.begin()+I
);
309 // WriteDatabase - Write the CDROM Database file /*{{{*/
310 // ---------------------------------------------------------------------
311 /* We rewrite the configuration class associated with the cdrom database. */
312 bool WriteDatabase(Configuration
&Cnf
)
314 string DFile
= _config
->FindFile("Dir::State::cdroms");
315 string NewFile
= DFile
+ ".new";
317 unlink(NewFile
.c_str());
318 ofstream
Out(NewFile
.c_str());
320 return _error
->Errno("ofstream::ofstream",
321 "Failed to open %s.new",DFile
.c_str());
323 /* Write out all of the configuration directives by walking the
324 configuration tree */
325 const Configuration::Item
*Top
= Cnf
.Tree(0);
328 // Print the config entry
329 if (Top
->Value
.empty() == false)
330 Out
<< Top
->FullTag() + " \"" << Top
->Value
<< "\";" << endl
;
338 while (Top
!= 0 && Top
->Next
== 0)
346 rename(DFile
.c_str(),string(DFile
+ '~').c_str());
347 if (rename(NewFile
.c_str(),DFile
.c_str()) != 0)
348 return _error
->Errno("rename","Failed to rename %s.new to %s",
349 DFile
.c_str(),DFile
.c_str());
354 // WriteSourceList - Write an updated sourcelist /*{{{*/
355 // ---------------------------------------------------------------------
356 /* This reads the old source list and copies it into the new one. It
357 appends the new CDROM entires just after the first block of comments.
358 This places them first in the file. It also removes any old entries
359 that were the same. */
360 bool WriteSourceList(string Name
,vector
<string
> &List
,bool Source
)
362 if (List
.size() == 0)
365 string File
= _config
->FindFile("Dir::Etc::sourcelist");
367 // Open the stream for reading
368 ifstream
F((FileExists(File
)?File
.c_str():"/dev/null"),
371 return _error
->Errno("ifstream::ifstream","Opening %s",File
.c_str());
373 string NewFile
= File
+ ".new";
374 unlink(NewFile
.c_str());
375 ofstream
Out(NewFile
.c_str());
377 return _error
->Errno("ofstream::ofstream",
378 "Failed to open %s.new",File
.c_str());
380 // Create a short uri without the path
381 string ShortURI
= "cdrom:[" + Name
+ "]/";
382 string ShortURI2
= "cdrom:" + Name
+ "/"; // For Compatibility
393 while (F
.eof() == false)
395 F
.getline(Buffer
,sizeof(Buffer
));
397 _strtabexpand(Buffer
,sizeof(Buffer
));
401 if (Buffer
[0] == '#' || Buffer
[0] == 0)
403 Out
<< Buffer
<< endl
;
409 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
411 string::size_type Space
= (*I
).find(' ');
412 if (Space
== string::npos
)
413 return _error
->Error("Internal error");
414 Out
<< Type
<< " cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
415 " " << string(*I
,Space
+1) << endl
;
423 const char *C
= Buffer
;
424 if (ParseQuoteWord(C
,cType
) == false ||
425 ParseQuoteWord(C
,URI
) == false)
427 Out
<< Buffer
<< endl
;
431 // Emit lines like this one
432 if (cType
!= Type
|| (string(URI
,0,ShortURI
.length()) != ShortURI
&&
433 string(URI
,0,ShortURI
.length()) != ShortURI2
))
435 Out
<< Buffer
<< endl
;
440 // Just in case the file was empty
443 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
445 string::size_type Space
= (*I
).find(' ');
446 if (Space
== string::npos
)
447 return _error
->Error("Internal error");
449 Out
<< "deb cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
450 " " << string(*I
,Space
+1) << endl
;
456 rename(File
.c_str(),string(File
+ '~').c_str());
457 if (rename(NewFile
.c_str(),File
.c_str()) != 0)
458 return _error
->Errno("rename","Failed to rename %s.new to %s",
459 File
.c_str(),File
.c_str());
465 // Prompt - Simple prompt /*{{{*/
466 // ---------------------------------------------------------------------
468 void Prompt(const char *Text
)
471 cout
<< Text
<< ' ' << flush
;
472 read(STDIN_FILENO
,&C
,1);
477 // PromptLine - Prompt for an input line /*{{{*/
478 // ---------------------------------------------------------------------
480 string
PromptLine(const char *Text
)
482 cout
<< Text
<< ':' << endl
;
490 // DoAdd - Add a new CDROM /*{{{*/
491 // ---------------------------------------------------------------------
492 /* This does the main add bit.. We show some status and things. The
493 sequence is to mount/umount the CD, Ident it then scan it for package
494 files and reduce that list. Then we copy over the package files and
495 verify them. Then rewrite the database files */
496 bool DoAdd(CommandLine
&)
499 string CDROM
= _config
->FindDir("Acquire::cdrom::mount","/cdrom/");
501 CDROM
= SafeGetCWD() + '/' + CDROM
;
503 cout
<< "Using CD-ROM mount point " << CDROM
<< endl
;
506 Configuration Database
;
507 string DFile
= _config
->FindFile("Dir::State::cdroms");
508 if (FileExists(DFile
) == true)
510 if (ReadConfigFile(Database
,DFile
) == false)
511 return _error
->Error("Unable to read the cdrom database %s",
515 // Unmount the CD and get the user to put in the one they want
516 if (_config
->FindB("APT::CDROM::NoMount",false) == false)
518 cout
<< "Unmounting CD-ROM" << endl
;
521 // Mount the new CDROM
522 Prompt("Please insert a Disc in the drive and press enter");
523 cout
<< "Mounting CD-ROM" << endl
;
524 if (MountCdrom(CDROM
) == false)
525 return _error
->Error("Failed to mount the cdrom.");
528 // Hash the CD to get an ID
529 cout
<< "Identifying.. " << flush
;
531 if (IdentCdrom(CDROM
,ID
) == false)
537 cout
<< '[' << ID
<< ']' << endl
;
539 cout
<< "Scanning Disc for index files.. " << flush
;
540 // Get the CD structure
542 vector
<string
> sList
;
543 string StartDir
= SafeGetCWD();
545 if (FindPackages(CDROM
,List
,sList
,InfoDir
) == false)
551 chdir(StartDir
.c_str());
553 if (_config
->FindB("Debug::aptcdrom",false) == true)
555 cout
<< "I found (binary):" << endl
;
556 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
558 cout
<< "I found (source):" << endl
;
559 for (vector
<string
>::iterator I
= sList
.begin(); I
!= sList
.end(); I
++)
564 DropBinaryArch(List
);
565 DropRepeats(List
,"Packages");
566 DropRepeats(sList
,"Sources");
567 cout
<< "Found " << List
.size() << " package indexes and " << sList
.size() <<
568 " source indexes." << endl
;
570 if (List
.size() == 0 && sList
.size() == 0)
571 return _error
->Error("Unable to locate any package files, perhaps this is not a Debian Disc");
573 // Check if the CD is in the database
575 if (Database
.Exists("CD::" + ID
) == false ||
576 _config
->FindB("APT::CDROM::Rename",false) == true)
578 // Try to use the CDs label if at all possible
579 if (InfoDir
.empty() == false &&
580 FileExists(InfoDir
+ "/info") == true)
582 ifstream
F(string(InfoDir
+ "/info").c_str());
586 if (Name
.empty() == false)
588 // Escape special characters
589 string::iterator J
= Name
.begin();
590 for (; J
!= Name
.end(); J
++)
591 if (*J
== '"' || *J
== ']' || *J
== '[')
594 cout
<< "Found label '" << Name
<< "'" << endl
;
595 Database
.Set("CD::" + ID
+ "::Label",Name
);
599 if (_config
->FindB("APT::CDROM::Rename",false) == true ||
600 Name
.empty() == true)
602 cout
<< "Please provide a name for this Disc, such as 'Debian 2.1r1 Disk 1'";
605 Name
= PromptLine("");
606 if (Name
.empty() == false &&
607 Name
.find('"') == string::npos
&&
608 Name
.find('[') == string::npos
&&
609 Name
.find(']') == string::npos
)
611 cout
<< "That is not a valid name, try again " << endl
;
616 Name
= Database
.Find("CD::" + ID
);
618 // Escape special characters
619 string::iterator J
= Name
.begin();
620 for (; J
!= Name
.end(); J
++)
621 if (*J
== '"' || *J
== ']' || *J
== '[')
624 Database
.Set("CD::" + ID
,Name
);
625 cout
<< "This Disc is called:" << endl
<< " '" << Name
<< "'" << endl
;
627 // Copy the package files to the state directory
630 if (Copy
.CopyPackages(CDROM
,Name
,List
) == false ||
631 SrcCopy
.CopyPackages(CDROM
,Name
,sList
) == false)
634 ReduceSourcelist(CDROM
,List
);
635 ReduceSourcelist(CDROM
,sList
);
637 // Write the database and sourcelist
638 if (_config
->FindB("APT::cdrom::NoAct",false) == false)
640 if (WriteDatabase(Database
) == false)
643 cout
<< "Writing new source list" << endl
;
644 if (WriteSourceList(Name
,List
,false) == false ||
645 WriteSourceList(Name
,sList
,true) == false)
649 // Print the sourcelist entries
650 cout
<< "Source List entries for this Disc are:" << endl
;
651 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
653 string::size_type Space
= (*I
).find(' ');
654 if (Space
== string::npos
)
655 return _error
->Error("Internal error");
657 cout
<< "deb cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
658 " " << string(*I
,Space
+1) << endl
;
661 for (vector
<string
>::iterator I
= sList
.begin(); I
!= sList
.end(); I
++)
663 string::size_type Space
= (*I
).find(' ');
664 if (Space
== string::npos
)
665 return _error
->Error("Internal error");
667 cout
<< "deb-src cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
668 " " << string(*I
,Space
+1) << endl
;
671 cout
<< "Repeat this process for the rest of the CDs in your set." << endl
;
673 // Unmount and finish
674 if (_config
->FindB("APT::CDROM::NoMount",false) == false)
680 // DoIdent - Ident a CDROM /*{{{*/
681 // ---------------------------------------------------------------------
683 bool DoIdent(CommandLine
&)
686 string CDROM
= _config
->FindDir("Acquire::cdrom::mount","/cdrom/");
688 CDROM
= SafeGetCWD() + '/' + CDROM
;
690 cout
<< "Using CD-ROM mount point " << CDROM
<< endl
;
691 cout
<< "Mounting CD-ROM" << endl
;
692 if (MountCdrom(CDROM
) == false)
693 return _error
->Error("Failed to mount the cdrom.");
695 // Hash the CD to get an ID
696 cout
<< "Identifying.. " << flush
;
698 if (IdentCdrom(CDROM
,ID
) == false)
704 cout
<< '[' << ID
<< ']' << endl
;
707 Configuration Database
;
708 string DFile
= _config
->FindFile("Dir::State::cdroms");
709 if (FileExists(DFile
) == true)
711 if (ReadConfigFile(Database
,DFile
) == false)
712 return _error
->Error("Unable to read the cdrom database %s",
715 cout
<< "Stored Label: '" << Database
.Find("CD::" + ID
) << "'" << endl
;
720 // ShowHelp - Show the help screen /*{{{*/
721 // ---------------------------------------------------------------------
725 ioprintf(cout
,_("%s %s for %s %s compiled on %s %s\n"),PACKAGE
,VERSION
,
726 COMMON_OS
,COMMON_CPU
,__DATE__
,__TIME__
);
727 if (_config
->FindB("version") == true)
731 "Usage: apt-cdrom [options] command\n"
733 "apt-cdrom is a tool to add CDROM's to APT's source list. The\n"
734 "CDROM mount point and device information is taken from apt.conf\n"
738 " add - Add a CDROM\n"
739 " ident - Report the identity of a CDROM\n"
742 " -h This help text\n"
743 " -d CD-ROM mount point\n"
744 " -r Rename a recognized CD-ROM\n"
746 " -f Fast mode, don't check package files\n"
747 " -a Thorough scan mode\n"
748 " -c=? Read this configuration file\n"
749 " -o=? Set an arbitary configuration option, eg -o dir::cache=/tmp\n"
755 int main(int argc
,const char *argv
[])
757 CommandLine::Args Args
[] = {
758 {'h',"help","help",0},
759 {'v',"version","version",0},
760 {'d',"cdrom","Acquire::cdrom::mount",CommandLine::HasArg
},
761 {'r',"rename","APT::CDROM::Rename",0},
762 {'m',"no-mount","APT::CDROM::NoMount",0},
763 {'f',"fast","APT::CDROM::Fast",0},
764 {'n',"just-print","APT::CDROM::NoAct",0},
765 {'n',"recon","APT::CDROM::NoAct",0},
766 {'n',"no-act","APT::CDROM::NoAct",0},
767 {'a',"thorough","APT::CDROM::Thorough",0},
768 {'c',"config-file",0,CommandLine::ConfigFile
},
769 {'o',"option",0,CommandLine::ArbItem
},
771 CommandLine::Dispatch Cmds
[] = {
776 // Parse the command line and initialize the package library
777 CommandLine
CmdL(Args
,_config
);
778 if (pkgInitConfig(*_config
) == false ||
779 CmdL
.Parse(argc
,argv
) == false ||
780 pkgInitSystem(*_config
,_system
) == false)
782 _error
->DumpErrors();
786 // See if the help should be shown
787 if (_config
->FindB("help") == true || _config
->FindB("version") == true ||
788 CmdL
.FileSize() == 0)
791 // Deal with stdout not being a tty
792 if (ttyname(STDOUT_FILENO
) == 0 && _config
->FindI("quiet",0) < 1)
793 _config
->Set("quiet","1");
795 // Match the operation
796 CmdL
.DispatchArg(Cmds
);
798 // Print any errors or warnings found during parsing
799 if (_error
->empty() == false)
801 bool Errors
= _error
->PendingError();
802 _error
->DumpErrors();
803 return Errors
== true?100:0;