- initial import of revision 374 from cnc
[apt.git] / cmdline / apt-cdrom.cc
1 // -*- mode: cpp; mode: fold -*-
2 // Description                                                          /*{{{*/
3 // $Id: apt-cdrom.cc,v 1.44 2003/09/12 01:48:33 mdz 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/fileutl.h>
18 #include <apt-pkg/progress.h>
19 #include <apt-pkg/cdromutl.h>
20 #include <apt-pkg/strutl.h>
21 #include <config.h>
22 #include <apti18n.h>
23     
24 // CNC:2002-07-11
25 #ifdef HAVE_RPM
26 #include "rpmindexcopy.h"
27 #else
28 #include "indexcopy.h"
29 #endif
30
31 // CNC:2003-02-14 - apti18n.h includes libintl.h which includes locale.h,
32 //                  as reported by Radu Greab.
33 //#include <locale.h>
34 #include <iostream>
35 #include <fstream>
36 #include <vector>
37 #include <algorithm>
38 #include <sys/stat.h>
39 #include <fcntl.h>
40 #include <dirent.h>
41 #include <unistd.h>
42 #include <stdio.h>
43                                                                         /*}}}*/
44
45 using namespace std;
46
47 // FindPackages - Find the package files on the CDROM                   /*{{{*/
48 // ---------------------------------------------------------------------
49 /* We look over the cdrom for package files. This is a recursive
50    search that short circuits when it his a package file in the dir.
51    This speeds it up greatly as the majority of the size is in the
52    binary-* sub dirs. */
53 bool FindPackages(string CD,vector<string> &List,vector<string> &SList,
54                   string &InfoDir,unsigned int Depth = 0)
55 {
56    static ino_t Inodes[9];
57    if (Depth >= 7)
58       return true;
59
60    if (CD[CD.length()-1] != '/')
61       CD += '/';   
62
63    if (chdir(CD.c_str()) != 0)
64       return _error->Errno("chdir","Unable to change to %s",CD.c_str());
65
66    // Look for a .disk subdirectory
67    struct stat Buf;
68    if (stat(".disk",&Buf) == 0)
69    {
70       if (InfoDir.empty() == true)
71          InfoDir = CD + ".disk/";
72    }
73
74    // Don't look into directories that have been marked to ingore.
75    if (stat(".aptignr",&Buf) == 0)
76       return true;
77    
78 // CNC:2002-07-11
79 #ifdef HAVE_RPM
80    bool Found = false;
81    if (stat("release",&Buf) == 0)
82       Found = true;
83 #else
84    /* Aha! We found some package files. We assume that everything under 
85       this dir is controlled by those package files so we don't look down
86       anymore */
87    if (stat("Packages",&Buf) == 0 || stat("Packages.gz",&Buf) == 0)
88    {
89       List.push_back(CD);
90       
91       // Continue down if thorough is given
92       if (_config->FindB("APT::CDROM::Thorough",false) == false)
93          return true;
94    }
95    if (stat("Sources.gz",&Buf) == 0 || stat("Sources",&Buf) == 0)
96    {
97       SList.push_back(CD);
98       
99       // Continue down if thorough is given
100       if (_config->FindB("APT::CDROM::Thorough",false) == false)
101          return true;
102    }
103 #endif
104    
105    DIR *D = opendir(".");
106    if (D == 0)
107       return _error->Errno("opendir","Unable to read %s",CD.c_str());
108    
109    // Run over the directory
110    for (struct dirent *Dir = readdir(D); Dir != 0; Dir = readdir(D))
111    {
112       // Skip some files..
113       if (strcmp(Dir->d_name,".") == 0 ||
114           strcmp(Dir->d_name,"..") == 0 ||
115           //strcmp(Dir->d_name,"source") == 0 ||
116           strcmp(Dir->d_name,".disk") == 0 ||
117 // CNC:2002-07-11
118 #ifdef HAVE_RPM
119           strncmp(Dir->d_name,"RPMS",4) == 0 ||
120           strncmp(Dir->d_name,"doc",3) == 0)
121 #else
122           strcmp(Dir->d_name,"experimental") == 0 ||
123           strcmp(Dir->d_name,"binary-all") == 0 ||
124           strcmp(Dir->d_name,"debian-installer") == 0)
125 #endif
126          continue;
127
128 // CNC:2002-07-11
129 #ifdef HAVE_RPM
130       if (strncmp(Dir->d_name, "pkglist.", 8) == 0 &&
131           strcmp(Dir->d_name+strlen(Dir->d_name)-4, ".bz2") == 0)
132       {
133          List.push_back(CD + string(Dir->d_name));
134          Found = true;
135          continue;
136       }
137       if (strncmp(Dir->d_name, "srclist.", 8) == 0 &&
138           strcmp(Dir->d_name+strlen(Dir->d_name)-4, ".bz2") == 0)
139       {
140          SList.push_back(CD + string(Dir->d_name));
141          Found = true;
142          continue;
143       }
144       if (_config->FindB("APT::CDROM::Thorough",false) == false &&
145           Found == true)
146          continue;
147 #endif
148
149       // See if the name is a sub directory
150       struct stat Buf;
151       if (stat(Dir->d_name,&Buf) != 0)
152          continue;      
153       
154       if (S_ISDIR(Buf.st_mode) == 0)
155          continue;
156       
157       unsigned int I;
158       for (I = 0; I != Depth; I++)
159          if (Inodes[I] == Buf.st_ino)
160             break;
161       if (I != Depth)
162          continue;
163       
164       // Store the inodes weve seen
165       Inodes[Depth] = Buf.st_ino;
166
167       // Descend
168       if (FindPackages(CD + Dir->d_name,List,SList,InfoDir,Depth+1) == false)
169          break;
170
171       if (chdir(CD.c_str()) != 0)
172          return _error->Errno("chdir","Unable to change to %s",CD.c_str());
173    };
174
175    closedir(D);
176    
177    return !_error->PendingError();
178 }
179                                                                         /*}}}*/
180 // DropBinaryArch - Dump dirs with a string like /binary-<foo>/         /*{{{*/
181 // ---------------------------------------------------------------------
182 /* Here we drop everything that is not this machines arch */
183 bool DropBinaryArch(vector<string> &List)
184 {
185    char S[300];
186    snprintf(S,sizeof(S),"/binary-%s/",
187             _config->Find("Apt::Architecture").c_str());
188    
189    for (unsigned int I = 0; I < List.size(); I++)
190    {
191       const char *Str = List[I].c_str();
192       
193       const char *Res;
194       if ((Res = strstr(Str,"/binary-")) == 0)
195          continue;
196
197       // Weird, remove it.
198       if (strlen(Res) < strlen(S))
199       {
200          List.erase(List.begin() + I);
201          I--;
202          continue;
203       }
204           
205       // See if it is our arch
206       if (stringcmp(Res,Res + strlen(S),S) == 0)
207          continue;
208       
209       // Erase it
210       List.erase(List.begin() + I);
211       I--;
212    }
213    
214    return true;
215 }
216                                                                         /*}}}*/
217 // Score - We compute a 'score' for a path                              /*{{{*/
218 // ---------------------------------------------------------------------
219 /* Paths are scored based on how close they come to what I consider
220    normal. That is ones that have 'dist' 'stable' 'testing' will score
221    higher than ones without. */
222 int Score(string Path)
223 {
224    int Res = 0;
225 #ifdef HAVE_RPM
226    if (Path.find("base/") != string::npos)
227       Res = 1;
228 #else
229    if (Path.find("stable/") != string::npos)
230       Res += 29;
231    if (Path.find("/binary-") != string::npos)
232       Res += 20;
233    if (Path.find("testing/") != string::npos)
234       Res += 28;
235    if (Path.find("unstable/") != string::npos)
236       Res += 27;
237    if (Path.find("/dists/") != string::npos)
238       Res += 40;
239    if (Path.find("/main/") != string::npos)
240       Res += 20;
241    if (Path.find("/contrib/") != string::npos)
242       Res += 20;
243    if (Path.find("/non-free/") != string::npos)
244       Res += 20;
245    if (Path.find("/non-US/") != string::npos)
246       Res += 20;
247    if (Path.find("/source/") != string::npos)
248       Res += 10;
249    if (Path.find("/debian/") != string::npos)
250       Res -= 10;
251 #endif
252    return Res;
253 }
254                                                                         /*}}}*/
255 // DropRepeats - Drop repeated files resulting from symlinks            /*{{{*/
256 // ---------------------------------------------------------------------
257 /* Here we go and stat every file that we found and strip dup inodes. */
258 bool DropRepeats(vector<string> &List,const char *Name)
259 {
260    // Get a list of all the inodes
261    ino_t *Inodes = new ino_t[List.size()];
262    for (unsigned int I = 0; I != List.size(); I++)
263    {
264       struct stat Buf;
265       // CNC:2003-02-14
266       if (stat((List[I]).c_str(),&Buf) != 0 &&
267           stat((List[I] + Name).c_str(),&Buf) != 0 &&
268           stat((List[I] + Name + ".gz").c_str(),&Buf) != 0)
269          _error->Errno("stat","Failed to stat %s%s",List[I].c_str(),
270                        Name);
271       Inodes[I] = Buf.st_ino;
272    }
273    
274    if (_error->PendingError() == true)
275       return false;
276    
277    // Look for dups
278    for (unsigned int I = 0; I != List.size(); I++)
279    {
280       for (unsigned int J = I+1; J < List.size(); J++)
281       {
282          // No match
283          if (Inodes[J] != Inodes[I])
284             continue;
285          
286          // We score the two paths.. and erase one
287          int ScoreA = Score(List[I]);
288          int ScoreB = Score(List[J]);
289          if (ScoreA < ScoreB)
290          {
291             List[I] = string();
292             break;
293          }
294          
295          List[J] = string();
296       }
297    }  
298  
299    // Wipe erased entries
300    for (unsigned int I = 0; I < List.size();)
301    {
302       if (List[I].empty() == false)
303          I++;
304       else
305          List.erase(List.begin()+I);
306    }
307    
308    return true;
309 }
310                                                                         /*}}}*/
311
312 // ReduceSourceList - Takes the path list and reduces it                /*{{{*/
313 // ---------------------------------------------------------------------
314 /* This takes the list of source list expressed entires and collects
315    similar ones to form a single entry for each dist */
316 void ReduceSourcelist(string CD,vector<string> &List)
317 {
318    sort(List.begin(),List.end());
319    
320    // Collect similar entries
321    for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
322    {
323       // Find a space..
324       string::size_type Space = (*I).find(' ');
325       if (Space == string::npos)
326          continue;
327       string::size_type SSpace = (*I).find(' ',Space + 1);
328       if (SSpace == string::npos)
329          continue;
330
331       string Word1 = string(*I,Space,SSpace-Space);
332       string Prefix = string(*I,0,Space);
333       for (vector<string>::iterator J = List.begin(); J != I; J++)
334       {
335          // Find a space..
336          string::size_type Space2 = (*J).find(' ');
337          if (Space2 == string::npos)
338             continue;
339          string::size_type SSpace2 = (*J).find(' ',Space2 + 1);
340          if (SSpace2 == string::npos)
341             continue;
342          
343          if (string(*J,0,Space2) != Prefix)
344             continue;
345          if (string(*J,Space2,SSpace2-Space2) != Word1)
346             continue;
347          
348          *J += string(*I,SSpace);
349          *I = string();
350       }
351    }   
352
353    // Wipe erased entries
354    for (unsigned int I = 0; I < List.size();)
355    {
356       if (List[I].empty() == false)
357          I++;
358       else
359          List.erase(List.begin()+I);
360    }
361 }
362                                                                         /*}}}*/
363 // WriteDatabase - Write the CDROM Database file                        /*{{{*/
364 // ---------------------------------------------------------------------
365 /* We rewrite the configuration class associated with the cdrom database. */
366 bool WriteDatabase(Configuration &Cnf)
367 {
368    string DFile = _config->FindFile("Dir::State::cdroms");
369    string NewFile = DFile + ".new";
370    
371    unlink(NewFile.c_str());
372    ofstream Out(NewFile.c_str());
373    if (!Out)
374       return _error->Errno("ofstream::ofstream",
375                            "Failed to open %s.new",DFile.c_str());
376    
377    /* Write out all of the configuration directives by walking the
378       configuration tree */
379    const Configuration::Item *Top = Cnf.Tree(0);
380    for (; Top != 0;)
381    {
382       // Print the config entry
383       if (Top->Value.empty() == false)
384          Out <<  Top->FullTag() + " \"" << Top->Value << "\";" << endl;
385       
386       if (Top->Child != 0)
387       {
388          Top = Top->Child;
389          continue;
390       }
391       
392       while (Top != 0 && Top->Next == 0)
393          Top = Top->Parent;
394       if (Top != 0)
395          Top = Top->Next;
396    }   
397
398    Out.close();
399    
400    rename(DFile.c_str(),string(DFile + '~').c_str());
401    if (rename(NewFile.c_str(),DFile.c_str()) != 0)
402       return _error->Errno("rename",_("Failed to rename %s.new to %s"),
403                            DFile.c_str(),DFile.c_str());
404
405    return true;
406 }
407                                                                         /*}}}*/
408 // WriteSourceList - Write an updated sourcelist                        /*{{{*/
409 // ---------------------------------------------------------------------
410 /* This reads the old source list and copies it into the new one. It 
411    appends the new CDROM entires just after the first block of comments.
412    This places them first in the file. It also removes any old entries
413    that were the same. */
414 bool WriteSourceList(string Name,vector<string> &List,bool Source)
415 {
416    if (List.size() == 0)
417       return true;
418
419    string File = _config->FindFile("Dir::Etc::sourcelist");
420
421    // Open the stream for reading
422    ifstream F((FileExists(File)?File.c_str():"/dev/null"),
423               ios::in );
424    if (!F != 0)
425       return _error->Errno("ifstream::ifstream",_("Opening %s"),File.c_str());
426
427    string NewFile = File + ".new";
428    unlink(NewFile.c_str());
429    ofstream Out(NewFile.c_str());
430    if (!Out)
431       return _error->Errno("ofstream::ofstream",
432                            _("Failed to open %s.new"),File.c_str());
433
434    // Create a short uri without the path
435    string ShortURI = "cdrom:[" + Name + "]/";   
436    string ShortURI2 = "cdrom:" + Name + "/";     // For Compatibility
437
438    const char *Type;
439 // CNC:2002-07-11
440 #ifdef HAVE_RPM
441    if (Source == true)
442       Type = "rpm-src";
443    else
444       Type = "rpm";
445 #else
446    if (Source == true)
447       Type = "deb-src";
448    else
449       Type = "deb";
450 #endif
451    
452    char Buffer[300];
453    int CurLine = 0;
454    bool First = true;
455    while (F.eof() == false)
456    {      
457       F.getline(Buffer,sizeof(Buffer));
458       CurLine++;
459       _strtabexpand(Buffer,sizeof(Buffer));
460       _strstrip(Buffer);
461             
462       // Comment or blank
463       if (Buffer[0] == '#' || Buffer[0] == 0)
464       {
465          Out << Buffer << endl;
466          continue;
467       }
468
469       if (First == true)
470       {
471          for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
472          {
473             string::size_type Space = (*I).find(' ');
474             if (Space == string::npos)
475                return _error->Error("Internal error");
476             Out << Type << " cdrom:[" << Name << "]/" << string(*I,0,Space) <<
477                " " << string(*I,Space+1) << endl;
478          }
479       }
480       First = false;
481       
482       // Grok it
483       string cType;
484       string URI;
485       const char *C = Buffer;
486       if (ParseQuoteWord(C,cType) == false ||
487           ParseQuoteWord(C,URI) == false)
488       {
489          Out << Buffer << endl;
490          continue;
491       }
492
493       // Emit lines like this one
494       if (cType != Type || (string(URI,0,ShortURI.length()) != ShortURI &&
495           string(URI,0,ShortURI.length()) != ShortURI2))
496       {
497          Out << Buffer << endl;
498          continue;
499       }      
500    }
501    
502    // Just in case the file was empty
503    if (First == true)
504    {
505       for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
506       {
507          string::size_type Space = (*I).find(' ');
508          if (Space == string::npos)
509             return _error->Error("Internal error");
510          
511 // CNC:2002-07-11
512 #ifdef HAVE_RPM
513          Out << "rpm cdrom:[" << Name << "]/" << string(*I,0,Space) << 
514             " " << string(*I,Space+1) << endl;
515 #else
516          Out << "deb cdrom:[" << Name << "]/" << string(*I,0,Space) << 
517             " " << string(*I,Space+1) << endl;
518 #endif
519       }
520    }
521    
522    Out.close();
523
524    rename(File.c_str(),string(File + '~').c_str());
525    if (rename(NewFile.c_str(),File.c_str()) != 0)
526       return _error->Errno("rename",_("Failed to rename %s.new to %s"),
527                            File.c_str(),File.c_str());
528    
529    return true;
530 }
531                                                                         /*}}}*/
532
533 // Prompt - Simple prompt                                               /*{{{*/
534 // ---------------------------------------------------------------------
535 /* */
536 void Prompt(const char *Text)
537 {
538    char C;
539    cout << Text << ' ' << flush;
540    read(STDIN_FILENO,&C,1);
541    if (C != '\n')
542       cout << endl;
543 }
544                                                                         /*}}}*/
545 // PromptLine - Prompt for an input line                                /*{{{*/
546 // ---------------------------------------------------------------------
547 /* */
548 string PromptLine(const char *Text)
549 {
550    cout << Text << ':' << endl;
551    
552    string Res;
553    getline(cin,Res);
554    return Res;
555 }
556                                                                         /*}}}*/
557
558 // DoAdd - Add a new CDROM                                              /*{{{*/
559 // ---------------------------------------------------------------------
560 /* This does the main add bit.. We show some status and things. The
561    sequence is to mount/umount the CD, Ident it then scan it for package 
562    files and reduce that list. Then we copy over the package files and
563    verify them. Then rewrite the database files */
564 bool DoAdd(CommandLine &)
565 {
566    // Startup
567    string CDROM = _config->FindDir("Acquire::cdrom::mount","/cdrom/");
568    if (CDROM[0] == '.')
569       CDROM= SafeGetCWD() + '/' + CDROM;
570    
571    cout << _("Using CD-ROM mount point ") << CDROM << endl;
572       
573    // Read the database
574    Configuration Database;
575    string DFile = _config->FindFile("Dir::State::cdroms");
576    if (FileExists(DFile) == true)
577    {
578       if (ReadConfigFile(Database,DFile) == false)
579          return _error->Error(_("Unable to read the cdrom database %s"),
580                               DFile.c_str());
581    }
582
583    // CNC:2002-10-29
584    bool PreFetch = false;
585    string PreFetchDir = _config->FindDir("Dir::State::prefetch");
586    string ID = _config->Find("APT::CDROM::ID");
587    if (ID.empty() == false && FileExists(PreFetchDir+"/"+ID))
588       PreFetch = true;
589    
590    // Unmount the CD and get the user to put in the one they want
591    // CNC:2002-10-29
592    bool Mounted = false;
593    if (PreFetch == false && _config->FindB("APT::CDROM::NoMount",false) == false)
594    {
595       Mounted = true;
596       cout << _("Unmounting CD-ROM") << endl;
597       UnmountCdrom(CDROM);
598
599       // Mount the new CDROM
600       Prompt(_("Please insert a Disc in the drive and press enter"));
601       cout << _("Mounting CD-ROM") << endl;
602       if (MountCdrom(CDROM) == false)
603          return _error->Error(_("Failed to mount the cdrom."));
604    }
605    
606    // Hash the CD to get an ID
607    cout << _("Identifying.. ") << flush;
608    // CNC:2002-10-29
609    // string ID;
610    if (ID.empty() == true && IdentCdrom(CDROM,ID) == false)
611    {
612       cout << endl;
613       return false;
614    }
615
616    // CNC:2002-10-29
617    if (PreFetch == false && FileExists(PreFetchDir+"/"+ID))
618       PreFetch = true;
619    string ScanDir = CDROM;
620    if (PreFetch == true)
621       ScanDir = PreFetchDir+"/"+ID;
622    if (ScanDir[ScanDir.length()-1] != '/')
623       ScanDir += '/';
624
625    cout << '[' << ID << ']' << endl;
626
627    cout << _("Scanning Disc for index files..  ") << flush;
628    // Get the CD structure
629    vector<string> List;
630    vector<string> sList;
631    string StartDir = SafeGetCWD();
632    string InfoDir;
633    // CNC:2002-10-29
634    if (FindPackages(ScanDir,List,sList,InfoDir) == false)
635    {
636       cout << endl;
637       return false;
638    }
639    
640    chdir(StartDir.c_str());
641
642    if (_config->FindB("Debug::aptcdrom",false) == true)
643    {
644       cout << _("I found (binary):") << endl;
645       for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
646          cout << *I << endl;
647       cout << _("I found (source):") << endl;
648       for (vector<string>::iterator I = sList.begin(); I != sList.end(); I++)
649          cout << *I << endl;
650    }   
651    
652    // Fix up the list
653 // CNC:2002-07-11
654 #ifdef HAVE_RPM
655    DropRepeats(List,"pkglist");
656    DropRepeats(sList,"srclist");
657 #else
658    DropBinaryArch(List);
659    DropRepeats(List,"Packages");
660    DropRepeats(sList,"Sources");
661 #endif
662    cout << _("Found ") << List.size() << _(" package indexes and ") << sList.size() << 
663       _(" source indexes.") << endl;
664
665    // CNC:2002-07-11
666    if (List.size() == 0 && sList.size() == 0)
667    {
668         if (Mounted && _config->FindB("APT::CDROM::NoMount",false) == false)
669              UnmountCdrom(CDROM);
670         return _error->Error(_("Unable to locate any package files, perhaps this is not an APT enabled disc"));
671    
672    }
673    // Check if the CD is in the database
674    string Name;
675    if (Database.Exists("CD::" + ID) == false ||
676        _config->FindB("APT::CDROM::Rename",false) == true)
677    {
678       // Try to use the CDs label if at all possible
679       if (InfoDir.empty() == false &&
680           FileExists(InfoDir + "/info") == true)
681       {
682          ifstream F(string(InfoDir + "/info").c_str());
683          if (!F == 0)
684             getline(F,Name);
685
686          if (Name.empty() == false)
687          {
688             // Escape special characters
689             string::iterator J = Name.begin();
690             for (; J != Name.end(); J++)
691                if (*J == '"' || *J == ']' || *J == '[')
692                   *J = '_';
693             
694             cout << _("Found label '") << Name << "'" << endl;
695             Database.Set("CD::" + ID + "::Label",Name);
696          }       
697       }
698       
699       if (_config->FindB("APT::CDROM::Rename",false) == true ||
700           Name.empty() == true)
701       {
702          // CNC:2003-11-25
703          cout << _("Please provide a name for this Disc, such as 'Distribution Disk 1'");
704          while (1)
705          {
706             Name = PromptLine("");
707             if (Name.empty() == false &&
708                 Name.find('"') == string::npos &&
709                 Name.find('[') == string::npos &&
710                 Name.find(']') == string::npos)
711                break;
712             cout << _("That is not a valid name, try again ") << endl;
713          }       
714       }      
715    }
716    else
717       Name = Database.Find("CD::" + ID);
718
719    // Escape special characters
720    string::iterator J = Name.begin();
721    for (; J != Name.end(); J++)
722       if (*J == '"' || *J == ']' || *J == '[')
723          *J = '_';
724    
725    Database.Set("CD::" + ID,Name);
726    cout << _("This Disc is called:") << endl << " '" << Name << "'" << endl;
727    
728    // Copy the package files to the state directory
729 // CNC:2002-07-11
730 #ifdef HAVE_RPM
731    RPMPackageCopy Copy;
732    RPMSourceCopy SrcCopy;
733 #else
734    PackageCopy Copy;
735    SourceCopy SrcCopy;
736 #endif
737    // CNC:2002-10-29
738    if (Copy.CopyPackages(ScanDir,Name,List) == false ||
739        SrcCopy.CopyPackages(ScanDir,Name,sList) == false)
740       return false;
741    
742    // CNC:2002-10-29
743    ReduceSourcelist(ScanDir,List);
744    ReduceSourcelist(ScanDir,sList);
745
746    // Write the database and sourcelist
747    if (_config->FindB("APT::cdrom::NoAct",false) == false)
748    {
749       if (WriteDatabase(Database) == false)
750          return false;
751       
752       cout << "Writing new source list" << endl;
753       if (WriteSourceList(Name,List,false) == false ||
754           WriteSourceList(Name,sList,true) == false)
755          return false;
756    }
757
758    // Print the sourcelist entries
759    cout << _("Source List entries for this Disc are:") << endl;
760    for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
761    {
762       string::size_type Space = (*I).find(' ');
763       if (Space == string::npos)
764          return _error->Error("Internal error");
765
766 // CNC:2002-07-11
767 #ifdef HAVE_RPM
768       cout << "rpm cdrom:[" << Name << "]/" << string(*I,0,Space) << 
769          " " << string(*I,Space+1) << endl;
770 #else
771       cout << "deb cdrom:[" << Name << "]/" << string(*I,0,Space) << 
772          " " << string(*I,Space+1) << endl;
773 #endif
774    }
775
776    for (vector<string>::iterator I = sList.begin(); I != sList.end(); I++)
777    {
778       string::size_type Space = (*I).find(' ');
779       if (Space == string::npos)
780          return _error->Error("Internal error");
781
782 // CNC:2002-07-11
783 #ifdef HAVE_RPM
784       cout << "rpm-src cdrom:[" << Name << "]/" << string(*I,0,Space) << 
785          " " << string(*I,Space+1) << endl;
786 #else
787       cout << "deb-src cdrom:[" << Name << "]/" << string(*I,0,Space) << 
788          " " << string(*I,Space+1) << endl;
789 #endif
790    }
791
792    cout << _("Repeat this process for the rest of the CDs in your set.") << endl;
793
794    // Unmount and finish
795    // CNC:2002-10-29
796    if (Mounted == true)
797       UnmountCdrom(CDROM);
798    
799    return true;
800 }
801                                                                         /*}}}*/
802 // DoIdent - Ident a CDROM                                              /*{{{*/
803 // ---------------------------------------------------------------------
804 /* */
805 bool DoIdent(CommandLine &)
806 {
807    // Startup
808    string CDROM = _config->FindDir("Acquire::cdrom::mount","/cdrom/");
809    if (CDROM[0] == '.')
810       CDROM= SafeGetCWD() + '/' + CDROM;
811    
812    cout << _("Using CD-ROM mount point ") << CDROM << endl;
813    cout << _("Mounting CD-ROM") << endl;
814    if (MountCdrom(CDROM) == false)
815       return _error->Error(_("Failed to mount the cdrom."));
816    
817    // Hash the CD to get an ID
818    cout << _("Identifying.. ") << flush;
819    string ID;
820    if (IdentCdrom(CDROM,ID) == false)
821    {
822       cout << endl;
823       return false;
824    }
825    
826    cout << '[' << ID << ']' << endl;
827
828    // Read the database
829    Configuration Database;
830    string DFile = _config->FindFile("Dir::State::cdroms");
831    if (FileExists(DFile) == true)
832    {
833       if (ReadConfigFile(Database,DFile) == false)
834          return _error->Error(_("Unable to read the cdrom database %s"),
835                               DFile.c_str());
836    }
837    cout << _("Stored Label: '") << Database.Find("CD::" + ID) << "'" << endl;
838    return true;
839 }
840                                                                         /*}}}*/
841
842 // ShowHelp - Show the help screen                                      /*{{{*/
843 // ---------------------------------------------------------------------
844 /* */
845 int ShowHelp()
846 {
847    ioprintf(cout,_("%s %s for %s %s compiled on %s %s\n"),PACKAGE,VERSION,
848             COMMON_OS,COMMON_CPU,__DATE__,__TIME__);
849    if (_config->FindB("version") == true)
850       return 0;
851    
852    cout << 
853     _("Usage: apt-cdrom [options] command\n"
854       "\n"
855       "apt-cdrom is a tool to add CDROM's to APT's source list. The\n"
856       "CDROM mount point and device information is taken from apt.conf\n"
857       "and /etc/fstab.\n"
858       "\n"
859       "Commands:\n"
860       "   add - Add a CDROM\n"
861       "   ident - Report the identity of a CDROM\n"
862       "\n"
863       "Options:\n"
864       "  -h   This help text\n"
865       "  -d   CD-ROM mount point\n"
866       "  -r   Rename a recognized CD-ROM\n"
867       "  -m   No mounting\n"
868       "  -f   Fast mode, don't check package files\n"
869       "  -a   Thorough scan mode\n"
870       "  -c=? Read this configuration file\n"
871       "  -o=? Set an arbitary configuration option, eg -o dir::cache=/tmp\n"
872       "See fstab(5)\n");
873    return 0;
874 }
875                                                                         /*}}}*/
876
877 int main(int argc,const char *argv[])
878 {
879    CommandLine::Args Args[] = {
880       {'h',"help","help",0},
881       {'v',"version","version",0},
882       {'d',"cdrom","Acquire::cdrom::mount",CommandLine::HasArg},
883       {'r',"rename","APT::CDROM::Rename",0},
884       {'m',"no-mount","APT::CDROM::NoMount",0},
885       {'f',"fast","APT::CDROM::Fast",0},
886       {'n',"just-print","APT::CDROM::NoAct",0},
887       {'n',"recon","APT::CDROM::NoAct",0},      
888       {'n',"no-act","APT::CDROM::NoAct",0},
889       {'a',"thorough","APT::CDROM::Thorough",0},
890       {'c',"config-file",0,CommandLine::ConfigFile},
891       {'o',"option",0,CommandLine::ArbItem},
892       {0,0,0,0}};
893    CommandLine::Dispatch Cmds[] = {
894       {"add",&DoAdd},
895       {"ident",&DoIdent},
896       {0,0}};
897
898    // Set up gettext support
899    setlocale(LC_ALL,"");
900    textdomain(PACKAGE);
901
902    // Parse the command line and initialize the package library
903    CommandLine CmdL(Args,_config);
904    if (pkgInitConfig(*_config) == false ||
905        CmdL.Parse(argc,argv) == false ||
906        pkgInitSystem(*_config,_system) == false)
907    {
908       _error->DumpErrors();
909       return 100;
910    }
911
912    // See if the help should be shown
913    if (_config->FindB("help") == true || _config->FindB("version") == true ||
914        CmdL.FileSize() == 0)
915       return ShowHelp();
916
917    // Deal with stdout not being a tty
918    if (ttyname(STDOUT_FILENO) == 0 && _config->FindI("quiet",0) < 1)
919       _config->Set("quiet","1");
920    
921    // Match the operation
922    CmdL.DispatchArg(Cmds);
923
924    // Print any errors or warnings found during parsing
925    if (_error->empty() == false)
926    {
927       bool Errors = _error->PendingError();
928       _error->DumpErrors();
929       return Errors == true?100:0;
930    }
931    
932    return 0;
933 }
934
935 // vim:sw=3:sts=3