]>
git.saurik.com Git - apt.git/blob - cmdline/apt-cdrom.cc
1 // -*- mode: cpp; mode: fold -*-
3 // $Id: apt-cdrom.cc,v 1.34 2000/01/17 07:11:49 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>
23 #include "indexcopy.h"
36 // FindPackages - Find the package files on the CDROM /*{{{*/
37 // ---------------------------------------------------------------------
38 /* We look over the cdrom for package files. This is a recursive
39 search that short circuits when it his a package file in the dir.
40 This speeds it up greatly as the majority of the size is in the
42 bool FindPackages(string CD
,vector
<string
> &List
,vector
<string
> &SList
,
43 string
&InfoDir
,unsigned int Depth
= 0)
45 static ino_t Inodes
[9];
49 if (CD
[CD
.length()-1] != '/')
52 if (chdir(CD
.c_str()) != 0)
53 return _error
->Errno("chdir","Unable to change to %s",CD
.c_str());
55 // Look for a .disk subdirectory
57 if (stat(".disk",&Buf
) == 0)
59 if (InfoDir
.empty() == true)
60 InfoDir
= CD
+ ".disk/";
63 /* Aha! We found some package files. We assume that everything under
64 this dir is controlled by those package files so we don't look down
66 if (stat("Packages",&Buf
) == 0 || stat("Packages.gz",&Buf
) == 0)
70 // Continue down if thorough is given
71 if (_config
->FindB("APT::CDROM::Thorough",false) == false)
74 if (stat("Sources.gz",&Buf
) == 0 || stat("Sources",&Buf
) == 0)
78 // Continue down if thorough is given
79 if (_config
->FindB("APT::CDROM::Thorough",false) == false)
83 DIR *D
= opendir(".");
85 return _error
->Errno("opendir","Unable to read %s",CD
.c_str());
87 // Run over the directory
88 for (struct dirent
*Dir
= readdir(D
); Dir
!= 0; Dir
= readdir(D
))
91 if (strcmp(Dir
->d_name
,".") == 0 ||
92 strcmp(Dir
->d_name
,"..") == 0 ||
93 //strcmp(Dir->d_name,"source") == 0 ||
94 strcmp(Dir
->d_name
,".disk") == 0 ||
95 strcmp(Dir
->d_name
,"experimental") == 0 ||
96 strcmp(Dir
->d_name
,"binary-all") == 0)
99 // See if the name is a sub directory
101 if (stat(Dir
->d_name
,&Buf
) != 0)
104 if (S_ISDIR(Buf
.st_mode
) == 0)
108 for (I
= 0; I
!= Depth
; I
++)
109 if (Inodes
[I
] == Buf
.st_ino
)
114 // Store the inodes weve seen
115 Inodes
[Depth
] = Buf
.st_ino
;
118 if (FindPackages(CD
+ Dir
->d_name
,List
,SList
,InfoDir
,Depth
+1) == false)
121 if (chdir(CD
.c_str()) != 0)
122 return _error
->Errno("chdir","Unable to change to ",CD
.c_str());
127 return !_error
->PendingError();
130 // DropBinaryArch - Dump dirs with a string like /binary-<foo>/ /*{{{*/
131 // ---------------------------------------------------------------------
132 /* Here we drop everything that is not this machines arch */
133 bool DropBinaryArch(vector
<string
> &List
)
136 sprintf(S
,"/binary-%s/",_config
->Find("Apt::Architecture").c_str());
138 for (unsigned int I
= 0; I
< List
.size(); I
++)
140 const char *Str
= List
[I
].c_str();
143 if ((Res
= strstr(Str
,"/binary-")) == 0)
147 if (strlen(Res
) < strlen(S
))
149 List
.erase(List
.begin() + I
);
154 // See if it is our arch
155 if (stringcmp(Res
,Res
+ strlen(S
),S
) == 0)
159 List
.erase(List
.begin() + I
);
166 // Score - We compute a 'score' for a path /*{{{*/
167 // ---------------------------------------------------------------------
168 /* Paths are scored based on how close they come to what I consider
169 normal. That is ones that have 'dist' 'stable' 'frozen' will score
170 higher than ones without. */
171 int Score(string Path
)
174 if (Path
.find("stable/") != string::npos
)
176 if (Path
.find("/binary-") != string::npos
)
178 if (Path
.find("frozen/") != string::npos
)
180 if (Path
.find("/dists/") != string::npos
)
182 if (Path
.find("/main/") != string::npos
)
184 if (Path
.find("/contrib/") != string::npos
)
186 if (Path
.find("/non-free/") != string::npos
)
188 if (Path
.find("/non-US/") != string::npos
)
190 if (Path
.find("/source/") != string::npos
)
192 if (Path
.find("/debian/") != string::npos
)
197 // DropRepeats - Drop repeated files resulting from symlinks /*{{{*/
198 // ---------------------------------------------------------------------
199 /* Here we go and stat every file that we found and strip dup inodes. */
200 bool DropRepeats(vector
<string
> &List
,const char *Name
)
202 // Get a list of all the inodes
203 ino_t
*Inodes
= new ino_t
[List
.size()];
204 for (unsigned int I
= 0; I
!= List
.size(); I
++)
207 if (stat((List
[I
] + Name
).c_str(),&Buf
) != 0 &&
208 stat((List
[I
] + Name
+ ".gz").c_str(),&Buf
) != 0)
209 _error
->Errno("stat","Failed to stat %s%s",List
[I
].c_str(),
211 Inodes
[I
] = Buf
.st_ino
;
214 if (_error
->PendingError() == true)
218 for (unsigned int I
= 0; I
!= List
.size(); I
++)
220 for (unsigned int J
= I
+1; J
< List
.size(); J
++)
223 if (Inodes
[J
] != Inodes
[I
])
226 // We score the two paths.. and erase one
227 int ScoreA
= Score(List
[I
]);
228 int ScoreB
= Score(List
[J
]);
239 // Wipe erased entries
240 for (unsigned int I
= 0; I
< List
.size();)
242 if (List
[I
].empty() == false)
245 List
.erase(List
.begin()+I
);
252 // ReduceSourceList - Takes the path list and reduces it /*{{{*/
253 // ---------------------------------------------------------------------
254 /* This takes the list of source list expressed entires and collects
255 similar ones to form a single entry for each dist */
256 bool ReduceSourcelist(string CD
,vector
<string
> &List
)
258 sort(List
.begin(),List
.end());
260 // Collect similar entries
261 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
264 string::size_type Space
= (*I
).find(' ');
265 if (Space
== string::npos
)
267 string::size_type SSpace
= (*I
).find(' ',Space
+ 1);
268 if (SSpace
== string::npos
)
271 string Word1
= string(*I
,Space
,SSpace
-Space
);
272 for (vector
<string
>::iterator J
= List
.begin(); J
!= I
; J
++)
275 string::size_type Space2
= (*J
).find(' ');
276 if (Space2
== string::npos
)
278 string::size_type SSpace2
= (*J
).find(' ',Space2
+ 1);
279 if (SSpace2
== string::npos
)
282 if (string(*J
,Space2
,SSpace2
-Space2
) != Word1
)
285 *J
+= string(*I
,SSpace
);
290 // Wipe erased entries
291 for (unsigned int I
= 0; I
< List
.size();)
293 if (List
[I
].empty() == false)
296 List
.erase(List
.begin()+I
);
300 // WriteDatabase - Write the CDROM Database file /*{{{*/
301 // ---------------------------------------------------------------------
302 /* We rewrite the configuration class associated with the cdrom database. */
303 bool WriteDatabase(Configuration
&Cnf
)
305 string DFile
= _config
->FindFile("Dir::State::cdroms");
306 string NewFile
= DFile
+ ".new";
308 unlink(NewFile
.c_str());
309 ofstream
Out(NewFile
.c_str());
311 return _error
->Errno("ofstream::ofstream",
312 "Failed to open %s.new",DFile
.c_str());
314 /* Write out all of the configuration directives by walking the
315 configuration tree */
316 const Configuration::Item
*Top
= Cnf
.Tree(0);
319 // Print the config entry
320 if (Top
->Value
.empty() == false)
321 Out
<< Top
->FullTag() + " \"" << Top
->Value
<< "\";" << endl
;
329 while (Top
!= 0 && Top
->Next
== 0)
337 rename(DFile
.c_str(),string(DFile
+ '~').c_str());
338 if (rename(NewFile
.c_str(),DFile
.c_str()) != 0)
339 return _error
->Errno("rename","Failed to rename %s.new to %s",
340 DFile
.c_str(),DFile
.c_str());
345 // WriteSourceList - Write an updated sourcelist /*{{{*/
346 // ---------------------------------------------------------------------
347 /* This reads the old source list and copies it into the new one. It
348 appends the new CDROM entires just after the first block of comments.
349 This places them first in the file. It also removes any old entries
350 that were the same. */
351 bool WriteSourceList(string Name
,vector
<string
> &List
,bool Source
)
353 if (List
.size() == 0)
356 string File
= _config
->FindFile("Dir::Etc::sourcelist");
358 // Open the stream for reading
359 ifstream
F(File
.c_str(),ios::in
| ios::nocreate
);
361 return _error
->Errno("ifstream::ifstream","Opening %s",File
.c_str());
363 string NewFile
= File
+ ".new";
364 unlink(NewFile
.c_str());
365 ofstream
Out(NewFile
.c_str());
367 return _error
->Errno("ofstream::ofstream",
368 "Failed to open %s.new",File
.c_str());
370 // Create a short uri without the path
371 string ShortURI
= "cdrom:[" + Name
+ "]/";
372 string ShortURI2
= "cdrom:" + Name
+ "/"; // For Compatibility
383 while (F
.eof() == false)
385 F
.getline(Buffer
,sizeof(Buffer
));
387 _strtabexpand(Buffer
,sizeof(Buffer
));
391 if (Buffer
[0] == '#' || Buffer
[0] == 0)
393 Out
<< Buffer
<< endl
;
399 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
401 string::size_type Space
= (*I
).find(' ');
402 if (Space
== string::npos
)
403 return _error
->Error("Internal error");
404 Out
<< Type
<< " cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
405 " " << string(*I
,Space
+1) << endl
;
413 const char *C
= Buffer
;
414 if (ParseQuoteWord(C
,cType
) == false ||
415 ParseQuoteWord(C
,URI
) == false)
417 Out
<< Buffer
<< endl
;
421 // Emit lines like this one
422 if (cType
!= Type
|| (string(URI
,0,ShortURI
.length()) != ShortURI
&&
423 string(URI
,0,ShortURI
.length()) != ShortURI2
))
425 Out
<< Buffer
<< endl
;
430 // Just in case the file was empty
433 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
435 string::size_type Space
= (*I
).find(' ');
436 if (Space
== string::npos
)
437 return _error
->Error("Internal error");
439 Out
<< "deb cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
440 " " << string(*I
,Space
+1) << endl
;
446 rename(File
.c_str(),string(File
+ '~').c_str());
447 if (rename(NewFile
.c_str(),File
.c_str()) != 0)
448 return _error
->Errno("rename","Failed to rename %s.new to %s",
449 File
.c_str(),File
.c_str());
455 // Prompt - Simple prompt /*{{{*/
456 // ---------------------------------------------------------------------
458 void Prompt(const char *Text
)
461 cout
<< Text
<< ' ' << flush
;
462 read(STDIN_FILENO
,&C
,1);
467 // PromptLine - Prompt for an input line /*{{{*/
468 // ---------------------------------------------------------------------
470 string
PromptLine(const char *Text
)
472 cout
<< Text
<< ':' << endl
;
480 // DoAdd - Add a new CDROM /*{{{*/
481 // ---------------------------------------------------------------------
482 /* This does the main add bit.. We show some status and things. The
483 sequence is to mount/umount the CD, Ident it then scan it for package
484 files and reduce that list. Then we copy over the package files and
485 verify them. Then rewrite the database files */
486 bool DoAdd(CommandLine
&)
489 string CDROM
= _config
->FindDir("Acquire::cdrom::mount","/cdrom/");
491 CDROM
= SafeGetCWD() + '/' + CDROM
;
493 cout
<< "Using CD-ROM mount point " << CDROM
<< endl
;
496 Configuration Database
;
497 string DFile
= _config
->FindFile("Dir::State::cdroms");
498 if (FileExists(DFile
) == true)
500 if (ReadConfigFile(Database
,DFile
) == false)
501 return _error
->Error("Unable to read the cdrom database %s",
505 // Unmount the CD and get the user to put in the one they want
506 if (_config
->FindB("APT::CDROM::NoMount",false) == false)
508 cout
<< "Unmounting CD-ROM" << endl
;
511 // Mount the new CDROM
512 Prompt("Please insert a Disc in the drive and press enter");
513 cout
<< "Mounting CD-ROM" << endl
;
514 if (MountCdrom(CDROM
) == false)
515 return _error
->Error("Failed to mount the cdrom.");
518 // Hash the CD to get an ID
519 cout
<< "Identifying.. " << flush
;
521 if (IdentCdrom(CDROM
,ID
) == false)
527 cout
<< '[' << ID
<< ']' << endl
;
529 cout
<< "Scanning Disc for index files.. " << flush
;
530 // Get the CD structure
532 vector
<string
> sList
;
533 string StartDir
= SafeGetCWD();
535 if (FindPackages(CDROM
,List
,sList
,InfoDir
) == false)
541 chdir(StartDir
.c_str());
543 if (_config
->FindB("Debug::aptcdrom",false) == true)
545 cout
<< "I found (binary):" << endl
;
546 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
548 cout
<< "I found (source):" << endl
;
549 for (vector
<string
>::iterator I
= sList
.begin(); I
!= sList
.end(); I
++)
554 DropBinaryArch(List
);
555 DropRepeats(List
,"Packages");
556 DropRepeats(sList
,"Sources");
557 cout
<< "Found " << List
.size() << " package indexes and " << sList
.size() <<
558 " source indexes." << endl
;
560 if (List
.size() == 0)
561 return _error
->Error("Unable to locate any package files, perhaps this is not a Debian Disc");
563 // Check if the CD is in the database
565 if (Database
.Exists("CD::" + ID
) == false ||
566 _config
->FindB("APT::CDROM::Rename",false) == true)
568 // Try to use the CDs label if at all possible
569 if (InfoDir
.empty() == false &&
570 FileExists(InfoDir
+ "/info") == true)
572 ifstream
F(string(InfoDir
+ "/info").c_str());
576 if (Name
.empty() == false)
578 cout
<< "Found label '" << Name
<< "'" << endl
;
579 Database
.Set("CD::" + ID
+ "::Label",Name
);
583 if (_config
->FindB("APT::CDROM::Rename",false) == true ||
584 Name
.empty() == true)
586 cout
<< "Please provide a name for this Disc, such as 'Debian 2.1r1 Disk 1'";
589 Name
= PromptLine("");
590 if (Name
.empty() == false &&
591 Name
.find('"') == string::npos
&&
592 Name
.find('[') == string::npos
&&
593 Name
.find(']') == string::npos
)
595 cout
<< "That is not a valid name, try again " << endl
;
600 Name
= Database
.Find("CD::" + ID
);
602 string::iterator J
= Name
.begin();
603 for (; J
!= Name
.end(); J
++)
604 if (*J
== '"' || *J
== ']' || *J
== '[')
607 Database
.Set("CD::" + ID
,Name
);
608 cout
<< "This Disc is called:" << endl
<< " '" << Name
<< "'" << endl
;
610 // Copy the package files to the state directory
613 if (Copy
.CopyPackages(CDROM
,Name
,List
) == false ||
614 SrcCopy
.CopyPackages(CDROM
,Name
,sList
) == false)
617 ReduceSourcelist(CDROM
,List
);
618 ReduceSourcelist(CDROM
,sList
);
620 // Write the database and sourcelist
621 if (_config
->FindB("APT::cdrom::NoAct",false) == false)
623 if (WriteDatabase(Database
) == false)
626 cout
<< "Writing new source list" << endl
;
627 if (WriteSourceList(Name
,List
,false) == false ||
628 WriteSourceList(Name
,sList
,true) == false)
632 // Print the sourcelist entries
633 cout
<< "Source List entries for this Disc are:" << endl
;
634 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
636 string::size_type Space
= (*I
).find(' ');
637 if (Space
== string::npos
)
638 return _error
->Error("Internal error");
640 cout
<< "deb cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
641 " " << string(*I
,Space
+1) << endl
;
644 for (vector
<string
>::iterator I
= sList
.begin(); I
!= sList
.end(); I
++)
646 string::size_type Space
= (*I
).find(' ');
647 if (Space
== string::npos
)
648 return _error
->Error("Internal error");
650 cout
<< "deb-src cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
651 " " << string(*I
,Space
+1) << endl
;
654 cout
<< "Repeat this process for the rest of the CDs in your set." << endl
;
656 // Unmount and finish
657 if (_config
->FindB("APT::CDROM::NoMount",false) == false)
664 // ShowHelp - Show the help screen /*{{{*/
665 // ---------------------------------------------------------------------
669 cout
<< PACKAGE
<< ' ' << VERSION
<< " for " << ARCHITECTURE
<<
670 " compiled on " << __DATE__
<< " " << __TIME__
<< endl
;
671 if (_config
->FindB("version") == true)
674 cout
<< "Usage: apt-cdrom [options] command" << endl
;
676 cout
<< "apt-cdrom is a tool to add CDROM's to APT's source list. The " << endl
;
677 cout
<< "CDROM mount point and device information is taken from apt.conf" << endl
;
678 cout
<< "and /etc/fstab." << endl
;
680 cout
<< "Commands:" << endl
;
681 cout
<< " add - Add a CDROM" << endl
;
683 cout
<< "Options:" << endl
;
684 cout
<< " -h This help text" << endl
;
685 cout
<< " -d CD-ROM mount point" << endl
;
686 cout
<< " -r Rename a recognized CD-ROM" << endl
;
687 cout
<< " -m No mounting" << endl
;
688 cout
<< " -f Fast mode, don't check package files" << endl
;
689 cout
<< " -a Thorough scan mode" << endl
;
690 cout
<< " -c=? Read this configuration file" << endl
;
691 cout
<< " -o=? Set an arbitary configuration option, eg -o dir::cache=/tmp" << endl
;
692 cout
<< "See fstab(5)" << endl
;
697 int main(int argc
,const char *argv
[])
699 CommandLine::Args Args
[] = {
700 {'h',"help","help",0},
701 {'v',"version","version",0},
702 {'d',"cdrom","Acquire::cdrom::mount",CommandLine::HasArg
},
703 {'r',"rename","APT::CDROM::Rename",0},
704 {'m',"no-mount","APT::CDROM::NoMount",0},
705 {'f',"fast","APT::CDROM::Fast",0},
706 {'n',"just-print","APT::CDROM::NoAct",0},
707 {'n',"recon","APT::CDROM::NoAct",0},
708 {'n',"no-act","APT::CDROM::NoAct",0},
709 {'a',"thorough","APT::CDROM::Thorough",0},
710 {'c',"config-file",0,CommandLine::ConfigFile
},
711 {'o',"option",0,CommandLine::ArbItem
},
713 CommandLine::Dispatch Cmds
[] = {
717 // Parse the command line and initialize the package library
718 CommandLine
CmdL(Args
,_config
);
719 if (pkgInitialize(*_config
) == false ||
720 CmdL
.Parse(argc
,argv
) == false)
722 _error
->DumpErrors();
726 // See if the help should be shown
727 if (_config
->FindB("help") == true ||
728 CmdL
.FileSize() == 0)
731 // Deal with stdout not being a tty
732 if (ttyname(STDOUT_FILENO
) == 0 && _config
->FindI("quiet",0) < 1)
733 _config
->Set("quiet","1");
735 // Match the operation
736 CmdL
.DispatchArg(Cmds
);
738 // Print any errors or warnings found during parsing
739 if (_error
->empty() == false)
741 bool Errors
= _error
->PendingError();
742 _error
->DumpErrors();
743 return Errors
== true?100:0;