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