]> git.saurik.com Git - apt.git/blob - cmdline/apt-cdrom.cc
Fixed extraneous showing of [working]
[apt.git] / cmdline / apt-cdrom.cc
1 // -*- mode: cpp; mode: fold -*-
2 // Description /*{{{*/
3 // $Id: apt-cdrom.cc,v 1.5 1998/11/28 08:39:46 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 if (Path.empty() == true)
353 Path = "/";
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;
362
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);
369
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);
375
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);
381
382 if (Binary != S)
383 return;
384
385 Path = Dist + ' ' + Comp;
386 }
387 /*}}}*/
388 // GrabFirst - Return the first Depth path components /*{{{*/
389 // ---------------------------------------------------------------------
390 /* */
391 bool 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;
403
404 To = string(Path,0,I+1);
405 return true;
406 }
407 /*}}}*/
408 // ChopDirs - Chop off the leading directory components /*{{{*/
409 // ---------------------------------------------------------------------
410 /* */
411 string 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 */
431 bool 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 */
462 bool 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
487 // CopyPackages - Copy the package files from the CD /*{{{*/
488 // ---------------------------------------------------------------------
489 /* */
490 bool CopyPackages(string CDROM,string Name,vector<string> &List)
491 {
492 OpTextProgress Progress;
493
494 bool NoStat = _config->FindB("APT::CDROM::Fast",false);
495 bool Debug = _config->FindB("Debug::aptcdrom",false);
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 }
507
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());
515
516 // Open the package file
517 FileFd Pkg(*I + "Packages",FileFd::ReadOnly);
518 pkgTagFile Parser(Pkg);
519 if (_error->PendingError() == true)
520 return false;
521
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;
532
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;
542 unsigned long Chop = 0;
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
552 if (Chop != 0)
553 File = OrigPath + ChopDirs(File,Chop);
554
555 // See if the file exists
556 if (NoStat == false || Hits < 10)
557 {
558 // Attempt to fix broken structure
559 if (Hits == 0)
560 {
561 if (ReconstructPrefix(Prefix,OrigPath,CDROM,File) == false &&
562 ReconstructChop(Chop,*I,File) == false)
563 {
564 NotFound++;
565 continue;
566 }
567 if (Chop != 0)
568 File = OrigPath + ChopDirs(File,Chop);
569 }
570
571 // Get the size
572 struct stat Buf;
573 if (stat(string(CDROM + Prefix + File).c_str(),&Buf) != 0)
574 {
575 NotFound++;
576 continue;
577 }
578
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;
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 }
617 if (Target.Write("\n",1) == false)
618 return false;
619 }
620 else
621 {
622 Section.GetSection(Start,Stop);
623 if (Target.Write(Start,Stop-Start) == false)
624 return false;
625 }
626 }
627
628 if (Debug == true)
629 cout << " Processed by using Prefix '" << Prefix << "' and chop " << Chop << endl;
630
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 */
699 bool ReduceSourcelist(string CD,vector<string> &List)
700 {
701 sort(List.begin(),List.end());
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;
710 string::size_type SSpace = (*I).find(' ',Space + 1);
711 if (SSpace == string::npos)
712 continue;
713
714 string Word1 = string(*I,Space,SSpace-Space);
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;
721 string::size_type SSpace2 = (*J).find(' ',Space2 + 1);
722 if (SSpace2 == string::npos)
723 continue;
724
725 if (string(*J,Space2,SSpace2-Space2) != Word1)
726 continue;
727
728 *J += string(*I,SSpace);
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 /*}}}*/
743 // WriteDatabase - Write the CDROM Database file /*{{{*/
744 // ---------------------------------------------------------------------
745 /* We rewrite the configuration class associated with the cdrom database. */
746 bool WriteDatabase(Configuration &Cnf)
747 {
748 string DFile = _config->FindFile("Dir::State::cdroms");
749 string NewFile = DFile + ".new";
750
751 unlink(NewFile.c_str());
752 ofstream Out(NewFile.c_str());
753 if (!Out)
754 return _error->Errno("ofstream::ofstream",
755 "Failed to open %s.new",DFile.c_str());
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());
781 if (rename(NewFile.c_str(),DFile.c_str()) != 0)
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 // ---------------------------------------------------------------------
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. */
794 bool WriteSourceList(string Name,vector<string> &List)
795 {
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
884 return true;
885 }
886 /*}}}*/
887
888 // Prompt - Simple prompt /*{{{*/
889 // ---------------------------------------------------------------------
890 /* */
891 void 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 /* */
903 string 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 // ---------------------------------------------------------------------
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 */
919 bool 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);
940
941 // Mount the new CDROM
942 Prompt("Please insert a Disc in the drive and press any key");
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
952 cout << "Identifying.. " << flush;
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());
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 }
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)
981 return _error->Error("Unable to locate any package files, perhaps this is not a Debian Disc");
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 {
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 ||
1003 Name.empty() == true)
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 }
1016 }
1017 else
1018 Name = Database.Find("CD::" + ID);
1019 Database.Set("CD::" + ID,Name);
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
1026 ReduceSourcelist(CDROM,List);
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;
1033
1034 cout << "Writing new source list" << endl;
1035 if (WriteSourceList(Name,List) == false)
1036 return false;
1037 }
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
1051 return true;
1052 }
1053 /*}}}*/
1054
1055 // ShowHelp - Show the help screen /*{{{*/
1056 // ---------------------------------------------------------------------
1057 /* */
1058 int 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;
1077 cout << " -f Fast mode, don't check package files" << endl;
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
1085 int 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},
1093 {'n',"just-print","APT::CDROM::NoAct",0},
1094 {'n',"recon","APT::CDROM::NoAct",0},
1095 {'n',"no-act","APT::CDROM::NoAct",0},
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 }