- move more common code from apt-get & apt-shell to cmdline
[apt.git] / cmdline / cmdline.cc
1 // -*- mode: c++; mode: fold -*-
2 #include <apt-pkg/configuration.h>
3 #include <apt-pkg/error.h>
4
5 #include <config.h>
6 #include <apti18n.h>
7
8 #include "cmdline.h"
9
10 #include <regex.h>
11
12 using namespace std;
13
14 // YnPrompt - Yes No Prompt.                                            /*{{{*/
15 // ---------------------------------------------------------------------
16 /* Returns true on a Yes.*/
17 bool YnPrompt()
18 {
19    if (_config->FindB("APT::Get::Assume-Yes",false) == true)
20    {
21       c1out << _("Y") << endl;
22       return true;
23    }
24
25    char response[1024] = "";
26    cin.getline(response, sizeof(response));
27
28    if (!cin)
29       return false;
30
31    if (strlen(response) == 0)
32       return true;
33
34    regex_t Pattern;
35    int Res;
36
37    Res = regcomp(&Pattern, nl_langinfo(YESEXPR),
38                  REG_EXTENDED|REG_ICASE|REG_NOSUB);
39
40    if (Res != 0) {
41       char Error[300];        
42       regerror(Res,&Pattern,Error,sizeof(Error));
43       return _error->Error(_("Regex compilation error - %s"),Error);
44    }
45    
46    Res = regexec(&Pattern, response, 0, NULL, 0);
47    if (Res == 0)
48       return true;
49    return false;
50 }
51                                                                         /*}}}*/
52 // AnalPrompt - Annoying Yes No Prompt.                                 /*{{{*/
53 // ---------------------------------------------------------------------
54 /* Returns true on a Yes.*/
55 bool AnalPrompt(const char *Text)
56 {
57    char Buf[1024];
58    cin.getline(Buf,sizeof(Buf));
59    if (strcmp(Buf,Text) == 0)
60       return true;
61    return false;
62 }
63                                                                         /*}}}*/
64 // ShowList - Show a list                                               /*{{{*/
65 // ---------------------------------------------------------------------
66 /* This prints out a string of space separated words with a title and 
67    a two space indent line wraped to the current screen width. */
68 bool ShowList(ostream &out,string Title,string List,string VersionsList)
69 {
70    if (List.empty() == true)
71       return true;
72    // trim trailing space
73    int NonSpace = List.find_last_not_of(' ');
74    if (NonSpace != -1)
75    {
76       List = List.erase(NonSpace + 1);
77       if (List.empty() == true)
78          return true;
79    }
80
81    // Acount for the leading space
82    int ScreenWidth = ::ScreenWidth - 3;
83       
84    out << Title << endl;
85    string::size_type Start = 0;
86    string::size_type VersionsStart = 0;
87    while (Start < List.size())
88    {
89       if(_config->FindB("APT::Get::Show-Versions",false) == true &&
90          VersionsList.size() > 0) {
91          string::size_type End;
92          string::size_type VersionsEnd;
93          
94          End = List.find(' ',Start);
95          VersionsEnd = VersionsList.find('\n', VersionsStart);
96
97          out << "   " << string(List,Start,End - Start) << " (" << 
98             string(VersionsList,VersionsStart,VersionsEnd - VersionsStart) << 
99             ")" << endl;
100
101          if (End == string::npos || End < Start)
102             End = Start + ScreenWidth;
103
104          Start = End + 1;
105          VersionsStart = VersionsEnd + 1;
106       } else {
107          string::size_type End;
108
109          if (Start + ScreenWidth >= List.size())
110             End = List.size();
111          else
112             End = List.rfind(' ',Start+ScreenWidth);
113
114          if (End == string::npos || End < Start)
115             End = Start + ScreenWidth;
116          out << "  " << string(List,Start,End - Start) << endl;
117          Start = End + 1;
118       }
119    }   
120
121    return false;
122 }
123
124 const char *op2str(int op)
125 {
126    switch (op & 0x0f)
127    {
128       case pkgCache::Dep::LessEq: return "<=";
129       case pkgCache::Dep::GreaterEq: return ">=";
130       case pkgCache::Dep::Less: return "<";
131       case pkgCache::Dep::Greater: return ">";
132       case pkgCache::Dep::Equals: return "=";
133       case pkgCache::Dep::NotEquals: return "!";
134       default: return "";
135    }
136 }
137
138 // SigWinch - Window size change signal handler                         /*{{{*/
139 // ---------------------------------------------------------------------
140 /* */
141 void SigWinch(int)
142 {
143    // Riped from GNU ls
144 #ifdef TIOCGWINSZ
145    struct winsize ws;
146
147    if (ioctl(1, TIOCGWINSZ, &ws) != -1 && ws.ws_col >= 5)
148       ScreenWidth = ws.ws_col - 1;
149 #endif
150 }
151
152 // CacheFile::NameComp - QSort compare by name                          /*{{{*/
153 // ---------------------------------------------------------------------
154 /* */
155 pkgCache *cmdCacheFile::SortCache = 0;
156 int cmdCacheFile::NameComp(const void *a,const void *b)
157 {
158    if (*(pkgCache::Package **)a == 0 || *(pkgCache::Package **)b == 0)
159       return *(pkgCache::Package **)a - *(pkgCache::Package **)b;
160
161    const pkgCache::Package &A = **(pkgCache::Package **)a;
162    const pkgCache::Package &B = **(pkgCache::Package **)b;
163
164    return strcmp(SortCache->StrP + A.Name,SortCache->StrP + B.Name);
165 }
166                                                                         /*}}}*/
167 // CacheFile::Sort - Sort by name                                       /*{{{*/
168 // ---------------------------------------------------------------------
169 /* */
170 void cmdCacheFile::Sort()
171 {
172    delete [] List;
173    List = new pkgCache::Package *[Cache->Head().PackageCount];
174    memset(List,0,sizeof(*List)*Cache->Head().PackageCount);
175    pkgCache::PkgIterator I = Cache->PkgBegin();
176    for (;I.end() != true; I++)
177       List[I->ID] = I;
178
179    SortCache = *this;
180    qsort(List,Cache->Head().PackageCount,sizeof(*List),NameComp);
181 }
182
183 // ShowBroken - Debugging aide                                          /*{{{*/
184 // ---------------------------------------------------------------------
185 /* This prints out the names of all the packages that are broken along
186    with the name of each each broken dependency and a quite version 
187    description.
188    
189    The output looks like:
190  The following packages have unmet dependencies:
191      exim: Depends: libc6 (>= 2.1.94) but 2.1.3-10 is to be installed
192            Depends: libldap2 (>= 2.0.2-2) but it is not going to be installed
193            Depends: libsasl7 but it is not going to be installed   
194  */
195 void ShowBroken(ostream &out,cmdCacheFile &Cache,bool Now,pkgDepCache::State *State)
196 {
197    out << _("The following packages have unmet dependencies:") << endl;
198    for (unsigned J = 0; J < Cache->Head().PackageCount; J++)
199    {
200       pkgCache::PkgIterator I(Cache,Cache.List[J]);
201       
202       if (Now == true)
203       {
204          if (Cache[I].NowBroken() == false)
205             continue;
206       }
207       else
208       {
209          if (Cache[I].InstBroken() == false)
210             continue;
211       }
212       
213       // Print out each package and the failed dependencies
214       out <<"  " <<  I.Name() << ":";
215       size_t Indent = strlen(I.Name()) + 3;
216       bool First = true;
217       pkgCache::VerIterator Ver;
218       
219       if (Now == true)
220          Ver = I.CurrentVer();
221       else
222          Ver = Cache[I].InstVerIter(Cache);
223       
224       if (Ver.end() == true)
225       {
226          out << endl;
227          continue;
228       }
229       
230       for (pkgCache::DepIterator D = Ver.DependsList(); D.end() == false;)
231       {
232          // Compute a single dependency element (glob or)
233          pkgCache::DepIterator Start;
234          pkgCache::DepIterator End;
235          D.GlobOr(Start,End);
236
237          // CNC:2003-02-22 - IsImportantDep() currently calls IsCritical(), so
238          //                  these two are currently doing the same thing. Check
239          //                  comments in IsImportantDep() definition.
240 #if 0
241          if (Cache->IsImportantDep(End) == false)
242             continue;
243 #else
244          if (End.IsCritical() == false)
245             continue;
246 #endif
247          
248          if (Now == true)
249          {
250             if ((Cache[End] & pkgDepCache::DepGNow) == pkgDepCache::DepGNow)
251                continue;
252          }
253          else
254          {
255             if ((Cache[End] & pkgDepCache::DepGInstall) == pkgDepCache::DepGInstall)
256                continue;
257          }
258          
259          bool FirstOr = true;
260          while (1)
261          {
262             if (First == false)
263                for (unsigned J = 0; J != Indent; J++)
264                   out << ' ';
265             First = false;
266
267             if (FirstOr == false)
268             {
269                for (size_t J = 0; J != strlen(End.DepType()) + 3; J++)
270                   out << ' ';
271             }
272             else
273                out << ' ' << End.DepType() << ": ";
274             FirstOr = false;
275             
276             out << Start.TargetPkg().Name();
277          
278             // Show a quick summary of the version requirements
279             if (Start.TargetVer() != 0)
280                out << " (" << Start.CompType() << " " << Start.TargetVer() << ")";
281             
282             /* Show a summary of the target package if possible. In the case
283                of virtual packages we show nothing */    
284             pkgCache::PkgIterator Targ = Start.TargetPkg();
285             if (Targ->ProvidesList == 0)
286             {
287                out << ' ';
288                pkgCache::VerIterator Ver = Cache[Targ].InstVerIter(Cache);
289                if (Now == true)
290                   Ver = Targ.CurrentVer();
291                     
292                if (Ver.end() == false)
293                {
294                   if (Now == true)
295                      ioprintf(out,_("but %s is installed"),Ver.VerStr());
296                   else
297                      ioprintf(out,_("but %s is to be installed"),Ver.VerStr());
298                }               
299                else
300                {
301                   if (Cache[Targ].CandidateVerIter(Cache).end() == true)
302                   {
303                      if (Targ->ProvidesList == 0)
304                         out << _("but it is not installable");
305                      else
306                         out << _("but it is a virtual package");
307                   }               
308                   else
309                      out << (Now?_("but it is not installed"):_("but it is not going to be installed"));
310                }               
311             }
312             
313             if (Start != End)
314                out << _(" or");
315             out << endl;
316             
317             if (Start == End)
318                break;
319             Start++;
320          }       
321       }     
322    }   
323 }
324                                                                         /*}}}*/
325 // ShowNew - Show packages to newly install                             /*{{{*/
326 // ---------------------------------------------------------------------
327 /* */
328 void ShowNew(ostream &out,cmdCacheFile &Cache,pkgDepCache::State *State)
329 {
330    /* Print out a list of packages that are going to be installed extra
331       to what the user asked */
332    string List;
333    string VersionsList;
334    for (unsigned J = 0; J < Cache->Head().PackageCount; J++)
335    {
336       pkgCache::PkgIterator I(Cache,Cache.List[J]);
337       if (Cache[I].NewInstall() == true &&
338           (State == NULL || (*State)[I].NewInstall() == false)) {
339          List += string(I.Name()) + " ";
340          VersionsList += string(Cache[I].CandVersion) + "\n";
341       }
342    }
343    
344    ShowList(out,_("The following NEW packages will be installed:"),List,VersionsList);
345 }
346                                                                         /*}}}*/
347 // ShowDel - Show packages to delete                                    /*{{{*/
348 // ---------------------------------------------------------------------
349 /* */
350 void ShowDel(ostream &out,cmdCacheFile &Cache,pkgDepCache::State *State)
351 {
352    /* Print out a list of packages that are going to be removed extra
353       to what the user asked */
354    string List, RepList; // CNC:2002-07-25
355    string VersionsList;
356    for (unsigned J = 0; J < Cache->Head().PackageCount; J++)
357    {
358       pkgCache::PkgIterator I(Cache,Cache.List[J]);
359       if (Cache[I].Delete() == true &&
360           (State == NULL || (*State)[I].Delete() == false))
361       {
362          // CNC:2002-07-25
363          bool Obsoleted = false;
364          string by;
365          for (pkgCache::DepIterator D = I.RevDependsList(); D.end() == false; D++)
366          {
367             if (D->Type == pkgCache::Dep::Obsoletes &&
368                 Cache[D.ParentPkg()].Install() &&
369                 (pkgCache::Version*)D.ParentVer() == Cache[D.ParentPkg()].InstallVer &&
370                 Cache->VS().CheckDep(I.CurrentVer().VerStr(), D) == true)
371             {
372                if (Obsoleted)
373                   by += ", " + string(D.ParentPkg().Name());
374                else
375                {
376                   Obsoleted = true;
377                   by = D.ParentPkg().Name();
378                }
379             }
380          }
381          if (Obsoleted)
382             RepList += string(I.Name()) + " (by " + by + ")  ";
383          else
384          {
385             if ((Cache[I].iFlags & pkgDepCache::Purge) == pkgDepCache::Purge)
386                List += string(I.Name()) + "* ";
387             else
388                List += string(I.Name()) + " ";
389          }
390      
391      // CNC:2004-03-09
392      VersionsList += string(I.CurrentVer().VerStr())+ "\n";
393       }
394    }
395    
396    // CNC:2002-07-25
397    ShowList(out,_("The following packages will be REPLACED:"),RepList,VersionsList);
398    ShowList(out,_("The following packages will be REMOVED:"),List,VersionsList);
399 }
400                                                                         /*}}}*/
401 // ShowKept - Show kept packages                                        /*{{{*/
402 // ---------------------------------------------------------------------
403 /* */
404 void ShowKept(ostream &out,cmdCacheFile &Cache,pkgDepCache::State *State)
405 {
406    string List;
407    string VersionsList;
408    for (unsigned J = 0; J < Cache->Head().PackageCount; J++)
409    {     
410       pkgCache::PkgIterator I(Cache,Cache.List[J]);
411       
412       if (State == NULL) {
413          // Not interesting
414          if (Cache[I].Upgrade() == true || Cache[I].Upgradable() == false ||
415              I->CurrentVer == 0 || Cache[I].Delete() == true)
416             continue;
417       } else {
418          // Not interesting
419          if (!((Cache[I].Install() == false && (*State)[I].Install() == true) ||
420                (Cache[I].Delete() == false && (*State)[I].Delete() == true)))
421             continue;
422       }
423       
424       List += string(I.Name()) + " ";
425       VersionsList += string(Cache[I].CurVersion) + " => " + Cache[I].CandVersion + "\n";
426    }
427    ShowList(out,_("The following packages have been kept back"),List,VersionsList);
428 }
429                                                                         /*}}}*/
430 // ShowUpgraded - Show upgraded packages                                /*{{{*/
431 // ---------------------------------------------------------------------
432 /* */
433 void ShowUpgraded(ostream &out,cmdCacheFile &Cache,pkgDepCache::State *State)
434 {
435    string List;
436    string VersionsList;
437    for (unsigned J = 0; J < Cache->Head().PackageCount; J++)
438    {
439       pkgCache::PkgIterator I(Cache,Cache.List[J]);
440       
441       if (State == NULL) {
442          // Not interesting
443          if (Cache[I].Upgrade() == false || Cache[I].NewInstall() == true)
444             continue;
445       } else {
446          // Not interesting
447          if (Cache[I].NewInstall() == true ||
448              !(Cache[I].Upgrade() == true && (*State)[I].Upgrade() == false))
449             continue;
450       }
451       
452       List += string(I.Name()) + " ";
453       VersionsList += string(Cache[I].CurVersion) + " => " + Cache[I].CandVersion + "\n";
454    }
455    ShowList(out,_("The following packages will be upgraded"),List,VersionsList);
456 }
457                                                                         /*}}}*/
458 // ShowDowngraded - Show downgraded packages                            /*{{{*/
459 // ---------------------------------------------------------------------
460 /* */
461 bool ShowDowngraded(ostream &out,cmdCacheFile &Cache,pkgDepCache::State *State)
462 {
463    string List;
464    string VersionsList;
465    for (unsigned J = 0; J < Cache->Head().PackageCount; J++)
466    {
467       pkgCache::PkgIterator I(Cache,Cache.List[J]);
468       
469       if (State == NULL) {
470          // Not interesting
471          if (Cache[I].Downgrade() == false || Cache[I].NewInstall() == true)
472             continue;
473       } else {
474          // Not interesting
475          if (Cache[I].NewInstall() == true ||
476              !(Cache[I].Downgrade() == true && (*State)[I].Downgrade() == false))
477             continue;
478       }
479       
480       List += string(I.Name()) + " ";
481       VersionsList += string(Cache[I].CurVersion) + " => " + Cache[I].CandVersion + "\n";
482    }
483    return ShowList(out,_("The following packages will be DOWNGRADED"),List,VersionsList);
484 }
485                                                                         /*}}}*/
486 // ShowHold - Show held but changed packages                            /*{{{*/
487 // ---------------------------------------------------------------------
488 /* */
489 bool ShowHold(ostream &out,cmdCacheFile &Cache,pkgDepCache::State *State)
490 {
491    string List;
492    string VersionsList;
493    for (unsigned J = 0; J < Cache->Head().PackageCount; J++)
494    {
495       pkgCache::PkgIterator I(Cache,Cache.List[J]);
496       if (Cache[I].InstallVer != (pkgCache::Version *)I.CurrentVer() &&
497           I->SelectedState == pkgCache::State::Hold &&
498           (State == NULL ||
499            Cache[I].InstallVer != (*State)[I].InstallVer)) {
500          List += string(I.Name()) + " ";
501          VersionsList += string(Cache[I].CurVersion) + " => " + Cache[I].CandVersion + "\n";
502       }
503    }
504
505    return ShowList(out,_("The following held packages will be changed:"),List,VersionsList);
506 }
507                                                                         /*}}}*/
508 // ShowEssential - Show an essential package warning                    /*{{{*/
509 // ---------------------------------------------------------------------
510 /* This prints out a warning message that is not to be ignored. It shows
511    all essential packages and their dependents that are to be removed. 
512    It is insanely risky to remove the dependents of an essential package! */
513 bool ShowEssential(ostream &out,cmdCacheFile &Cache,pkgDepCache::State *State)
514 {
515    string List;
516    string VersionsList;
517    bool *Added = new bool[Cache->Head().PackageCount];
518    for (unsigned int I = 0; I != Cache->Head().PackageCount; I++)
519       Added[I] = false;
520    
521    for (unsigned J = 0; J < Cache->Head().PackageCount; J++)
522    {
523       pkgCache::PkgIterator I(Cache,Cache.List[J]);
524       if ((I->Flags & pkgCache::Flag::Essential) != pkgCache::Flag::Essential &&
525           (I->Flags & pkgCache::Flag::Important) != pkgCache::Flag::Important)
526          continue;
527       
528       // The essential package is being removed
529       if (Cache[I].Delete() == true &&
530           (State == NULL || (*State)[I].Delete() == false))
531       {
532          if (Added[I->ID] == false)
533          {
534             // CNC:2003-03-21 - Do not consider a problem if that package is being obsoleted
535             //                  by something else.
536             bool Obsoleted = false;
537             for (pkgCache::DepIterator D = I.RevDependsList(); D.end() == false; D++)
538             {
539                if (D->Type == pkgCache::Dep::Obsoletes &&
540                    Cache[D.ParentPkg()].Install() &&
541                    ((pkgCache::Version*)D.ParentVer() == Cache[D.ParentPkg()].InstallVer ||
542                     (pkgCache::Version*)D.ParentVer() == ((pkgCache::Version*)D.ParentPkg().CurrentVer())) &&
543                    Cache->VS().CheckDep(I.CurrentVer().VerStr(), D) == true)
544                {
545                   Obsoleted = true;
546                   break;
547                }
548             }
549             if (Obsoleted == false) {
550                Added[I->ID] = true;
551                List += string(I.Name()) + " ";
552             }
553         //VersionsList += string(Cache[I].CurVersion) + "\n"; ???
554          }
555       }
556       
557       if (I->CurrentVer == 0)
558          continue;
559
560       // Print out any essential package depenendents that are to be removed
561       for (pkgCache::DepIterator D = I.CurrentVer().DependsList(); D.end() == false; D++)
562       {
563          // Skip everything but depends
564          if (D->Type != pkgCache::Dep::PreDepends &&
565              D->Type != pkgCache::Dep::Depends)
566             continue;
567          
568          pkgCache::PkgIterator P = D.SmartTargetPkg();
569          if (Cache[P].Delete() == true &&
570              (State == NULL || (*State)[P].Delete() == false))
571          {
572             if (Added[P->ID] == true)
573                continue;
574
575             // CNC:2003-03-21 - Do not consider a problem if that package is being obsoleted
576             //                  by something else.
577             bool Obsoleted = false;
578             for (pkgCache::DepIterator D = P.RevDependsList(); D.end() == false; D++)
579             {
580                if (D->Type == pkgCache::Dep::Obsoletes &&
581                    Cache[D.ParentPkg()].Install() &&
582                    ((pkgCache::Version*)D.ParentVer() == Cache[D.ParentPkg()].InstallVer ||
583                     (pkgCache::Version*)D.ParentVer() == ((pkgCache::Version*)D.ParentPkg().CurrentVer())) &&
584                    Cache->VS().CheckDep(P.CurrentVer().VerStr(), D) == true)
585                {
586                   Obsoleted = true;
587                   break;
588                }
589             }
590             if (Obsoleted == true)
591                continue;
592
593             Added[P->ID] = true;
594             
595             char S[300];
596             snprintf(S,sizeof(S),_("%s (due to %s) "),P.Name(),I.Name());
597             List += S;
598         //VersionsList += "\n"; ???
599          }       
600       }      
601    }
602    
603    delete [] Added;
604    return ShowList(out,_("WARNING: The following essential packages will be removed\n"
605                          "This should NOT be done unless you know exactly what you are doing!"),List,VersionsList);
606 }
607                                                                         /*}}}*/
608 // Stats - Show some statistics                                         /*{{{*/
609 // ---------------------------------------------------------------------
610 /* */
611 void Stats(ostream &out,pkgDepCache &Dep,pkgDepCache::State *State)
612 {
613    unsigned long Upgrade = 0;
614    unsigned long Downgrade = 0;
615    unsigned long Install = 0;
616    unsigned long ReInstall = 0;
617    // CNC:2002-07-29
618    unsigned long Replace = 0;
619    unsigned long Remove = 0;
620    unsigned long Keep = 0;
621    for (pkgCache::PkgIterator I = Dep.PkgBegin(); I.end() == false; I++)
622    {
623       if (Dep[I].NewInstall() == true &&
624           (State == NULL || (*State)[I].NewInstall() == false))
625          Install++;
626       else
627       {
628          if (Dep[I].Upgrade() == true &&
629              (State == NULL || (*State)[I].Upgrade() == false))
630             Upgrade++;
631          else
632             if (Dep[I].Downgrade() == true &&
633                 (State == NULL || (*State)[I].Downgrade() == false))
634                Downgrade++;
635             else
636                if (State != NULL &&
637                    (((*State)[I].NewInstall() == true && Dep[I].NewInstall() == false) ||
638                     ((*State)[I].Upgrade() == true && Dep[I].Upgrade() == false) ||
639                     ((*State)[I].Downgrade() == true && Dep[I].Downgrade() == false)))
640                   Keep++;
641       }
642       // CNC:2002-07-29
643       if (Dep[I].Delete() == true &&
644           (State == NULL || (*State)[I].Delete() == false))
645       {
646          bool Obsoleted = false;
647          string by;
648          for (pkgCache::DepIterator D = I.RevDependsList();
649               D.end() == false; D++)
650          {
651             if (D->Type == pkgCache::Dep::Obsoletes &&
652                 Dep[D.ParentPkg()].Install() &&
653                 (pkgCache::Version*)D.ParentVer() == Dep[D.ParentPkg()].InstallVer &&
654                 Dep.VS().CheckDep(I.CurrentVer().VerStr(), D) == true)
655             {
656                Obsoleted = true;
657                break;
658             }
659          }
660          if (Obsoleted)
661             Replace++;
662          else
663             Remove++;
664       }
665       else if ((Dep[I].iFlags & pkgDepCache::ReInstall) == pkgDepCache::ReInstall &&
666                (State == NULL || !((*State)[I].iFlags & pkgDepCache::ReInstall)))
667          ReInstall++;
668    }   
669
670    ioprintf(out,_("%lu upgraded, %lu newly installed, "),
671             Upgrade,Install);
672    
673    if (ReInstall != 0)
674       ioprintf(out,_("%lu reinstalled, "),ReInstall);
675    if (Downgrade != 0)
676       ioprintf(out,_("%lu downgraded, "),Downgrade);
677    // CNC:2002-07-29
678    if (Replace != 0)
679       ioprintf(out,_("%lu replaced, "),Replace);
680
681    // CNC:2002-07-29
682    if (State == NULL)
683       ioprintf(out,_("%lu removed and %lu not upgraded.\n"),
684                Remove,Dep.KeepCount());
685    else
686       ioprintf(out,_("%lu removed and %lu kept.\n"),Remove,Keep);
687
688    
689    if (Dep.BadCount() != 0)
690       ioprintf(out,_("%lu not fully installed or removed.\n"),
691                Dep.BadCount());
692 }
693                                                                         /*}}}*/
694 // vim:sts=3:sw=3