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