New changes
[ntk/apt.git] / cmdline / apt-cdrom.cc
CommitLineData
83d89a9f
AL
1// -*- mode: cpp; mode: fold -*-
2// Description /*{{{*/
18444708 3// $Id: apt-cdrom.cc,v 1.2 1998/11/27 04:49:42 jgg Exp $
83d89a9f
AL
4/* ######################################################################
5
18444708
AL
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.
83d89a9f
AL
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>
18444708 25#include <fstream>
83d89a9f
AL
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/* */
41bool 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
18444708 51 for (int I = 0; I != 10; I++)
83d89a9f 52 close(I);
18444708 53 for (int I = 0; I != 3; I++)
83d89a9f 54 dup2(open("/dev/null",O_RDWR),I);
18444708 55
83d89a9f
AL
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.. */
82bool 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
18444708 92 for (int I = 0; I != 10; I++)
83d89a9f 93 close(I);
18444708 94 for (int I = 0; I != 3; I++)
83d89a9f
AL
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. */
124bool 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// FindPackage - 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. */
172bool FindPackages(string CD,vector<string> &List, int Depth = 0)
173{
174 if (Depth >= 5)
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 {
213 _error->Errno("Stat","Stat failed for %s",Dir->d_name);
214 break;
215 }
216
217 if (S_ISDIR(Buf.st_mode) == 0)
218 continue;
219
220 // Descend
221 if (FindPackages(CD + Dir->d_name,List,Depth+1) == false)
222 break;
223
224 if (chdir(CD.c_str()) != 0)
225 return _error->Errno("chdir","Unable to change to ",CD.c_str());
226 };
227
228 closedir(D);
229
230 return !_error->PendingError();
231}
232 /*}}}*/
233// CopyPackages - Copy the package files from the CD /*{{{*/
234// ---------------------------------------------------------------------
235/* */
236bool CopyPackages(string CDROM,string Name,vector<string> &List)
237{
238 OpTextProgress Progress;
239
240 bool NoStat = _config->FindB("APT::CDROM::Fast",false);
241
242 // Prepare the progress indicator
243 unsigned long TotalSize = 0;
244 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
245 {
246 struct stat Buf;
247 if (stat(string(*I + "Packages").c_str(),&Buf) != 0)
248 return _error->Errno("stat","Stat failed for %s",
249 string(*I + "Packages").c_str());
250 TotalSize += Buf.st_size;
251 }
252
253 unsigned long CurrentSize = 0;
254 unsigned int NotFound = 0;
255 unsigned int WrongSize = 0;
256 unsigned int Packages = 0;
257 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
258 {
259 // Open the package file
260 FileFd Pkg(*I + "Packages",FileFd::ReadOnly);
261 pkgTagFile Parser(Pkg);
262 if (_error->PendingError() == true)
263 return false;
264
265 // Open the output file
266 char S[400];
267 sprintf(S,"cdrom:%s/%sPackages",Name.c_str(),(*I).c_str() + CDROM.length());
268 string TargetF = _config->FindDir("Dir::State::lists") + "partial/";
18444708
AL
269 TargetF += URItoFileName(S);
270 if (_config->FindB("APT::CDROM::NoAct",false) == true)
271 TargetF = "/dev/null";
272 FileFd Target(TargetF,FileFd::WriteEmpty);
83d89a9f
AL
273 if (_error->PendingError() == true)
274 return false;
275
276 // Setup the progress meter
277 Progress.OverallProgress(CurrentSize,TotalSize,Pkg.Size(),
278 "Reading Package Lists");
279
280 // Parse
281 Progress.SubProgress(Pkg.Size());
282 pkgTagSection Section;
283 while (Parser.Step(Section) == true)
284 {
285 Progress.Progress(Parser.Offset());
286
287 string File = Section.FindS("Filename");
288 unsigned long Size = Section.FindI("Size");
289 if (File.empty() || Size == 0)
290 return _error->Error("Cannot find filename or size tag");
291
292 // See if the file exists
293 if (NoStat == false)
294 {
295 struct stat Buf;
296 File = CDROM + File;
297 if (stat(File.c_str(),&Buf) != 0)
298 {
299 NotFound++;
300 continue;
301 }
302
303 // Size match
304 if ((unsigned)Buf.st_size != Size)
305 {
306 WrongSize++;
307 continue;
308 }
309 }
310
311 Packages++;
312
313 // Copy it to the target package file
314 const char *Start;
315 const char *Stop;
316 Section.GetSection(Start,Stop);
317 if (Target.Write(Start,Stop-Start) == false)
318 return false;
319 }
320
321 CurrentSize += Pkg.Size();
322 }
323 Progress.Done();
324
325 // Some stats
326 cout << "Wrote " << Packages << " package records" ;
327 if (NotFound != 0)
328 cout << " with " << NotFound << " missing files";
329 if (NotFound != 0 && WrongSize != 0)
330 cout << " and";
331 if (WrongSize != 0)
332 cout << " with " << WrongSize << " mismatched files";
333 cout << '.' << endl;
334 if (NotFound + WrongSize > 10)
335 cout << "Alot of package entires were discarded, perhaps this CD is funny?" << endl;
336}
337 /*}}}*/
338// DropBinaryArch - Dump dirs with a string like /binary-<foo>/ /*{{{*/
339// ---------------------------------------------------------------------
340/* Here we drop everything that is not this machines arch */
341bool DropBinaryArch(vector<string> &List)
342{
343 char S[300];
344 sprintf(S,"/binary-%s/",_config->Find("Apt::Architecture").c_str());
345
346 for (unsigned int I = 0; I < List.size(); I++)
347 {
348 const char *Str = List[I].c_str();
349
350 const char *Res;
351 if ((Res = strstr(Str,"/binary-")) == 0)
352 continue;
353
354 // Weird, remove it.
355 if (strlen(Res) < strlen(S))
356 {
357 List.erase(List.begin() + I);
358 I--;
359 continue;
360 }
361
362 // See if it is our arch
363 if (stringcmp(Res,Res + strlen(S),S) == 0)
364 continue;
365
366 // Erase it
367 List.erase(List.begin() + I);
368 I--;
369 }
370
371 return true;
372}
373 /*}}}*/
374// Score - We compute a 'score' for a path /*{{{*/
375// ---------------------------------------------------------------------
376/* Paths are scored based on how close they come to what I consider
377 normal. That is ones that have 'dist' 'stable' 'frozen' will score
378 higher than ones without. */
379int Score(string Path)
380{
381 int Res = 0;
382 if (Path.find("stable/") != string::npos)
383 Res += 2;
384 if (Path.find("frozen/") != string::npos)
385 Res += 2;
386 if (Path.find("/dists/") != string::npos)
387 Res += 4;
388 if (Path.find("/main/") != string::npos)
389 Res += 2;
390 if (Path.find("/contrib/") != string::npos)
391 Res += 2;
392 if (Path.find("/non-free/") != string::npos)
393 Res += 2;
394 if (Path.find("/non-US/") != string::npos)
395 Res += 2;
396 return Res;
397}
398 /*}}}*/
399// DropRepeats - Drop repeated files resulting from symlinks /*{{{*/
400// ---------------------------------------------------------------------
401/* Here we go and stat every file that we found and strip dup inodes. */
402bool DropRepeats(vector<string> &List)
403{
404 // Get a list of all the inodes
405 ino_t *Inodes = new ino_t[List.size()];
406 for (unsigned int I = 0; I != List.size(); I++)
407 {
408 struct stat Buf;
409 if (stat(List[I].c_str(),&Buf) != 0)
410 _error->Errno("stat","Failed to stat %s",List[I].c_str());
411 Inodes[I] = Buf.st_ino;
412 }
413
414 // Look for dups
415 for (unsigned int I = 0; I != List.size(); I++)
416 {
417 for (unsigned int J = I+1; J < List.size(); J++)
418 {
419 // No match
420 if (Inodes[J] != Inodes[I])
421 continue;
422
423 // We score the two paths.. and erase one
424 int ScoreA = Score(List[I]);
425 int ScoreB = Score(List[J]);
426 if (ScoreA < ScoreB)
427 {
428 List[I] = string();
429 break;
430 }
431
432 List[J] = string();
433 }
434 }
435
436 // Wipe erased entries
437 for (unsigned int I = 0; I < List.size();)
438 {
439 if (List[I].empty() == false)
440 I++;
441 else
442 List.erase(List.begin()+I);
443 }
444
445 return true;
446}
447 /*}}}*/
448// ConvertToSourceList - Takes the path list and converts it /*{{{*/
449// ---------------------------------------------------------------------
450/* This looks at each element and decides if it can be expressed using
451 dists/ form or if it requires an absolute specficiation. It also
452 strips the leading CDROM path from the paths. */
453bool ConvertToSourcelist(string CD,vector<string> &List)
454{
455 char S[300];
456 sprintf(S,"binary-%s",_config->Find("Apt::Architecture").c_str());
457
458 sort(List.begin(),List.end());
459
460 // Convert to source list notation
461 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
462 {
463 // Strip the cdrom base path
464 *I = string(*I,CD.length());
465
466 // Too short to be a dists/ type
467 if ((*I).length() < strlen("dists/"))
468 continue;
469
470 // Not a dists type.
471 if (stringcmp((*I).begin(),(*I).begin()+strlen("dists/"),"dists/") != 0)
472 continue;
473
474 // Isolate the dist
475 string::size_type Slash = strlen("dists/");
476 string::size_type Slash2 = (*I).find('/',Slash + 1);
477 if (Slash2 == string::npos || Slash2 + 2 >= (*I).length())
478 continue;
479 string Dist = string(*I,Slash,Slash2 - Slash);
480
481 // Isolate the component
482 Slash = (*I).find('/',Slash2+1);
483 if (Slash == string::npos || Slash + 2 >= (*I).length())
484 continue;
485 string Comp = string(*I,Slash2+1,Slash - Slash2-1);
486
487 // Verify the trailing binar - bit
488 Slash2 = (*I).find('/',Slash + 1);
489 if (Slash == string::npos)
490 continue;
491 string Binary = string(*I,Slash+1,Slash2 - Slash-1);
492
493 if (Binary != S)
494 continue;
495
496 *I = Dist + ' ' + Comp;
497 }
498
499 // Collect similar entries
500 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
501 {
502 // Find a space..
503 string::size_type Space = (*I).find(' ');
504 if (Space == string::npos)
505 continue;
506
507 string Word1 = string(*I,0,Space);
508 for (vector<string>::iterator J = List.begin(); J != I; J++)
509 {
510 // Find a space..
511 string::size_type Space2 = (*J).find(' ');
512 if (Space2 == string::npos)
513 continue;
514
515 if (string(*J,0,Space2) != Word1)
516 continue;
517
518 *J += string(*I,Space);
519 *I = string();
520 }
521 }
522
523 // Wipe erased entries
524 for (unsigned int I = 0; I < List.size();)
525 {
526 if (List[I].empty() == false)
527 I++;
528 else
529 List.erase(List.begin()+I);
530 }
531}
532 /*}}}*/
18444708
AL
533// WriteDatabase - Write the CDROM Database file /*{{{*/
534// ---------------------------------------------------------------------
535/* */
536bool WriteDatabase(Configuration &Cnf)
537{
538 string DFile = _config->FindFile("Dir::State::cdroms");
539
540 ofstream Out(string(DFile + ".new").c_str());
541 if (!Out)
542 return _error->Error("Failed to open %s.new",DFile.c_str());
543
544 /* Write out all of the configuration directives by walking the
545 configuration tree */
546 const Configuration::Item *Top = Cnf.Tree(0);
547 for (; Top != 0;)
548 {
549 // Print the config entry
550 if (Top->Value.empty() == false)
551 Out << Top->FullTag() + " \"" << Top->Value << "\";" << endl;
552
553 if (Top->Child != 0)
554 {
555 Top = Top->Child;
556 continue;
557 }
558
559 while (Top != 0 && Top->Next == 0)
560 Top = Top->Parent;
561 if (Top != 0)
562 Top = Top->Next;
563 }
564
565 Out.close();
566
567 rename(DFile.c_str(),string(DFile + '~').c_str());
568 if (rename(string(DFile + ".new").c_str(),DFile.c_str()) != 0)
569 return _error->Errno("rename","Failed to rename %s.new to %s",
570 DFile.c_str(),DFile.c_str());
571
572 return true;
573}
574 /*}}}*/
575// WriteSourceList - Write an updated sourcelist /*{{{*/
576// ---------------------------------------------------------------------
577/* */
578bool WriteSourceList(string Name,vector<string> &List)
579{
580 return true;
581}
582 /*}}}*/
83d89a9f
AL
583
584// Prompt - Simple prompt /*{{{*/
585// ---------------------------------------------------------------------
586/* */
587void Prompt(const char *Text)
588{
589 char C;
590 cout << Text << ' ' << flush;
591 read(STDIN_FILENO,&C,1);
592 if (C != '\n')
593 cout << endl;
594}
595 /*}}}*/
596// PromptLine - Prompt for an input line /*{{{*/
597// ---------------------------------------------------------------------
598/* */
599string PromptLine(const char *Text)
600{
601 cout << Text << ':' << endl;
602
603 string Res;
604 getline(cin,Res);
605 return Res;
606}
607 /*}}}*/
608
609// DoAdd - Add a new CDROM /*{{{*/
610// ---------------------------------------------------------------------
611/* */
612bool DoAdd(CommandLine &)
613{
614 // Startup
615 string CDROM = _config->FindDir("Acquire::cdrom::mount","/cdrom/");
616 cout << "Using CD-ROM mount point " << CDROM << endl;
617
618 // Read the database
619 Configuration Database;
620 string DFile = _config->FindFile("Dir::State::cdroms");
621 if (FileExists(DFile) == true)
622 {
623 if (ReadConfigFile(Database,DFile) == false)
624 return _error->Error("Unable to read the cdrom database %s",
625 DFile.c_str());
626 }
627
628 // Unmount the CD and get the user to put in the one they want
629 if (_config->FindB("APT::CDROM::NoMount",false) == false)
630 {
631 cout << "Unmounting CD-ROM" << endl;
632 UnmountCdrom(CDROM);
18444708 633
83d89a9f 634 // Mount the new CDROM
18444708 635 Prompt("Please insert a Disc in the drive and press any key");
83d89a9f
AL
636 cout << "Mounting CD-ROM" << endl;
637 if (MountCdrom(CDROM) == false)
638 {
639 cout << "Failed to mount the cdrom." << endl;
640 return false;
641 }
642 }
643
644 // Hash the CD to get an ID
18444708 645 cout << "Identifying.. " << flush;
83d89a9f
AL
646 string ID;
647 if (IdentCdrom(CDROM,ID) == false)
648 return false;
649 cout << '[' << ID << ']' << endl;
650
651 cout << "Scanning Disc for index files.. " << flush;
652 // Get the CD structure
653 vector<string> List;
654 string StartDir = SafeGetCWD();
655 if (FindPackages(CDROM,List) == false)
656 return false;
657 chdir(StartDir.c_str());
658
659 // Fix up the list
660 DropBinaryArch(List);
661 DropRepeats(List);
662 cout << "Found " << List.size() << " package index files." << endl;
663
664 if (List.size() == 0)
665 return _error->Error("Unable to locate any package files, perhaps this is not a debian CD-ROM");
666
667 // Check if the CD is in the database
668 string Name;
669 if (Database.Exists("CD::" + ID) == false ||
670 _config->FindB("APT::CDROM::Rename",false) == true)
671 {
672 cout << "Please provide a name for this CD-ROM, such as 'Debian 2.1r1 Disk 1'";
673 Name = PromptLine("");
674 }
675 else
676 Name = Database.Find("CD::" + ID);
18444708 677 Database.Set("CD::" + ID,Name);
83d89a9f
AL
678 cout << "This Disc is called '" << Name << "'" << endl;
679
680 // Copy the package files to the state directory
681 if (CopyPackages(CDROM,Name,List) == false)
682 return false;
683
684 ConvertToSourcelist(CDROM,List);
685
686 // Print the sourcelist entries
687 cout << "Source List entires for this Disc are:" << endl;
688 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
689 cout << "deb \"cdrom:" << Name << "/\" " << *I << endl;
18444708
AL
690
691 // Write the database and sourcelist
692 if (_config->FindB("APT::cdrom::NoAct",false) == false)
693 {
694 if (WriteDatabase(Database) == false)
695 return false;
696 }
83d89a9f
AL
697
698 return true;
699}
700 /*}}}*/
701
702// ShowHelp - Show the help screen /*{{{*/
703// ---------------------------------------------------------------------
704/* */
705int ShowHelp()
706{
707 cout << PACKAGE << ' ' << VERSION << " for " << ARCHITECTURE <<
708 " compiled on " << __DATE__ << " " << __TIME__ << endl;
709
710 cout << "Usage: apt-cdrom [options] command" << endl;
711 cout << endl;
712 cout << "apt-cdrom is a tool to add CDROM's to APT's source list. The " << endl;
713 cout << "CDROM mount point and device information is taken from apt.conf" << endl;
714 cout << "and /etc/fstab." << endl;
715 cout << endl;
716 cout << "Commands:" << endl;
717 cout << " add - Add a CDROM" << endl;
718 cout << endl;
719 cout << "Options:" << endl;
720 cout << " -h This help text" << endl;
721 cout << " -d CD-ROM mount point" << endl;
722 cout << " -r Rename a recognized CD-ROM" << endl;
723 cout << " -m No mounting" << endl;
18444708 724 cout << " -f Fast mode, don't check package files" << endl;
83d89a9f
AL
725 cout << " -c=? Read this configuration file" << endl;
726 cout << " -o=? Set an arbitary configuration option, ie -o dir::cache=/tmp" << endl;
727 cout << "See fstab(5)" << endl;
728 return 100;
729}
730 /*}}}*/
731
732int main(int argc,const char *argv[])
733{
734 CommandLine::Args Args[] = {
735 {'h',"help","help",0},
736 {'d',"cdrom","Acquire::cdrom::mount",CommandLine::HasArg},
737 {'r',"rename","APT::CDROM::Rename",0},
738 {'m',"no-mount","APT::CDROM::NoMount",0},
739 {'f',"fast","APT::CDROM::Fast",0},
18444708
AL
740 {'n',"just-print","APT::CDROM::NoAct",0},
741 {'n',"recon","APT::CDROM::NoAct",0},
742 {'n',"no-act","APT::CDROM::NoAct",0},
83d89a9f
AL
743 {'c',"config-file",0,CommandLine::ConfigFile},
744 {'o',"option",0,CommandLine::ArbItem},
745 {0,0,0,0}};
746 CommandLine::Dispatch Cmds[] = {
747 {"add",&DoAdd},
748 {0,0}};
749
750 // Parse the command line and initialize the package library
751 CommandLine CmdL(Args,_config);
752 if (pkgInitialize(*_config) == false ||
753 CmdL.Parse(argc,argv) == false)
754 {
755 _error->DumpErrors();
756 return 100;
757 }
758
759 // See if the help should be shown
760 if (_config->FindB("help") == true ||
761 CmdL.FileSize() == 0)
762 return ShowHelp();
763
764 // Match the operation
765 CmdL.DispatchArg(Cmds);
766
767 // Print any errors or warnings found during parsing
768 if (_error->empty() == false)
769 {
770 bool Errors = _error->PendingError();
771 _error->DumpErrors();
772 return Errors == true?100:0;
773 }
774
775 return 0;
776}