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