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