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