]>
git.saurik.com Git - apt.git/blob - cmdline/apt-cdrom.cc
1 // -*- mode: cpp; mode: fold -*-
3 // $Id: apt-cdrom.cc,v 1.45 2003/11/19 23:50:51 mdz 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"
40 // FindPackages - Find the package files on the CDROM /*{{{*/
41 // ---------------------------------------------------------------------
42 /* We look over the cdrom for package files. This is a recursive
43 search that short circuits when it his a package file in the dir.
44 This speeds it up greatly as the majority of the size is in the
46 bool FindPackages(string CD
,vector
<string
> &List
,vector
<string
> &SList
,
47 string
&InfoDir
,unsigned int Depth
= 0)
49 static ino_t Inodes
[9];
53 if (CD
[CD
.length()-1] != '/')
56 if (chdir(CD
.c_str()) != 0)
57 return _error
->Errno("chdir","Unable to change to %s",CD
.c_str());
59 // Look for a .disk subdirectory
61 if (stat(".disk",&Buf
) == 0)
63 if (InfoDir
.empty() == true)
64 InfoDir
= CD
+ ".disk/";
67 // Don't look into directories that have been marked to ingore.
68 if (stat(".aptignr",&Buf
) == 0)
71 /* Aha! We found some package files. We assume that everything under
72 this dir is controlled by those package files so we don't look down
74 if (stat("Packages",&Buf
) == 0 || stat("Packages.gz",&Buf
) == 0)
78 // Continue down if thorough is given
79 if (_config
->FindB("APT::CDROM::Thorough",false) == false)
82 if (stat("Sources.gz",&Buf
) == 0 || stat("Sources",&Buf
) == 0)
86 // Continue down if thorough is given
87 if (_config
->FindB("APT::CDROM::Thorough",false) == false)
91 DIR *D
= opendir(".");
93 return _error
->Errno("opendir","Unable to read %s",CD
.c_str());
95 // Run over the directory
96 for (struct dirent
*Dir
= readdir(D
); Dir
!= 0; Dir
= readdir(D
))
99 if (strcmp(Dir
->d_name
,".") == 0 ||
100 strcmp(Dir
->d_name
,"..") == 0 ||
101 //strcmp(Dir->d_name,"source") == 0 ||
102 strcmp(Dir
->d_name
,".disk") == 0 ||
103 strcmp(Dir
->d_name
,"experimental") == 0 ||
104 strcmp(Dir
->d_name
,"binary-all") == 0 ||
105 strcmp(Dir
->d_name
,"debian-installer") == 0)
108 // See if the name is a sub directory
110 if (stat(Dir
->d_name
,&Buf
) != 0)
113 if (S_ISDIR(Buf
.st_mode
) == 0)
117 for (I
= 0; I
!= Depth
; I
++)
118 if (Inodes
[I
] == Buf
.st_ino
)
123 // Store the inodes weve seen
124 Inodes
[Depth
] = Buf
.st_ino
;
127 if (FindPackages(CD
+ Dir
->d_name
,List
,SList
,InfoDir
,Depth
+1) == false)
130 if (chdir(CD
.c_str()) != 0)
131 return _error
->Errno("chdir","Unable to change to %s",CD
.c_str());
136 return !_error
->PendingError();
139 // DropBinaryArch - Dump dirs with a string like /binary-<foo>/ /*{{{*/
140 // ---------------------------------------------------------------------
141 /* Here we drop everything that is not this machines arch */
142 bool DropBinaryArch(vector
<string
> &List
)
145 snprintf(S
,sizeof(S
),"/binary-%s/",
146 _config
->Find("Apt::Architecture").c_str());
148 for (unsigned int I
= 0; I
< List
.size(); I
++)
150 const char *Str
= List
[I
].c_str();
153 if ((Res
= strstr(Str
,"/binary-")) == 0)
157 if (strlen(Res
) < strlen(S
))
159 List
.erase(List
.begin() + I
);
164 // See if it is our arch
165 if (stringcmp(Res
,Res
+ strlen(S
),S
) == 0)
169 List
.erase(List
.begin() + I
);
176 // Score - We compute a 'score' for a path /*{{{*/
177 // ---------------------------------------------------------------------
178 /* Paths are scored based on how close they come to what I consider
179 normal. That is ones that have 'dist' 'stable' 'testing' will score
180 higher than ones without. */
181 int Score(string Path
)
184 if (Path
.find("stable/") != string::npos
)
186 if (Path
.find("/binary-") != string::npos
)
188 if (Path
.find("testing/") != string::npos
)
190 if (Path
.find("unstable/") != string::npos
)
192 if (Path
.find("/dists/") != string::npos
)
194 if (Path
.find("/main/") != string::npos
)
196 if (Path
.find("/contrib/") != string::npos
)
198 if (Path
.find("/non-free/") != string::npos
)
200 if (Path
.find("/non-US/") != string::npos
)
202 if (Path
.find("/source/") != string::npos
)
204 if (Path
.find("/debian/") != string::npos
)
209 // DropRepeats - Drop repeated files resulting from symlinks /*{{{*/
210 // ---------------------------------------------------------------------
211 /* Here we go and stat every file that we found and strip dup inodes. */
212 bool DropRepeats(vector
<string
> &List
,const char *Name
)
214 // Get a list of all the inodes
215 ino_t
*Inodes
= new ino_t
[List
.size()];
216 for (unsigned int I
= 0; I
!= List
.size(); I
++)
219 if (stat((List
[I
] + Name
).c_str(),&Buf
) != 0 &&
220 stat((List
[I
] + Name
+ ".gz").c_str(),&Buf
) != 0)
221 _error
->Errno("stat","Failed to stat %s%s",List
[I
].c_str(),
223 Inodes
[I
] = Buf
.st_ino
;
226 if (_error
->PendingError() == true)
230 for (unsigned int I
= 0; I
!= List
.size(); I
++)
232 for (unsigned int J
= I
+1; J
< List
.size(); J
++)
235 if (Inodes
[J
] != Inodes
[I
])
238 // We score the two paths.. and erase one
239 int ScoreA
= Score(List
[I
]);
240 int ScoreB
= Score(List
[J
]);
251 // Wipe erased entries
252 for (unsigned int I
= 0; I
< List
.size();)
254 if (List
[I
].empty() == false)
257 List
.erase(List
.begin()+I
);
264 // ReduceSourceList - Takes the path list and reduces it /*{{{*/
265 // ---------------------------------------------------------------------
266 /* This takes the list of source list expressed entires and collects
267 similar ones to form a single entry for each dist */
268 void ReduceSourcelist(string CD
,vector
<string
> &List
)
270 sort(List
.begin(),List
.end());
272 // Collect similar entries
273 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
276 string::size_type Space
= (*I
).find(' ');
277 if (Space
== string::npos
)
279 string::size_type SSpace
= (*I
).find(' ',Space
+ 1);
280 if (SSpace
== string::npos
)
283 string Word1
= string(*I
,Space
,SSpace
-Space
);
284 string Prefix
= string(*I
,0,Space
);
285 for (vector
<string
>::iterator J
= List
.begin(); J
!= I
; J
++)
288 string::size_type Space2
= (*J
).find(' ');
289 if (Space2
== string::npos
)
291 string::size_type SSpace2
= (*J
).find(' ',Space2
+ 1);
292 if (SSpace2
== string::npos
)
295 if (string(*J
,0,Space2
) != Prefix
)
297 if (string(*J
,Space2
,SSpace2
-Space2
) != Word1
)
300 *J
+= string(*I
,SSpace
);
305 // Wipe erased entries
306 for (unsigned int I
= 0; I
< List
.size();)
308 if (List
[I
].empty() == false)
311 List
.erase(List
.begin()+I
);
315 // WriteDatabase - Write the CDROM Database file /*{{{*/
316 // ---------------------------------------------------------------------
317 /* We rewrite the configuration class associated with the cdrom database. */
318 bool WriteDatabase(Configuration
&Cnf
)
320 string DFile
= _config
->FindFile("Dir::State::cdroms");
321 string NewFile
= DFile
+ ".new";
323 unlink(NewFile
.c_str());
324 ofstream
Out(NewFile
.c_str());
326 return _error
->Errno("ofstream::ofstream",
327 "Failed to open %s.new",DFile
.c_str());
329 /* Write out all of the configuration directives by walking the
330 configuration tree */
331 const Configuration::Item
*Top
= Cnf
.Tree(0);
334 // Print the config entry
335 if (Top
->Value
.empty() == false)
336 Out
<< Top
->FullTag() + " \"" << Top
->Value
<< "\";" << endl
;
344 while (Top
!= 0 && Top
->Next
== 0)
352 rename(DFile
.c_str(),string(DFile
+ '~').c_str());
353 if (rename(NewFile
.c_str(),DFile
.c_str()) != 0)
354 return _error
->Errno("rename","Failed to rename %s.new to %s",
355 DFile
.c_str(),DFile
.c_str());
360 // WriteSourceList - Write an updated sourcelist /*{{{*/
361 // ---------------------------------------------------------------------
362 /* This reads the old source list and copies it into the new one. It
363 appends the new CDROM entires just after the first block of comments.
364 This places them first in the file. It also removes any old entries
365 that were the same. */
366 bool WriteSourceList(string Name
,vector
<string
> &List
,bool Source
)
368 if (List
.size() == 0)
371 string File
= _config
->FindFile("Dir::Etc::sourcelist");
373 // Open the stream for reading
374 ifstream
F((FileExists(File
)?File
.c_str():"/dev/null"),
377 return _error
->Errno("ifstream::ifstream","Opening %s",File
.c_str());
379 string NewFile
= File
+ ".new";
380 unlink(NewFile
.c_str());
381 ofstream
Out(NewFile
.c_str());
383 return _error
->Errno("ofstream::ofstream",
384 "Failed to open %s.new",File
.c_str());
386 // Create a short uri without the path
387 string ShortURI
= "cdrom:[" + Name
+ "]/";
388 string ShortURI2
= "cdrom:" + Name
+ "/"; // For Compatibility
399 while (F
.eof() == false)
401 F
.getline(Buffer
,sizeof(Buffer
));
403 _strtabexpand(Buffer
,sizeof(Buffer
));
407 if (Buffer
[0] == '#' || Buffer
[0] == 0)
409 Out
<< Buffer
<< endl
;
415 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
417 string::size_type Space
= (*I
).find(' ');
418 if (Space
== string::npos
)
419 return _error
->Error("Internal error");
420 Out
<< Type
<< " cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
421 " " << string(*I
,Space
+1) << endl
;
429 const char *C
= Buffer
;
430 if (ParseQuoteWord(C
,cType
) == false ||
431 ParseQuoteWord(C
,URI
) == false)
433 Out
<< Buffer
<< endl
;
437 // Emit lines like this one
438 if (cType
!= Type
|| (string(URI
,0,ShortURI
.length()) != ShortURI
&&
439 string(URI
,0,ShortURI
.length()) != ShortURI2
))
441 Out
<< Buffer
<< endl
;
446 // Just in case the file was empty
449 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
451 string::size_type Space
= (*I
).find(' ');
452 if (Space
== string::npos
)
453 return _error
->Error("Internal error");
455 Out
<< "deb cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
456 " " << string(*I
,Space
+1) << endl
;
462 rename(File
.c_str(),string(File
+ '~').c_str());
463 if (rename(NewFile
.c_str(),File
.c_str()) != 0)
464 return _error
->Errno("rename","Failed to rename %s.new to %s",
465 File
.c_str(),File
.c_str());
471 // Prompt - Simple prompt /*{{{*/
472 // ---------------------------------------------------------------------
474 void Prompt(const char *Text
)
477 cout
<< Text
<< ' ' << flush
;
478 read(STDIN_FILENO
,&C
,1);
483 // PromptLine - Prompt for an input line /*{{{*/
484 // ---------------------------------------------------------------------
486 string
PromptLine(const char *Text
)
488 cout
<< Text
<< ':' << endl
;
496 // DoAdd - Add a new CDROM /*{{{*/
497 // ---------------------------------------------------------------------
498 /* This does the main add bit.. We show some status and things. The
499 sequence is to mount/umount the CD, Ident it then scan it for package
500 files and reduce that list. Then we copy over the package files and
501 verify them. Then rewrite the database files */
502 bool DoAdd(CommandLine
&)
505 string CDROM
= _config
->FindDir("Acquire::cdrom::mount","/cdrom/");
507 CDROM
= SafeGetCWD() + '/' + CDROM
;
509 cout
<< "Using CD-ROM mount point " << CDROM
<< endl
;
512 Configuration Database
;
513 string DFile
= _config
->FindFile("Dir::State::cdroms");
514 if (FileExists(DFile
) == true)
516 if (ReadConfigFile(Database
,DFile
) == false)
517 return _error
->Error("Unable to read the cdrom database %s",
521 // Unmount the CD and get the user to put in the one they want
522 if (_config
->FindB("APT::CDROM::NoMount",false) == false)
524 cout
<< "Unmounting CD-ROM" << endl
;
527 // Mount the new CDROM
528 Prompt("Please insert a Disc in the drive and press enter");
529 cout
<< "Mounting CD-ROM" << endl
;
530 if (MountCdrom(CDROM
) == false)
531 return _error
->Error("Failed to mount the cdrom.");
534 // Hash the CD to get an ID
535 cout
<< "Identifying.. " << flush
;
537 if (IdentCdrom(CDROM
,ID
) == false)
543 cout
<< '[' << ID
<< ']' << endl
;
545 cout
<< "Scanning Disc for index files.. " << flush
;
546 // Get the CD structure
548 vector
<string
> sList
;
549 string StartDir
= SafeGetCWD();
551 if (FindPackages(CDROM
,List
,sList
,InfoDir
) == false)
557 chdir(StartDir
.c_str());
559 if (_config
->FindB("Debug::aptcdrom",false) == true)
561 cout
<< "I found (binary):" << endl
;
562 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
564 cout
<< "I found (source):" << endl
;
565 for (vector
<string
>::iterator I
= sList
.begin(); I
!= sList
.end(); I
++)
570 DropBinaryArch(List
);
571 DropRepeats(List
,"Packages");
572 DropRepeats(sList
,"Sources");
573 cout
<< "Found " << List
.size() << " package indexes and " << sList
.size() <<
574 " source indexes." << endl
;
576 if (List
.size() == 0 && sList
.size() == 0)
577 return _error
->Error("Unable to locate any package files, perhaps this is not a Debian Disc");
579 // Check if the CD is in the database
581 if (Database
.Exists("CD::" + ID
) == false ||
582 _config
->FindB("APT::CDROM::Rename",false) == true)
584 // Try to use the CDs label if at all possible
585 if (InfoDir
.empty() == false &&
586 FileExists(InfoDir
+ "/info") == true)
588 ifstream
F(string(InfoDir
+ "/info").c_str());
592 if (Name
.empty() == false)
594 // Escape special characters
595 string::iterator J
= Name
.begin();
596 for (; J
!= Name
.end(); J
++)
597 if (*J
== '"' || *J
== ']' || *J
== '[')
600 cout
<< "Found label '" << Name
<< "'" << endl
;
601 Database
.Set("CD::" + ID
+ "::Label",Name
);
605 if (_config
->FindB("APT::CDROM::Rename",false) == true ||
606 Name
.empty() == true)
608 cout
<< "Please provide a name for this Disc, such as 'Debian 2.1r1 Disk 1'";
611 Name
= PromptLine("");
612 if (Name
.empty() == false &&
613 Name
.find('"') == string::npos
&&
614 Name
.find('[') == string::npos
&&
615 Name
.find(']') == string::npos
)
617 cout
<< "That is not a valid name, try again " << endl
;
622 Name
= Database
.Find("CD::" + ID
);
624 // Escape special characters
625 string::iterator J
= Name
.begin();
626 for (; J
!= Name
.end(); J
++)
627 if (*J
== '"' || *J
== ']' || *J
== '[')
630 Database
.Set("CD::" + ID
,Name
);
631 cout
<< "This Disc is called:" << endl
<< " '" << Name
<< "'" << endl
;
633 // Copy the package files to the state directory
636 if (Copy
.CopyPackages(CDROM
,Name
,List
) == false ||
637 SrcCopy
.CopyPackages(CDROM
,Name
,sList
) == false)
640 ReduceSourcelist(CDROM
,List
);
641 ReduceSourcelist(CDROM
,sList
);
643 // Write the database and sourcelist
644 if (_config
->FindB("APT::cdrom::NoAct",false) == false)
646 if (WriteDatabase(Database
) == false)
649 cout
<< "Writing new source list" << endl
;
650 if (WriteSourceList(Name
,List
,false) == false ||
651 WriteSourceList(Name
,sList
,true) == false)
655 // Print the sourcelist entries
656 cout
<< "Source List entries for this Disc are:" << endl
;
657 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
659 string::size_type Space
= (*I
).find(' ');
660 if (Space
== string::npos
)
661 return _error
->Error("Internal error");
663 cout
<< "deb cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
664 " " << string(*I
,Space
+1) << endl
;
667 for (vector
<string
>::iterator I
= sList
.begin(); I
!= sList
.end(); I
++)
669 string::size_type Space
= (*I
).find(' ');
670 if (Space
== string::npos
)
671 return _error
->Error("Internal error");
673 cout
<< "deb-src cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
674 " " << string(*I
,Space
+1) << endl
;
677 cout
<< "Repeat this process for the rest of the CDs in your set." << endl
;
679 // Unmount and finish
680 if (_config
->FindB("APT::CDROM::NoMount",false) == false)
686 // DoIdent - Ident a CDROM /*{{{*/
687 // ---------------------------------------------------------------------
689 bool DoIdent(CommandLine
&)
692 string CDROM
= _config
->FindDir("Acquire::cdrom::mount","/cdrom/");
694 CDROM
= SafeGetCWD() + '/' + CDROM
;
696 cout
<< "Using CD-ROM mount point " << CDROM
<< endl
;
697 cout
<< "Mounting CD-ROM" << endl
;
698 if (MountCdrom(CDROM
) == false)
699 return _error
->Error("Failed to mount the cdrom.");
701 // Hash the CD to get an ID
702 cout
<< "Identifying.. " << flush
;
704 if (IdentCdrom(CDROM
,ID
) == false)
710 cout
<< '[' << ID
<< ']' << endl
;
713 Configuration Database
;
714 string DFile
= _config
->FindFile("Dir::State::cdroms");
715 if (FileExists(DFile
) == true)
717 if (ReadConfigFile(Database
,DFile
) == false)
718 return _error
->Error("Unable to read the cdrom database %s",
721 cout
<< "Stored Label: '" << Database
.Find("CD::" + ID
) << "'" << endl
;
726 // ShowHelp - Show the help screen /*{{{*/
727 // ---------------------------------------------------------------------
731 ioprintf(cout
,_("%s %s for %s %s compiled on %s %s\n"),PACKAGE
,VERSION
,
732 COMMON_OS
,COMMON_CPU
,__DATE__
,__TIME__
);
733 if (_config
->FindB("version") == true)
737 "Usage: apt-cdrom [options] command\n"
739 "apt-cdrom is a tool to add CDROM's to APT's source list. The\n"
740 "CDROM mount point and device information is taken from apt.conf\n"
744 " add - Add a CDROM\n"
745 " ident - Report the identity of a CDROM\n"
748 " -h This help text\n"
749 " -d CD-ROM mount point\n"
750 " -r Rename a recognized CD-ROM\n"
752 " -f Fast mode, don't check package files\n"
753 " -a Thorough scan mode\n"
754 " -c=? Read this configuration file\n"
755 " -o=? Set an arbitary configuration option, eg -o dir::cache=/tmp\n"
761 int main(int argc
,const char *argv
[])
763 CommandLine::Args Args
[] = {
764 {'h',"help","help",0},
765 {'v',"version","version",0},
766 {'d',"cdrom","Acquire::cdrom::mount",CommandLine::HasArg
},
767 {'r',"rename","APT::CDROM::Rename",0},
768 {'m',"no-mount","APT::CDROM::NoMount",0},
769 {'f',"fast","APT::CDROM::Fast",0},
770 {'n',"just-print","APT::CDROM::NoAct",0},
771 {'n',"recon","APT::CDROM::NoAct",0},
772 {'n',"no-act","APT::CDROM::NoAct",0},
773 {'a',"thorough","APT::CDROM::Thorough",0},
774 {'c',"config-file",0,CommandLine::ConfigFile
},
775 {'o',"option",0,CommandLine::ArbItem
},
777 CommandLine::Dispatch Cmds
[] = {
782 // Set up gettext support
783 setlocale(LC_ALL
,"");
786 // Parse the command line and initialize the package library
787 CommandLine
CmdL(Args
,_config
);
788 if (pkgInitConfig(*_config
) == false ||
789 CmdL
.Parse(argc
,argv
) == false ||
790 pkgInitSystem(*_config
,_system
) == false)
792 _error
->DumpErrors();
796 // See if the help should be shown
797 if (_config
->FindB("help") == true || _config
->FindB("version") == true ||
798 CmdL
.FileSize() == 0)
801 // Deal with stdout not being a tty
802 if (isatty(STDOUT_FILENO
) && _config
->FindI("quiet",0) < 1)
803 _config
->Set("quiet","1");
805 // Match the operation
806 CmdL
.DispatchArg(Cmds
);
808 // Print any errors or warnings found during parsing
809 if (_error
->empty() == false)
811 bool Errors
= _error
->PendingError();
812 _error
->DumpErrors();
813 return Errors
== true?100:0;