]>
git.saurik.com Git - apt.git/blob - cmdline/apt-cdrom.cc
1 // -*- mode: cpp; mode: fold -*-
3 // $Id: apt-cdrom.cc,v 1.28 1999/07/12 02:59:36 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)
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
,"experimental") == 0 ||
95 strcmp(Dir
->d_name
,"binary-all") == 0)
98 // See if the name is a sub directory
100 if (stat(Dir
->d_name
,&Buf
) != 0)
103 if (S_ISDIR(Buf
.st_mode
) == 0)
107 for (I
= 0; I
!= Depth
; I
++)
108 if (Inodes
[I
] == Buf
.st_ino
)
113 // Store the inodes weve seen
114 Inodes
[Depth
] = Buf
.st_ino
;
117 if (FindPackages(CD
+ Dir
->d_name
,List
,SList
,InfoDir
,Depth
+1) == false)
120 if (chdir(CD
.c_str()) != 0)
121 return _error
->Errno("chdir","Unable to change to ",CD
.c_str());
126 return !_error
->PendingError();
129 // DropBinaryArch - Dump dirs with a string like /binary-<foo>/ /*{{{*/
130 // ---------------------------------------------------------------------
131 /* Here we drop everything that is not this machines arch */
132 bool DropBinaryArch(vector
<string
> &List
)
135 sprintf(S
,"/binary-%s/",_config
->Find("Apt::Architecture").c_str());
137 for (unsigned int I
= 0; I
< List
.size(); I
++)
139 const char *Str
= List
[I
].c_str();
142 if ((Res
= strstr(Str
,"/binary-")) == 0)
146 if (strlen(Res
) < strlen(S
))
148 List
.erase(List
.begin() + I
);
153 // See if it is our arch
154 if (stringcmp(Res
,Res
+ strlen(S
),S
) == 0)
158 List
.erase(List
.begin() + I
);
165 // Score - We compute a 'score' for a path /*{{{*/
166 // ---------------------------------------------------------------------
167 /* Paths are scored based on how close they come to what I consider
168 normal. That is ones that have 'dist' 'stable' 'frozen' will score
169 higher than ones without. */
170 int Score(string Path
)
173 if (Path
.find("stable/") != string::npos
)
175 if (Path
.find("/binary-") != string::npos
)
177 if (Path
.find("frozen/") != string::npos
)
179 if (Path
.find("/dists/") != string::npos
)
181 if (Path
.find("/main/") != string::npos
)
183 if (Path
.find("/contrib/") != string::npos
)
185 if (Path
.find("/non-free/") != string::npos
)
187 if (Path
.find("/non-US/") != string::npos
)
189 if (Path
.find("/source/") != string::npos
)
194 // DropRepeats - Drop repeated files resulting from symlinks /*{{{*/
195 // ---------------------------------------------------------------------
196 /* Here we go and stat every file that we found and strip dup inodes. */
197 bool DropRepeats(vector
<string
> &List
,const char *Name
)
199 // Get a list of all the inodes
200 ino_t
*Inodes
= new ino_t
[List
.size()];
201 for (unsigned int I
= 0; I
!= List
.size(); I
++)
204 if (stat((List
[I
] + Name
).c_str(),&Buf
) != 0 &&
205 stat((List
[I
] + Name
+ ".gz").c_str(),&Buf
) != 0)
206 _error
->Errno("stat","Failed to stat %s%s",List
[I
].c_str(),
208 Inodes
[I
] = Buf
.st_ino
;
211 if (_error
->PendingError() == true)
215 for (unsigned int I
= 0; I
!= List
.size(); I
++)
217 for (unsigned int J
= I
+1; J
< List
.size(); J
++)
220 if (Inodes
[J
] != Inodes
[I
])
223 // We score the two paths.. and erase one
224 int ScoreA
= Score(List
[I
]);
225 int ScoreB
= Score(List
[J
]);
236 // Wipe erased entries
237 for (unsigned int I
= 0; I
< List
.size();)
239 if (List
[I
].empty() == false)
242 List
.erase(List
.begin()+I
);
249 // ReduceSourceList - Takes the path list and reduces it /*{{{*/
250 // ---------------------------------------------------------------------
251 /* This takes the list of source list expressed entires and collects
252 similar ones to form a single entry for each dist */
253 bool ReduceSourcelist(string CD
,vector
<string
> &List
)
255 sort(List
.begin(),List
.end());
257 // Collect similar entries
258 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
261 string::size_type Space
= (*I
).find(' ');
262 if (Space
== string::npos
)
264 string::size_type SSpace
= (*I
).find(' ',Space
+ 1);
265 if (SSpace
== string::npos
)
268 string Word1
= string(*I
,Space
,SSpace
-Space
);
269 for (vector
<string
>::iterator J
= List
.begin(); J
!= I
; J
++)
272 string::size_type Space2
= (*J
).find(' ');
273 if (Space2
== string::npos
)
275 string::size_type SSpace2
= (*J
).find(' ',Space2
+ 1);
276 if (SSpace2
== string::npos
)
279 if (string(*J
,Space2
,SSpace2
-Space2
) != Word1
)
282 *J
+= string(*I
,SSpace
);
287 // Wipe erased entries
288 for (unsigned int I
= 0; I
< List
.size();)
290 if (List
[I
].empty() == false)
293 List
.erase(List
.begin()+I
);
297 // WriteDatabase - Write the CDROM Database file /*{{{*/
298 // ---------------------------------------------------------------------
299 /* We rewrite the configuration class associated with the cdrom database. */
300 bool WriteDatabase(Configuration
&Cnf
)
302 string DFile
= _config
->FindFile("Dir::State::cdroms");
303 string NewFile
= DFile
+ ".new";
305 unlink(NewFile
.c_str());
306 ofstream
Out(NewFile
.c_str());
308 return _error
->Errno("ofstream::ofstream",
309 "Failed to open %s.new",DFile
.c_str());
311 /* Write out all of the configuration directives by walking the
312 configuration tree */
313 const Configuration::Item
*Top
= Cnf
.Tree(0);
316 // Print the config entry
317 if (Top
->Value
.empty() == false)
318 Out
<< Top
->FullTag() + " \"" << Top
->Value
<< "\";" << endl
;
326 while (Top
!= 0 && Top
->Next
== 0)
334 rename(DFile
.c_str(),string(DFile
+ '~').c_str());
335 if (rename(NewFile
.c_str(),DFile
.c_str()) != 0)
336 return _error
->Errno("rename","Failed to rename %s.new to %s",
337 DFile
.c_str(),DFile
.c_str());
342 // WriteSourceList - Write an updated sourcelist /*{{{*/
343 // ---------------------------------------------------------------------
344 /* This reads the old source list and copies it into the new one. It
345 appends the new CDROM entires just after the first block of comments.
346 This places them first in the file. It also removes any old entries
347 that were the same. */
348 bool WriteSourceList(string Name
,vector
<string
> &List
,bool Source
)
350 if (List
.size() == 0)
353 string File
= _config
->FindFile("Dir::Etc::sourcelist");
355 // Open the stream for reading
356 ifstream
F(File
.c_str(),ios::in
| ios::nocreate
);
358 return _error
->Errno("ifstream::ifstream","Opening %s",File
.c_str());
360 string NewFile
= File
+ ".new";
361 unlink(NewFile
.c_str());
362 ofstream
Out(NewFile
.c_str());
364 return _error
->Errno("ofstream::ofstream",
365 "Failed to open %s.new",File
.c_str());
367 // Create a short uri without the path
368 string ShortURI
= "cdrom:" + Name
+ "/";
379 while (F
.eof() == false)
381 F
.getline(Buffer
,sizeof(Buffer
));
383 _strtabexpand(Buffer
,sizeof(Buffer
));
387 if (Buffer
[0] == '#' || Buffer
[0] == 0)
389 Out
<< Buffer
<< endl
;
395 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
397 string::size_type Space
= (*I
).find(' ');
398 if (Space
== string::npos
)
399 return _error
->Error("Internal error");
400 Out
<< Type
<< " \"cdrom:" << Name
<< "/" << string(*I
,0,Space
) <<
401 "\" " << string(*I
,Space
+1) << endl
;
410 if (ParseQuoteWord(C
,cType
) == false ||
411 ParseQuoteWord(C
,URI
) == false)
413 Out
<< Buffer
<< endl
;
417 // Emit lines like this one
418 if (cType
!= Type
|| string(URI
,0,ShortURI
.length()) != ShortURI
)
420 Out
<< Buffer
<< endl
;
425 // Just in case the file was empty
428 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
430 string::size_type Space
= (*I
).find(' ');
431 if (Space
== string::npos
)
432 return _error
->Error("Internal error");
434 Out
<< "deb \"cdrom:" << Name
<< "/" << string(*I
,0,Space
) <<
435 "\" " << string(*I
,Space
+1) << endl
;
441 rename(File
.c_str(),string(File
+ '~').c_str());
442 if (rename(NewFile
.c_str(),File
.c_str()) != 0)
443 return _error
->Errno("rename","Failed to rename %s.new to %s",
444 File
.c_str(),File
.c_str());
450 // Prompt - Simple prompt /*{{{*/
451 // ---------------------------------------------------------------------
453 void Prompt(const char *Text
)
456 cout
<< Text
<< ' ' << flush
;
457 read(STDIN_FILENO
,&C
,1);
462 // PromptLine - Prompt for an input line /*{{{*/
463 // ---------------------------------------------------------------------
465 string
PromptLine(const char *Text
)
467 cout
<< Text
<< ':' << endl
;
475 // DoAdd - Add a new CDROM /*{{{*/
476 // ---------------------------------------------------------------------
477 /* This does the main add bit.. We show some status and things. The
478 sequence is to mount/umount the CD, Ident it then scan it for package
479 files and reduce that list. Then we copy over the package files and
480 verify them. Then rewrite the database files */
481 bool DoAdd(CommandLine
&)
484 string CDROM
= _config
->FindDir("Acquire::cdrom::mount","/cdrom/");
486 CDROM
= SafeGetCWD() + '/' + CDROM
;
488 cout
<< "Using CD-ROM mount point " << CDROM
<< endl
;
491 Configuration Database
;
492 string DFile
= _config
->FindFile("Dir::State::cdroms");
493 if (FileExists(DFile
) == true)
495 if (ReadConfigFile(Database
,DFile
) == false)
496 return _error
->Error("Unable to read the cdrom database %s",
500 // Unmount the CD and get the user to put in the one they want
501 if (_config
->FindB("APT::CDROM::NoMount",false) == false)
503 cout
<< "Unmounting CD-ROM" << endl
;
506 // Mount the new CDROM
507 Prompt("Please insert a Disc in the drive and press enter");
508 cout
<< "Mounting CD-ROM" << endl
;
509 if (MountCdrom(CDROM
) == false)
510 return _error
->Error("Failed to mount the cdrom.");
513 // Hash the CD to get an ID
514 cout
<< "Identifying.. " << flush
;
516 if (IdentCdrom(CDROM
,ID
) == false)
522 cout
<< '[' << ID
<< ']' << endl
;
524 cout
<< "Scanning Disc for index files.. " << flush
;
525 // Get the CD structure
527 vector
<string
> sList
;
528 string StartDir
= SafeGetCWD();
530 if (FindPackages(CDROM
,List
,sList
,InfoDir
) == false)
536 chdir(StartDir
.c_str());
538 if (_config
->FindB("Debug::aptcdrom",false) == true)
540 cout
<< "I found:" << endl
;
541 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
548 DropBinaryArch(List
);
549 DropRepeats(List
,"Packages");
550 DropRepeats(sList
,"Sources");
551 cout
<< "Found " << List
.size() << " package indexes and " << sList
.size() <<
552 " source indexes." << endl
;
554 if (List
.size() == 0)
555 return _error
->Error("Unable to locate any package files, perhaps this is not a Debian Disc");
557 // Check if the CD is in the database
559 if (Database
.Exists("CD::" + ID
) == false ||
560 _config
->FindB("APT::CDROM::Rename",false) == true)
562 // Try to use the CDs label if at all possible
563 if (InfoDir
.empty() == false &&
564 FileExists(InfoDir
+ "/info") == true)
566 ifstream
F(string(InfoDir
+ "/info").c_str());
570 if (Name
.empty() == false)
572 cout
<< "Found label '" << Name
<< "'" << endl
;
573 Database
.Set("CD::" + ID
+ "::Label",Name
);
577 if (_config
->FindB("APT::CDROM::Rename",false) == true ||
578 Name
.empty() == true)
580 cout
<< "Please provide a name for this Disc, such as 'Debian 2.1r1 Disk 1'";
583 Name
= PromptLine("");
584 if (Name
.empty() == false &&
585 Name
.find('"') == string::npos
&&
586 Name
.find(':') == string::npos
&&
587 Name
.find('/') == string::npos
)
589 cout
<< "That is not a valid name, try again " << endl
;
594 Name
= Database
.Find("CD::" + ID
);
596 string::iterator J
= Name
.begin();
597 for (; J
!= Name
.end(); J
++)
598 if (*J
== '/' || *J
== '"' || *J
== ':')
601 Database
.Set("CD::" + ID
,Name
);
602 cout
<< "This Disc is called '" << Name
<< "'" << endl
;
604 // Copy the package files to the state directory
607 if (Copy
.CopyPackages(CDROM
,Name
,List
) == false ||
608 SrcCopy
.CopyPackages(CDROM
,Name
,sList
) == false)
611 ReduceSourcelist(CDROM
,List
);
612 ReduceSourcelist(CDROM
,sList
);
614 // Write the database and sourcelist
615 if (_config
->FindB("APT::cdrom::NoAct",false) == false)
617 if (WriteDatabase(Database
) == false)
620 cout
<< "Writing new source list" << endl
;
621 if (WriteSourceList(Name
,List
,false) == false ||
622 WriteSourceList(Name
,sList
,true) == false)
626 // Print the sourcelist entries
627 cout
<< "Source List entries for this Disc are:" << endl
;
628 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
630 string::size_type Space
= (*I
).find(' ');
631 if (Space
== string::npos
)
632 return _error
->Error("Internal error");
634 cout
<< "deb \"cdrom:" << Name
<< "/" << string(*I
,0,Space
) <<
635 "\" " << string(*I
,Space
+1) << endl
;
638 for (vector
<string
>::iterator I
= sList
.begin(); I
!= sList
.end(); I
++)
640 string::size_type Space
= (*I
).find(' ');
641 if (Space
== string::npos
)
642 return _error
->Error("Internal error");
644 cout
<< "deb-src \"cdrom:" << Name
<< "/" << string(*I
,0,Space
) <<
645 "\" " << string(*I
,Space
+1) << endl
;
648 cout
<< "Repeat this process for the rest of the CDs in your set." << endl
;
653 // ShowHelp - Show the help screen /*{{{*/
654 // ---------------------------------------------------------------------
658 cout
<< PACKAGE
<< ' ' << VERSION
<< " for " << ARCHITECTURE
<<
659 " compiled on " << __DATE__
<< " " << __TIME__
<< endl
;
660 if (_config
->FindB("version") == true)
663 cout
<< "Usage: apt-cdrom [options] command" << endl
;
665 cout
<< "apt-cdrom is a tool to add CDROM's to APT's source list. The " << endl
;
666 cout
<< "CDROM mount point and device information is taken from apt.conf" << endl
;
667 cout
<< "and /etc/fstab." << endl
;
669 cout
<< "Commands:" << endl
;
670 cout
<< " add - Add a CDROM" << endl
;
672 cout
<< "Options:" << endl
;
673 cout
<< " -h This help text" << endl
;
674 cout
<< " -d CD-ROM mount point" << endl
;
675 cout
<< " -r Rename a recognized CD-ROM" << endl
;
676 cout
<< " -m No mounting" << endl
;
677 cout
<< " -f Fast mode, don't check package files" << endl
;
678 cout
<< " -a Thorough scan mode" << endl
;
679 cout
<< " -c=? Read this configuration file" << endl
;
680 cout
<< " -o=? Set an arbitary configuration option, eg -o dir::cache=/tmp" << endl
;
681 cout
<< "See fstab(5)" << endl
;
686 int main(int argc
,const char *argv
[])
688 CommandLine::Args Args
[] = {
689 {'h',"help","help",0},
690 {'v',"version","version",0},
691 {'d',"cdrom","Acquire::cdrom::mount",CommandLine::HasArg
},
692 {'r',"rename","APT::CDROM::Rename",0},
693 {'m',"no-mount","APT::CDROM::NoMount",0},
694 {'f',"fast","APT::CDROM::Fast",0},
695 {'n',"just-print","APT::CDROM::NoAct",0},
696 {'n',"recon","APT::CDROM::NoAct",0},
697 {'n',"no-act","APT::CDROM::NoAct",0},
698 {'a',"thorough","APT::CDROM::Thorough",0},
699 {'c',"config-file",0,CommandLine::ConfigFile
},
700 {'o',"option",0,CommandLine::ArbItem
},
702 CommandLine::Dispatch Cmds
[] = {
706 // Parse the command line and initialize the package library
707 CommandLine
CmdL(Args
,_config
);
708 if (pkgInitialize(*_config
) == false ||
709 CmdL
.Parse(argc
,argv
) == false)
711 _error
->DumpErrors();
715 // See if the help should be shown
716 if (_config
->FindB("help") == true ||
717 CmdL
.FileSize() == 0)
720 // Deal with stdout not being a tty
721 if (ttyname(STDOUT_FILENO
) == 0 && _config
->FindI("quiet",0) < 1)
722 _config
->Set("quiet","1");
724 // Match the operation
725 CmdL
.DispatchArg(Cmds
);
727 // Print any errors or warnings found during parsing
728 if (_error
->empty() == false)
730 bool Errors
= _error
->PendingError();
731 _error
->DumpErrors();
732 return Errors
== true?100:0;