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