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