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