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