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