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