]>
git.saurik.com Git - apt.git/blob - cmdline/apt-cdrom.cc
1 // -*- mode: cpp; mode: fold -*-
3 // $Id: apt-cdrom.cc,v 1.42 2002/02/15 03:40:00 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"
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)
107 // See if the name is a sub directory
109 if (stat(Dir
->d_name
,&Buf
) != 0)
112 if (S_ISDIR(Buf
.st_mode
) == 0)
116 for (I
= 0; I
!= Depth
; I
++)
117 if (Inodes
[I
] == Buf
.st_ino
)
122 // Store the inodes weve seen
123 Inodes
[Depth
] = Buf
.st_ino
;
126 if (FindPackages(CD
+ Dir
->d_name
,List
,SList
,InfoDir
,Depth
+1) == false)
129 if (chdir(CD
.c_str()) != 0)
130 return _error
->Errno("chdir","Unable to change to %s",CD
.c_str());
135 return !_error
->PendingError();
138 // DropBinaryArch - Dump dirs with a string like /binary-<foo>/ /*{{{*/
139 // ---------------------------------------------------------------------
140 /* Here we drop everything that is not this machines arch */
141 bool DropBinaryArch(vector
<string
> &List
)
144 snprintf(S
,sizeof(S
),"/binary-%s/",
145 _config
->Find("Apt::Architecture").c_str());
147 for (unsigned int I
= 0; I
< List
.size(); I
++)
149 const char *Str
= List
[I
].c_str();
152 if ((Res
= strstr(Str
,"/binary-")) == 0)
156 if (strlen(Res
) < strlen(S
))
158 List
.erase(List
.begin() + I
);
163 // See if it is our arch
164 if (stringcmp(Res
,Res
+ strlen(S
),S
) == 0)
168 List
.erase(List
.begin() + I
);
175 // Score - We compute a 'score' for a path /*{{{*/
176 // ---------------------------------------------------------------------
177 /* Paths are scored based on how close they come to what I consider
178 normal. That is ones that have 'dist' 'stable' 'frozen' will score
179 higher than ones without. */
180 int Score(string Path
)
183 if (Path
.find("stable/") != string::npos
)
185 if (Path
.find("/binary-") != string::npos
)
187 if (Path
.find("frozen/") != string::npos
)
189 if (Path
.find("unstable/") != string::npos
)
191 if (Path
.find("/dists/") != string::npos
)
193 if (Path
.find("/main/") != string::npos
)
195 if (Path
.find("/contrib/") != string::npos
)
197 if (Path
.find("/non-free/") != string::npos
)
199 if (Path
.find("/non-US/") != string::npos
)
201 if (Path
.find("/source/") != string::npos
)
203 if (Path
.find("/debian/") != string::npos
)
208 // DropRepeats - Drop repeated files resulting from symlinks /*{{{*/
209 // ---------------------------------------------------------------------
210 /* Here we go and stat every file that we found and strip dup inodes. */
211 bool DropRepeats(vector
<string
> &List
,const char *Name
)
213 // Get a list of all the inodes
214 ino_t
*Inodes
= new ino_t
[List
.size()];
215 for (unsigned int I
= 0; I
!= List
.size(); I
++)
218 if (stat((List
[I
] + Name
).c_str(),&Buf
) != 0 &&
219 stat((List
[I
] + Name
+ ".gz").c_str(),&Buf
) != 0)
220 _error
->Errno("stat","Failed to stat %s%s",List
[I
].c_str(),
222 Inodes
[I
] = Buf
.st_ino
;
225 if (_error
->PendingError() == true)
229 for (unsigned int I
= 0; I
!= List
.size(); I
++)
231 for (unsigned int J
= I
+1; J
< List
.size(); J
++)
234 if (Inodes
[J
] != Inodes
[I
])
237 // We score the two paths.. and erase one
238 int ScoreA
= Score(List
[I
]);
239 int ScoreB
= Score(List
[J
]);
250 // Wipe erased entries
251 for (unsigned int I
= 0; I
< List
.size();)
253 if (List
[I
].empty() == false)
256 List
.erase(List
.begin()+I
);
263 // ReduceSourceList - Takes the path list and reduces it /*{{{*/
264 // ---------------------------------------------------------------------
265 /* This takes the list of source list expressed entires and collects
266 similar ones to form a single entry for each dist */
267 void ReduceSourcelist(string CD
,vector
<string
> &List
)
269 sort(List
.begin(),List
.end());
271 // Collect similar entries
272 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
275 string::size_type Space
= (*I
).find(' ');
276 if (Space
== string::npos
)
278 string::size_type SSpace
= (*I
).find(' ',Space
+ 1);
279 if (SSpace
== string::npos
)
282 string Word1
= string(*I
,Space
,SSpace
-Space
);
283 string Prefix
= string(*I
,0,Space
);
284 for (vector
<string
>::iterator J
= List
.begin(); J
!= I
; J
++)
287 string::size_type Space2
= (*J
).find(' ');
288 if (Space2
== string::npos
)
290 string::size_type SSpace2
= (*J
).find(' ',Space2
+ 1);
291 if (SSpace2
== string::npos
)
294 if (string(*J
,0,Space2
) != Prefix
)
296 if (string(*J
,Space2
,SSpace2
-Space2
) != Word1
)
299 *J
+= string(*I
,SSpace
);
304 // Wipe erased entries
305 for (unsigned int I
= 0; I
< List
.size();)
307 if (List
[I
].empty() == false)
310 List
.erase(List
.begin()+I
);
314 // WriteDatabase - Write the CDROM Database file /*{{{*/
315 // ---------------------------------------------------------------------
316 /* We rewrite the configuration class associated with the cdrom database. */
317 bool WriteDatabase(Configuration
&Cnf
)
319 string DFile
= _config
->FindFile("Dir::State::cdroms");
320 string NewFile
= DFile
+ ".new";
322 unlink(NewFile
.c_str());
323 ofstream
Out(NewFile
.c_str());
325 return _error
->Errno("ofstream::ofstream",
326 "Failed to open %s.new",DFile
.c_str());
328 /* Write out all of the configuration directives by walking the
329 configuration tree */
330 const Configuration::Item
*Top
= Cnf
.Tree(0);
333 // Print the config entry
334 if (Top
->Value
.empty() == false)
335 Out
<< Top
->FullTag() + " \"" << Top
->Value
<< "\";" << endl
;
343 while (Top
!= 0 && Top
->Next
== 0)
351 rename(DFile
.c_str(),string(DFile
+ '~').c_str());
352 if (rename(NewFile
.c_str(),DFile
.c_str()) != 0)
353 return _error
->Errno("rename","Failed to rename %s.new to %s",
354 DFile
.c_str(),DFile
.c_str());
359 // WriteSourceList - Write an updated sourcelist /*{{{*/
360 // ---------------------------------------------------------------------
361 /* This reads the old source list and copies it into the new one. It
362 appends the new CDROM entires just after the first block of comments.
363 This places them first in the file. It also removes any old entries
364 that were the same. */
365 bool WriteSourceList(string Name
,vector
<string
> &List
,bool Source
)
367 if (List
.size() == 0)
370 string File
= _config
->FindFile("Dir::Etc::sourcelist");
372 // Open the stream for reading
373 ifstream
F((FileExists(File
)?File
.c_str():"/dev/null"),
376 return _error
->Errno("ifstream::ifstream","Opening %s",File
.c_str());
378 string NewFile
= File
+ ".new";
379 unlink(NewFile
.c_str());
380 ofstream
Out(NewFile
.c_str());
382 return _error
->Errno("ofstream::ofstream",
383 "Failed to open %s.new",File
.c_str());
385 // Create a short uri without the path
386 string ShortURI
= "cdrom:[" + Name
+ "]/";
387 string ShortURI2
= "cdrom:" + Name
+ "/"; // For Compatibility
398 while (F
.eof() == false)
400 F
.getline(Buffer
,sizeof(Buffer
));
402 _strtabexpand(Buffer
,sizeof(Buffer
));
406 if (Buffer
[0] == '#' || Buffer
[0] == 0)
408 Out
<< Buffer
<< endl
;
414 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
416 string::size_type Space
= (*I
).find(' ');
417 if (Space
== string::npos
)
418 return _error
->Error("Internal error");
419 Out
<< Type
<< " cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
420 " " << string(*I
,Space
+1) << endl
;
428 const char *C
= Buffer
;
429 if (ParseQuoteWord(C
,cType
) == false ||
430 ParseQuoteWord(C
,URI
) == false)
432 Out
<< Buffer
<< endl
;
436 // Emit lines like this one
437 if (cType
!= Type
|| (string(URI
,0,ShortURI
.length()) != ShortURI
&&
438 string(URI
,0,ShortURI
.length()) != ShortURI2
))
440 Out
<< Buffer
<< endl
;
445 // Just in case the file was empty
448 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
450 string::size_type Space
= (*I
).find(' ');
451 if (Space
== string::npos
)
452 return _error
->Error("Internal error");
454 Out
<< "deb cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
455 " " << string(*I
,Space
+1) << endl
;
461 rename(File
.c_str(),string(File
+ '~').c_str());
462 if (rename(NewFile
.c_str(),File
.c_str()) != 0)
463 return _error
->Errno("rename","Failed to rename %s.new to %s",
464 File
.c_str(),File
.c_str());
470 // Prompt - Simple prompt /*{{{*/
471 // ---------------------------------------------------------------------
473 void Prompt(const char *Text
)
476 cout
<< Text
<< ' ' << flush
;
477 read(STDIN_FILENO
,&C
,1);
482 // PromptLine - Prompt for an input line /*{{{*/
483 // ---------------------------------------------------------------------
485 string
PromptLine(const char *Text
)
487 cout
<< Text
<< ':' << endl
;
495 // DoAdd - Add a new CDROM /*{{{*/
496 // ---------------------------------------------------------------------
497 /* This does the main add bit.. We show some status and things. The
498 sequence is to mount/umount the CD, Ident it then scan it for package
499 files and reduce that list. Then we copy over the package files and
500 verify them. Then rewrite the database files */
501 bool DoAdd(CommandLine
&)
504 string CDROM
= _config
->FindDir("Acquire::cdrom::mount","/cdrom/");
506 CDROM
= SafeGetCWD() + '/' + CDROM
;
508 cout
<< "Using CD-ROM mount point " << CDROM
<< endl
;
511 Configuration Database
;
512 string DFile
= _config
->FindFile("Dir::State::cdroms");
513 if (FileExists(DFile
) == true)
515 if (ReadConfigFile(Database
,DFile
) == false)
516 return _error
->Error("Unable to read the cdrom database %s",
520 // Unmount the CD and get the user to put in the one they want
521 if (_config
->FindB("APT::CDROM::NoMount",false) == false)
523 cout
<< "Unmounting CD-ROM" << endl
;
526 // Mount the new CDROM
527 Prompt("Please insert a Disc in the drive and press enter");
528 cout
<< "Mounting CD-ROM" << endl
;
529 if (MountCdrom(CDROM
) == false)
530 return _error
->Error("Failed to mount the cdrom.");
533 // Hash the CD to get an ID
534 cout
<< "Identifying.. " << flush
;
536 if (IdentCdrom(CDROM
,ID
) == false)
542 cout
<< '[' << ID
<< ']' << endl
;
544 cout
<< "Scanning Disc for index files.. " << flush
;
545 // Get the CD structure
547 vector
<string
> sList
;
548 string StartDir
= SafeGetCWD();
550 if (FindPackages(CDROM
,List
,sList
,InfoDir
) == false)
556 chdir(StartDir
.c_str());
558 if (_config
->FindB("Debug::aptcdrom",false) == true)
560 cout
<< "I found (binary):" << endl
;
561 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
563 cout
<< "I found (source):" << endl
;
564 for (vector
<string
>::iterator I
= sList
.begin(); I
!= sList
.end(); I
++)
569 DropBinaryArch(List
);
570 DropRepeats(List
,"Packages");
571 DropRepeats(sList
,"Sources");
572 cout
<< "Found " << List
.size() << " package indexes and " << sList
.size() <<
573 " source indexes." << endl
;
575 if (List
.size() == 0 && sList
.size() == 0)
576 return _error
->Error("Unable to locate any package files, perhaps this is not a Debian Disc");
578 // Check if the CD is in the database
580 if (Database
.Exists("CD::" + ID
) == false ||
581 _config
->FindB("APT::CDROM::Rename",false) == true)
583 // Try to use the CDs label if at all possible
584 if (InfoDir
.empty() == false &&
585 FileExists(InfoDir
+ "/info") == true)
587 ifstream
F(string(InfoDir
+ "/info").c_str());
591 if (Name
.empty() == false)
593 // Escape special characters
594 string::iterator J
= Name
.begin();
595 for (; J
!= Name
.end(); J
++)
596 if (*J
== '"' || *J
== ']' || *J
== '[')
599 cout
<< "Found label '" << Name
<< "'" << endl
;
600 Database
.Set("CD::" + ID
+ "::Label",Name
);
604 if (_config
->FindB("APT::CDROM::Rename",false) == true ||
605 Name
.empty() == true)
607 cout
<< "Please provide a name for this Disc, such as 'Debian 2.1r1 Disk 1'";
610 Name
= PromptLine("");
611 if (Name
.empty() == false &&
612 Name
.find('"') == string::npos
&&
613 Name
.find('[') == string::npos
&&
614 Name
.find(']') == string::npos
)
616 cout
<< "That is not a valid name, try again " << endl
;
621 Name
= Database
.Find("CD::" + ID
);
623 // Escape special characters
624 string::iterator J
= Name
.begin();
625 for (; J
!= Name
.end(); J
++)
626 if (*J
== '"' || *J
== ']' || *J
== '[')
629 Database
.Set("CD::" + ID
,Name
);
630 cout
<< "This Disc is called:" << endl
<< " '" << Name
<< "'" << endl
;
632 // Copy the package files to the state directory
635 if (Copy
.CopyPackages(CDROM
,Name
,List
) == false ||
636 SrcCopy
.CopyPackages(CDROM
,Name
,sList
) == false)
639 ReduceSourcelist(CDROM
,List
);
640 ReduceSourcelist(CDROM
,sList
);
642 // Write the database and sourcelist
643 if (_config
->FindB("APT::cdrom::NoAct",false) == false)
645 if (WriteDatabase(Database
) == false)
648 cout
<< "Writing new source list" << endl
;
649 if (WriteSourceList(Name
,List
,false) == false ||
650 WriteSourceList(Name
,sList
,true) == false)
654 // Print the sourcelist entries
655 cout
<< "Source List entries for this Disc are:" << endl
;
656 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
658 string::size_type Space
= (*I
).find(' ');
659 if (Space
== string::npos
)
660 return _error
->Error("Internal error");
662 cout
<< "deb cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
663 " " << string(*I
,Space
+1) << endl
;
666 for (vector
<string
>::iterator I
= sList
.begin(); I
!= sList
.end(); I
++)
668 string::size_type Space
= (*I
).find(' ');
669 if (Space
== string::npos
)
670 return _error
->Error("Internal error");
672 cout
<< "deb-src cdrom:[" << Name
<< "]/" << string(*I
,0,Space
) <<
673 " " << string(*I
,Space
+1) << endl
;
676 cout
<< "Repeat this process for the rest of the CDs in your set." << endl
;
678 // Unmount and finish
679 if (_config
->FindB("APT::CDROM::NoMount",false) == false)
685 // DoIdent - Ident a CDROM /*{{{*/
686 // ---------------------------------------------------------------------
688 bool DoIdent(CommandLine
&)
691 string CDROM
= _config
->FindDir("Acquire::cdrom::mount","/cdrom/");
693 CDROM
= SafeGetCWD() + '/' + CDROM
;
695 cout
<< "Using CD-ROM mount point " << CDROM
<< endl
;
696 cout
<< "Mounting CD-ROM" << endl
;
697 if (MountCdrom(CDROM
) == false)
698 return _error
->Error("Failed to mount the cdrom.");
700 // Hash the CD to get an ID
701 cout
<< "Identifying.. " << flush
;
703 if (IdentCdrom(CDROM
,ID
) == false)
709 cout
<< '[' << ID
<< ']' << endl
;
712 Configuration Database
;
713 string DFile
= _config
->FindFile("Dir::State::cdroms");
714 if (FileExists(DFile
) == true)
716 if (ReadConfigFile(Database
,DFile
) == false)
717 return _error
->Error("Unable to read the cdrom database %s",
720 cout
<< "Stored Label: '" << Database
.Find("CD::" + ID
) << "'" << endl
;
725 // ShowHelp - Show the help screen /*{{{*/
726 // ---------------------------------------------------------------------
730 ioprintf(cout
,_("%s %s for %s %s compiled on %s %s\n"),PACKAGE
,VERSION
,
731 COMMON_OS
,COMMON_CPU
,__DATE__
,__TIME__
);
732 if (_config
->FindB("version") == true)
736 "Usage: apt-cdrom [options] command\n"
738 "apt-cdrom is a tool to add CDROM's to APT's source list. The\n"
739 "CDROM mount point and device information is taken from apt.conf\n"
743 " add - Add a CDROM\n"
744 " ident - Report the identity of a CDROM\n"
747 " -h This help text\n"
748 " -d CD-ROM mount point\n"
749 " -r Rename a recognized CD-ROM\n"
751 " -f Fast mode, don't check package files\n"
752 " -a Thorough scan mode\n"
753 " -c=? Read this configuration file\n"
754 " -o=? Set an arbitary configuration option, eg -o dir::cache=/tmp\n"
760 int main(int argc
,const char *argv
[])
762 CommandLine::Args Args
[] = {
763 {'h',"help","help",0},
764 {'v',"version","version",0},
765 {'d',"cdrom","Acquire::cdrom::mount",CommandLine::HasArg
},
766 {'r',"rename","APT::CDROM::Rename",0},
767 {'m',"no-mount","APT::CDROM::NoMount",0},
768 {'f',"fast","APT::CDROM::Fast",0},
769 {'n',"just-print","APT::CDROM::NoAct",0},
770 {'n',"recon","APT::CDROM::NoAct",0},
771 {'n',"no-act","APT::CDROM::NoAct",0},
772 {'a',"thorough","APT::CDROM::Thorough",0},
773 {'c',"config-file",0,CommandLine::ConfigFile
},
774 {'o',"option",0,CommandLine::ArbItem
},
776 CommandLine::Dispatch Cmds
[] = {
781 // Set up gettext support
782 setlocale(LC_ALL
,"");
785 // Parse the command line and initialize the package library
786 CommandLine
CmdL(Args
,_config
);
787 if (pkgInitConfig(*_config
) == false ||
788 CmdL
.Parse(argc
,argv
) == false ||
789 pkgInitSystem(*_config
,_system
) == false)
791 _error
->DumpErrors();
795 // See if the help should be shown
796 if (_config
->FindB("help") == true || _config
->FindB("version") == true ||
797 CmdL
.FileSize() == 0)
800 // Deal with stdout not being a tty
801 if (ttyname(STDOUT_FILENO
) == 0 && _config
->FindI("quiet",0) < 1)
802 _config
->Set("quiet","1");
804 // Match the operation
805 CmdL
.DispatchArg(Cmds
);
807 // Print any errors or warnings found during parsing
808 if (_error
->empty() == false)
810 bool Errors
= _error
->PendingError();
811 _error
->DumpErrors();
812 return Errors
== true?100:0;