]> git.saurik.com Git - apt.git/blame - cmdline/apt-cdrom.cc
Made apt-cache regenerate its cache in memory
[apt.git] / cmdline / apt-cdrom.cc
CommitLineData
83d89a9f
AL
1// -*- mode: cpp; mode: fold -*-
2// Description /*{{{*/
1886ebc3 3// $Id: apt-cdrom.cc,v 1.22 1999/04/07 06:00:20 jgg Exp $
83d89a9f
AL
4/* ######################################################################
5
18444708
AL
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.
83d89a9f
AL
10
11 ##################################################################### */
12 /*}}}*/
13// Include Files /*{{{*/
14#include <apt-pkg/cmndline.h>
15#include <apt-pkg/error.h>
16#include <apt-pkg/init.h>
83d89a9f
AL
17#include <apt-pkg/fileutl.h>
18#include <apt-pkg/progress.h>
19#include <apt-pkg/tagfile.h>
65ae8fab 20#include <apt-pkg/cdromutl.h>
cdcc6d34 21#include <apt-pkg/strutl.h>
83d89a9f
AL
22#include <config.h>
23
24#include <iostream>
18444708 25#include <fstream>
83d89a9f
AL
26#include <vector>
27#include <algorithm>
83d89a9f
AL
28#include <sys/stat.h>
29#include <fcntl.h>
30#include <dirent.h>
31#include <unistd.h>
32#include <stdio.h>
33 /*}}}*/
34
4dfaa253 35// FindPackages - Find the package files on the CDROM /*{{{*/
83d89a9f
AL
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. */
22177db9
AL
41bool FindPackages(string CD,vector<string> &List,string &InfoDir,
42 unsigned int Depth = 0)
83d89a9f 43{
2c78c00b 44 static ino_t Inodes[9];
4dfaa253 45 if (Depth >= 7)
83d89a9f
AL
46 return true;
47
48 if (CD[CD.length()-1] != '/')
49 CD += '/';
50
51 if (chdir(CD.c_str()) != 0)
52 return _error->Errno("chdir","Unable to change to %s",CD.c_str());
53
54 /* Aha! We found some package files. We assume that everything under
55 this dir is controlled by those package files so we don't look down
56 anymore */
57 struct stat Buf;
988d60d1 58 if (stat("Packages",&Buf) == 0)
83d89a9f
AL
59 {
60 List.push_back(CD);
c60d151b
AL
61
62 // Continue down if thorough is given
63 if (_config->FindB("APT::CDROM::Thorough",false) == false)
64 return true;
83d89a9f
AL
65 }
66
22177db9
AL
67 // Look for a .disk subdirectory
68 if (stat(".disk",&Buf) == 0)
69 {
70 if (InfoDir.empty() == true)
71 InfoDir = CD + ".disk/";
72 }
73
83d89a9f
AL
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)
4dfaa253 92 continue;
83d89a9f
AL
93
94 if (S_ISDIR(Buf.st_mode) == 0)
95 continue;
96
e02343ac
AL
97 unsigned int I;
98 for (I = 0; I != Depth; I++)
99 if (Inodes[I] == Buf.st_ino)
100 break;
f1663bdf 101 if (I != Depth)
9bf3ee5c 102 continue;
f1663bdf 103
e02343ac
AL
104 // Store the inodes weve seen
105 Inodes[Depth] = Buf.st_ino;
106
83d89a9f 107 // Descend
22177db9 108 if (FindPackages(CD + Dir->d_name,List,InfoDir,Depth+1) == false)
83d89a9f
AL
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 /*}}}*/
83d89a9f
AL
120// DropBinaryArch - Dump dirs with a string like /binary-<foo>/ /*{{{*/
121// ---------------------------------------------------------------------
122/* Here we drop everything that is not this machines arch */
123bool 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. */
161int Score(string Path)
162{
163 int Res = 0;
164 if (Path.find("stable/") != string::npos)
165 Res += 2;
f1663bdf
AL
166 if (Path.find("/binary-") != string::npos)
167 Res += 2;
83d89a9f
AL
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. */
186bool 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;
2c78c00b 193 if (stat((List[I] + "Packages").c_str(),&Buf) != 0)
988d60d1 194 _error->Errno("stat","Failed to stat %sPackages",List[I].c_str());
83d89a9f
AL
195 Inodes[I] = Buf.st_ino;
196 }
197
988d60d1
AL
198 if (_error->PendingError() == true)
199 return false;
200
83d89a9f
AL
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 /*}}}*/
4dfaa253 235// ConvertToSourceList - Convert a Path to a sourcelist entry /*{{{*/
83d89a9f 236// ---------------------------------------------------------------------
4dfaa253
AL
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. */
240void ConvertToSourceList(string CD,string &Path)
83d89a9f
AL
241{
242 char S[300];
243 sprintf(S,"binary-%s",_config->Find("Apt::Architecture").c_str());
4dfaa253
AL
244
245 // Strip the cdrom base path
246 Path = string(Path,CD.length());
3b7525a6
AL
247 if (Path.empty() == true)
248 Path = "/";
4dfaa253
AL
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;
83d89a9f 257
4dfaa253
AL
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);
83d89a9f 264
4dfaa253
AL
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);
83d89a9f 270
4dfaa253
AL
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);
83d89a9f 276
4dfaa253
AL
277 if (Binary != S)
278 return;
279
280 Path = Dist + ' ' + Comp;
281}
282 /*}}}*/
283// GrabFirst - Return the first Depth path components /*{{{*/
284// ---------------------------------------------------------------------
285/* */
286bool 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;
83d89a9f 298
4dfaa253
AL
299 To = string(Path,0,I+1);
300 return true;
301}
302 /*}}}*/
179ce12b
AL
303// ChopDirs - Chop off the leading directory components /*{{{*/
304// ---------------------------------------------------------------------
305/* */
306string 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 */
326bool 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 */
357bool 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
4dfaa253
AL
382// CopyPackages - Copy the package files from the CD /*{{{*/
383// ---------------------------------------------------------------------
384/* */
385bool CopyPackages(string CDROM,string Name,vector<string> &List)
386{
387 OpTextProgress Progress;
388
389 bool NoStat = _config->FindB("APT::CDROM::Fast",false);
179ce12b 390 bool Debug = _config->FindB("Debug::aptcdrom",false);
4dfaa253
AL
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 }
83d89a9f 402
4dfaa253
AL
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());
83d89a9f 410
4dfaa253
AL
411 // Open the package file
412 FileFd Pkg(*I + "Packages",FileFd::ReadOnly);
413 pkgTagFile Parser(Pkg);
414 if (_error->PendingError() == true)
415 return false;
83d89a9f 416
4dfaa253
AL
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;
83d89a9f 427
4dfaa253
AL
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;
179ce12b 437 unsigned long Chop = 0;
4dfaa253
AL
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
179ce12b
AL
447 if (Chop != 0)
448 File = OrigPath + ChopDirs(File,Chop);
449
4dfaa253
AL
450 // See if the file exists
451 if (NoStat == false || Hits < 10)
452 {
179ce12b
AL
453 // Attempt to fix broken structure
454 if (Hits == 0)
4dfaa253 455 {
179ce12b
AL
456 if (ReconstructPrefix(Prefix,OrigPath,CDROM,File) == false &&
457 ReconstructChop(Chop,*I,File) == false)
4dfaa253 458 {
4dfaa253 459 NotFound++;
179ce12b 460 continue;
4dfaa253 461 }
179ce12b
AL
462 if (Chop != 0)
463 File = OrigPath + ChopDirs(File,Chop);
4dfaa253
AL
464 }
465
179ce12b
AL
466 // Get the size
467 struct stat Buf;
468 if (stat(string(CDROM + Prefix + File).c_str(),&Buf) != 0)
469 {
470 NotFound++;
4dfaa253 471 continue;
179ce12b
AL
472 }
473
4dfaa253
AL
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;
179ce12b
AL
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 if (Target.Write(Start,Stop-Start) == false)
510 return false;
511 }
3b7525a6
AL
512 if (Target.Write("\n",1) == false)
513 return false;
179ce12b
AL
514 }
515 else
516 {
517 Section.GetSection(Start,Stop);
518 if (Target.Write(Start,Stop-Start) == false)
519 return false;
520 }
4dfaa253
AL
521 }
522
179ce12b
AL
523 if (Debug == true)
524 cout << " Processed by using Prefix '" << Prefix << "' and chop " << Chop << endl;
525
4dfaa253
AL
526 if (_config->FindB("APT::CDROM::NoAct",false) == false)
527 {
528 // Move out of the partial directory
529 Target.Close();
530 string FinalF = _config->FindDir("Dir::State::lists");
531 FinalF += URItoFileName(S);
532 if (rename(TargetF.c_str(),FinalF.c_str()) != 0)
533 return _error->Errno("rename","Failed to rename");
534
535 // Copy the release file
536 sprintf(S,"cdrom:%s/%sRelease",Name.c_str(),(*I).c_str() + CDROM.length());
537 string TargetF = _config->FindDir("Dir::State::lists") + "partial/";
538 TargetF += URItoFileName(S);
539 if (FileExists(*I + "Release") == true)
540 {
541 FileFd Target(TargetF,FileFd::WriteEmpty);
542 FileFd Rel(*I + "Release",FileFd::ReadOnly);
543 if (_error->PendingError() == true)
544 return false;
545
546 if (CopyFile(Rel,Target) == false)
547 return false;
548 }
549 else
550 {
551 // Empty release file
552 FileFd Target(TargetF,FileFd::WriteEmpty);
553 }
554
555 // Rename the release file
556 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
562 /* Mangle the source to be in the proper notation with
563 prefix dist [component] */
564 *I = string(*I,Prefix.length());
565 ConvertToSourceList(CDROM,*I);
566 *I = Prefix + ' ' + *I;
567
568 CurrentSize += Pkg.Size();
569 }
570 Progress.Done();
571
572 // Some stats
573 cout << "Wrote " << Packages << " package records" ;
574 if (NotFound != 0)
575 cout << " with " << NotFound << " missing files";
576 if (NotFound != 0 && WrongSize != 0)
577 cout << " and";
578 if (WrongSize != 0)
579 cout << " with " << WrongSize << " mismatched files";
580 cout << '.' << endl;
581
582 if (Packages == 0)
583 return _error->Error("No valid package records were found.");
584
585 if (NotFound + WrongSize > 10)
586 cout << "Alot of package entires were discarded, perhaps this CD is funny?" << endl;
65ae8fab
AL
587
588 return true;
4dfaa253
AL
589}
590 /*}}}*/
591
592// ReduceSourceList - Takes the path list and reduces it /*{{{*/
593// ---------------------------------------------------------------------
594/* This takes the list of source list expressed entires and collects
595 similar ones to form a single entry for each dist */
596bool ReduceSourcelist(string CD,vector<string> &List)
597{
598 sort(List.begin(),List.end());
83d89a9f
AL
599
600 // Collect similar entries
601 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
602 {
603 // Find a space..
604 string::size_type Space = (*I).find(' ');
605 if (Space == string::npos)
606 continue;
4dfaa253
AL
607 string::size_type SSpace = (*I).find(' ',Space + 1);
608 if (SSpace == string::npos)
609 continue;
83d89a9f 610
4dfaa253 611 string Word1 = string(*I,Space,SSpace-Space);
83d89a9f
AL
612 for (vector<string>::iterator J = List.begin(); J != I; J++)
613 {
614 // Find a space..
615 string::size_type Space2 = (*J).find(' ');
616 if (Space2 == string::npos)
617 continue;
4dfaa253
AL
618 string::size_type SSpace2 = (*J).find(' ',Space2 + 1);
619 if (SSpace2 == string::npos)
620 continue;
83d89a9f 621
4dfaa253 622 if (string(*J,Space2,SSpace2-Space2) != Word1)
83d89a9f
AL
623 continue;
624
4dfaa253 625 *J += string(*I,SSpace);
83d89a9f
AL
626 *I = string();
627 }
628 }
629
630 // Wipe erased entries
631 for (unsigned int I = 0; I < List.size();)
632 {
633 if (List[I].empty() == false)
634 I++;
635 else
636 List.erase(List.begin()+I);
637 }
638}
639 /*}}}*/
18444708
AL
640// WriteDatabase - Write the CDROM Database file /*{{{*/
641// ---------------------------------------------------------------------
4dfaa253 642/* We rewrite the configuration class associated with the cdrom database. */
18444708
AL
643bool WriteDatabase(Configuration &Cnf)
644{
645 string DFile = _config->FindFile("Dir::State::cdroms");
4dfaa253
AL
646 string NewFile = DFile + ".new";
647
648 unlink(NewFile.c_str());
649 ofstream Out(NewFile.c_str());
18444708 650 if (!Out)
4dfaa253
AL
651 return _error->Errno("ofstream::ofstream",
652 "Failed to open %s.new",DFile.c_str());
18444708
AL
653
654 /* Write out all of the configuration directives by walking the
655 configuration tree */
656 const Configuration::Item *Top = Cnf.Tree(0);
657 for (; Top != 0;)
658 {
659 // Print the config entry
660 if (Top->Value.empty() == false)
661 Out << Top->FullTag() + " \"" << Top->Value << "\";" << endl;
662
663 if (Top->Child != 0)
664 {
665 Top = Top->Child;
666 continue;
667 }
668
669 while (Top != 0 && Top->Next == 0)
670 Top = Top->Parent;
671 if (Top != 0)
672 Top = Top->Next;
673 }
674
675 Out.close();
676
677 rename(DFile.c_str(),string(DFile + '~').c_str());
4dfaa253 678 if (rename(NewFile.c_str(),DFile.c_str()) != 0)
18444708
AL
679 return _error->Errno("rename","Failed to rename %s.new to %s",
680 DFile.c_str(),DFile.c_str());
681
682 return true;
683}
684 /*}}}*/
685// WriteSourceList - Write an updated sourcelist /*{{{*/
686// ---------------------------------------------------------------------
4dfaa253
AL
687/* This reads the old source list and copies it into the new one. It
688 appends the new CDROM entires just after the first block of comments.
689 This places them first in the file. It also removes any old entries
690 that were the same. */
18444708
AL
691bool WriteSourceList(string Name,vector<string> &List)
692{
4dfaa253
AL
693 string File = _config->FindFile("Dir::Etc::sourcelist");
694
695 // Open the stream for reading
696 ifstream F(File.c_str(),ios::in | ios::nocreate);
697 if (!F != 0)
698 return _error->Errno("ifstream::ifstream","Opening %s",File.c_str());
699
700 string NewFile = File + ".new";
701 unlink(NewFile.c_str());
702 ofstream Out(NewFile.c_str());
703 if (!Out)
704 return _error->Errno("ofstream::ofstream",
705 "Failed to open %s.new",File.c_str());
706
707 // Create a short uri without the path
708 string ShortURI = "cdrom:" + Name + "/";
709
710 char Buffer[300];
711 int CurLine = 0;
712 bool First = true;
713 while (F.eof() == false)
714 {
715 F.getline(Buffer,sizeof(Buffer));
716 CurLine++;
717 _strtabexpand(Buffer,sizeof(Buffer));
718 _strstrip(Buffer);
719
720 // Comment or blank
721 if (Buffer[0] == '#' || Buffer[0] == 0)
722 {
723 Out << Buffer << endl;
724 continue;
725 }
726
727 if (First == true)
728 {
729 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
730 {
731 string::size_type Space = (*I).find(' ');
732 if (Space == string::npos)
733 return _error->Error("Internal error");
734
735 Out << "deb \"cdrom:" << Name << "/" << string(*I,0,Space) <<
736 "\" " << string(*I,Space+1) << endl;
737 }
738 }
739 First = false;
740
741 // Grok it
742 string Type;
743 string URI;
744 char *C = Buffer;
745 if (ParseQuoteWord(C,Type) == false ||
746 ParseQuoteWord(C,URI) == false)
747 {
748 Out << Buffer << endl;
749 continue;
750 }
751
752 // Emit lines like this one
753 if (Type != "deb" || string(URI,0,ShortURI.length()) != ShortURI)
754 {
755 Out << Buffer << endl;
756 continue;
757 }
758 }
759
760 // Just in case the file was empty
761 if (First == true)
762 {
763 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
764 {
765 string::size_type Space = (*I).find(' ');
766 if (Space == string::npos)
767 return _error->Error("Internal error");
768
769 Out << "deb \"cdrom:" << Name << "/" << string(*I,0,Space) <<
770 "\" " << string(*I,Space+1) << endl;
771 }
772 }
773
774 Out.close();
775
776 rename(File.c_str(),string(File + '~').c_str());
777 if (rename(NewFile.c_str(),File.c_str()) != 0)
778 return _error->Errno("rename","Failed to rename %s.new to %s",
779 File.c_str(),File.c_str());
780
18444708
AL
781 return true;
782}
783 /*}}}*/
83d89a9f
AL
784
785// Prompt - Simple prompt /*{{{*/
786// ---------------------------------------------------------------------
787/* */
788void Prompt(const char *Text)
789{
790 char C;
791 cout << Text << ' ' << flush;
792 read(STDIN_FILENO,&C,1);
793 if (C != '\n')
794 cout << endl;
795}
796 /*}}}*/
797// PromptLine - Prompt for an input line /*{{{*/
798// ---------------------------------------------------------------------
799/* */
800string PromptLine(const char *Text)
801{
802 cout << Text << ':' << endl;
803
804 string Res;
805 getline(cin,Res);
806 return Res;
807}
808 /*}}}*/
809
810// DoAdd - Add a new CDROM /*{{{*/
811// ---------------------------------------------------------------------
4dfaa253
AL
812/* This does the main add bit.. We show some status and things. The
813 sequence is to mount/umount the CD, Ident it then scan it for package
814 files and reduce that list. Then we copy over the package files and
815 verify them. Then rewrite the database files */
83d89a9f
AL
816bool DoAdd(CommandLine &)
817{
818 // Startup
819 string CDROM = _config->FindDir("Acquire::cdrom::mount","/cdrom/");
ac49a1e5
AL
820 if (CDROM[0] == '.')
821 CDROM= SafeGetCWD() + '/' + CDROM;
83d89a9f 822
ac49a1e5
AL
823 cout << "Using CD-ROM mount point " << CDROM << endl;
824
83d89a9f
AL
825 // Read the database
826 Configuration Database;
827 string DFile = _config->FindFile("Dir::State::cdroms");
828 if (FileExists(DFile) == true)
829 {
830 if (ReadConfigFile(Database,DFile) == false)
831 return _error->Error("Unable to read the cdrom database %s",
832 DFile.c_str());
833 }
834
835 // Unmount the CD and get the user to put in the one they want
836 if (_config->FindB("APT::CDROM::NoMount",false) == false)
837 {
838 cout << "Unmounting CD-ROM" << endl;
839 UnmountCdrom(CDROM);
18444708 840
83d89a9f 841 // Mount the new CDROM
6c907975 842 Prompt("Please insert a Disc in the drive and press enter");
83d89a9f
AL
843 cout << "Mounting CD-ROM" << endl;
844 if (MountCdrom(CDROM) == false)
6c907975 845 return _error->Error("Failed to mount the cdrom.");
83d89a9f
AL
846 }
847
848 // Hash the CD to get an ID
18444708 849 cout << "Identifying.. " << flush;
83d89a9f
AL
850 string ID;
851 if (IdentCdrom(CDROM,ID) == false)
ac49a1e5
AL
852 {
853 cout << endl;
83d89a9f 854 return false;
ac49a1e5
AL
855 }
856
83d89a9f
AL
857 cout << '[' << ID << ']' << endl;
858
859 cout << "Scanning Disc for index files.. " << flush;
860 // Get the CD structure
861 vector<string> List;
862 string StartDir = SafeGetCWD();
22177db9
AL
863 string InfoDir;
864 if (FindPackages(CDROM,List,InfoDir) == false)
ac49a1e5
AL
865 {
866 cout << endl;
83d89a9f 867 return false;
ac49a1e5
AL
868 }
869
83d89a9f 870 chdir(StartDir.c_str());
4dfaa253
AL
871
872 if (_config->FindB("Debug::aptcdrom",false) == true)
873 {
874 cout << "I found:" << endl;
875 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
876 {
877 cout << *I << endl;
878 }
879 }
83d89a9f
AL
880
881 // Fix up the list
882 DropBinaryArch(List);
883 DropRepeats(List);
884 cout << "Found " << List.size() << " package index files." << endl;
885
886 if (List.size() == 0)
4dfaa253 887 return _error->Error("Unable to locate any package files, perhaps this is not a Debian Disc");
83d89a9f
AL
888
889 // Check if the CD is in the database
890 string Name;
891 if (Database.Exists("CD::" + ID) == false ||
892 _config->FindB("APT::CDROM::Rename",false) == true)
893 {
4dfaa253 894 // Try to use the CDs label if at all possible
22177db9 895 if (InfoDir.empty() == false &&
1886ebc3 896 FileExists(InfoDir + "/info") == true)
4dfaa253 897 {
1886ebc3 898 ifstream F(string(InfoDir + "/info").c_str());
4dfaa253
AL
899 if (!F == 0)
900 getline(F,Name);
901
902 if (Name.empty() == false)
903 {
904 cout << "Found label '" << Name << "'" << endl;
905 Database.Set("CD::" + ID + "::Label",Name);
906 }
907 }
908
909 if (_config->FindB("APT::CDROM::Rename",false) == true ||
179ce12b 910 Name.empty() == true)
4dfaa253
AL
911 {
912 cout << "Please provide a name for this Disc, such as 'Debian 2.1r1 Disk 1'";
913 while (1)
914 {
915 Name = PromptLine("");
916 if (Name.empty() == false &&
735a058b
AL
917 Name.find('"') == string::npos &&
918 Name.find(':') == string::npos &&
4dfaa253
AL
919 Name.find('/') == string::npos)
920 break;
921 cout << "That is not a valid name, try again " << endl;
735a058b 922 }
4dfaa253 923 }
83d89a9f
AL
924 }
925 else
926 Name = Database.Find("CD::" + ID);
735a058b
AL
927
928 string::iterator J = Name.begin();
929 for (; J != Name.end(); J++)
930 if (*J == '/' || *J == '"' || *J == ':')
931 *J = '_';
932
18444708 933 Database.Set("CD::" + ID,Name);
83d89a9f
AL
934 cout << "This Disc is called '" << Name << "'" << endl;
935
936 // Copy the package files to the state directory
937 if (CopyPackages(CDROM,Name,List) == false)
938 return false;
939
4dfaa253 940 ReduceSourcelist(CDROM,List);
18444708
AL
941
942 // Write the database and sourcelist
943 if (_config->FindB("APT::cdrom::NoAct",false) == false)
944 {
945 if (WriteDatabase(Database) == false)
946 return false;
4dfaa253
AL
947
948 cout << "Writing new source list" << endl;
949 if (WriteSourceList(Name,List) == false)
950 return false;
18444708 951 }
4dfaa253
AL
952
953 // Print the sourcelist entries
ab5498ab 954 cout << "Source List entries for this Disc are:" << endl;
4dfaa253
AL
955 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
956 {
957 string::size_type Space = (*I).find(' ');
958 if (Space == string::npos)
959 return _error->Error("Internal error");
960
961 cout << "deb \"cdrom:" << Name << "/" << string(*I,0,Space) <<
962 "\" " << string(*I,Space+1) << endl;
963 }
964
83d89a9f
AL
965 return true;
966}
967 /*}}}*/
968
969// ShowHelp - Show the help screen /*{{{*/
970// ---------------------------------------------------------------------
971/* */
972int ShowHelp()
973{
974 cout << PACKAGE << ' ' << VERSION << " for " << ARCHITECTURE <<
975 " compiled on " << __DATE__ << " " << __TIME__ << endl;
04aa15a8
AL
976 if (_config->FindB("version") == true)
977 return 100;
83d89a9f
AL
978
979 cout << "Usage: apt-cdrom [options] command" << endl;
980 cout << endl;
981 cout << "apt-cdrom is a tool to add CDROM's to APT's source list. The " << endl;
982 cout << "CDROM mount point and device information is taken from apt.conf" << endl;
983 cout << "and /etc/fstab." << endl;
984 cout << endl;
985 cout << "Commands:" << endl;
986 cout << " add - Add a CDROM" << endl;
987 cout << endl;
988 cout << "Options:" << endl;
989 cout << " -h This help text" << endl;
990 cout << " -d CD-ROM mount point" << endl;
991 cout << " -r Rename a recognized CD-ROM" << endl;
992 cout << " -m No mounting" << endl;
18444708 993 cout << " -f Fast mode, don't check package files" << endl;
9bf3ee5c 994 cout << " -a Thorough scan mode" << endl;
83d89a9f 995 cout << " -c=? Read this configuration file" << endl;
7974b907 996 cout << " -o=? Set an arbitary configuration option, eg -o dir::cache=/tmp" << endl;
83d89a9f
AL
997 cout << "See fstab(5)" << endl;
998 return 100;
999}
1000 /*}}}*/
1001
1002int main(int argc,const char *argv[])
1003{
1004 CommandLine::Args Args[] = {
1005 {'h',"help","help",0},
04aa15a8 1006 {'v',"version","version",0},
83d89a9f
AL
1007 {'d',"cdrom","Acquire::cdrom::mount",CommandLine::HasArg},
1008 {'r',"rename","APT::CDROM::Rename",0},
1009 {'m',"no-mount","APT::CDROM::NoMount",0},
1010 {'f',"fast","APT::CDROM::Fast",0},
c60d151b 1011 {'n',"just-print","APT::CDROM::NoAct",0},
18444708 1012 {'n',"recon","APT::CDROM::NoAct",0},
c60d151b
AL
1013 {'n',"no-act","APT::CDROM::NoAct",0},
1014 {'a',"thorough","APT::CDROM::Thorough",0},
83d89a9f
AL
1015 {'c',"config-file",0,CommandLine::ConfigFile},
1016 {'o',"option",0,CommandLine::ArbItem},
1017 {0,0,0,0}};
1018 CommandLine::Dispatch Cmds[] = {
1019 {"add",&DoAdd},
1020 {0,0}};
1021
1022 // Parse the command line and initialize the package library
1023 CommandLine CmdL(Args,_config);
1024 if (pkgInitialize(*_config) == false ||
1025 CmdL.Parse(argc,argv) == false)
1026 {
1027 _error->DumpErrors();
1028 return 100;
1029 }
1030
1031 // See if the help should be shown
1032 if (_config->FindB("help") == true ||
1033 CmdL.FileSize() == 0)
1034 return ShowHelp();
a9a5908d
AL
1035
1036 // Deal with stdout not being a tty
1037 if (ttyname(STDOUT_FILENO) == 0 && _config->FindI("quiet",0) < 1)
1038 _config->Set("quiet","1");
83d89a9f
AL
1039
1040 // Match the operation
1041 CmdL.DispatchArg(Cmds);
1042
1043 // Print any errors or warnings found during parsing
1044 if (_error->empty() == false)
1045 {
1046 bool Errors = _error->PendingError();
1047 _error->DumpErrors();
1048 return Errors == true?100:0;
1049 }
1050
1051 return 0;
1052}