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