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