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