source: projects/synaptic/trunk/common/rcdscanner.cc @ 280

Revision 280, 19.8 KB checked in by yasumichi, 15 years ago (diff)

first import

Line 
1/* rcdscanner.cc
2 *
3 * Copyright (c) 2000-2003 Conectiva S/A
4 *
5 * Author: Alfredo K. Kojima <kojima@conectiva.com.br>
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2 of the
10 * License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
20 * USA
21 */
22
23#include<config.h>
24#ifndef HAVE_APTPKG_CDROM
25
26#include <sys/stat.h>
27#include <sys/fcntl.h>
28#include <dirent.h>
29#include <unistd.h>
30#include <iostream>
31#include <fstream>
32#include <algorithm>
33#include <cstdio>
34
35#include <apt-pkg/error.h>
36#include <apt-pkg/fileutl.h>
37#include <apt-pkg/configuration.h>
38#include <apt-pkg/cdromutl.h>
39#include <apt-pkg/strutl.h>
40
41#include "i18n.h"
42#include "rcdscanner.h"
43
44#ifdef HAVE_RPM
45#include "rpmindexcopy.h"
46#else
47#include "indexcopy.h"
48#endif
49
50using namespace std;
51
52// ReduceSourceList - Takes the path list and reduces it                /*{{{*/
53// ---------------------------------------------------------------------
54/* This takes the list of source list expressed entires and collects
55   similar ones to form a single entry for each dist */
56void ReduceSourcelist(string CD, vector<string> &List)
57{
58   sort(List.begin(), List.end());
59
60   // Collect similar entries
61   for (vector<string>::iterator I = List.begin(); I != List.end(); I++) {
62      // Find a space..
63      string::size_type Space = (*I).find(' ');
64      if (Space == string::npos)
65         continue;
66      string::size_type SSpace = (*I).find(' ', Space + 1);
67      if (SSpace == string::npos)
68         continue;
69
70      string Word1 = string(*I, Space, SSpace - Space);
71      string Prefix = string(*I, 0, Space);
72      for (vector<string>::iterator J = List.begin(); J != I; J++) {
73         // Find a space..
74         string::size_type Space2 = (*J).find(' ');
75         if (Space2 == string::npos)
76            continue;
77         string::size_type SSpace2 = (*J).find(' ', Space2 + 1);
78         if (SSpace2 == string::npos)
79            continue;
80
81         if (string(*J, 0, Space2) != Prefix)
82            continue;
83         if (string(*J, Space2, SSpace2 - Space2) != Word1)
84            continue;
85
86         *J += string(*I, SSpace);
87         *I = string();
88      }
89   }
90
91   // Wipe erased entries
92   for (unsigned int I = 0; I < List.size();) {
93      if (List[I].empty() == false)
94         I++;
95      else
96         List.erase(List.begin() + I);
97   }
98}
99
100                                                                        /*}}} */
101bool RCDScanner::writeDatabase()
102{
103   _database->Set("CD::" + _cdId, _cdName);
104
105   string DFile = _config->FindFile("Dir::State::cdroms");
106   string NewFile = DFile + ".new";
107
108   unlink(NewFile.c_str());
109   ofstream Out(NewFile.c_str());
110   if (!Out)
111      return _error->Errno("ofstream::ofstream",
112                           _("Failed to open %s.new"), DFile.c_str());
113
114   /* Write out all of the configuration directives by walking the
115      configuration tree */
116   const Configuration::Item *Top = _database->Tree(0);
117   for (; Top != 0;) {
118      // Print the config entry
119      if (Top->Value.empty() == false)
120         Out << Top->FullTag() + " \"" << Top->Value << "\";" << endl;
121
122      if (Top->Child != 0) {
123         Top = Top->Child;
124         continue;
125      }
126
127      while (Top != 0 && Top->Next == 0)
128         Top = Top->Parent;
129      if (Top != 0)
130         Top = Top->Next;
131   }
132
133   Out.close();
134
135   rename(DFile.c_str(), string(DFile + '~').c_str());
136   if (rename(NewFile.c_str(), DFile.c_str()) != 0)
137      return _error->Errno("rename", _("Failed to rename %s.new to %s"),
138                           DFile.c_str(), DFile.c_str());
139
140   return true;
141}
142
143bool RCDScanner::writeSourceList(vector<string> &list, bool pkg)
144{
145   // copy&paste from apt-cdrom
146
147   if (list.size() == 0)
148      return true;
149
150   string File = _config->FindFile("Dir::Etc::sourcelist");
151
152   // Open the stream for reading
153   ifstream F((FileExists(File) ? File.c_str() : "/dev/null"), ios::in);
154   if (!F != 0)
155      return _error->Errno("ifstream::ifstream", "Opening %s", File.c_str());
156
157   string NewFile = File + ".new";
158   unlink(NewFile.c_str());
159   ofstream Out(NewFile.c_str());
160   if (!Out)
161      return _error->Errno("ofstream::ofstream",
162                           _("Failed to open %s.new"), File.c_str());
163
164   // Create a short uri without the path
165   string ShortURI = "cdrom:[" + _cdName + "]/";
166   string ShortURI2 = "cdrom:" + _cdName + "/"; // For Compatibility
167
168   string ShortOldURI;
169   string ShortOldURI2;
170   if (_cdOldName.empty() == false) {
171      ShortOldURI = "cdrom:[" + _cdOldName + "]/";
172      ShortOldURI2 = "cdrom:" + _cdOldName + "/";
173   }
174
175   string Type;
176
177   if (pkg)
178      Type = pkgSourceType().c_str();
179   else
180      Type = srcSourceType().c_str();
181
182   char Buffer[300];
183   int CurLine = 0;
184   bool First = true;
185   while (F.eof() == false) {
186      F.getline(Buffer, sizeof(Buffer));
187      CurLine++;
188      _strtabexpand(Buffer, sizeof(Buffer));
189      _strstrip(Buffer);
190
191      // Comment or blank
192      if (Buffer[0] == '#' || Buffer[0] == 0) {
193         Out << Buffer << endl;
194         continue;
195      }
196
197      if (First == true) {
198         for (vector<string>::iterator I = list.begin(); I != list.end();
199              I++) {
200            string::size_type Space = (*I).find(' ');
201            if (Space == string::npos)
202               return _error->Error(_("Internal error"));
203            Out << Type << " cdrom:[" << _cdName << "]/" << string(*I, 0,
204                                                                   Space) <<
205               " " << string(*I, Space + 1) << endl;
206         }
207      }
208      First = false;
209
210      // Grok it
211      string cType;
212      string URI;
213      const char *C = Buffer;
214      if (ParseQuoteWord(C, cType) == false || ParseQuoteWord(C, URI) == false) {
215         Out << Buffer << endl;
216         continue;
217      }
218      // Omit lines like this one
219      if (cType != Type
220          || (string(URI, 0, ShortURI.length()) != ShortURI &&
221              string(URI, 0, ShortURI.length()) != ShortURI2 &&
222              (_cdOldName.empty()
223               || (string(URI, 0, ShortOldURI.length()) != ShortOldURI &&
224                   string(URI, 0, ShortOldURI.length()) != ShortOldURI2)))) {
225         Out << Buffer << endl;
226         continue;
227      }
228   }
229
230   // Just in case the file was empty
231   if (First == true) {
232      for (vector<string>::iterator I = list.begin(); I != list.end(); I++) {
233         string::size_type Space = (*I).find(' ');
234         if (Space == string::npos)
235            return _error->Error(_("Internal error"));
236
237         Out << Type << " cdrom:[" << _cdName << "]/" << string(*I, 0,
238                                                                Space) << " "
239            << string(*I, Space + 1) << endl;
240      }
241   }
242
243   Out.close();
244
245   rename(File.c_str(), string(File + '~').c_str());
246   if (rename(NewFile.c_str(), File.c_str()) != 0)
247      return _error->Errno("rename", _("Failed to rename %s.new to %s"),
248                           File.c_str(), File.c_str());
249
250   return true;
251}
252
253
254bool RCDScanner::start(RCDScanProgress *progress)
255{
256   _cdName = "";
257   _scannedOk = false;
258
259   progress->setTotal(STEP_LAST);
260   progress->update(_("Preparing..."), STEP_PREPARE);
261
262   // Startup
263   string CDROM = _config->FindDir("Acquire::cdrom::mount", "/mnt/cdrom/");
264   if (CDROM[0] == '.')
265      CDROM = SafeGetCWD() + '/' + CDROM;
266   string DEVICE = _config->FindDir("Acquire::cdrom::device", "/dev/cdrom/");
267
268   if (!_database)
269      _database = new Configuration();
270
271   string DFile = _config->FindFile("Dir::State::cdroms");
272   if (FileExists(DFile) == true) {
273      if (ReadConfigFile(*_database, DFile) == false) {
274         return _error->Error(_("Unable to read the cdrom database %s"),
275                              DFile.c_str());
276      }
277   }
278   // Unmount the CD and get the user to put in the one they want
279   _cdromMounted = false;
280   if (_config->FindB("APT::CDROM::NoMount", false) == false) {
281      progress->update(_("Unmounting CD-ROM..."), STEP_UNMOUNT);
282      UnmountCdrom(CDROM);
283
284      progress->update(_("Waiting for disc..."), STEP_WAIT);
285      if (_userDialog->proceed(_("Insert a disc in the drive.")) == false)
286         return false;
287
288      // Mount the new CDROM
289      progress->update(_("Mounting CD-ROM..."), STEP_MOUNT);
290
291      if (MountCdrom(CDROM, DEVICE) == false)
292         return _error->Error(_("Failed to mount the cdrom."));
293      _cdromMounted = true;
294   }
295
296   progress->update(_("Identifying disc..."), STEP_IDENT);
297
298   if (!IdentCdrom(CDROM, _cdId)) {
299      return _error->Error(_("Couldn't identify disc."));
300   }
301
302   progress->update(_("Scanning disc..."), STEP_SCAN);
303
304   string cwd = SafeGetCWD();
305
306   _pkgList.clear();
307   _srcList.clear();
308   _infoDir = "";
309
310   if (!scanDirectory(CDROM, progress)) {
311      chdir(cwd.c_str());
312      return false;
313   }
314
315   chdir(cwd.c_str());
316
317   progress->update(_("Cleaning package lists..."), STEP_CLEAN);
318
319   cleanPkgList(_pkgList);
320   cleanSrcList(_srcList);
321
322   if (_pkgList.size() == 0 && _srcList.size() == 0) {
323      progress->update(_("Unmounting CD-ROM..."), STEP_UNMOUNT2);
324
325      if (_cdromMounted
326          && _config->FindB("APT::CDROM::NoMount", false) == false) {
327         UnmountCdrom(CDROM);
328         _cdromMounted = false;
329      }
330      return _error->Error(_("Unable to locate any package files. "
331                             "Perhaps this is not an APT enabled disc."));
332   }
333
334   _scannedOk = true;
335   return true;
336}
337
338void RCDScanner::countLists(int &pkgLists, int &srcLists)
339{
340   pkgLists = _pkgList.size();
341   srcLists = _srcList.size();
342}
343
344string RCDScanner::getDiscName()
345{
346   string name = "";
347
348   if (_database->Exists("CD::" + _cdId)) {
349      name = _database->Find("CD::" + _cdId, "");
350      _cdOldName = name;
351   } else if (!_infoDir.empty() && FileExists(_infoDir + "/info")) {
352      ifstream F(string(_infoDir + "/info").c_str());
353      if (!F == 0)
354         getline(F, name);
355
356      if (name.empty() == false) {
357         // Escape special characters
358         string::iterator J = name.begin();
359         for (; J != name.end(); J++)
360            if (*J == '"' || *J == ']' || *J == '[')
361               *J = '_';
362      }
363   }
364
365   return name;
366}
367
368bool RCDScanner::setDiscName(string name)
369{
370   if (name.empty() == true ||
371       name.find('"') != string::npos ||
372       name.find('[') != string::npos || name.find(']') != string::npos)
373      return false;
374   _cdName = name;
375   return true;
376}
377
378bool RCDScanner::finish(RCDScanProgress *progress)
379{
380   if (_scannedOk == false) {
381      return _error->Error(_("Disc not successfully scanned."));
382   }
383
384   if (_cdName.empty() == true) {
385      return _error->Error(_("Empty disc name."));
386   }
387
388   progress->update(_("Registering disc..."), STEP_REGISTER);
389
390   if (writeDatabase() == false) {
391      return false;
392   }
393   // Copy the package files to the state directory
394#ifdef HAVE_RPM
395   RPMPackageCopy Copy;
396   RPMSourceCopy SrcCopy;
397#else
398   PackageCopy Copy;
399   SourceCopy SrcCopy;
400#endif
401
402   progress->update(_("Copying package lists..."), STEP_COPY);
403
404   string CDROM = _config->FindDir("Acquire::cdrom::mount", "/cdrom/");
405
406   if (Copy.CopyPackages(CDROM, _cdName, _pkgList) == false ||
407       SrcCopy.CopyPackages(CDROM, _cdName, _srcList) == false) {
408      return false;
409   }
410
411   progress->update(_("Writing sources list..."), STEP_WRITE);
412
413   ReduceSourcelist(CDROM, _pkgList);
414   ReduceSourcelist(CDROM, _srcList);
415
416   if (!writeSourceList(_pkgList, true)
417       || !writeSourceList(_srcList, false)) {
418      return false;
419   }
420
421   if (_cdromMounted) {
422      progress->update(_("Unmounting CD-ROM..."), STEP_UNMOUNT3);
423      UnmountCdrom(CDROM);
424   }
425
426   progress->update(_("Done!"), STEP_LAST);
427
428   return true;
429}
430
431void RCDScanner::unmount()
432{
433   string CDROM = _config->FindDir("Acquire::cdrom::mount", "/cdrom/");
434   if (_cdromMounted)
435      UnmountCdrom(CDROM);
436}
437
438// DropBinaryArch - Dump dirs with a string like /binary-<foo>/         /*{{{*/
439// ---------------------------------------------------------------------
440/* Here we drop everything that is not this machines arch */
441bool DropBinaryArch(vector<string> &List)
442{
443   char S[300];
444   snprintf(S, sizeof(S), "/binary-%s/",
445            _config->Find("Apt::Architecture").c_str());
446
447   for (unsigned int I = 0; I < List.size(); I++) {
448      const char *Str = List[I].c_str();
449
450      const char *Res;
451      if ((Res = strstr(Str, "/binary-")) == 0)
452         continue;
453
454      // Weird, remove it.
455      if (strlen(Res) < strlen(S)) {
456         List.erase(List.begin() + I);
457         I--;
458         continue;
459      }
460      // See if it is our arch
461      if (stringcmp(Res, Res + strlen(S), S) == 0)
462         continue;
463
464      // Erase it
465      List.erase(List.begin() + I);
466      I--;
467   }
468
469   return true;
470}
471
472                                                                        /*}}} */
473// Score - We compute a 'score' for a path                              /*{{{*/
474// ---------------------------------------------------------------------
475/* Paths are scored based on how close they come to what I consider
476   normal. That is ones that have 'dist' 'stable' 'frozen' will score
477   higher than ones without. */
478int Score(string Path)
479{
480   int Res = 0;
481#ifdef HAVE_RPM
482   if (Path.find("base/") != string::npos)
483      Res = 1;
484#else
485   if (Path.find("stable/") != string::npos)
486      Res += 29;
487   if (Path.find("/binary-") != string::npos)
488      Res += 20;
489   if (Path.find("frozen/") != string::npos)
490      Res += 28;
491   if (Path.find("unstable/") != string::npos)
492      Res += 27;
493   if (Path.find("/dists/") != string::npos)
494      Res += 40;
495   if (Path.find("/main/") != string::npos)
496      Res += 20;
497   if (Path.find("/contrib/") != string::npos)
498      Res += 20;
499   if (Path.find("/non-free/") != string::npos)
500      Res += 20;
501   if (Path.find("/non-US/") != string::npos)
502      Res += 20;
503   if (Path.find("/source/") != string::npos)
504      Res += 10;
505   if (Path.find("/debian/") != string::npos)
506      Res -= 10;
507#endif
508   return Res;
509}
510
511                                                                        /*}}} */
512// DropRepeats - Drop repeated files resulting from symlinks            /*{{{*/
513// ---------------------------------------------------------------------
514/* Here we go and stat every file that we found and strip dup inodes. */
515bool DropRepeats(vector<string> &List, const char *Name)
516{
517   // Get a list of all the inodes
518   ino_t *Inodes = new ino_t[List.size()];
519   for (unsigned int I = 0; I != List.size(); I++) {
520      struct stat Buf;
521      if (stat((List[I]).c_str(), &Buf) != 0 &&
522          stat((List[I] + Name).c_str(), &Buf) != 0 &&
523          stat((List[I] + Name + ".gz").c_str(), &Buf) != 0)
524         _error->Errno("stat", _("Failed to stat %s%s"), List[I].c_str(),
525                       Name);
526      Inodes[I] = Buf.st_ino;
527   }
528
529   if (_error->PendingError() == true)
530      return false;
531
532   // Look for dups
533   for (unsigned int I = 0; I != List.size(); I++) {
534      for (unsigned int J = I + 1; J < List.size(); J++) {
535         // No match
536         if (Inodes[J] != Inodes[I])
537            continue;
538
539         // We score the two paths.. and erase one
540         int ScoreA = Score(List[I]);
541         int ScoreB = Score(List[J]);
542         if (ScoreA < ScoreB) {
543            List[I] = string();
544            break;
545         }
546
547         List[J] = string();
548      }
549   }
550
551   // Wipe erased entries
552   for (unsigned int I = 0; I < List.size();) {
553      if (List[I].empty() == false)
554         I++;
555      else
556         List.erase(List.begin() + I);
557   }
558
559   return true;
560}
561
562void RCDScanner::cleanPkgList(vector<string> &list)
563{
564#ifdef HAVE_RPM
565   DropRepeats(list, "pkglist");
566#else
567   DropBinaryArch(list);
568   DropRepeats(list, "Packages");
569#endif
570}
571
572void RCDScanner::cleanSrcList(vector<string> &list)
573{
574#ifdef HAVE_RPM
575   DropRepeats(list, "srclist");
576#else
577   DropRepeats(list, "Sources");
578#endif
579}
580
581string RCDScanner::pkgSourceType() const
582{
583#ifdef HAVE_RPM
584   return "rpm";
585#else
586   return "deb";
587#endif
588}
589
590string RCDScanner::srcSourceType() const
591{
592#ifdef HAVE_RPM
593   return "rpm-src";
594#else
595   return "deb-src";
596#endif
597}
598
599#if 0
600static int strrcmp_(const char *a, const char *b)
601{
602   int la = strlen(a);
603   int lb = strlen(b);
604
605   if (la == 0 || lb == 0)
606      return 0;
607
608   if (la > lb)
609      return strcmp(&a[la - lb], b);
610   else
611      return strcmp(&b[lb - la], a);
612}
613#endif
614
615bool RCDScanner::scanDirectory(string CD, RCDScanProgress *progress,
616                               int Depth)
617{
618   static ino_t Inodes[9];
619   if (Depth >= 7)
620      return true;
621
622   if (CD[CD.length() - 1] != '/')
623      CD += '/';
624
625   if (chdir(CD.c_str()) != 0)
626      return _error->Errno("chdir", _("Unable to change to %s"), CD.c_str());
627
628   // Look for a .disk subdirectory
629   struct stat Buf;
630   if (stat(".disk", &Buf) == 0) {
631      if (_infoDir.empty() == true)
632         _infoDir = CD + ".disk/";
633   }
634   // Don't look into directories that have been marked to ingore.
635   if (stat(".aptignr", &Buf) == 0)
636      return true;
637
638#ifdef HAVE_RPM
639   bool Found = false;
640   if (stat("release", &Buf) == 0)
641      Found = true;
642#else
643   /* Aha! We found some package files. We assume that everything under
644      this dir is controlled by those package files so we don't look down
645      anymore */
646   if (stat("Packages", &Buf) == 0 || stat("Packages.gz", &Buf) == 0) {
647      _pkgList.push_back(CD);
648
649      // Continue down if thorough is given
650      if (_config->FindB("APT::CDROM::Thorough", false) == false)
651         return true;
652   }
653   if (stat("Sources.gz", &Buf) == 0 || stat("Sources", &Buf) == 0) {
654      _srcList.push_back(CD);
655
656      // Continue down if thorough is given
657      if (_config->FindB("APT::CDROM::Thorough", false) == false)
658         return true;
659   }
660#endif
661
662   DIR *D = opendir(".");
663   if (D == 0)
664      return _error->Errno("opendir", _("Unable to read %s"), CD.c_str());
665
666   // Run over the directory
667   for (struct dirent * Dir = readdir(D); Dir != 0; Dir = readdir(D)) {
668      // Skip some files..
669      if (strcmp(Dir->d_name, ".") == 0 || strcmp(Dir->d_name, "..") == 0 ||
670          //strcmp(Dir->d_name,"source") == 0 ||
671          strcmp(Dir->d_name, ".disk") == 0 ||
672#ifdef HAVE_RPM
673          strncmp(Dir->d_name, "RPMS", 4) == 0 ||
674          strncmp(Dir->d_name, "doc", 3) == 0)
675#else
676          strcmp(Dir->d_name, "experimental") == 0 ||
677          strcmp(Dir->d_name, "binary-all") == 0)
678#endif
679         continue;
680
681#ifdef HAVE_RPM
682      if (strncmp(Dir->d_name, "pkglist.", 8) == 0 &&
683          strcmp(Dir->d_name + strlen(Dir->d_name) - 4, ".bz2") == 0) {
684         _pkgList.push_back(CD + string(Dir->d_name));
685         Found = true;
686         continue;
687      }
688      if (strncmp(Dir->d_name, "srclist.", 8) == 0 &&
689          strcmp(Dir->d_name + strlen(Dir->d_name) - 4, ".bz2") == 0) {
690         _srcList.push_back(CD + string(Dir->d_name));
691         Found = true;
692         continue;
693      }
694      if (_config->FindB("APT::CDROM::Thorough", false) == false &&
695          Found == true)
696         continue;
697#endif
698
699      // See if the name is a sub directory
700      struct stat Buf;
701      if (stat(Dir->d_name, &Buf) != 0)
702         continue;
703
704      if (S_ISDIR(Buf.st_mode) == 0)
705         continue;
706
707      int I;
708      for (I = 0; I != Depth; I++)
709         if (Inodes[I] == Buf.st_ino)
710            break;
711      if (I != Depth)
712         continue;
713
714      // Store the inodes weve seen
715      Inodes[Depth] = Buf.st_ino;
716
717      // Descend
718      if (scanDirectory(CD + Dir->d_name, progress, Depth + 1) == false)
719         break;
720
721      if (chdir(CD.c_str()) != 0)
722         return _error->Errno("chdir", _("Unable to change to %s"),
723                              CD.c_str());
724   }
725
726   closedir(D);
727
728   return !_error->PendingError();
729}
730
731#endif
732// vim:sts=4:sw=4
Note: See TracBrowser for help on using the repository browser.