]>
git.saurik.com Git - apt.git/blob - cmdline/apt-cdrom.cc
1 // -*- mode: cpp; mode: fold -*-
3 // $Id: apt-cdrom.cc,v 1.32 1999/09/03 05:46:48 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
)
195 // DropRepeats - Drop repeated files resulting from symlinks /*{{{*/
196 // ---------------------------------------------------------------------
197 /* Here we go and stat every file that we found and strip dup inodes. */
198 bool DropRepeats(vector
<string
> &List
,const char *Name
)
200 // Get a list of all the inodes
201 ino_t
*Inodes
= new ino_t
[List
.size()];
202 for (unsigned int I
= 0; I
!= List
.size(); I
++)
205 if (stat((List
[I
] + Name
).c_str(),&Buf
) != 0 &&
206 stat((List
[I
] + Name
+ ".gz").c_str(),&Buf
) != 0)
207 _error
->Errno("stat","Failed to stat %s%s",List
[I
].c_str(),
209 Inodes
[I
] = Buf
.st_ino
;
212 if (_error
->PendingError() == true)
216 for (unsigned int I
= 0; I
!= List
.size(); I
++)
218 for (unsigned int J
= I
+1; J
< List
.size(); J
++)
221 if (Inodes
[J
] != Inodes
[I
])
224 // We score the two paths.. and erase one
225 int ScoreA
= Score(List
[I
]);
226 int ScoreB
= Score(List
[J
]);
237 // Wipe erased entries
238 for (unsigned int I
= 0; I
< List
.size();)
240 if (List
[I
].empty() == false)
243 List
.erase(List
.begin()+I
);
250 // ReduceSourceList - Takes the path list and reduces it /*{{{*/
251 // ---------------------------------------------------------------------
252 /* This takes the list of source list expressed entires and collects
253 similar ones to form a single entry for each dist */
254 bool ReduceSourcelist(string CD
,vector
<string
> &List
)
256 sort(List
.begin(),List
.end());
258 // Collect similar entries
259 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
262 string::size_type Space
= (*I
).find(' ');
263 if (Space
== string::npos
)
265 string::size_type SSpace
= (*I
).find(' ',Space
+ 1);
266 if (SSpace
== string::npos
)
269 string Word1
= string(*I
,Space
,SSpace
-Space
);
270 for (vector
<string
>::iterator J
= List
.begin(); J
!= I
; J
++)
273 string::size_type Space2
= (*J
).find(' ');
274 if (Space2
== string::npos
)
276 string::size_type SSpace2
= (*J
).find(' ',Space2
+ 1);
277 if (SSpace2
== string::npos
)
280 if (string(*J
,Space2
,SSpace2
-Space2
) != Word1
)
283 *J
+= string(*I
,SSpace
);
288 // Wipe erased entries
289 for (unsigned int I
= 0; I
< List
.size();)
291 if (List
[I
].empty() == false)
294 List
.erase(List
.begin()+I
);
298 // WriteDatabase - Write the CDROM Database file /*{{{*/
299 // ---------------------------------------------------------------------
300 /* We rewrite the configuration class associated with the cdrom database. */
301 bool WriteDatabase(Configuration
&Cnf
)
303 string DFile
= _config
->FindFile("Dir::State::cdroms");
304 string NewFile
= DFile
+ ".new";
306 unlink(NewFile
.c_str());
307 ofstream
Out(NewFile
.c_str());
309 return _error
->Errno("ofstream::ofstream",
310 "Failed to open %s.new",DFile
.c_str());
312 /* Write out all of the configuration directives by walking the
313 configuration tree */
314 const Configuration::Item
*Top
= Cnf
.Tree(0);
317 // Print the config entry
318 if (Top
->Value
.empty() == false)
319 Out
<< Top
->FullTag() + " \"" << Top
->Value
<< "\";" << endl
;
327 while (Top
!= 0 && Top
->Next
== 0)
335 rename(DFile
.c_str(),string(DFile
+ '~').c_str());
336 if (rename(NewFile
.c_str(),DFile
.c_str()) != 0)
337 return _error
->Errno("rename","Failed to rename %s.new to %s",
338 DFile
.c_str(),DFile
.c_str());
343 // WriteSourceList - Write an updated sourcelist /*{{{*/
344 // ---------------------------------------------------------------------
345 /* This reads the old source list and copies it into the new one. It
346 appends the new CDROM entires just after the first block of comments.
347 This places them first in the file. It also removes any old entries
348 that were the same. */
349 bool WriteSourceList(string Name
,vector
<string
> &List
,bool Source
)
351 if (List
.size() == 0)
354 string File
= _config
->FindFile("Dir::Etc::sourcelist");
356 // Open the stream for reading
357 ifstream
F(File
.c_str(),ios::in
| ios::nocreate
);
359 return _error
->Errno("ifstream::ifstream","Opening %s",File
.c_str());
361 string NewFile
= File
+ ".new";
362 unlink(NewFile
.c_str());
363 ofstream
Out(NewFile
.c_str());
365 return _error
->Errno("ofstream::ofstream",
366 "Failed to open %s.new",File
.c_str());
368 // Create a short uri without the path
369 string ShortURI
= "cdrom:" + Name
+ "/";
380 while (F
.eof() == false)
382 F
.getline(Buffer
,sizeof(Buffer
));
384 _strtabexpand(Buffer
,sizeof(Buffer
));
388 if (Buffer
[0] == '#' || Buffer
[0] == 0)
390 Out
<< Buffer
<< endl
;
396 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
398 string::size_type Space
= (*I
).find(' ');
399 if (Space
== string::npos
)
400 return _error
->Error("Internal error");
401 Out
<< Type
<< " \"cdrom:" << Name
<< "/" << string(*I
,0,Space
) <<
402 "\" " << string(*I
,Space
+1) << endl
;
410 const char *C
= Buffer
;
411 if (ParseQuoteWord(C
,cType
) == false ||
412 ParseQuoteWord(C
,URI
) == false)
414 Out
<< Buffer
<< endl
;
418 // Emit lines like this one
419 if (cType
!= Type
|| string(URI
,0,ShortURI
.length()) != ShortURI
)
421 Out
<< Buffer
<< endl
;
426 // Just in case the file was empty
429 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
431 string::size_type Space
= (*I
).find(' ');
432 if (Space
== string::npos
)
433 return _error
->Error("Internal error");
435 Out
<< "deb \"cdrom:" << Name
<< "/" << string(*I
,0,Space
) <<
436 "\" " << string(*I
,Space
+1) << endl
;
442 rename(File
.c_str(),string(File
+ '~').c_str());
443 if (rename(NewFile
.c_str(),File
.c_str()) != 0)
444 return _error
->Errno("rename","Failed to rename %s.new to %s",
445 File
.c_str(),File
.c_str());
451 // Prompt - Simple prompt /*{{{*/
452 // ---------------------------------------------------------------------
454 void Prompt(const char *Text
)
457 cout
<< Text
<< ' ' << flush
;
458 read(STDIN_FILENO
,&C
,1);
463 // PromptLine - Prompt for an input line /*{{{*/
464 // ---------------------------------------------------------------------
466 string
PromptLine(const char *Text
)
468 cout
<< Text
<< ':' << endl
;
476 // DoAdd - Add a new CDROM /*{{{*/
477 // ---------------------------------------------------------------------
478 /* This does the main add bit.. We show some status and things. The
479 sequence is to mount/umount the CD, Ident it then scan it for package
480 files and reduce that list. Then we copy over the package files and
481 verify them. Then rewrite the database files */
482 bool DoAdd(CommandLine
&)
485 string CDROM
= _config
->FindDir("Acquire::cdrom::mount","/cdrom/");
487 CDROM
= SafeGetCWD() + '/' + CDROM
;
489 cout
<< "Using CD-ROM mount point " << CDROM
<< endl
;
492 Configuration Database
;
493 string DFile
= _config
->FindFile("Dir::State::cdroms");
494 if (FileExists(DFile
) == true)
496 if (ReadConfigFile(Database
,DFile
) == false)
497 return _error
->Error("Unable to read the cdrom database %s",
501 // Unmount the CD and get the user to put in the one they want
502 if (_config
->FindB("APT::CDROM::NoMount",false) == false)
504 cout
<< "Unmounting CD-ROM" << endl
;
507 // Mount the new CDROM
508 Prompt("Please insert a Disc in the drive and press enter");
509 cout
<< "Mounting CD-ROM" << endl
;
510 if (MountCdrom(CDROM
) == false)
511 return _error
->Error("Failed to mount the cdrom.");
514 // Hash the CD to get an ID
515 cout
<< "Identifying.. " << flush
;
517 if (IdentCdrom(CDROM
,ID
) == false)
523 cout
<< '[' << ID
<< ']' << endl
;
525 cout
<< "Scanning Disc for index files.. " << flush
;
526 // Get the CD structure
528 vector
<string
> sList
;
529 string StartDir
= SafeGetCWD();
531 if (FindPackages(CDROM
,List
,sList
,InfoDir
) == false)
537 chdir(StartDir
.c_str());
539 if (_config
->FindB("Debug::aptcdrom",false) == true)
541 cout
<< "I found (binary):" << endl
;
542 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
544 cout
<< "I found (source):" << endl
;
545 for (vector
<string
>::iterator I
= sList
.begin(); I
!= sList
.end(); I
++)
550 DropBinaryArch(List
);
551 DropRepeats(List
,"Packages");
552 DropRepeats(sList
,"Sources");
553 cout
<< "Found " << List
.size() << " package indexes and " << sList
.size() <<
554 " source indexes." << endl
;
556 if (List
.size() == 0)
557 return _error
->Error("Unable to locate any package files, perhaps this is not a Debian Disc");
559 // Check if the CD is in the database
561 if (Database
.Exists("CD::" + ID
) == false ||
562 _config
->FindB("APT::CDROM::Rename",false) == true)
564 // Try to use the CDs label if at all possible
565 if (InfoDir
.empty() == false &&
566 FileExists(InfoDir
+ "/info") == true)
568 ifstream
F(string(InfoDir
+ "/info").c_str());
572 if (Name
.empty() == false)
574 cout
<< "Found label '" << Name
<< "'" << endl
;
575 Database
.Set("CD::" + ID
+ "::Label",Name
);
579 if (_config
->FindB("APT::CDROM::Rename",false) == true ||
580 Name
.empty() == true)
582 cout
<< "Please provide a name for this Disc, such as 'Debian 2.1r1 Disk 1'";
585 Name
= PromptLine("");
586 if (Name
.empty() == false &&
587 Name
.find('"') == string::npos
&&
588 Name
.find(':') == string::npos
&&
589 Name
.find('/') == string::npos
)
591 cout
<< "That is not a valid name, try again " << endl
;
596 Name
= Database
.Find("CD::" + ID
);
598 string::iterator J
= Name
.begin();
599 for (; J
!= Name
.end(); J
++)
600 if (*J
== '/' || *J
== '"' || *J
== ':')
603 Database
.Set("CD::" + ID
,Name
);
604 cout
<< "This Disc is called '" << Name
<< "'" << endl
;
606 // Copy the package files to the state directory
609 if (Copy
.CopyPackages(CDROM
,Name
,List
) == false ||
610 SrcCopy
.CopyPackages(CDROM
,Name
,sList
) == false)
613 ReduceSourcelist(CDROM
,List
);
614 ReduceSourcelist(CDROM
,sList
);
616 // Write the database and sourcelist
617 if (_config
->FindB("APT::cdrom::NoAct",false) == false)
619 if (WriteDatabase(Database
) == false)
622 cout
<< "Writing new source list" << endl
;
623 if (WriteSourceList(Name
,List
,false) == false ||
624 WriteSourceList(Name
,sList
,true) == false)
628 // Print the sourcelist entries
629 cout
<< "Source List entries for this Disc are:" << endl
;
630 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
632 string::size_type Space
= (*I
).find(' ');
633 if (Space
== string::npos
)
634 return _error
->Error("Internal error");
636 cout
<< "deb \"cdrom:" << Name
<< "/" << string(*I
,0,Space
) <<
637 "\" " << string(*I
,Space
+1) << endl
;
640 for (vector
<string
>::iterator I
= sList
.begin(); I
!= sList
.end(); I
++)
642 string::size_type Space
= (*I
).find(' ');
643 if (Space
== string::npos
)
644 return _error
->Error("Internal error");
646 cout
<< "deb-src \"cdrom:" << Name
<< "/" << string(*I
,0,Space
) <<
647 "\" " << string(*I
,Space
+1) << endl
;
650 cout
<< "Repeat this process for the rest of the CDs in your set." << endl
;
655 // ShowHelp - Show the help screen /*{{{*/
656 // ---------------------------------------------------------------------
660 cout
<< PACKAGE
<< ' ' << VERSION
<< " for " << ARCHITECTURE
<<
661 " compiled on " << __DATE__
<< " " << __TIME__
<< endl
;
662 if (_config
->FindB("version") == true)
665 cout
<< "Usage: apt-cdrom [options] command" << endl
;
667 cout
<< "apt-cdrom is a tool to add CDROM's to APT's source list. The " << endl
;
668 cout
<< "CDROM mount point and device information is taken from apt.conf" << endl
;
669 cout
<< "and /etc/fstab." << endl
;
671 cout
<< "Commands:" << endl
;
672 cout
<< " add - Add a CDROM" << endl
;
674 cout
<< "Options:" << endl
;
675 cout
<< " -h This help text" << endl
;
676 cout
<< " -d CD-ROM mount point" << endl
;
677 cout
<< " -r Rename a recognized CD-ROM" << endl
;
678 cout
<< " -m No mounting" << endl
;
679 cout
<< " -f Fast mode, don't check package files" << endl
;
680 cout
<< " -a Thorough scan mode" << endl
;
681 cout
<< " -c=? Read this configuration file" << endl
;
682 cout
<< " -o=? Set an arbitary configuration option, eg -o dir::cache=/tmp" << endl
;
683 cout
<< "See fstab(5)" << endl
;
688 int main(int argc
,const char *argv
[])
690 CommandLine::Args Args
[] = {
691 {'h',"help","help",0},
692 {'v',"version","version",0},
693 {'d',"cdrom","Acquire::cdrom::mount",CommandLine::HasArg
},
694 {'r',"rename","APT::CDROM::Rename",0},
695 {'m',"no-mount","APT::CDROM::NoMount",0},
696 {'f',"fast","APT::CDROM::Fast",0},
697 {'n',"just-print","APT::CDROM::NoAct",0},
698 {'n',"recon","APT::CDROM::NoAct",0},
699 {'n',"no-act","APT::CDROM::NoAct",0},
700 {'a',"thorough","APT::CDROM::Thorough",0},
701 {'c',"config-file",0,CommandLine::ConfigFile
},
702 {'o',"option",0,CommandLine::ArbItem
},
704 CommandLine::Dispatch Cmds
[] = {
708 // Parse the command line and initialize the package library
709 CommandLine
CmdL(Args
,_config
);
710 if (pkgInitialize(*_config
) == false ||
711 CmdL
.Parse(argc
,argv
) == false)
713 _error
->DumpErrors();
717 // See if the help should be shown
718 if (_config
->FindB("help") == true ||
719 CmdL
.FileSize() == 0)
722 // Deal with stdout not being a tty
723 if (ttyname(STDOUT_FILENO
) == 0 && _config
->FindI("quiet",0) < 1)
724 _config
->Set("quiet","1");
726 // Match the operation
727 CmdL
.DispatchArg(Cmds
);
729 // Print any errors or warnings found during parsing
730 if (_error
->empty() == false)
732 bool Errors
= _error
->PendingError();
733 _error
->DumpErrors();
734 return Errors
== true?100:0;