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