]> git.saurik.com Git - apt.git/blob - cmdline/apt-cdrom.cc
More fixes
[apt.git] / cmdline / apt-cdrom.cc
1 // -*- mode: cpp; mode: fold -*-
2 // Description /*{{{*/
3 // $Id: apt-cdrom.cc,v 1.23 1999/05/29 03:32:30 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 if (NoStat == false || Hits < 10)
452 {
453 // Attempt to fix broken structure
454 if (Hits == 0)
455 {
456 if (ReconstructPrefix(Prefix,OrigPath,CDROM,File) == false &&
457 ReconstructChop(Chop,*I,File) == false)
458 {
459 NotFound++;
460 continue;
461 }
462 if (Chop != 0)
463 File = OrigPath + ChopDirs(File,Chop);
464 }
465
466 // Get the size
467 struct stat Buf;
468 if (stat(string(CDROM + Prefix + File).c_str(),&Buf) != 0)
469 {
470 NotFound++;
471 continue;
472 }
473
474 // Size match
475 if ((unsigned)Buf.st_size != Size)
476 {
477 WrongSize++;
478 continue;
479 }
480 }
481
482 Packages++;
483 Hits++;
484
485 // Copy it to the target package file
486 const char *Start;
487 const char *Stop;
488 if (Chop != 0)
489 {
490 // Mangle the output filename
491 const char *Filename;
492 Section.Find("Filename",Filename,Stop);
493
494 /* We need to rewrite the filename field so we emit
495 all fields except the filename file and rewrite that one */
496 for (unsigned int I = 0; I != Section.Count(); I++)
497 {
498 Section.Get(Start,Stop,I);
499 if (Start <= Filename && Stop > Filename)
500 {
501 char S[500];
502 sprintf(S,"Filename: %s\n",File.c_str());
503 if (I + 1 == Section.Count())
504 strcat(S,"\n");
505 if (Target.Write(S,strlen(S)) == false)
506 return false;
507 }
508 else
509 {
510 if (Target.Write(Start,Stop-Start) == false)
511 return false;
512 if (Stop[-1] != '\n')
513 if (Target.Write("\n",1) == false)
514 return false;
515 }
516 }
517 if (Target.Write("\n",1) == false)
518 return false;
519 }
520 else
521 {
522 Section.GetSection(Start,Stop);
523 if (Target.Write(Start,Stop-Start) == false)
524 return false;
525 }
526 }
527
528 if (Debug == true)
529 cout << " Processed by using Prefix '" << Prefix << "' and chop " << Chop << endl;
530
531 if (_config->FindB("APT::CDROM::NoAct",false) == false)
532 {
533 // Move out of the partial directory
534 Target.Close();
535 string FinalF = _config->FindDir("Dir::State::lists");
536 FinalF += URItoFileName(S);
537 if (rename(TargetF.c_str(),FinalF.c_str()) != 0)
538 return _error->Errno("rename","Failed to rename");
539
540 // Copy the release file
541 sprintf(S,"cdrom:%s/%sRelease",Name.c_str(),(*I).c_str() + CDROM.length());
542 string TargetF = _config->FindDir("Dir::State::lists") + "partial/";
543 TargetF += URItoFileName(S);
544 if (FileExists(*I + "Release") == true)
545 {
546 FileFd Target(TargetF,FileFd::WriteEmpty);
547 FileFd Rel(*I + "Release",FileFd::ReadOnly);
548 if (_error->PendingError() == true)
549 return false;
550
551 if (CopyFile(Rel,Target) == false)
552 return false;
553 }
554 else
555 {
556 // Empty release file
557 FileFd Target(TargetF,FileFd::WriteEmpty);
558 }
559
560 // Rename the release file
561 FinalF = _config->FindDir("Dir::State::lists");
562 FinalF += URItoFileName(S);
563 if (rename(TargetF.c_str(),FinalF.c_str()) != 0)
564 return _error->Errno("rename","Failed to rename");
565 }
566
567 /* Mangle the source to be in the proper notation with
568 prefix dist [component] */
569 *I = string(*I,Prefix.length());
570 ConvertToSourceList(CDROM,*I);
571 *I = Prefix + ' ' + *I;
572
573 CurrentSize += Pkg.Size();
574 }
575 Progress.Done();
576
577 // Some stats
578 cout << "Wrote " << Packages << " package records" ;
579 if (NotFound != 0)
580 cout << " with " << NotFound << " missing files";
581 if (NotFound != 0 && WrongSize != 0)
582 cout << " and";
583 if (WrongSize != 0)
584 cout << " with " << WrongSize << " mismatched files";
585 cout << '.' << endl;
586
587 if (Packages == 0)
588 return _error->Error("No valid package records were found.");
589
590 if (NotFound + WrongSize > 10)
591 cout << "Alot of package entires were discarded, perhaps this CD is funny?" << endl;
592
593 return true;
594 }
595 /*}}}*/
596
597 // ReduceSourceList - Takes the path list and reduces it /*{{{*/
598 // ---------------------------------------------------------------------
599 /* This takes the list of source list expressed entires and collects
600 similar ones to form a single entry for each dist */
601 bool ReduceSourcelist(string CD,vector<string> &List)
602 {
603 sort(List.begin(),List.end());
604
605 // Collect similar entries
606 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
607 {
608 // Find a space..
609 string::size_type Space = (*I).find(' ');
610 if (Space == string::npos)
611 continue;
612 string::size_type SSpace = (*I).find(' ',Space + 1);
613 if (SSpace == string::npos)
614 continue;
615
616 string Word1 = string(*I,Space,SSpace-Space);
617 for (vector<string>::iterator J = List.begin(); J != I; J++)
618 {
619 // Find a space..
620 string::size_type Space2 = (*J).find(' ');
621 if (Space2 == string::npos)
622 continue;
623 string::size_type SSpace2 = (*J).find(' ',Space2 + 1);
624 if (SSpace2 == string::npos)
625 continue;
626
627 if (string(*J,Space2,SSpace2-Space2) != Word1)
628 continue;
629
630 *J += string(*I,SSpace);
631 *I = string();
632 }
633 }
634
635 // Wipe erased entries
636 for (unsigned int I = 0; I < List.size();)
637 {
638 if (List[I].empty() == false)
639 I++;
640 else
641 List.erase(List.begin()+I);
642 }
643 }
644 /*}}}*/
645 // WriteDatabase - Write the CDROM Database file /*{{{*/
646 // ---------------------------------------------------------------------
647 /* We rewrite the configuration class associated with the cdrom database. */
648 bool WriteDatabase(Configuration &Cnf)
649 {
650 string DFile = _config->FindFile("Dir::State::cdroms");
651 string NewFile = DFile + ".new";
652
653 unlink(NewFile.c_str());
654 ofstream Out(NewFile.c_str());
655 if (!Out)
656 return _error->Errno("ofstream::ofstream",
657 "Failed to open %s.new",DFile.c_str());
658
659 /* Write out all of the configuration directives by walking the
660 configuration tree */
661 const Configuration::Item *Top = Cnf.Tree(0);
662 for (; Top != 0;)
663 {
664 // Print the config entry
665 if (Top->Value.empty() == false)
666 Out << Top->FullTag() + " \"" << Top->Value << "\";" << endl;
667
668 if (Top->Child != 0)
669 {
670 Top = Top->Child;
671 continue;
672 }
673
674 while (Top != 0 && Top->Next == 0)
675 Top = Top->Parent;
676 if (Top != 0)
677 Top = Top->Next;
678 }
679
680 Out.close();
681
682 rename(DFile.c_str(),string(DFile + '~').c_str());
683 if (rename(NewFile.c_str(),DFile.c_str()) != 0)
684 return _error->Errno("rename","Failed to rename %s.new to %s",
685 DFile.c_str(),DFile.c_str());
686
687 return true;
688 }
689 /*}}}*/
690 // WriteSourceList - Write an updated sourcelist /*{{{*/
691 // ---------------------------------------------------------------------
692 /* This reads the old source list and copies it into the new one. It
693 appends the new CDROM entires just after the first block of comments.
694 This places them first in the file. It also removes any old entries
695 that were the same. */
696 bool WriteSourceList(string Name,vector<string> &List)
697 {
698 string File = _config->FindFile("Dir::Etc::sourcelist");
699
700 // Open the stream for reading
701 ifstream F(File.c_str(),ios::in | ios::nocreate);
702 if (!F != 0)
703 return _error->Errno("ifstream::ifstream","Opening %s",File.c_str());
704
705 string NewFile = File + ".new";
706 unlink(NewFile.c_str());
707 ofstream Out(NewFile.c_str());
708 if (!Out)
709 return _error->Errno("ofstream::ofstream",
710 "Failed to open %s.new",File.c_str());
711
712 // Create a short uri without the path
713 string ShortURI = "cdrom:" + Name + "/";
714
715 char Buffer[300];
716 int CurLine = 0;
717 bool First = true;
718 while (F.eof() == false)
719 {
720 F.getline(Buffer,sizeof(Buffer));
721 CurLine++;
722 _strtabexpand(Buffer,sizeof(Buffer));
723 _strstrip(Buffer);
724
725 // Comment or blank
726 if (Buffer[0] == '#' || Buffer[0] == 0)
727 {
728 Out << Buffer << endl;
729 continue;
730 }
731
732 if (First == true)
733 {
734 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
735 {
736 string::size_type Space = (*I).find(' ');
737 if (Space == string::npos)
738 return _error->Error("Internal error");
739
740 Out << "deb \"cdrom:" << Name << "/" << string(*I,0,Space) <<
741 "\" " << string(*I,Space+1) << endl;
742 }
743 }
744 First = false;
745
746 // Grok it
747 string Type;
748 string URI;
749 char *C = Buffer;
750 if (ParseQuoteWord(C,Type) == false ||
751 ParseQuoteWord(C,URI) == false)
752 {
753 Out << Buffer << endl;
754 continue;
755 }
756
757 // Emit lines like this one
758 if (Type != "deb" || string(URI,0,ShortURI.length()) != ShortURI)
759 {
760 Out << Buffer << endl;
761 continue;
762 }
763 }
764
765 // Just in case the file was empty
766 if (First == true)
767 {
768 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
769 {
770 string::size_type Space = (*I).find(' ');
771 if (Space == string::npos)
772 return _error->Error("Internal error");
773
774 Out << "deb \"cdrom:" << Name << "/" << string(*I,0,Space) <<
775 "\" " << string(*I,Space+1) << endl;
776 }
777 }
778
779 Out.close();
780
781 rename(File.c_str(),string(File + '~').c_str());
782 if (rename(NewFile.c_str(),File.c_str()) != 0)
783 return _error->Errno("rename","Failed to rename %s.new to %s",
784 File.c_str(),File.c_str());
785
786 return true;
787 }
788 /*}}}*/
789
790 // Prompt - Simple prompt /*{{{*/
791 // ---------------------------------------------------------------------
792 /* */
793 void Prompt(const char *Text)
794 {
795 char C;
796 cout << Text << ' ' << flush;
797 read(STDIN_FILENO,&C,1);
798 if (C != '\n')
799 cout << endl;
800 }
801 /*}}}*/
802 // PromptLine - Prompt for an input line /*{{{*/
803 // ---------------------------------------------------------------------
804 /* */
805 string PromptLine(const char *Text)
806 {
807 cout << Text << ':' << endl;
808
809 string Res;
810 getline(cin,Res);
811 return Res;
812 }
813 /*}}}*/
814
815 // DoAdd - Add a new CDROM /*{{{*/
816 // ---------------------------------------------------------------------
817 /* This does the main add bit.. We show some status and things. The
818 sequence is to mount/umount the CD, Ident it then scan it for package
819 files and reduce that list. Then we copy over the package files and
820 verify them. Then rewrite the database files */
821 bool DoAdd(CommandLine &)
822 {
823 // Startup
824 string CDROM = _config->FindDir("Acquire::cdrom::mount","/cdrom/");
825 if (CDROM[0] == '.')
826 CDROM= SafeGetCWD() + '/' + CDROM;
827
828 cout << "Using CD-ROM mount point " << CDROM << endl;
829
830 // Read the database
831 Configuration Database;
832 string DFile = _config->FindFile("Dir::State::cdroms");
833 if (FileExists(DFile) == true)
834 {
835 if (ReadConfigFile(Database,DFile) == false)
836 return _error->Error("Unable to read the cdrom database %s",
837 DFile.c_str());
838 }
839
840 // Unmount the CD and get the user to put in the one they want
841 if (_config->FindB("APT::CDROM::NoMount",false) == false)
842 {
843 cout << "Unmounting CD-ROM" << endl;
844 UnmountCdrom(CDROM);
845
846 // Mount the new CDROM
847 Prompt("Please insert a Disc in the drive and press enter");
848 cout << "Mounting CD-ROM" << endl;
849 if (MountCdrom(CDROM) == false)
850 return _error->Error("Failed to mount the cdrom.");
851 }
852
853 // Hash the CD to get an ID
854 cout << "Identifying.. " << flush;
855 string ID;
856 if (IdentCdrom(CDROM,ID) == false)
857 {
858 cout << endl;
859 return false;
860 }
861
862 cout << '[' << ID << ']' << endl;
863
864 cout << "Scanning Disc for index files.. " << flush;
865 // Get the CD structure
866 vector<string> List;
867 string StartDir = SafeGetCWD();
868 string InfoDir;
869 if (FindPackages(CDROM,List,InfoDir) == false)
870 {
871 cout << endl;
872 return false;
873 }
874
875 chdir(StartDir.c_str());
876
877 if (_config->FindB("Debug::aptcdrom",false) == true)
878 {
879 cout << "I found:" << endl;
880 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
881 {
882 cout << *I << endl;
883 }
884 }
885
886 // Fix up the list
887 DropBinaryArch(List);
888 DropRepeats(List);
889 cout << "Found " << List.size() << " package index files." << endl;
890
891 if (List.size() == 0)
892 return _error->Error("Unable to locate any package files, perhaps this is not a Debian Disc");
893
894 // Check if the CD is in the database
895 string Name;
896 if (Database.Exists("CD::" + ID) == false ||
897 _config->FindB("APT::CDROM::Rename",false) == true)
898 {
899 // Try to use the CDs label if at all possible
900 if (InfoDir.empty() == false &&
901 FileExists(InfoDir + "/info") == true)
902 {
903 ifstream F(string(InfoDir + "/info").c_str());
904 if (!F == 0)
905 getline(F,Name);
906
907 if (Name.empty() == false)
908 {
909 cout << "Found label '" << Name << "'" << endl;
910 Database.Set("CD::" + ID + "::Label",Name);
911 }
912 }
913
914 if (_config->FindB("APT::CDROM::Rename",false) == true ||
915 Name.empty() == true)
916 {
917 cout << "Please provide a name for this Disc, such as 'Debian 2.1r1 Disk 1'";
918 while (1)
919 {
920 Name = PromptLine("");
921 if (Name.empty() == false &&
922 Name.find('"') == string::npos &&
923 Name.find(':') == string::npos &&
924 Name.find('/') == string::npos)
925 break;
926 cout << "That is not a valid name, try again " << endl;
927 }
928 }
929 }
930 else
931 Name = Database.Find("CD::" + ID);
932
933 string::iterator J = Name.begin();
934 for (; J != Name.end(); J++)
935 if (*J == '/' || *J == '"' || *J == ':')
936 *J = '_';
937
938 Database.Set("CD::" + ID,Name);
939 cout << "This Disc is called '" << Name << "'" << endl;
940
941 // Copy the package files to the state directory
942 if (CopyPackages(CDROM,Name,List) == false)
943 return false;
944
945 ReduceSourcelist(CDROM,List);
946
947 // Write the database and sourcelist
948 if (_config->FindB("APT::cdrom::NoAct",false) == false)
949 {
950 if (WriteDatabase(Database) == false)
951 return false;
952
953 cout << "Writing new source list" << endl;
954 if (WriteSourceList(Name,List) == false)
955 return false;
956 }
957
958 // Print the sourcelist entries
959 cout << "Source List entries for this Disc are:" << endl;
960 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
961 {
962 string::size_type Space = (*I).find(' ');
963 if (Space == string::npos)
964 return _error->Error("Internal error");
965
966 cout << "deb \"cdrom:" << Name << "/" << string(*I,0,Space) <<
967 "\" " << string(*I,Space+1) << endl;
968 }
969
970 return true;
971 }
972 /*}}}*/
973
974 // ShowHelp - Show the help screen /*{{{*/
975 // ---------------------------------------------------------------------
976 /* */
977 int ShowHelp()
978 {
979 cout << PACKAGE << ' ' << VERSION << " for " << ARCHITECTURE <<
980 " compiled on " << __DATE__ << " " << __TIME__ << endl;
981 if (_config->FindB("version") == true)
982 return 100;
983
984 cout << "Usage: apt-cdrom [options] command" << endl;
985 cout << endl;
986 cout << "apt-cdrom is a tool to add CDROM's to APT's source list. The " << endl;
987 cout << "CDROM mount point and device information is taken from apt.conf" << endl;
988 cout << "and /etc/fstab." << endl;
989 cout << endl;
990 cout << "Commands:" << endl;
991 cout << " add - Add a CDROM" << endl;
992 cout << endl;
993 cout << "Options:" << endl;
994 cout << " -h This help text" << endl;
995 cout << " -d CD-ROM mount point" << endl;
996 cout << " -r Rename a recognized CD-ROM" << endl;
997 cout << " -m No mounting" << endl;
998 cout << " -f Fast mode, don't check package files" << endl;
999 cout << " -a Thorough scan mode" << endl;
1000 cout << " -c=? Read this configuration file" << endl;
1001 cout << " -o=? Set an arbitary configuration option, eg -o dir::cache=/tmp" << endl;
1002 cout << "See fstab(5)" << endl;
1003 return 100;
1004 }
1005 /*}}}*/
1006
1007 int main(int argc,const char *argv[])
1008 {
1009 CommandLine::Args Args[] = {
1010 {'h',"help","help",0},
1011 {'v',"version","version",0},
1012 {'d',"cdrom","Acquire::cdrom::mount",CommandLine::HasArg},
1013 {'r',"rename","APT::CDROM::Rename",0},
1014 {'m',"no-mount","APT::CDROM::NoMount",0},
1015 {'f',"fast","APT::CDROM::Fast",0},
1016 {'n',"just-print","APT::CDROM::NoAct",0},
1017 {'n',"recon","APT::CDROM::NoAct",0},
1018 {'n',"no-act","APT::CDROM::NoAct",0},
1019 {'a',"thorough","APT::CDROM::Thorough",0},
1020 {'c',"config-file",0,CommandLine::ConfigFile},
1021 {'o',"option",0,CommandLine::ArbItem},
1022 {0,0,0,0}};
1023 CommandLine::Dispatch Cmds[] = {
1024 {"add",&DoAdd},
1025 {0,0}};
1026
1027 // Parse the command line and initialize the package library
1028 CommandLine CmdL(Args,_config);
1029 if (pkgInitialize(*_config) == false ||
1030 CmdL.Parse(argc,argv) == false)
1031 {
1032 _error->DumpErrors();
1033 return 100;
1034 }
1035
1036 // See if the help should be shown
1037 if (_config->FindB("help") == true ||
1038 CmdL.FileSize() == 0)
1039 return ShowHelp();
1040
1041 // Deal with stdout not being a tty
1042 if (ttyname(STDOUT_FILENO) == 0 && _config->FindI("quiet",0) < 1)
1043 _config->Set("quiet","1");
1044
1045 // Match the operation
1046 CmdL.DispatchArg(Cmds);
1047
1048 // Print any errors or warnings found during parsing
1049 if (_error->empty() == false)
1050 {
1051 bool Errors = _error->PendingError();
1052 _error->DumpErrors();
1053 return Errors == true?100:0;
1054 }
1055
1056 return 0;
1057 }