]>
git.saurik.com Git - apt.git/blob - cmdline/apt-cdrom.cc
204cef36637938fb7b1b377b0ca1b8684f839d1d
1 // -*- mode: cpp; mode: fold -*-
3 // $Id: apt-cdrom.cc,v 1.35 2000/05/10 06:03:52 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("unstable/") != string::npos
)
182 if (Path
.find("/dists/") != string::npos
)
184 if (Path
.find("/main/") != string::npos
)
186 if (Path
.find("/contrib/") != string::npos
)
188 if (Path
.find("/non-free/") != string::npos
)
190 if (Path
.find("/non-US/") != string::npos
)
192 if (Path
.find("/source/") != string::npos
)
194 if (Path
.find("/debian/") != string::npos
)
199 // DropRepeats - Drop repeated files resulting from symlinks /*{{{*/
200 // ---------------------------------------------------------------------
201 /* Here we go and stat every file that we found and strip dup inodes. */
202 bool DropRepeats(vector
<string
> &List
,const char *Name
)
204 // Get a list of all the inodes
205 ino_t
*Inodes
= new ino_t
[List
.size()];
206 for (unsigned int I
= 0; I
!= List
.size(); I
++)
209 if (stat((List
[I
] + Name
).c_str(),&Buf
) != 0 &&
210 stat((List
[I
] + Name
+ ".gz").c_str(),&Buf
) != 0)
211 _error
->Errno("stat","Failed to stat %s%s",List
[I
].c_str(),
213 Inodes
[I
] = Buf
.st_ino
;
216 if (_error
->PendingError() == true)
220 for (unsigned int I
= 0; I
!= List
.size(); I
++)
222 for (unsigned int J
= I
+1; J
< List
.size(); J
++)
225 if (Inodes
[J
] != Inodes
[I
])
228 // We score the two paths.. and erase one
229 int ScoreA
= Score(List
[I
]);
230 int ScoreB
= Score(List
[J
]);
241 // Wipe erased entries
242 for (unsigned int I
= 0; I
< List
.size();)
244 if (List
[I
].empty() == false)
247 List
.erase(List
.begin()+I
);
254 // ReduceSourceList - Takes the path list and reduces it /*{{{*/
255 // ---------------------------------------------------------------------
256 /* This takes the list of source list expressed entires and collects
257 similar ones to form a single entry for each dist */
258 bool ReduceSourcelist(string CD
,vector
<string
> &List
)
260 sort(List
.begin(),List
.end());
262 // Collect similar entries
263 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
266 string::size_type Space
= (*I
).find(' ');
267 if (Space
== string::npos
)
269 string::size_type SSpace
= (*I
).find(' ',Space
+ 1);
270 if (SSpace
== string::npos
)
273 string Word1
= string(*I
,Space
,SSpace
-Space
);
274 for (vector
<string
>::iterator J
= List
.begin(); J
!= I
; J
++)
277 string::size_type Space2
= (*J
).find(' ');
278 if (Space2
== string::npos
)
280 string::size_type SSpace2
= (*J
).find(' ',Space2
+ 1);
281 if (SSpace2
== string::npos
)
284 if (string(*J
,Space2
,SSpace2
-Space2
) != Word1
)
287 *J
+= string(*I
,SSpace
);
292 // Wipe erased entries
293 for (unsigned int I
= 0; I
< List
.size();)
295 if (List
[I
].empty() == false)
298 List
.erase(List
.begin()+I
);
302 // WriteDatabase - Write the CDROM Database file /*{{{*/
303 // ---------------------------------------------------------------------
304 /* We rewrite the configuration class associated with the cdrom database. */
305 bool WriteDatabase(Configuration
&Cnf
)
307 string DFile
= _config
->FindFile("Dir::State::cdroms");
308 string NewFile
= DFile
+ ".new";
310 unlink(NewFile
.c_str());
311 ofstream
Out(NewFile
.c_str());
313 return _error
->Errno("ofstream::ofstream",
314 "Failed to open %s.new",DFile
.c_str());
316 /* Write out all of the configuration directives by walking the
317 configuration tree */
318 const Configuration::Item
*Top
= Cnf
.Tree(0);
321 // Print the config entry
322 if (Top
->Value
.empty() == false)
323 Out
<< Top
->FullTag() + " \"" << Top
->Value
<< "\";" << endl
;
331 while (Top
!= 0 && Top
->Next
== 0)
339 rename(DFile
.c_str(),string(DFile
+ '~').c_str());
340 if (rename(NewFile
.c_str(),DFile
.c_str()) != 0)
341 return _error
->Errno("rename","Failed to rename %s.new to %s",
342 DFile
.c_str(),DFile
.c_str());
347 // WriteSourceList - Write an updated sourcelist /*{{{*/
348 // ---------------------------------------------------------------------
349 /* This reads the old source list and copies it into the new one. It
350 appends the new CDROM entires just after the first block of comments.
351 This places them first in the file. It also removes any old entries
352 that were the same. */
353 bool WriteSourceList(string Name
,vector
<string
> &List
,bool Source
)
355 if (List
.size() == 0)
358 string File
= _config
->FindFile("Dir::Etc::sourcelist");
360 // Open the stream for reading
361 ifstream
F(File
.c_str(),ios::in
| ios::nocreate
);
363 return _error
->Errno("ifstream::ifstream","Opening %s",File
.c_str());
365 string NewFile
= File
+ ".new";
366 unlink(NewFile
.c_str());
367 ofstream
Out(NewFile
.c_str());
369 return _error
->Errno("ofstream::ofstream",
370 "Failed to open %s.new",File
.c_str());
372 // Create a short uri without the path
373 string ShortURI
= "cdrom:[" + Name
+ "]/";
374 string ShortURI2
= "cdrom:" + Name
+ "/"; // For Compatibility
385 while (F
.eof() == false)
387 F
.getline(Buffer
,sizeof(Buffer
));
389 _strtabexpand(Buffer
,sizeof(Buffer
));
393 if (Buffer
[0] == '#' || Buffer
[0] == 0)
395 Out
<< Buffer
<< endl
;
401 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
403 string::size_type Space
= (*I
).find(' ');
404 if (Space
== string::npos
)
405 return _error
->Error("Internal error");
406 Out
<< Type
<< " cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
407 " " << string(*I
,Space
+1) << endl
;
415 const char *C
= Buffer
;
416 if (ParseQuoteWord(C
,cType
) == false ||
417 ParseQuoteWord(C
,URI
) == false)
419 Out
<< Buffer
<< endl
;
423 // Emit lines like this one
424 if (cType
!= Type
|| (string(URI
,0,ShortURI
.length()) != ShortURI
&&
425 string(URI
,0,ShortURI
.length()) != ShortURI2
))
427 Out
<< Buffer
<< endl
;
432 // Just in case the file was empty
435 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
437 string::size_type Space
= (*I
).find(' ');
438 if (Space
== string::npos
)
439 return _error
->Error("Internal error");
441 Out
<< "deb cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
442 " " << string(*I
,Space
+1) << endl
;
448 rename(File
.c_str(),string(File
+ '~').c_str());
449 if (rename(NewFile
.c_str(),File
.c_str()) != 0)
450 return _error
->Errno("rename","Failed to rename %s.new to %s",
451 File
.c_str(),File
.c_str());
457 // Prompt - Simple prompt /*{{{*/
458 // ---------------------------------------------------------------------
460 void Prompt(const char *Text
)
463 cout
<< Text
<< ' ' << flush
;
464 read(STDIN_FILENO
,&C
,1);
469 // PromptLine - Prompt for an input line /*{{{*/
470 // ---------------------------------------------------------------------
472 string
PromptLine(const char *Text
)
474 cout
<< Text
<< ':' << endl
;
482 // DoAdd - Add a new CDROM /*{{{*/
483 // ---------------------------------------------------------------------
484 /* This does the main add bit.. We show some status and things. The
485 sequence is to mount/umount the CD, Ident it then scan it for package
486 files and reduce that list. Then we copy over the package files and
487 verify them. Then rewrite the database files */
488 bool DoAdd(CommandLine
&)
491 string CDROM
= _config
->FindDir("Acquire::cdrom::mount","/cdrom/");
493 CDROM
= SafeGetCWD() + '/' + CDROM
;
495 cout
<< "Using CD-ROM mount point " << CDROM
<< endl
;
498 Configuration Database
;
499 string DFile
= _config
->FindFile("Dir::State::cdroms");
500 if (FileExists(DFile
) == true)
502 if (ReadConfigFile(Database
,DFile
) == false)
503 return _error
->Error("Unable to read the cdrom database %s",
507 // Unmount the CD and get the user to put in the one they want
508 if (_config
->FindB("APT::CDROM::NoMount",false) == false)
510 cout
<< "Unmounting CD-ROM" << endl
;
513 // Mount the new CDROM
514 Prompt("Please insert a Disc in the drive and press enter");
515 cout
<< "Mounting CD-ROM" << endl
;
516 if (MountCdrom(CDROM
) == false)
517 return _error
->Error("Failed to mount the cdrom.");
520 // Hash the CD to get an ID
521 cout
<< "Identifying.. " << flush
;
523 if (IdentCdrom(CDROM
,ID
) == false)
529 cout
<< '[' << ID
<< ']' << endl
;
531 cout
<< "Scanning Disc for index files.. " << flush
;
532 // Get the CD structure
534 vector
<string
> sList
;
535 string StartDir
= SafeGetCWD();
537 if (FindPackages(CDROM
,List
,sList
,InfoDir
) == false)
543 chdir(StartDir
.c_str());
545 if (_config
->FindB("Debug::aptcdrom",false) == true)
547 cout
<< "I found (binary):" << endl
;
548 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
550 cout
<< "I found (source):" << endl
;
551 for (vector
<string
>::iterator I
= sList
.begin(); I
!= sList
.end(); I
++)
556 DropBinaryArch(List
);
557 DropRepeats(List
,"Packages");
558 DropRepeats(sList
,"Sources");
559 cout
<< "Found " << List
.size() << " package indexes and " << sList
.size() <<
560 " source indexes." << endl
;
562 if (List
.size() == 0 && sList
.size() == 0)
563 return _error
->Error("Unable to locate any package files, perhaps this is not a Debian Disc");
565 // Check if the CD is in the database
567 if (Database
.Exists("CD::" + ID
) == false ||
568 _config
->FindB("APT::CDROM::Rename",false) == true)
570 // Try to use the CDs label if at all possible
571 if (InfoDir
.empty() == false &&
572 FileExists(InfoDir
+ "/info") == true)
574 ifstream
F(string(InfoDir
+ "/info").c_str());
578 if (Name
.empty() == false)
580 cout
<< "Found label '" << Name
<< "'" << endl
;
581 Database
.Set("CD::" + ID
+ "::Label",Name
);
585 if (_config
->FindB("APT::CDROM::Rename",false) == true ||
586 Name
.empty() == true)
588 cout
<< "Please provide a name for this Disc, such as 'Debian 2.1r1 Disk 1'";
591 Name
= PromptLine("");
592 if (Name
.empty() == false &&
593 Name
.find('"') == string::npos
&&
594 Name
.find('[') == string::npos
&&
595 Name
.find(']') == string::npos
)
597 cout
<< "That is not a valid name, try again " << endl
;
602 Name
= Database
.Find("CD::" + ID
);
604 // Escape special characters
605 string::iterator J
= Name
.begin();
606 for (; J
!= Name
.end(); J
++)
607 if (*J
== '"' || *J
== ']' || *J
== '[')
610 Database
.Set("CD::" + ID
,Name
);
611 cout
<< "This Disc is called:" << endl
<< " '" << Name
<< "'" << endl
;
613 // Copy the package files to the state directory
616 if (Copy
.CopyPackages(CDROM
,Name
,List
) == false ||
617 SrcCopy
.CopyPackages(CDROM
,Name
,sList
) == false)
620 ReduceSourcelist(CDROM
,List
);
621 ReduceSourcelist(CDROM
,sList
);
623 // Write the database and sourcelist
624 if (_config
->FindB("APT::cdrom::NoAct",false) == false)
626 if (WriteDatabase(Database
) == false)
629 cout
<< "Writing new source list" << endl
;
630 if (WriteSourceList(Name
,List
,false) == false ||
631 WriteSourceList(Name
,sList
,true) == false)
635 // Print the sourcelist entries
636 cout
<< "Source List entries for this Disc are:" << endl
;
637 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
639 string::size_type Space
= (*I
).find(' ');
640 if (Space
== string::npos
)
641 return _error
->Error("Internal error");
643 cout
<< "deb cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
644 " " << string(*I
,Space
+1) << endl
;
647 for (vector
<string
>::iterator I
= sList
.begin(); I
!= sList
.end(); I
++)
649 string::size_type Space
= (*I
).find(' ');
650 if (Space
== string::npos
)
651 return _error
->Error("Internal error");
653 cout
<< "deb-src cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
654 " " << string(*I
,Space
+1) << endl
;
657 cout
<< "Repeat this process for the rest of the CDs in your set." << endl
;
659 // Unmount and finish
660 if (_config
->FindB("APT::CDROM::NoMount",false) == false)
667 // ShowHelp - Show the help screen /*{{{*/
668 // ---------------------------------------------------------------------
672 cout
<< PACKAGE
<< ' ' << VERSION
<< " for " << ARCHITECTURE
<<
673 " compiled on " << __DATE__
<< " " << __TIME__
<< endl
;
674 if (_config
->FindB("version") == true)
677 cout
<< "Usage: apt-cdrom [options] command" << endl
;
679 cout
<< "apt-cdrom is a tool to add CDROM's to APT's source list. The " << endl
;
680 cout
<< "CDROM mount point and device information is taken from apt.conf" << endl
;
681 cout
<< "and /etc/fstab." << endl
;
683 cout
<< "Commands:" << endl
;
684 cout
<< " add - Add a CDROM" << endl
;
686 cout
<< "Options:" << endl
;
687 cout
<< " -h This help text" << endl
;
688 cout
<< " -d CD-ROM mount point" << endl
;
689 cout
<< " -r Rename a recognized CD-ROM" << endl
;
690 cout
<< " -m No mounting" << endl
;
691 cout
<< " -f Fast mode, don't check package files" << endl
;
692 cout
<< " -a Thorough scan mode" << endl
;
693 cout
<< " -c=? Read this configuration file" << endl
;
694 cout
<< " -o=? Set an arbitary configuration option, eg -o dir::cache=/tmp" << endl
;
695 cout
<< "See fstab(5)" << endl
;
700 int main(int argc
,const char *argv
[])
702 CommandLine::Args Args
[] = {
703 {'h',"help","help",0},
704 {'v',"version","version",0},
705 {'d',"cdrom","Acquire::cdrom::mount",CommandLine::HasArg
},
706 {'r',"rename","APT::CDROM::Rename",0},
707 {'m',"no-mount","APT::CDROM::NoMount",0},
708 {'f',"fast","APT::CDROM::Fast",0},
709 {'n',"just-print","APT::CDROM::NoAct",0},
710 {'n',"recon","APT::CDROM::NoAct",0},
711 {'n',"no-act","APT::CDROM::NoAct",0},
712 {'a',"thorough","APT::CDROM::Thorough",0},
713 {'c',"config-file",0,CommandLine::ConfigFile
},
714 {'o',"option",0,CommandLine::ArbItem
},
716 CommandLine::Dispatch Cmds
[] = {
720 // Parse the command line and initialize the package library
721 CommandLine
CmdL(Args
,_config
);
722 if (pkgInitialize(*_config
) == false ||
723 CmdL
.Parse(argc
,argv
) == false)
725 _error
->DumpErrors();
729 // See if the help should be shown
730 if (_config
->FindB("help") == true || _config
->FindB("version") == true ||
731 CmdL
.FileSize() == 0)
734 // Deal with stdout not being a tty
735 if (ttyname(STDOUT_FILENO
) == 0 && _config
->FindI("quiet",0) < 1)
736 _config
->Set("quiet","1");
738 // Match the operation
739 CmdL
.DispatchArg(Cmds
);
741 // Print any errors or warnings found during parsing
742 if (_error
->empty() == false)
744 bool Errors
= _error
->PendingError();
745 _error
->DumpErrors();
746 return Errors
== true?100:0;