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