]>
git.saurik.com Git - apt.git/blob - cmdline/apt-cdrom.cc
1 // -*- mode: cpp; mode: fold -*-
3 // $Id: apt-cdrom.cc,v 1.36 2001/02/20 07:03:17 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 sprintf(S
,"/binary-%s/",_config
->Find("Apt::Architecture").c_str());
139 for (unsigned int I
= 0; I
< List
.size(); I
++)
141 const char *Str
= List
[I
].c_str();
144 if ((Res
= strstr(Str
,"/binary-")) == 0)
148 if (strlen(Res
) < strlen(S
))
150 List
.erase(List
.begin() + I
);
155 // See if it is our arch
156 if (stringcmp(Res
,Res
+ strlen(S
),S
) == 0)
160 List
.erase(List
.begin() + I
);
167 // Score - We compute a 'score' for a path /*{{{*/
168 // ---------------------------------------------------------------------
169 /* Paths are scored based on how close they come to what I consider
170 normal. That is ones that have 'dist' 'stable' 'frozen' will score
171 higher than ones without. */
172 int Score(string Path
)
175 if (Path
.find("stable/") != string::npos
)
177 if (Path
.find("/binary-") != string::npos
)
179 if (Path
.find("frozen/") != string::npos
)
181 if (Path
.find("unstable/") != string::npos
)
183 if (Path
.find("/dists/") != string::npos
)
185 if (Path
.find("/main/") != string::npos
)
187 if (Path
.find("/contrib/") != string::npos
)
189 if (Path
.find("/non-free/") != string::npos
)
191 if (Path
.find("/non-US/") != string::npos
)
193 if (Path
.find("/source/") != string::npos
)
195 if (Path
.find("/debian/") != string::npos
)
200 // DropRepeats - Drop repeated files resulting from symlinks /*{{{*/
201 // ---------------------------------------------------------------------
202 /* Here we go and stat every file that we found and strip dup inodes. */
203 bool DropRepeats(vector
<string
> &List
,const char *Name
)
205 // Get a list of all the inodes
206 ino_t
*Inodes
= new ino_t
[List
.size()];
207 for (unsigned int I
= 0; I
!= List
.size(); I
++)
210 if (stat((List
[I
] + Name
).c_str(),&Buf
) != 0 &&
211 stat((List
[I
] + Name
+ ".gz").c_str(),&Buf
) != 0)
212 _error
->Errno("stat","Failed to stat %s%s",List
[I
].c_str(),
214 Inodes
[I
] = Buf
.st_ino
;
217 if (_error
->PendingError() == true)
221 for (unsigned int I
= 0; I
!= List
.size(); I
++)
223 for (unsigned int J
= I
+1; J
< List
.size(); J
++)
226 if (Inodes
[J
] != Inodes
[I
])
229 // We score the two paths.. and erase one
230 int ScoreA
= Score(List
[I
]);
231 int ScoreB
= Score(List
[J
]);
242 // Wipe erased entries
243 for (unsigned int I
= 0; I
< List
.size();)
245 if (List
[I
].empty() == false)
248 List
.erase(List
.begin()+I
);
255 // ReduceSourceList - Takes the path list and reduces it /*{{{*/
256 // ---------------------------------------------------------------------
257 /* This takes the list of source list expressed entires and collects
258 similar ones to form a single entry for each dist */
259 void ReduceSourcelist(string CD
,vector
<string
> &List
)
261 sort(List
.begin(),List
.end());
263 // Collect similar entries
264 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
267 string::size_type Space
= (*I
).find(' ');
268 if (Space
== string::npos
)
270 string::size_type SSpace
= (*I
).find(' ',Space
+ 1);
271 if (SSpace
== string::npos
)
274 string Word1
= string(*I
,Space
,SSpace
-Space
);
275 string Prefix
= string(*I
,0,Space
);
276 for (vector
<string
>::iterator J
= List
.begin(); J
!= I
; J
++)
279 string::size_type Space2
= (*J
).find(' ');
280 if (Space2
== string::npos
)
282 string::size_type SSpace2
= (*J
).find(' ',Space2
+ 1);
283 if (SSpace2
== string::npos
)
286 if (string(*J
,0,Space2
) != Prefix
)
288 if (string(*J
,Space2
,SSpace2
-Space2
) != Word1
)
291 *J
+= string(*I
,SSpace
);
296 // Wipe erased entries
297 for (unsigned int I
= 0; I
< List
.size();)
299 if (List
[I
].empty() == false)
302 List
.erase(List
.begin()+I
);
306 // WriteDatabase - Write the CDROM Database file /*{{{*/
307 // ---------------------------------------------------------------------
308 /* We rewrite the configuration class associated with the cdrom database. */
309 bool WriteDatabase(Configuration
&Cnf
)
311 string DFile
= _config
->FindFile("Dir::State::cdroms");
312 string NewFile
= DFile
+ ".new";
314 unlink(NewFile
.c_str());
315 ofstream
Out(NewFile
.c_str());
317 return _error
->Errno("ofstream::ofstream",
318 "Failed to open %s.new",DFile
.c_str());
320 /* Write out all of the configuration directives by walking the
321 configuration tree */
322 const Configuration::Item
*Top
= Cnf
.Tree(0);
325 // Print the config entry
326 if (Top
->Value
.empty() == false)
327 Out
<< Top
->FullTag() + " \"" << Top
->Value
<< "\";" << endl
;
335 while (Top
!= 0 && Top
->Next
== 0)
343 rename(DFile
.c_str(),string(DFile
+ '~').c_str());
344 if (rename(NewFile
.c_str(),DFile
.c_str()) != 0)
345 return _error
->Errno("rename","Failed to rename %s.new to %s",
346 DFile
.c_str(),DFile
.c_str());
351 // WriteSourceList - Write an updated sourcelist /*{{{*/
352 // ---------------------------------------------------------------------
353 /* This reads the old source list and copies it into the new one. It
354 appends the new CDROM entires just after the first block of comments.
355 This places them first in the file. It also removes any old entries
356 that were the same. */
357 bool WriteSourceList(string Name
,vector
<string
> &List
,bool Source
)
359 if (List
.size() == 0)
362 string File
= _config
->FindFile("Dir::Etc::sourcelist");
364 // Open the stream for reading
365 ifstream
F((FileExists(File
)?File
.c_str():"/dev/null"),
366 ios::in
| ios::nocreate
);
368 return _error
->Errno("ifstream::ifstream","Opening %s",File
.c_str());
370 string NewFile
= File
+ ".new";
371 unlink(NewFile
.c_str());
372 ofstream
Out(NewFile
.c_str());
374 return _error
->Errno("ofstream::ofstream",
375 "Failed to open %s.new",File
.c_str());
377 // Create a short uri without the path
378 string ShortURI
= "cdrom:[" + Name
+ "]/";
379 string ShortURI2
= "cdrom:" + Name
+ "/"; // For Compatibility
390 while (F
.eof() == false)
392 F
.getline(Buffer
,sizeof(Buffer
));
394 _strtabexpand(Buffer
,sizeof(Buffer
));
398 if (Buffer
[0] == '#' || Buffer
[0] == 0)
400 Out
<< Buffer
<< endl
;
406 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
408 string::size_type Space
= (*I
).find(' ');
409 if (Space
== string::npos
)
410 return _error
->Error("Internal error");
411 Out
<< Type
<< " cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
412 " " << string(*I
,Space
+1) << endl
;
420 const char *C
= Buffer
;
421 if (ParseQuoteWord(C
,cType
) == false ||
422 ParseQuoteWord(C
,URI
) == false)
424 Out
<< Buffer
<< endl
;
428 // Emit lines like this one
429 if (cType
!= Type
|| (string(URI
,0,ShortURI
.length()) != ShortURI
&&
430 string(URI
,0,ShortURI
.length()) != ShortURI2
))
432 Out
<< Buffer
<< endl
;
437 // Just in case the file was empty
440 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
442 string::size_type Space
= (*I
).find(' ');
443 if (Space
== string::npos
)
444 return _error
->Error("Internal error");
446 Out
<< "deb cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
447 " " << string(*I
,Space
+1) << endl
;
453 rename(File
.c_str(),string(File
+ '~').c_str());
454 if (rename(NewFile
.c_str(),File
.c_str()) != 0)
455 return _error
->Errno("rename","Failed to rename %s.new to %s",
456 File
.c_str(),File
.c_str());
462 // Prompt - Simple prompt /*{{{*/
463 // ---------------------------------------------------------------------
465 void Prompt(const char *Text
)
468 cout
<< Text
<< ' ' << flush
;
469 read(STDIN_FILENO
,&C
,1);
474 // PromptLine - Prompt for an input line /*{{{*/
475 // ---------------------------------------------------------------------
477 string
PromptLine(const char *Text
)
479 cout
<< Text
<< ':' << endl
;
487 // DoAdd - Add a new CDROM /*{{{*/
488 // ---------------------------------------------------------------------
489 /* This does the main add bit.. We show some status and things. The
490 sequence is to mount/umount the CD, Ident it then scan it for package
491 files and reduce that list. Then we copy over the package files and
492 verify them. Then rewrite the database files */
493 bool DoAdd(CommandLine
&)
496 string CDROM
= _config
->FindDir("Acquire::cdrom::mount","/cdrom/");
498 CDROM
= SafeGetCWD() + '/' + CDROM
;
500 cout
<< "Using CD-ROM mount point " << CDROM
<< endl
;
503 Configuration Database
;
504 string DFile
= _config
->FindFile("Dir::State::cdroms");
505 if (FileExists(DFile
) == true)
507 if (ReadConfigFile(Database
,DFile
) == false)
508 return _error
->Error("Unable to read the cdrom database %s",
512 // Unmount the CD and get the user to put in the one they want
513 if (_config
->FindB("APT::CDROM::NoMount",false) == false)
515 cout
<< "Unmounting CD-ROM" << endl
;
518 // Mount the new CDROM
519 Prompt("Please insert a Disc in the drive and press enter");
520 cout
<< "Mounting CD-ROM" << endl
;
521 if (MountCdrom(CDROM
) == false)
522 return _error
->Error("Failed to mount the cdrom.");
525 // Hash the CD to get an ID
526 cout
<< "Identifying.. " << flush
;
528 if (IdentCdrom(CDROM
,ID
) == false)
534 cout
<< '[' << ID
<< ']' << endl
;
536 cout
<< "Scanning Disc for index files.. " << flush
;
537 // Get the CD structure
539 vector
<string
> sList
;
540 string StartDir
= SafeGetCWD();
542 if (FindPackages(CDROM
,List
,sList
,InfoDir
) == false)
548 chdir(StartDir
.c_str());
550 if (_config
->FindB("Debug::aptcdrom",false) == true)
552 cout
<< "I found (binary):" << endl
;
553 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
555 cout
<< "I found (source):" << endl
;
556 for (vector
<string
>::iterator I
= sList
.begin(); I
!= sList
.end(); I
++)
561 DropBinaryArch(List
);
562 DropRepeats(List
,"Packages");
563 DropRepeats(sList
,"Sources");
564 cout
<< "Found " << List
.size() << " package indexes and " << sList
.size() <<
565 " source indexes." << endl
;
567 if (List
.size() == 0 && sList
.size() == 0)
568 return _error
->Error("Unable to locate any package files, perhaps this is not a Debian Disc");
570 // Check if the CD is in the database
572 if (Database
.Exists("CD::" + ID
) == false ||
573 _config
->FindB("APT::CDROM::Rename",false) == true)
575 // Try to use the CDs label if at all possible
576 if (InfoDir
.empty() == false &&
577 FileExists(InfoDir
+ "/info") == true)
579 ifstream
F(string(InfoDir
+ "/info").c_str());
583 if (Name
.empty() == false)
585 // Escape special characters
586 string::iterator J
= Name
.begin();
587 for (; J
!= Name
.end(); J
++)
588 if (*J
== '"' || *J
== ']' || *J
== '[')
591 cout
<< "Found label '" << Name
<< "'" << endl
;
592 Database
.Set("CD::" + ID
+ "::Label",Name
);
596 if (_config
->FindB("APT::CDROM::Rename",false) == true ||
597 Name
.empty() == true)
599 cout
<< "Please provide a name for this Disc, such as 'Debian 2.1r1 Disk 1'";
602 Name
= PromptLine("");
603 if (Name
.empty() == false &&
604 Name
.find('"') == string::npos
&&
605 Name
.find('[') == string::npos
&&
606 Name
.find(']') == string::npos
)
608 cout
<< "That is not a valid name, try again " << endl
;
613 Name
= Database
.Find("CD::" + ID
);
615 // Escape special characters
616 string::iterator J
= Name
.begin();
617 for (; J
!= Name
.end(); J
++)
618 if (*J
== '"' || *J
== ']' || *J
== '[')
621 Database
.Set("CD::" + ID
,Name
);
622 cout
<< "This Disc is called:" << endl
<< " '" << Name
<< "'" << endl
;
624 // Copy the package files to the state directory
627 if (Copy
.CopyPackages(CDROM
,Name
,List
) == false ||
628 SrcCopy
.CopyPackages(CDROM
,Name
,sList
) == false)
631 ReduceSourcelist(CDROM
,List
);
632 ReduceSourcelist(CDROM
,sList
);
634 // Write the database and sourcelist
635 if (_config
->FindB("APT::cdrom::NoAct",false) == false)
637 if (WriteDatabase(Database
) == false)
640 cout
<< "Writing new source list" << endl
;
641 if (WriteSourceList(Name
,List
,false) == false ||
642 WriteSourceList(Name
,sList
,true) == false)
646 // Print the sourcelist entries
647 cout
<< "Source List entries for this Disc are:" << endl
;
648 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
650 string::size_type Space
= (*I
).find(' ');
651 if (Space
== string::npos
)
652 return _error
->Error("Internal error");
654 cout
<< "deb cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
655 " " << string(*I
,Space
+1) << endl
;
658 for (vector
<string
>::iterator I
= sList
.begin(); I
!= sList
.end(); I
++)
660 string::size_type Space
= (*I
).find(' ');
661 if (Space
== string::npos
)
662 return _error
->Error("Internal error");
664 cout
<< "deb-src cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
665 " " << string(*I
,Space
+1) << endl
;
668 cout
<< "Repeat this process for the rest of the CDs in your set." << endl
;
670 // Unmount and finish
671 if (_config
->FindB("APT::CDROM::NoMount",false) == false)
677 // DoIdent - Ident a CDROM /*{{{*/
678 // ---------------------------------------------------------------------
680 bool DoIdent(CommandLine
&)
683 string CDROM
= _config
->FindDir("Acquire::cdrom::mount","/cdrom/");
685 CDROM
= SafeGetCWD() + '/' + CDROM
;
687 cout
<< "Using CD-ROM mount point " << CDROM
<< endl
;
688 cout
<< "Mounting CD-ROM" << endl
;
689 if (MountCdrom(CDROM
) == false)
690 return _error
->Error("Failed to mount the cdrom.");
692 // Hash the CD to get an ID
693 cout
<< "Identifying.. " << flush
;
695 if (IdentCdrom(CDROM
,ID
) == false)
701 cout
<< '[' << ID
<< ']' << endl
;
704 Configuration Database
;
705 string DFile
= _config
->FindFile("Dir::State::cdroms");
706 if (FileExists(DFile
) == true)
708 if (ReadConfigFile(Database
,DFile
) == false)
709 return _error
->Error("Unable to read the cdrom database %s",
712 cout
<< "Stored Label: '" << Database
.Find("CD::" + ID
) << "'" << endl
;
717 // ShowHelp - Show the help screen /*{{{*/
718 // ---------------------------------------------------------------------
722 ioprintf(cout
,_("%s %s for %s %s compiled on %s %s\n"),PACKAGE
,VERSION
,
723 COMMON_OS
,COMMON_CPU
,__DATE__
,__TIME__
);
724 if (_config
->FindB("version") == true)
728 "Usage: apt-cdrom [options] command\n"
730 "apt-cdrom is a tool to add CDROM's to APT's source list. The\n"
731 "CDROM mount point and device information is taken from apt.conf\n"
735 " add - Add a CDROM\n"
736 " ident - Report the identity of a CDROM\n"
739 " -h This help text\n"
740 " -d CD-ROM mount point\n"
741 " -r Rename a recognized CD-ROM\n"
743 " -f Fast mode, don't check package files\n"
744 " -a Thorough scan mode\n"
745 " -c=? Read this configuration file\n"
746 " -o=? Set an arbitary configuration option, eg -o dir::cache=/tmp\n"
752 int main(int argc
,const char *argv
[])
754 CommandLine::Args Args
[] = {
755 {'h',"help","help",0},
756 {'v',"version","version",0},
757 {'d',"cdrom","Acquire::cdrom::mount",CommandLine::HasArg
},
758 {'r',"rename","APT::CDROM::Rename",0},
759 {'m',"no-mount","APT::CDROM::NoMount",0},
760 {'f',"fast","APT::CDROM::Fast",0},
761 {'n',"just-print","APT::CDROM::NoAct",0},
762 {'n',"recon","APT::CDROM::NoAct",0},
763 {'n',"no-act","APT::CDROM::NoAct",0},
764 {'a',"thorough","APT::CDROM::Thorough",0},
765 {'c',"config-file",0,CommandLine::ConfigFile
},
766 {'o',"option",0,CommandLine::ArbItem
},
768 CommandLine::Dispatch Cmds
[] = {
773 // Parse the command line and initialize the package library
774 CommandLine
CmdL(Args
,_config
);
775 if (pkgInitConfig(*_config
) == false ||
776 CmdL
.Parse(argc
,argv
) == false ||
777 pkgInitSystem(*_config
,_system
) == false)
779 _error
->DumpErrors();
783 // See if the help should be shown
784 if (_config
->FindB("help") == true || _config
->FindB("version") == true ||
785 CmdL
.FileSize() == 0)
788 // Deal with stdout not being a tty
789 if (ttyname(STDOUT_FILENO
) == 0 && _config
->FindI("quiet",0) < 1)
790 _config
->Set("quiet","1");
792 // Match the operation
793 CmdL
.DispatchArg(Cmds
);
795 // Print any errors or warnings found during parsing
796 if (_error
->empty() == false)
798 bool Errors
= _error
->PendingError();
799 _error
->DumpErrors();
800 return Errors
== true?100:0;