]> git.saurik.com Git - apt.git/blob - cmdline/apt-cdrom.cc
d524cc0e6da8b133735d9b20d40a019e6a597075
[apt.git] / cmdline / apt-cdrom.cc
1 // -*- mode: cpp; mode: fold -*-
2 // Description /*{{{*/
3 // $Id: apt-cdrom.cc,v 1.4 1998/11/28 03:54:34 jgg Exp $
4 /* ######################################################################
5
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.
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>
25 #include <fstream>
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 /* */
41 bool 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
51 for (int I = 0; I != 10; I++)
52 close(I);
53 for (int I = 0; I != 3; I++)
54 dup2(open("/dev/null",O_RDWR),I);
55
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.. */
82 bool 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
92 for (int I = 0; I != 10; I++)
93 close(I);
94 for (int I = 0; I != 3; I++)
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. */
124 bool 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
166 // FindPackages - Find the package files on the CDROM /*{{{*/
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. */
172 bool FindPackages(string CD,vector<string> &List, int Depth = 0)
173 {
174 if (Depth >= 7)
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)
212 continue;
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 /*}}}*/
230 // DropBinaryArch - Dump dirs with a string like /binary-<foo>/ /*{{{*/
231 // ---------------------------------------------------------------------
232 /* Here we drop everything that is not this machines arch */
233 bool 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. */
271 int 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. */
294 bool 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 /*}}}*/
340 // ConvertToSourceList - Convert a Path to a sourcelist entry /*{{{*/
341 // ---------------------------------------------------------------------
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. */
345 void ConvertToSourceList(string CD,string &Path)
346 {
347 char S[300];
348 sprintf(S,"binary-%s",_config->Find("Apt::Architecture").c_str());
349
350 // Strip the cdrom base path
351 Path = string(Path,CD.length());
352
353 // Too short to be a dists/ type
354 if (Path.length() < strlen("dists/"))
355 return;
356
357 // Not a dists type.
358 if (stringcmp(Path.begin(),Path.begin()+strlen("dists/"),"dists/") != 0)
359 return;
360
361 // Isolate the dist
362 string::size_type Slash = strlen("dists/");
363 string::size_type Slash2 = Path.find('/',Slash + 1);
364 if (Slash2 == string::npos || Slash2 + 2 >= Path.length())
365 return;
366 string Dist = string(Path,Slash,Slash2 - Slash);
367
368 // Isolate the component
369 Slash = Path.find('/',Slash2+1);
370 if (Slash == string::npos || Slash + 2 >= Path.length())
371 return;
372 string Comp = string(Path,Slash2+1,Slash - Slash2-1);
373
374 // Verify the trailing binar - bit
375 Slash2 = Path.find('/',Slash + 1);
376 if (Slash == string::npos)
377 return;
378 string Binary = string(Path,Slash+1,Slash2 - Slash-1);
379
380 if (Binary != S)
381 return;
382
383 Path = Dist + ' ' + Comp;
384 }
385 /*}}}*/
386 // GrabFirst - Return the first Depth path components /*{{{*/
387 // ---------------------------------------------------------------------
388 /* */
389 bool GrabFirst(string Path,string &To,unsigned int Depth)
390 {
391 string::size_type I = 0;
392 do
393 {
394 I = Path.find('/',I+1);
395 Depth--;
396 }
397 while (I != string::npos && Depth != 0);
398
399 if (I == string::npos)
400 return false;
401
402 To = string(Path,0,I+1);
403 return true;
404 }
405 /*}}}*/
406 // ChopDirs - Chop off the leading directory components /*{{{*/
407 // ---------------------------------------------------------------------
408 /* */
409 string ChopDirs(string Path,unsigned int Depth)
410 {
411 string::size_type I = 0;
412 do
413 {
414 I = Path.find('/',I+1);
415 Depth--;
416 }
417 while (I != string::npos && Depth != 0);
418
419 if (I == string::npos)
420 return string();
421
422 return string(Path,I+1);
423 }
424 /*}}}*/
425 // ReconstructPrefix - Fix strange prefixing /*{{{*/
426 // ---------------------------------------------------------------------
427 /* This prepends dir components from the path to the package files to
428 the path to the deb until it is found */
429 bool ReconstructPrefix(string &Prefix,string OrigPath,string CD,
430 string File)
431 {
432 bool Debug = _config->FindB("Debug::aptcdrom",false);
433 unsigned int Depth = 1;
434 string MyPrefix = Prefix;
435 while (1)
436 {
437 struct stat Buf;
438 if (stat(string(CD + MyPrefix + File).c_str(),&Buf) != 0)
439 {
440 if (Debug == true)
441 cout << "Failed, " << CD + MyPrefix + File << endl;
442 if (GrabFirst(OrigPath,MyPrefix,Depth++) == true)
443 continue;
444
445 return false;
446 }
447 else
448 {
449 Prefix = MyPrefix;
450 return true;
451 }
452 }
453 return false;
454 }
455 /*}}}*/
456 // ReconstructChop - Fixes bad source paths /*{{{*/
457 // ---------------------------------------------------------------------
458 /* This removes path components from the filename and prepends the location
459 of the package files until a file is found */
460 bool ReconstructChop(unsigned long &Chop,string Dir,string File)
461 {
462 // Attempt to reconstruct the filename
463 unsigned long Depth = 0;
464 while (1)
465 {
466 struct stat Buf;
467 if (stat(string(Dir + File).c_str(),&Buf) != 0)
468 {
469 File = ChopDirs(File,1);
470 Depth++;
471 if (File.empty() == false)
472 continue;
473 return false;
474 }
475 else
476 {
477 Chop = Depth;
478 return true;
479 }
480 }
481 return false;
482 }
483 /*}}}*/
484
485 // CopyPackages - Copy the package files from the CD /*{{{*/
486 // ---------------------------------------------------------------------
487 /* */
488 bool CopyPackages(string CDROM,string Name,vector<string> &List)
489 {
490 OpTextProgress Progress;
491
492 bool NoStat = _config->FindB("APT::CDROM::Fast",false);
493 bool Debug = _config->FindB("Debug::aptcdrom",false);
494
495 // Prepare the progress indicator
496 unsigned long TotalSize = 0;
497 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
498 {
499 struct stat Buf;
500 if (stat(string(*I + "Packages").c_str(),&Buf) != 0)
501 return _error->Errno("stat","Stat failed for %s",
502 string(*I + "Packages").c_str());
503 TotalSize += Buf.st_size;
504 }
505
506 unsigned long CurrentSize = 0;
507 unsigned int NotFound = 0;
508 unsigned int WrongSize = 0;
509 unsigned int Packages = 0;
510 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
511 {
512 string OrigPath = string(*I,CDROM.length());
513
514 // Open the package file
515 FileFd Pkg(*I + "Packages",FileFd::ReadOnly);
516 pkgTagFile Parser(Pkg);
517 if (_error->PendingError() == true)
518 return false;
519
520 // Open the output file
521 char S[400];
522 sprintf(S,"cdrom:%s/%sPackages",Name.c_str(),(*I).c_str() + CDROM.length());
523 string TargetF = _config->FindDir("Dir::State::lists") + "partial/";
524 TargetF += URItoFileName(S);
525 if (_config->FindB("APT::CDROM::NoAct",false) == true)
526 TargetF = "/dev/null";
527 FileFd Target(TargetF,FileFd::WriteEmpty);
528 if (_error->PendingError() == true)
529 return false;
530
531 // Setup the progress meter
532 Progress.OverallProgress(CurrentSize,TotalSize,Pkg.Size(),
533 "Reading Package Lists");
534
535 // Parse
536 Progress.SubProgress(Pkg.Size());
537 pkgTagSection Section;
538 string Prefix;
539 unsigned long Hits = 0;
540 unsigned long Chop = 0;
541 while (Parser.Step(Section) == true)
542 {
543 Progress.Progress(Parser.Offset());
544
545 string File = Section.FindS("Filename");
546 unsigned long Size = Section.FindI("Size");
547 if (File.empty() || Size == 0)
548 return _error->Error("Cannot find filename or size tag");
549
550 if (Chop != 0)
551 File = OrigPath + ChopDirs(File,Chop);
552
553 // See if the file exists
554 if (NoStat == false || Hits < 10)
555 {
556 // Attempt to fix broken structure
557 if (Hits == 0)
558 {
559 if (ReconstructPrefix(Prefix,OrigPath,CDROM,File) == false &&
560 ReconstructChop(Chop,*I,File) == false)
561 {
562 NotFound++;
563 continue;
564 }
565 if (Chop != 0)
566 File = OrigPath + ChopDirs(File,Chop);
567 }
568
569 // Get the size
570 struct stat Buf;
571 if (stat(string(CDROM + Prefix + File).c_str(),&Buf) != 0)
572 {
573 NotFound++;
574 continue;
575 }
576
577 // Size match
578 if ((unsigned)Buf.st_size != Size)
579 {
580 WrongSize++;
581 continue;
582 }
583 }
584
585 Packages++;
586 Hits++;
587
588 // Copy it to the target package file
589 const char *Start;
590 const char *Stop;
591 if (Chop != 0)
592 {
593 // Mangle the output filename
594 const char *Filename;
595 Section.Find("Filename",Filename,Stop);
596
597 /* We need to rewrite the filename field so we emit
598 all fields except the filename file and rewrite that one */
599 for (unsigned int I = 0; I != Section.Count(); I++)
600 {
601 Section.Get(Start,Stop,I);
602 if (Start <= Filename && Stop > Filename)
603 {
604 char S[500];
605 sprintf(S,"Filename: %s\n",File.c_str());
606 if (I + 1 == Section.Count())
607 strcat(S,"\n");
608 if (Target.Write(S,strlen(S)) == false)
609 return false;
610 }
611 else
612 if (Target.Write(Start,Stop-Start) == false)
613 return false;
614 }
615
616 }
617 else
618 {
619 Section.GetSection(Start,Stop);
620 if (Target.Write(Start,Stop-Start) == false)
621 return false;
622 }
623 }
624
625 if (Debug == true)
626 cout << " Processed by using Prefix '" << Prefix << "' and chop " << Chop << endl;
627
628 if (_config->FindB("APT::CDROM::NoAct",false) == false)
629 {
630 // Move out of the partial directory
631 Target.Close();
632 string FinalF = _config->FindDir("Dir::State::lists");
633 FinalF += URItoFileName(S);
634 if (rename(TargetF.c_str(),FinalF.c_str()) != 0)
635 return _error->Errno("rename","Failed to rename");
636
637 // Copy the release file
638 sprintf(S,"cdrom:%s/%sRelease",Name.c_str(),(*I).c_str() + CDROM.length());
639 string TargetF = _config->FindDir("Dir::State::lists") + "partial/";
640 TargetF += URItoFileName(S);
641 if (FileExists(*I + "Release") == true)
642 {
643 FileFd Target(TargetF,FileFd::WriteEmpty);
644 FileFd Rel(*I + "Release",FileFd::ReadOnly);
645 if (_error->PendingError() == true)
646 return false;
647
648 if (CopyFile(Rel,Target) == false)
649 return false;
650 }
651 else
652 {
653 // Empty release file
654 FileFd Target(TargetF,FileFd::WriteEmpty);
655 }
656
657 // Rename the release file
658 FinalF = _config->FindDir("Dir::State::lists");
659 FinalF += URItoFileName(S);
660 if (rename(TargetF.c_str(),FinalF.c_str()) != 0)
661 return _error->Errno("rename","Failed to rename");
662 }
663
664 /* Mangle the source to be in the proper notation with
665 prefix dist [component] */
666 *I = string(*I,Prefix.length());
667 ConvertToSourceList(CDROM,*I);
668 *I = Prefix + ' ' + *I;
669
670 CurrentSize += Pkg.Size();
671 }
672 Progress.Done();
673
674 // Some stats
675 cout << "Wrote " << Packages << " package records" ;
676 if (NotFound != 0)
677 cout << " with " << NotFound << " missing files";
678 if (NotFound != 0 && WrongSize != 0)
679 cout << " and";
680 if (WrongSize != 0)
681 cout << " with " << WrongSize << " mismatched files";
682 cout << '.' << endl;
683
684 if (Packages == 0)
685 return _error->Error("No valid package records were found.");
686
687 if (NotFound + WrongSize > 10)
688 cout << "Alot of package entires were discarded, perhaps this CD is funny?" << endl;
689 }
690 /*}}}*/
691
692 // ReduceSourceList - Takes the path list and reduces it /*{{{*/
693 // ---------------------------------------------------------------------
694 /* This takes the list of source list expressed entires and collects
695 similar ones to form a single entry for each dist */
696 bool ReduceSourcelist(string CD,vector<string> &List)
697 {
698 sort(List.begin(),List.end());
699
700 // Collect similar entries
701 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
702 {
703 // Find a space..
704 string::size_type Space = (*I).find(' ');
705 if (Space == string::npos)
706 continue;
707 string::size_type SSpace = (*I).find(' ',Space + 1);
708 if (SSpace == string::npos)
709 continue;
710
711 string Word1 = string(*I,Space,SSpace-Space);
712 for (vector<string>::iterator J = List.begin(); J != I; J++)
713 {
714 // Find a space..
715 string::size_type Space2 = (*J).find(' ');
716 if (Space2 == string::npos)
717 continue;
718 string::size_type SSpace2 = (*J).find(' ',Space2 + 1);
719 if (SSpace2 == string::npos)
720 continue;
721
722 if (string(*J,Space2,SSpace2-Space2) != Word1)
723 continue;
724
725 *J += string(*I,SSpace);
726 *I = string();
727 }
728 }
729
730 // Wipe erased entries
731 for (unsigned int I = 0; I < List.size();)
732 {
733 if (List[I].empty() == false)
734 I++;
735 else
736 List.erase(List.begin()+I);
737 }
738 }
739 /*}}}*/
740 // WriteDatabase - Write the CDROM Database file /*{{{*/
741 // ---------------------------------------------------------------------
742 /* We rewrite the configuration class associated with the cdrom database. */
743 bool WriteDatabase(Configuration &Cnf)
744 {
745 string DFile = _config->FindFile("Dir::State::cdroms");
746 string NewFile = DFile + ".new";
747
748 unlink(NewFile.c_str());
749 ofstream Out(NewFile.c_str());
750 if (!Out)
751 return _error->Errno("ofstream::ofstream",
752 "Failed to open %s.new",DFile.c_str());
753
754 /* Write out all of the configuration directives by walking the
755 configuration tree */
756 const Configuration::Item *Top = Cnf.Tree(0);
757 for (; Top != 0;)
758 {
759 // Print the config entry
760 if (Top->Value.empty() == false)
761 Out << Top->FullTag() + " \"" << Top->Value << "\";" << endl;
762
763 if (Top->Child != 0)
764 {
765 Top = Top->Child;
766 continue;
767 }
768
769 while (Top != 0 && Top->Next == 0)
770 Top = Top->Parent;
771 if (Top != 0)
772 Top = Top->Next;
773 }
774
775 Out.close();
776
777 rename(DFile.c_str(),string(DFile + '~').c_str());
778 if (rename(NewFile.c_str(),DFile.c_str()) != 0)
779 return _error->Errno("rename","Failed to rename %s.new to %s",
780 DFile.c_str(),DFile.c_str());
781
782 return true;
783 }
784 /*}}}*/
785 // WriteSourceList - Write an updated sourcelist /*{{{*/
786 // ---------------------------------------------------------------------
787 /* This reads the old source list and copies it into the new one. It
788 appends the new CDROM entires just after the first block of comments.
789 This places them first in the file. It also removes any old entries
790 that were the same. */
791 bool WriteSourceList(string Name,vector<string> &List)
792 {
793 string File = _config->FindFile("Dir::Etc::sourcelist");
794
795 // Open the stream for reading
796 ifstream F(File.c_str(),ios::in | ios::nocreate);
797 if (!F != 0)
798 return _error->Errno("ifstream::ifstream","Opening %s",File.c_str());
799
800 string NewFile = File + ".new";
801 unlink(NewFile.c_str());
802 ofstream Out(NewFile.c_str());
803 if (!Out)
804 return _error->Errno("ofstream::ofstream",
805 "Failed to open %s.new",File.c_str());
806
807 // Create a short uri without the path
808 string ShortURI = "cdrom:" + Name + "/";
809
810 char Buffer[300];
811 int CurLine = 0;
812 bool First = true;
813 while (F.eof() == false)
814 {
815 F.getline(Buffer,sizeof(Buffer));
816 CurLine++;
817 _strtabexpand(Buffer,sizeof(Buffer));
818 _strstrip(Buffer);
819
820 // Comment or blank
821 if (Buffer[0] == '#' || Buffer[0] == 0)
822 {
823 Out << Buffer << endl;
824 continue;
825 }
826
827 if (First == true)
828 {
829 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
830 {
831 string::size_type Space = (*I).find(' ');
832 if (Space == string::npos)
833 return _error->Error("Internal error");
834
835 Out << "deb \"cdrom:" << Name << "/" << string(*I,0,Space) <<
836 "\" " << string(*I,Space+1) << endl;
837 }
838 }
839 First = false;
840
841 // Grok it
842 string Type;
843 string URI;
844 char *C = Buffer;
845 if (ParseQuoteWord(C,Type) == false ||
846 ParseQuoteWord(C,URI) == false)
847 {
848 Out << Buffer << endl;
849 continue;
850 }
851
852 // Emit lines like this one
853 if (Type != "deb" || string(URI,0,ShortURI.length()) != ShortURI)
854 {
855 Out << Buffer << endl;
856 continue;
857 }
858 }
859
860 // Just in case the file was empty
861 if (First == true)
862 {
863 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
864 {
865 string::size_type Space = (*I).find(' ');
866 if (Space == string::npos)
867 return _error->Error("Internal error");
868
869 Out << "deb \"cdrom:" << Name << "/" << string(*I,0,Space) <<
870 "\" " << string(*I,Space+1) << endl;
871 }
872 }
873
874 Out.close();
875
876 rename(File.c_str(),string(File + '~').c_str());
877 if (rename(NewFile.c_str(),File.c_str()) != 0)
878 return _error->Errno("rename","Failed to rename %s.new to %s",
879 File.c_str(),File.c_str());
880
881 return true;
882 }
883 /*}}}*/
884
885 // Prompt - Simple prompt /*{{{*/
886 // ---------------------------------------------------------------------
887 /* */
888 void Prompt(const char *Text)
889 {
890 char C;
891 cout << Text << ' ' << flush;
892 read(STDIN_FILENO,&C,1);
893 if (C != '\n')
894 cout << endl;
895 }
896 /*}}}*/
897 // PromptLine - Prompt for an input line /*{{{*/
898 // ---------------------------------------------------------------------
899 /* */
900 string PromptLine(const char *Text)
901 {
902 cout << Text << ':' << endl;
903
904 string Res;
905 getline(cin,Res);
906 return Res;
907 }
908 /*}}}*/
909
910 // DoAdd - Add a new CDROM /*{{{*/
911 // ---------------------------------------------------------------------
912 /* This does the main add bit.. We show some status and things. The
913 sequence is to mount/umount the CD, Ident it then scan it for package
914 files and reduce that list. Then we copy over the package files and
915 verify them. Then rewrite the database files */
916 bool DoAdd(CommandLine &)
917 {
918 // Startup
919 string CDROM = _config->FindDir("Acquire::cdrom::mount","/cdrom/");
920 cout << "Using CD-ROM mount point " << CDROM << endl;
921
922 // Read the database
923 Configuration Database;
924 string DFile = _config->FindFile("Dir::State::cdroms");
925 if (FileExists(DFile) == true)
926 {
927 if (ReadConfigFile(Database,DFile) == false)
928 return _error->Error("Unable to read the cdrom database %s",
929 DFile.c_str());
930 }
931
932 // Unmount the CD and get the user to put in the one they want
933 if (_config->FindB("APT::CDROM::NoMount",false) == false)
934 {
935 cout << "Unmounting CD-ROM" << endl;
936 UnmountCdrom(CDROM);
937
938 // Mount the new CDROM
939 Prompt("Please insert a Disc in the drive and press any key");
940 cout << "Mounting CD-ROM" << endl;
941 if (MountCdrom(CDROM) == false)
942 {
943 cout << "Failed to mount the cdrom." << endl;
944 return false;
945 }
946 }
947
948 // Hash the CD to get an ID
949 cout << "Identifying.. " << flush;
950 string ID;
951 if (IdentCdrom(CDROM,ID) == false)
952 return false;
953 cout << '[' << ID << ']' << endl;
954
955 cout << "Scanning Disc for index files.. " << flush;
956 // Get the CD structure
957 vector<string> List;
958 string StartDir = SafeGetCWD();
959 if (FindPackages(CDROM,List) == false)
960 return false;
961 chdir(StartDir.c_str());
962
963 if (_config->FindB("Debug::aptcdrom",false) == true)
964 {
965 cout << "I found:" << endl;
966 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
967 {
968 cout << *I << endl;
969 }
970 }
971
972 // Fix up the list
973 DropBinaryArch(List);
974 DropRepeats(List);
975 cout << "Found " << List.size() << " package index files." << endl;
976
977 if (List.size() == 0)
978 return _error->Error("Unable to locate any package files, perhaps this is not a Debian Disc");
979
980 // Check if the CD is in the database
981 string Name;
982 if (Database.Exists("CD::" + ID) == false ||
983 _config->FindB("APT::CDROM::Rename",false) == true)
984 {
985 // Try to use the CDs label if at all possible
986 if (FileExists(CDROM + "/.disk/info") == true)
987 {
988 ifstream F(string(CDROM+ "/.disk/info").c_str());
989 if (!F == 0)
990 getline(F,Name);
991
992 if (Name.empty() == false)
993 {
994 cout << "Found label '" << Name << "'" << endl;
995 Database.Set("CD::" + ID + "::Label",Name);
996 }
997 }
998
999 if (_config->FindB("APT::CDROM::Rename",false) == true ||
1000 Name.empty() == true)
1001 {
1002 cout << "Please provide a name for this Disc, such as 'Debian 2.1r1 Disk 1'";
1003 while (1)
1004 {
1005 Name = PromptLine("");
1006 if (Name.empty() == false &&
1007 Name.find('/') == string::npos)
1008 break;
1009 cout << "That is not a valid name, try again " << endl;
1010 }
1011
1012 }
1013 }
1014 else
1015 Name = Database.Find("CD::" + ID);
1016 Database.Set("CD::" + ID,Name);
1017 cout << "This Disc is called '" << Name << "'" << endl;
1018
1019 // Copy the package files to the state directory
1020 if (CopyPackages(CDROM,Name,List) == false)
1021 return false;
1022
1023 ReduceSourcelist(CDROM,List);
1024
1025 // Write the database and sourcelist
1026 if (_config->FindB("APT::cdrom::NoAct",false) == false)
1027 {
1028 if (WriteDatabase(Database) == false)
1029 return false;
1030
1031 cout << "Writing new source list" << endl;
1032 if (WriteSourceList(Name,List) == false)
1033 return false;
1034 }
1035
1036 // Print the sourcelist entries
1037 cout << "Source List entires for this Disc are:" << endl;
1038 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
1039 {
1040 string::size_type Space = (*I).find(' ');
1041 if (Space == string::npos)
1042 return _error->Error("Internal error");
1043
1044 cout << "deb \"cdrom:" << Name << "/" << string(*I,0,Space) <<
1045 "\" " << string(*I,Space+1) << endl;
1046 }
1047
1048 return true;
1049 }
1050 /*}}}*/
1051
1052 // ShowHelp - Show the help screen /*{{{*/
1053 // ---------------------------------------------------------------------
1054 /* */
1055 int ShowHelp()
1056 {
1057 cout << PACKAGE << ' ' << VERSION << " for " << ARCHITECTURE <<
1058 " compiled on " << __DATE__ << " " << __TIME__ << endl;
1059
1060 cout << "Usage: apt-cdrom [options] command" << endl;
1061 cout << endl;
1062 cout << "apt-cdrom is a tool to add CDROM's to APT's source list. The " << endl;
1063 cout << "CDROM mount point and device information is taken from apt.conf" << endl;
1064 cout << "and /etc/fstab." << endl;
1065 cout << endl;
1066 cout << "Commands:" << endl;
1067 cout << " add - Add a CDROM" << endl;
1068 cout << endl;
1069 cout << "Options:" << endl;
1070 cout << " -h This help text" << endl;
1071 cout << " -d CD-ROM mount point" << endl;
1072 cout << " -r Rename a recognized CD-ROM" << endl;
1073 cout << " -m No mounting" << endl;
1074 cout << " -f Fast mode, don't check package files" << endl;
1075 cout << " -c=? Read this configuration file" << endl;
1076 cout << " -o=? Set an arbitary configuration option, ie -o dir::cache=/tmp" << endl;
1077 cout << "See fstab(5)" << endl;
1078 return 100;
1079 }
1080 /*}}}*/
1081
1082 int main(int argc,const char *argv[])
1083 {
1084 CommandLine::Args Args[] = {
1085 {'h',"help","help",0},
1086 {'d',"cdrom","Acquire::cdrom::mount",CommandLine::HasArg},
1087 {'r',"rename","APT::CDROM::Rename",0},
1088 {'m',"no-mount","APT::CDROM::NoMount",0},
1089 {'f',"fast","APT::CDROM::Fast",0},
1090 {'n',"just-print","APT::CDROM::NoAct",0},
1091 {'n',"recon","APT::CDROM::NoAct",0},
1092 {'n',"no-act","APT::CDROM::NoAct",0},
1093 {'c',"config-file",0,CommandLine::ConfigFile},
1094 {'o',"option",0,CommandLine::ArbItem},
1095 {0,0,0,0}};
1096 CommandLine::Dispatch Cmds[] = {
1097 {"add",&DoAdd},
1098 {0,0}};
1099
1100 // Parse the command line and initialize the package library
1101 CommandLine CmdL(Args,_config);
1102 if (pkgInitialize(*_config) == false ||
1103 CmdL.Parse(argc,argv) == false)
1104 {
1105 _error->DumpErrors();
1106 return 100;
1107 }
1108
1109 // See if the help should be shown
1110 if (_config->FindB("help") == true ||
1111 CmdL.FileSize() == 0)
1112 return ShowHelp();
1113
1114 // Match the operation
1115 CmdL.DispatchArg(Cmds);
1116
1117 // Print any errors or warnings found during parsing
1118 if (_error->empty() == false)
1119 {
1120 bool Errors = _error->PendingError();
1121 _error->DumpErrors();
1122 return Errors == true?100:0;
1123 }
1124
1125 return 0;
1126 }