]> git.saurik.com Git - apt.git/blob - cmdline/apt-cdrom.cc
7875b1444c303bb3e22e27f62ab92ca59858b359
[apt.git] / cmdline / apt-cdrom.cc
1 // -*- mode: cpp; mode: fold -*-
2 // Description /*{{{*/
3 // $Id: apt-cdrom.cc,v 1.9 1998/12/09 00:11:50 jgg Exp $
4 /* ######################################################################
5
6 APT CDROM - Tool for handling APT's CDROM database.
7
8 Currently the only option is 'add' which will take the current CD
9 in the drive and add it into the database.
10
11 ##################################################################### */
12 /*}}}*/
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/tagfile.h>
20 #include <apt-pkg/cdromutl.h>
21 #include <strutl.h>
22 #include <config.h>
23
24 #include <iostream>
25 #include <fstream>
26 #include <vector>
27 #include <algorithm>
28 #include <sys/stat.h>
29 #include <fcntl.h>
30 #include <dirent.h>
31 #include <unistd.h>
32 #include <stdio.h>
33 /*}}}*/
34
35 // FindPackages - Find the package files on the CDROM /*{{{*/
36 // ---------------------------------------------------------------------
37 /* We look over the cdrom for package files. This is a recursive
38 search that short circuits when it his a package file in the dir.
39 This speeds it up greatly as the majority of the size is in the
40 binary-* sub dirs. */
41 bool FindPackages(string CD,vector<string> &List, int Depth = 0)
42 {
43 if (Depth >= 7)
44 return true;
45
46 if (CD[CD.length()-1] != '/')
47 CD += '/';
48
49 if (chdir(CD.c_str()) != 0)
50 return _error->Errno("chdir","Unable to change to %s",CD.c_str());
51
52 /* Aha! We found some package files. We assume that everything under
53 this dir is controlled by those package files so we don't look down
54 anymore */
55 struct stat Buf;
56 if (stat("Packages",&Buf) == 0 ||
57 stat("Packages.gz",&Buf) == 0)
58 {
59 List.push_back(CD);
60
61 // Continue down if thorough is given
62 if (_config->FindB("APT::CDROM::Thorough",false) == false)
63 return true;
64 }
65
66 bool Thorough = _config->FindB("APT::CDROM::Thorough",false);
67 DIR *D = opendir(".");
68 if (D == 0)
69 return _error->Errno("opendir","Unable to read %s",CD.c_str());
70
71 // Run over the directory
72 for (struct dirent *Dir = readdir(D); Dir != 0; Dir = readdir(D))
73 {
74 // Skip some files..
75 if (strcmp(Dir->d_name,".") == 0 ||
76 strcmp(Dir->d_name,"..") == 0 ||
77 strcmp(Dir->d_name,"source") == 0 ||
78 strcmp(Dir->d_name,"experimental") == 0 ||
79 strcmp(Dir->d_name,"binary-all") == 0)
80 continue;
81
82 // See if the name is a sub directory
83 struct stat Buf;
84 if (stat(Dir->d_name,&Buf) != 0)
85 continue;
86
87 if (S_ISDIR(Buf.st_mode) == 0)
88 continue;
89
90 if (Thorough == false && S_ISLNK(Buf.st_mode) != 0)
91 continue;
92
93 // Descend
94 if (FindPackages(CD + Dir->d_name,List,Depth+1) == false)
95 break;
96
97 if (chdir(CD.c_str()) != 0)
98 return _error->Errno("chdir","Unable to change to ",CD.c_str());
99 };
100
101 closedir(D);
102
103 return !_error->PendingError();
104 }
105 /*}}}*/
106 // DropBinaryArch - Dump dirs with a string like /binary-<foo>/ /*{{{*/
107 // ---------------------------------------------------------------------
108 /* Here we drop everything that is not this machines arch */
109 bool DropBinaryArch(vector<string> &List)
110 {
111 char S[300];
112 sprintf(S,"/binary-%s/",_config->Find("Apt::Architecture").c_str());
113
114 for (unsigned int I = 0; I < List.size(); I++)
115 {
116 const char *Str = List[I].c_str();
117
118 const char *Res;
119 if ((Res = strstr(Str,"/binary-")) == 0)
120 continue;
121
122 // Weird, remove it.
123 if (strlen(Res) < strlen(S))
124 {
125 List.erase(List.begin() + I);
126 I--;
127 continue;
128 }
129
130 // See if it is our arch
131 if (stringcmp(Res,Res + strlen(S),S) == 0)
132 continue;
133
134 // Erase it
135 List.erase(List.begin() + I);
136 I--;
137 }
138
139 return true;
140 }
141 /*}}}*/
142 // Score - We compute a 'score' for a path /*{{{*/
143 // ---------------------------------------------------------------------
144 /* Paths are scored based on how close they come to what I consider
145 normal. That is ones that have 'dist' 'stable' 'frozen' will score
146 higher than ones without. */
147 int Score(string Path)
148 {
149 int Res = 0;
150 if (Path.find("stable/") != string::npos)
151 Res += 2;
152 if (Path.find("frozen/") != string::npos)
153 Res += 2;
154 if (Path.find("/dists/") != string::npos)
155 Res += 4;
156 if (Path.find("/main/") != string::npos)
157 Res += 2;
158 if (Path.find("/contrib/") != string::npos)
159 Res += 2;
160 if (Path.find("/non-free/") != string::npos)
161 Res += 2;
162 if (Path.find("/non-US/") != string::npos)
163 Res += 2;
164 return Res;
165 }
166 /*}}}*/
167 // DropRepeats - Drop repeated files resulting from symlinks /*{{{*/
168 // ---------------------------------------------------------------------
169 /* Here we go and stat every file that we found and strip dup inodes. */
170 bool DropRepeats(vector<string> &List)
171 {
172 // Get a list of all the inodes
173 ino_t *Inodes = new ino_t[List.size()];
174 for (unsigned int I = 0; I != List.size(); I++)
175 {
176 struct stat Buf;
177 if (stat(List[I].c_str(),&Buf) != 0)
178 _error->Errno("stat","Failed to stat %s",List[I].c_str());
179 Inodes[I] = Buf.st_ino;
180 }
181
182 // Look for dups
183 for (unsigned int I = 0; I != List.size(); I++)
184 {
185 for (unsigned int J = I+1; J < List.size(); J++)
186 {
187 // No match
188 if (Inodes[J] != Inodes[I])
189 continue;
190
191 // We score the two paths.. and erase one
192 int ScoreA = Score(List[I]);
193 int ScoreB = Score(List[J]);
194 if (ScoreA < ScoreB)
195 {
196 List[I] = string();
197 break;
198 }
199
200 List[J] = string();
201 }
202 }
203
204 // Wipe erased entries
205 for (unsigned int I = 0; I < List.size();)
206 {
207 if (List[I].empty() == false)
208 I++;
209 else
210 List.erase(List.begin()+I);
211 }
212
213 return true;
214 }
215 /*}}}*/
216 // ConvertToSourceList - Convert a Path to a sourcelist entry /*{{{*/
217 // ---------------------------------------------------------------------
218 /* We look for things in dists/ notation and convert them to
219 <dist> <component> form otherwise it is left alone. This also strips
220 the CD path. */
221 void ConvertToSourceList(string CD,string &Path)
222 {
223 char S[300];
224 sprintf(S,"binary-%s",_config->Find("Apt::Architecture").c_str());
225
226 // Strip the cdrom base path
227 Path = string(Path,CD.length());
228 if (Path.empty() == true)
229 Path = "/";
230
231 // Too short to be a dists/ type
232 if (Path.length() < strlen("dists/"))
233 return;
234
235 // Not a dists type.
236 if (stringcmp(Path.begin(),Path.begin()+strlen("dists/"),"dists/") != 0)
237 return;
238
239 // Isolate the dist
240 string::size_type Slash = strlen("dists/");
241 string::size_type Slash2 = Path.find('/',Slash + 1);
242 if (Slash2 == string::npos || Slash2 + 2 >= Path.length())
243 return;
244 string Dist = string(Path,Slash,Slash2 - Slash);
245
246 // Isolate the component
247 Slash = Path.find('/',Slash2+1);
248 if (Slash == string::npos || Slash + 2 >= Path.length())
249 return;
250 string Comp = string(Path,Slash2+1,Slash - Slash2-1);
251
252 // Verify the trailing binar - bit
253 Slash2 = Path.find('/',Slash + 1);
254 if (Slash == string::npos)
255 return;
256 string Binary = string(Path,Slash+1,Slash2 - Slash-1);
257
258 if (Binary != S)
259 return;
260
261 Path = Dist + ' ' + Comp;
262 }
263 /*}}}*/
264 // GrabFirst - Return the first Depth path components /*{{{*/
265 // ---------------------------------------------------------------------
266 /* */
267 bool GrabFirst(string Path,string &To,unsigned int Depth)
268 {
269 string::size_type I = 0;
270 do
271 {
272 I = Path.find('/',I+1);
273 Depth--;
274 }
275 while (I != string::npos && Depth != 0);
276
277 if (I == string::npos)
278 return false;
279
280 To = string(Path,0,I+1);
281 return true;
282 }
283 /*}}}*/
284 // ChopDirs - Chop off the leading directory components /*{{{*/
285 // ---------------------------------------------------------------------
286 /* */
287 string ChopDirs(string Path,unsigned int Depth)
288 {
289 string::size_type I = 0;
290 do
291 {
292 I = Path.find('/',I+1);
293 Depth--;
294 }
295 while (I != string::npos && Depth != 0);
296
297 if (I == string::npos)
298 return string();
299
300 return string(Path,I+1);
301 }
302 /*}}}*/
303 // ReconstructPrefix - Fix strange prefixing /*{{{*/
304 // ---------------------------------------------------------------------
305 /* This prepends dir components from the path to the package files to
306 the path to the deb until it is found */
307 bool ReconstructPrefix(string &Prefix,string OrigPath,string CD,
308 string File)
309 {
310 bool Debug = _config->FindB("Debug::aptcdrom",false);
311 unsigned int Depth = 1;
312 string MyPrefix = Prefix;
313 while (1)
314 {
315 struct stat Buf;
316 if (stat(string(CD + MyPrefix + File).c_str(),&Buf) != 0)
317 {
318 if (Debug == true)
319 cout << "Failed, " << CD + MyPrefix + File << endl;
320 if (GrabFirst(OrigPath,MyPrefix,Depth++) == true)
321 continue;
322
323 return false;
324 }
325 else
326 {
327 Prefix = MyPrefix;
328 return true;
329 }
330 }
331 return false;
332 }
333 /*}}}*/
334 // ReconstructChop - Fixes bad source paths /*{{{*/
335 // ---------------------------------------------------------------------
336 /* This removes path components from the filename and prepends the location
337 of the package files until a file is found */
338 bool ReconstructChop(unsigned long &Chop,string Dir,string File)
339 {
340 // Attempt to reconstruct the filename
341 unsigned long Depth = 0;
342 while (1)
343 {
344 struct stat Buf;
345 if (stat(string(Dir + File).c_str(),&Buf) != 0)
346 {
347 File = ChopDirs(File,1);
348 Depth++;
349 if (File.empty() == false)
350 continue;
351 return false;
352 }
353 else
354 {
355 Chop = Depth;
356 return true;
357 }
358 }
359 return false;
360 }
361 /*}}}*/
362
363 // CopyPackages - Copy the package files from the CD /*{{{*/
364 // ---------------------------------------------------------------------
365 /* */
366 bool CopyPackages(string CDROM,string Name,vector<string> &List)
367 {
368 OpTextProgress Progress;
369
370 bool NoStat = _config->FindB("APT::CDROM::Fast",false);
371 bool Debug = _config->FindB("Debug::aptcdrom",false);
372
373 // Prepare the progress indicator
374 unsigned long TotalSize = 0;
375 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
376 {
377 struct stat Buf;
378 if (stat(string(*I + "Packages").c_str(),&Buf) != 0)
379 return _error->Errno("stat","Stat failed for %s",
380 string(*I + "Packages").c_str());
381 TotalSize += Buf.st_size;
382 }
383
384 unsigned long CurrentSize = 0;
385 unsigned int NotFound = 0;
386 unsigned int WrongSize = 0;
387 unsigned int Packages = 0;
388 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
389 {
390 string OrigPath = string(*I,CDROM.length());
391
392 // Open the package file
393 FileFd Pkg(*I + "Packages",FileFd::ReadOnly);
394 pkgTagFile Parser(Pkg);
395 if (_error->PendingError() == true)
396 return false;
397
398 // Open the output file
399 char S[400];
400 sprintf(S,"cdrom:%s/%sPackages",Name.c_str(),(*I).c_str() + CDROM.length());
401 string TargetF = _config->FindDir("Dir::State::lists") + "partial/";
402 TargetF += URItoFileName(S);
403 if (_config->FindB("APT::CDROM::NoAct",false) == true)
404 TargetF = "/dev/null";
405 FileFd Target(TargetF,FileFd::WriteEmpty);
406 if (_error->PendingError() == true)
407 return false;
408
409 // Setup the progress meter
410 Progress.OverallProgress(CurrentSize,TotalSize,Pkg.Size(),
411 "Reading Package Lists");
412
413 // Parse
414 Progress.SubProgress(Pkg.Size());
415 pkgTagSection Section;
416 string Prefix;
417 unsigned long Hits = 0;
418 unsigned long Chop = 0;
419 while (Parser.Step(Section) == true)
420 {
421 Progress.Progress(Parser.Offset());
422
423 string File = Section.FindS("Filename");
424 unsigned long Size = Section.FindI("Size");
425 if (File.empty() || Size == 0)
426 return _error->Error("Cannot find filename or size tag");
427
428 if (Chop != 0)
429 File = OrigPath + ChopDirs(File,Chop);
430
431 // See if the file exists
432 if (NoStat == false || Hits < 10)
433 {
434 // Attempt to fix broken structure
435 if (Hits == 0)
436 {
437 if (ReconstructPrefix(Prefix,OrigPath,CDROM,File) == false &&
438 ReconstructChop(Chop,*I,File) == false)
439 {
440 NotFound++;
441 continue;
442 }
443 if (Chop != 0)
444 File = OrigPath + ChopDirs(File,Chop);
445 }
446
447 // Get the size
448 struct stat Buf;
449 if (stat(string(CDROM + Prefix + File).c_str(),&Buf) != 0)
450 {
451 NotFound++;
452 continue;
453 }
454
455 // Size match
456 if ((unsigned)Buf.st_size != Size)
457 {
458 WrongSize++;
459 continue;
460 }
461 }
462
463 Packages++;
464 Hits++;
465
466 // Copy it to the target package file
467 const char *Start;
468 const char *Stop;
469 if (Chop != 0)
470 {
471 // Mangle the output filename
472 const char *Filename;
473 Section.Find("Filename",Filename,Stop);
474
475 /* We need to rewrite the filename field so we emit
476 all fields except the filename file and rewrite that one */
477 for (unsigned int I = 0; I != Section.Count(); I++)
478 {
479 Section.Get(Start,Stop,I);
480 if (Start <= Filename && Stop > Filename)
481 {
482 char S[500];
483 sprintf(S,"Filename: %s\n",File.c_str());
484 if (I + 1 == Section.Count())
485 strcat(S,"\n");
486 if (Target.Write(S,strlen(S)) == false)
487 return false;
488 }
489 else
490 if (Target.Write(Start,Stop-Start) == false)
491 return false;
492 }
493 if (Target.Write("\n",1) == false)
494 return false;
495 }
496 else
497 {
498 Section.GetSection(Start,Stop);
499 if (Target.Write(Start,Stop-Start) == false)
500 return false;
501 }
502 }
503
504 if (Debug == true)
505 cout << " Processed by using Prefix '" << Prefix << "' and chop " << Chop << endl;
506
507 if (_config->FindB("APT::CDROM::NoAct",false) == false)
508 {
509 // Move out of the partial directory
510 Target.Close();
511 string FinalF = _config->FindDir("Dir::State::lists");
512 FinalF += URItoFileName(S);
513 if (rename(TargetF.c_str(),FinalF.c_str()) != 0)
514 return _error->Errno("rename","Failed to rename");
515
516 // Copy the release file
517 sprintf(S,"cdrom:%s/%sRelease",Name.c_str(),(*I).c_str() + CDROM.length());
518 string TargetF = _config->FindDir("Dir::State::lists") + "partial/";
519 TargetF += URItoFileName(S);
520 if (FileExists(*I + "Release") == true)
521 {
522 FileFd Target(TargetF,FileFd::WriteEmpty);
523 FileFd Rel(*I + "Release",FileFd::ReadOnly);
524 if (_error->PendingError() == true)
525 return false;
526
527 if (CopyFile(Rel,Target) == false)
528 return false;
529 }
530 else
531 {
532 // Empty release file
533 FileFd Target(TargetF,FileFd::WriteEmpty);
534 }
535
536 // Rename the release file
537 FinalF = _config->FindDir("Dir::State::lists");
538 FinalF += URItoFileName(S);
539 if (rename(TargetF.c_str(),FinalF.c_str()) != 0)
540 return _error->Errno("rename","Failed to rename");
541 }
542
543 /* Mangle the source to be in the proper notation with
544 prefix dist [component] */
545 *I = string(*I,Prefix.length());
546 ConvertToSourceList(CDROM,*I);
547 *I = Prefix + ' ' + *I;
548
549 CurrentSize += Pkg.Size();
550 }
551 Progress.Done();
552
553 // Some stats
554 cout << "Wrote " << Packages << " package records" ;
555 if (NotFound != 0)
556 cout << " with " << NotFound << " missing files";
557 if (NotFound != 0 && WrongSize != 0)
558 cout << " and";
559 if (WrongSize != 0)
560 cout << " with " << WrongSize << " mismatched files";
561 cout << '.' << endl;
562
563 if (Packages == 0)
564 return _error->Error("No valid package records were found.");
565
566 if (NotFound + WrongSize > 10)
567 cout << "Alot of package entires were discarded, perhaps this CD is funny?" << endl;
568
569 return true;
570 }
571 /*}}}*/
572
573 // ReduceSourceList - Takes the path list and reduces it /*{{{*/
574 // ---------------------------------------------------------------------
575 /* This takes the list of source list expressed entires and collects
576 similar ones to form a single entry for each dist */
577 bool ReduceSourcelist(string CD,vector<string> &List)
578 {
579 sort(List.begin(),List.end());
580
581 // Collect similar entries
582 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
583 {
584 // Find a space..
585 string::size_type Space = (*I).find(' ');
586 if (Space == string::npos)
587 continue;
588 string::size_type SSpace = (*I).find(' ',Space + 1);
589 if (SSpace == string::npos)
590 continue;
591
592 string Word1 = string(*I,Space,SSpace-Space);
593 for (vector<string>::iterator J = List.begin(); J != I; J++)
594 {
595 // Find a space..
596 string::size_type Space2 = (*J).find(' ');
597 if (Space2 == string::npos)
598 continue;
599 string::size_type SSpace2 = (*J).find(' ',Space2 + 1);
600 if (SSpace2 == string::npos)
601 continue;
602
603 if (string(*J,Space2,SSpace2-Space2) != Word1)
604 continue;
605
606 *J += string(*I,SSpace);
607 *I = string();
608 }
609 }
610
611 // Wipe erased entries
612 for (unsigned int I = 0; I < List.size();)
613 {
614 if (List[I].empty() == false)
615 I++;
616 else
617 List.erase(List.begin()+I);
618 }
619 }
620 /*}}}*/
621 // WriteDatabase - Write the CDROM Database file /*{{{*/
622 // ---------------------------------------------------------------------
623 /* We rewrite the configuration class associated with the cdrom database. */
624 bool WriteDatabase(Configuration &Cnf)
625 {
626 string DFile = _config->FindFile("Dir::State::cdroms");
627 string NewFile = DFile + ".new";
628
629 unlink(NewFile.c_str());
630 ofstream Out(NewFile.c_str());
631 if (!Out)
632 return _error->Errno("ofstream::ofstream",
633 "Failed to open %s.new",DFile.c_str());
634
635 /* Write out all of the configuration directives by walking the
636 configuration tree */
637 const Configuration::Item *Top = Cnf.Tree(0);
638 for (; Top != 0;)
639 {
640 // Print the config entry
641 if (Top->Value.empty() == false)
642 Out << Top->FullTag() + " \"" << Top->Value << "\";" << endl;
643
644 if (Top->Child != 0)
645 {
646 Top = Top->Child;
647 continue;
648 }
649
650 while (Top != 0 && Top->Next == 0)
651 Top = Top->Parent;
652 if (Top != 0)
653 Top = Top->Next;
654 }
655
656 Out.close();
657
658 rename(DFile.c_str(),string(DFile + '~').c_str());
659 if (rename(NewFile.c_str(),DFile.c_str()) != 0)
660 return _error->Errno("rename","Failed to rename %s.new to %s",
661 DFile.c_str(),DFile.c_str());
662
663 return true;
664 }
665 /*}}}*/
666 // WriteSourceList - Write an updated sourcelist /*{{{*/
667 // ---------------------------------------------------------------------
668 /* This reads the old source list and copies it into the new one. It
669 appends the new CDROM entires just after the first block of comments.
670 This places them first in the file. It also removes any old entries
671 that were the same. */
672 bool WriteSourceList(string Name,vector<string> &List)
673 {
674 string File = _config->FindFile("Dir::Etc::sourcelist");
675
676 // Open the stream for reading
677 ifstream F(File.c_str(),ios::in | ios::nocreate);
678 if (!F != 0)
679 return _error->Errno("ifstream::ifstream","Opening %s",File.c_str());
680
681 string NewFile = File + ".new";
682 unlink(NewFile.c_str());
683 ofstream Out(NewFile.c_str());
684 if (!Out)
685 return _error->Errno("ofstream::ofstream",
686 "Failed to open %s.new",File.c_str());
687
688 // Create a short uri without the path
689 string ShortURI = "cdrom:" + Name + "/";
690
691 char Buffer[300];
692 int CurLine = 0;
693 bool First = true;
694 while (F.eof() == false)
695 {
696 F.getline(Buffer,sizeof(Buffer));
697 CurLine++;
698 _strtabexpand(Buffer,sizeof(Buffer));
699 _strstrip(Buffer);
700
701 // Comment or blank
702 if (Buffer[0] == '#' || Buffer[0] == 0)
703 {
704 Out << Buffer << endl;
705 continue;
706 }
707
708 if (First == true)
709 {
710 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
711 {
712 string::size_type Space = (*I).find(' ');
713 if (Space == string::npos)
714 return _error->Error("Internal error");
715
716 Out << "deb \"cdrom:" << Name << "/" << string(*I,0,Space) <<
717 "\" " << string(*I,Space+1) << endl;
718 }
719 }
720 First = false;
721
722 // Grok it
723 string Type;
724 string URI;
725 char *C = Buffer;
726 if (ParseQuoteWord(C,Type) == false ||
727 ParseQuoteWord(C,URI) == false)
728 {
729 Out << Buffer << endl;
730 continue;
731 }
732
733 // Emit lines like this one
734 if (Type != "deb" || string(URI,0,ShortURI.length()) != ShortURI)
735 {
736 Out << Buffer << endl;
737 continue;
738 }
739 }
740
741 // Just in case the file was empty
742 if (First == true)
743 {
744 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
745 {
746 string::size_type Space = (*I).find(' ');
747 if (Space == string::npos)
748 return _error->Error("Internal error");
749
750 Out << "deb \"cdrom:" << Name << "/" << string(*I,0,Space) <<
751 "\" " << string(*I,Space+1) << endl;
752 }
753 }
754
755 Out.close();
756
757 rename(File.c_str(),string(File + '~').c_str());
758 if (rename(NewFile.c_str(),File.c_str()) != 0)
759 return _error->Errno("rename","Failed to rename %s.new to %s",
760 File.c_str(),File.c_str());
761
762 return true;
763 }
764 /*}}}*/
765
766 // Prompt - Simple prompt /*{{{*/
767 // ---------------------------------------------------------------------
768 /* */
769 void Prompt(const char *Text)
770 {
771 char C;
772 cout << Text << ' ' << flush;
773 read(STDIN_FILENO,&C,1);
774 if (C != '\n')
775 cout << endl;
776 }
777 /*}}}*/
778 // PromptLine - Prompt for an input line /*{{{*/
779 // ---------------------------------------------------------------------
780 /* */
781 string PromptLine(const char *Text)
782 {
783 cout << Text << ':' << endl;
784
785 string Res;
786 getline(cin,Res);
787 return Res;
788 }
789 /*}}}*/
790
791 // DoAdd - Add a new CDROM /*{{{*/
792 // ---------------------------------------------------------------------
793 /* This does the main add bit.. We show some status and things. The
794 sequence is to mount/umount the CD, Ident it then scan it for package
795 files and reduce that list. Then we copy over the package files and
796 verify them. Then rewrite the database files */
797 bool DoAdd(CommandLine &)
798 {
799 // Startup
800 string CDROM = _config->FindDir("Acquire::cdrom::mount","/cdrom/");
801 if (CDROM[0] == '.')
802 CDROM= SafeGetCWD() + '/' + CDROM;
803
804 cout << "Using CD-ROM mount point " << CDROM << endl;
805
806 // Read the database
807 Configuration Database;
808 string DFile = _config->FindFile("Dir::State::cdroms");
809 if (FileExists(DFile) == true)
810 {
811 if (ReadConfigFile(Database,DFile) == false)
812 return _error->Error("Unable to read the cdrom database %s",
813 DFile.c_str());
814 }
815
816 // Unmount the CD and get the user to put in the one they want
817 if (_config->FindB("APT::CDROM::NoMount",false) == false)
818 {
819 cout << "Unmounting CD-ROM" << endl;
820 UnmountCdrom(CDROM);
821
822 // Mount the new CDROM
823 Prompt("Please insert a Disc in the drive and press any key");
824 cout << "Mounting CD-ROM" << endl;
825 if (MountCdrom(CDROM) == false)
826 {
827 cout << "Failed to mount the cdrom." << endl;
828 return false;
829 }
830 }
831
832 // Hash the CD to get an ID
833 cout << "Identifying.. " << flush;
834 string ID;
835 if (IdentCdrom(CDROM,ID) == false)
836 {
837 cout << endl;
838 return false;
839 }
840
841 cout << '[' << ID << ']' << endl;
842
843 cout << "Scanning Disc for index files.. " << flush;
844 // Get the CD structure
845 vector<string> List;
846 string StartDir = SafeGetCWD();
847 if (FindPackages(CDROM,List) == false)
848 {
849 cout << endl;
850 return false;
851 }
852
853 chdir(StartDir.c_str());
854
855 if (_config->FindB("Debug::aptcdrom",false) == true)
856 {
857 cout << "I found:" << endl;
858 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
859 {
860 cout << *I << endl;
861 }
862 }
863
864 // Fix up the list
865 DropBinaryArch(List);
866 DropRepeats(List);
867 cout << "Found " << List.size() << " package index files." << endl;
868
869 if (List.size() == 0)
870 return _error->Error("Unable to locate any package files, perhaps this is not a Debian Disc");
871
872 // Check if the CD is in the database
873 string Name;
874 if (Database.Exists("CD::" + ID) == false ||
875 _config->FindB("APT::CDROM::Rename",false) == true)
876 {
877 // Try to use the CDs label if at all possible
878 if (FileExists(CDROM + "/.disk/info") == true)
879 {
880 ifstream F(string(CDROM+ "/.disk/info").c_str());
881 if (!F == 0)
882 getline(F,Name);
883
884 if (Name.empty() == false)
885 {
886 cout << "Found label '" << Name << "'" << endl;
887 Database.Set("CD::" + ID + "::Label",Name);
888 }
889 }
890
891 if (_config->FindB("APT::CDROM::Rename",false) == true ||
892 Name.empty() == true)
893 {
894 cout << "Please provide a name for this Disc, such as 'Debian 2.1r1 Disk 1'";
895 while (1)
896 {
897 Name = PromptLine("");
898 if (Name.empty() == false &&
899 Name.find('/') == string::npos)
900 break;
901 cout << "That is not a valid name, try again " << endl;
902 }
903
904 }
905 }
906 else
907 Name = Database.Find("CD::" + ID);
908 Database.Set("CD::" + ID,Name);
909 cout << "This Disc is called '" << Name << "'" << endl;
910
911 // Copy the package files to the state directory
912 if (CopyPackages(CDROM,Name,List) == false)
913 return false;
914
915 ReduceSourcelist(CDROM,List);
916
917 // Write the database and sourcelist
918 if (_config->FindB("APT::cdrom::NoAct",false) == false)
919 {
920 if (WriteDatabase(Database) == false)
921 return false;
922
923 cout << "Writing new source list" << endl;
924 if (WriteSourceList(Name,List) == false)
925 return false;
926 }
927
928 // Print the sourcelist entries
929 cout << "Source List entires for this Disc are:" << endl;
930 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
931 {
932 string::size_type Space = (*I).find(' ');
933 if (Space == string::npos)
934 return _error->Error("Internal error");
935
936 cout << "deb \"cdrom:" << Name << "/" << string(*I,0,Space) <<
937 "\" " << string(*I,Space+1) << endl;
938 }
939
940 return true;
941 }
942 /*}}}*/
943
944 // ShowHelp - Show the help screen /*{{{*/
945 // ---------------------------------------------------------------------
946 /* */
947 int ShowHelp()
948 {
949 cout << PACKAGE << ' ' << VERSION << " for " << ARCHITECTURE <<
950 " compiled on " << __DATE__ << " " << __TIME__ << endl;
951
952 cout << "Usage: apt-cdrom [options] command" << endl;
953 cout << endl;
954 cout << "apt-cdrom is a tool to add CDROM's to APT's source list. The " << endl;
955 cout << "CDROM mount point and device information is taken from apt.conf" << endl;
956 cout << "and /etc/fstab." << endl;
957 cout << endl;
958 cout << "Commands:" << endl;
959 cout << " add - Add a CDROM" << endl;
960 cout << endl;
961 cout << "Options:" << endl;
962 cout << " -h This help text" << endl;
963 cout << " -d CD-ROM mount point" << endl;
964 cout << " -r Rename a recognized CD-ROM" << endl;
965 cout << " -m No mounting" << endl;
966 cout << " -f Fast mode, don't check package files" << endl;
967 cout << " -a Thorough scan mode" << endl;
968 cout << " -c=? Read this configuration file" << endl;
969 cout << " -o=? Set an arbitary configuration option, ie -o dir::cache=/tmp" << endl;
970 cout << "See fstab(5)" << endl;
971 return 100;
972 }
973 /*}}}*/
974
975 int main(int argc,const char *argv[])
976 {
977 CommandLine::Args Args[] = {
978 {'h',"help","help",0},
979 {'d',"cdrom","Acquire::cdrom::mount",CommandLine::HasArg},
980 {'r',"rename","APT::CDROM::Rename",0},
981 {'m',"no-mount","APT::CDROM::NoMount",0},
982 {'f',"fast","APT::CDROM::Fast",0},
983 {'n',"just-print","APT::CDROM::NoAct",0},
984 {'n',"recon","APT::CDROM::NoAct",0},
985 {'n',"no-act","APT::CDROM::NoAct",0},
986 {'a',"thorough","APT::CDROM::Thorough",0},
987 {'c',"config-file",0,CommandLine::ConfigFile},
988 {'o',"option",0,CommandLine::ArbItem},
989 {0,0,0,0}};
990 CommandLine::Dispatch Cmds[] = {
991 {"add",&DoAdd},
992 {0,0}};
993
994 // Parse the command line and initialize the package library
995 CommandLine CmdL(Args,_config);
996 if (pkgInitialize(*_config) == false ||
997 CmdL.Parse(argc,argv) == false)
998 {
999 _error->DumpErrors();
1000 return 100;
1001 }
1002
1003 // See if the help should be shown
1004 if (_config->FindB("help") == true ||
1005 CmdL.FileSize() == 0)
1006 return ShowHelp();
1007
1008 // Match the operation
1009 CmdL.DispatchArg(Cmds);
1010
1011 // Print any errors or warnings found during parsing
1012 if (_error->empty() == false)
1013 {
1014 bool Errors = _error->PendingError();
1015 _error->DumpErrors();
1016 return Errors == true?100:0;
1017 }
1018
1019 return 0;
1020 }