]>
git.saurik.com Git - apt.git/blob - cmdline/apt-cdrom.cc
   1 // -*- mode: cpp; mode: fold -*- 
   3 // $Id: apt-cdrom.cc,v 1.28 1999/07/12 02:59:36 jgg Exp $ 
   4 /* ###################################################################### 
   6    APT CDROM - Tool for handling APT's CDROM database. 
   8    Currently the only option is 'add' which will take the current CD 
   9    in the drive and add it into the database. 
  11    ##################################################################### */ 
  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> 
  23 #include "indexcopy.h" 
  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 
  42 bool FindPackages(string CD
,vector
<string
> &List
,vector
<string
> &SList
, 
  43                   string 
&InfoDir
,unsigned int Depth 
= 0) 
  45    static ino_t Inodes
[9]; 
  49    if (CD
[CD
.length()-1] != '/') 
  52    if (chdir(CD
.c_str()) != 0) 
  53       return _error
->Errno("chdir","Unable to change to %s",CD
.c_str()); 
  55    // Look for a .disk subdirectory 
  57    if (stat(".disk",&Buf
) == 0) 
  59       if (InfoDir
.empty() == true) 
  60          InfoDir 
= CD 
+ ".disk/"; 
  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 
  66    if (stat("Packages",&Buf
) == 0)  
  70       // Continue down if thorough is given 
  71       if (_config
->FindB("APT::CDROM::Thorough",false) == false) 
  74    if (stat("Sources.gz",&Buf
) == 0 || stat("Sources",&Buf
) == 0) 
  78       // Continue down if thorough is given 
  79       if (_config
->FindB("APT::CDROM::Thorough",false) == false) 
  83    DIR *D 
= opendir("."); 
  85       return _error
->Errno("opendir","Unable to read %s",CD
.c_str()); 
  87    // Run over the directory 
  88    for (struct dirent 
*Dir 
= readdir(D
); Dir 
!= 0; Dir 
= readdir(D
)) 
  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
,"experimental") == 0 || 
  95           strcmp(Dir
->d_name
,"binary-all") == 0) 
  98       // See if the name is a sub directory 
 100       if (stat(Dir
->d_name
,&Buf
) != 0) 
 103       if (S_ISDIR(Buf
.st_mode
) == 0) 
 107       for (I 
= 0; I 
!= Depth
; I
++) 
 108          if (Inodes
[I
] == Buf
.st_ino
) 
 113       // Store the inodes weve seen 
 114       Inodes
[Depth
] = Buf
.st_ino
; 
 117       if (FindPackages(CD 
+ Dir
->d_name
,List
,SList
,InfoDir
,Depth
+1) == false) 
 120       if (chdir(CD
.c_str()) != 0) 
 121          return _error
->Errno("chdir","Unable to change to ",CD
.c_str()); 
 126    return !_error
->PendingError(); 
 129 // DropBinaryArch - Dump dirs with a string like /binary-<foo>/         /*{{{*/ 
 130 // --------------------------------------------------------------------- 
 131 /* Here we drop everything that is not this machines arch */ 
 132 bool DropBinaryArch(vector
<string
> &List
) 
 135    sprintf(S
,"/binary-%s/",_config
->Find("Apt::Architecture").c_str()); 
 137    for (unsigned int I 
= 0; I 
< List
.size(); I
++) 
 139       const char *Str 
= List
[I
].c_str(); 
 142       if ((Res 
= strstr(Str
,"/binary-")) == 0) 
 146       if (strlen(Res
) < strlen(S
)) 
 148          List
.erase(List
.begin() + I
); 
 153       // See if it is our arch 
 154       if (stringcmp(Res
,Res 
+ strlen(S
),S
) == 0) 
 158       List
.erase(List
.begin() + I
); 
 165 // Score - We compute a 'score' for a path                              /*{{{*/ 
 166 // --------------------------------------------------------------------- 
 167 /* Paths are scored based on how close they come to what I consider 
 168    normal. That is ones that have 'dist' 'stable' 'frozen' will score 
 169    higher than ones without. */ 
 170 int Score(string Path
) 
 173    if (Path
.find("stable/") != string::npos
) 
 175    if (Path
.find("/binary-") != string::npos
) 
 177    if (Path
.find("frozen/") != string::npos
) 
 179    if (Path
.find("/dists/") != string::npos
) 
 181    if (Path
.find("/main/") != string::npos
) 
 183    if (Path
.find("/contrib/") != string::npos
) 
 185    if (Path
.find("/non-free/") != string::npos
) 
 187    if (Path
.find("/non-US/") != string::npos
) 
 189    if (Path
.find("/source/") != string::npos
) 
 194 // DropRepeats - Drop repeated files resulting from symlinks            /*{{{*/ 
 195 // --------------------------------------------------------------------- 
 196 /* Here we go and stat every file that we found and strip dup inodes. */ 
 197 bool DropRepeats(vector
<string
> &List
,const char *Name
) 
 199    // Get a list of all the inodes 
 200    ino_t 
*Inodes 
= new ino_t
[List
.size()]; 
 201    for (unsigned int I 
= 0; I 
!= List
.size(); I
++) 
 204       if (stat((List
[I
] + Name
).c_str(),&Buf
) != 0 && 
 205           stat((List
[I
] + Name 
+ ".gz").c_str(),&Buf
) != 0) 
 206          _error
->Errno("stat","Failed to stat %s%s",List
[I
].c_str(), 
 208       Inodes
[I
] = Buf
.st_ino
; 
 211    if (_error
->PendingError() == true) 
 215    for (unsigned int I 
= 0; I 
!= List
.size(); I
++) 
 217       for (unsigned int J 
= I
+1; J 
< List
.size(); J
++) 
 220          if (Inodes
[J
] != Inodes
[I
]) 
 223          // We score the two paths.. and erase one 
 224          int ScoreA 
= Score(List
[I
]); 
 225          int ScoreB 
= Score(List
[J
]); 
 236    // Wipe erased entries 
 237    for (unsigned int I 
= 0; I 
< List
.size();) 
 239       if (List
[I
].empty() == false) 
 242          List
.erase(List
.begin()+I
); 
 249 // ReduceSourceList - Takes the path list and reduces it                /*{{{*/ 
 250 // --------------------------------------------------------------------- 
 251 /* This takes the list of source list expressed entires and collects 
 252    similar ones to form a single entry for each dist */ 
 253 bool ReduceSourcelist(string CD
,vector
<string
> &List
) 
 255    sort(List
.begin(),List
.end()); 
 257    // Collect similar entries 
 258    for (vector
<string
>::iterator I 
= List
.begin(); I 
!= List
.end(); I
++) 
 261       string::size_type Space 
= (*I
).find(' '); 
 262       if (Space 
== string::npos
) 
 264       string::size_type SSpace 
= (*I
).find(' ',Space 
+ 1); 
 265       if (SSpace 
== string::npos
) 
 268       string Word1 
= string(*I
,Space
,SSpace
-Space
); 
 269       for (vector
<string
>::iterator J 
= List
.begin(); J 
!= I
; J
++) 
 272          string::size_type Space2 
= (*J
).find(' '); 
 273          if (Space2 
== string::npos
) 
 275          string::size_type SSpace2 
= (*J
).find(' ',Space2 
+ 1); 
 276          if (SSpace2 
== string::npos
) 
 279          if (string(*J
,Space2
,SSpace2
-Space2
) != Word1
) 
 282          *J 
+= string(*I
,SSpace
); 
 287    // Wipe erased entries 
 288    for (unsigned int I 
= 0; I 
< List
.size();) 
 290       if (List
[I
].empty() == false) 
 293          List
.erase(List
.begin()+I
); 
 297 // WriteDatabase - Write the CDROM Database file                        /*{{{*/ 
 298 // --------------------------------------------------------------------- 
 299 /* We rewrite the configuration class associated with the cdrom database. */ 
 300 bool WriteDatabase(Configuration 
&Cnf
) 
 302    string DFile 
= _config
->FindFile("Dir::State::cdroms"); 
 303    string NewFile 
= DFile 
+ ".new"; 
 305    unlink(NewFile
.c_str()); 
 306    ofstream 
Out(NewFile
.c_str()); 
 308       return _error
->Errno("ofstream::ofstream", 
 309                            "Failed to open %s.new",DFile
.c_str()); 
 311    /* Write out all of the configuration directives by walking the 
 312       configuration tree */ 
 313    const Configuration::Item 
*Top 
= Cnf
.Tree(0); 
 316       // Print the config entry 
 317       if (Top
->Value
.empty() == false) 
 318          Out 
<<  Top
->FullTag() + " \"" << Top
->Value 
<< "\";" << endl
; 
 326       while (Top 
!= 0 && Top
->Next 
== 0) 
 334    rename(DFile
.c_str(),string(DFile 
+ '~').c_str()); 
 335    if (rename(NewFile
.c_str(),DFile
.c_str()) != 0) 
 336       return _error
->Errno("rename","Failed to rename %s.new to %s", 
 337                            DFile
.c_str(),DFile
.c_str()); 
 342 // WriteSourceList - Write an updated sourcelist                        /*{{{*/ 
 343 // --------------------------------------------------------------------- 
 344 /* This reads the old source list and copies it into the new one. It  
 345    appends the new CDROM entires just after the first block of comments. 
 346    This places them first in the file. It also removes any old entries 
 347    that were the same. */ 
 348 bool WriteSourceList(string Name
,vector
<string
> &List
,bool Source
) 
 350    if (List
.size() == 0) 
 353    string File 
= _config
->FindFile("Dir::Etc::sourcelist"); 
 355    // Open the stream for reading 
 356    ifstream 
F(File
.c_str(),ios::in 
| ios::nocreate
); 
 358       return _error
->Errno("ifstream::ifstream","Opening %s",File
.c_str()); 
 360    string NewFile 
= File 
+ ".new"; 
 361    unlink(NewFile
.c_str()); 
 362    ofstream 
Out(NewFile
.c_str()); 
 364       return _error
->Errno("ofstream::ofstream", 
 365                            "Failed to open %s.new",File
.c_str()); 
 367    // Create a short uri without the path 
 368    string ShortURI 
= "cdrom:" + Name 
+ "/";    
 379    while (F
.eof() == false) 
 381       F
.getline(Buffer
,sizeof(Buffer
)); 
 383       _strtabexpand(Buffer
,sizeof(Buffer
)); 
 387       if (Buffer
[0] == '#' || Buffer
[0] == 0) 
 389          Out 
<< Buffer 
<< endl
; 
 395          for (vector
<string
>::iterator I 
= List
.begin(); I 
!= List
.end(); I
++) 
 397             string::size_type Space 
= (*I
).find(' '); 
 398             if (Space 
== string::npos
) 
 399                return _error
->Error("Internal error"); 
 400             Out 
<< Type 
<< " \"cdrom:" << Name 
<< "/" << string(*I
,0,Space
) <<  
 401                "\" " << string(*I
,Space
+1) << endl
; 
 410       if (ParseQuoteWord(C
,cType
) == false || 
 411           ParseQuoteWord(C
,URI
) == false) 
 413          Out 
<< Buffer 
<< endl
; 
 417       // Emit lines like this one 
 418       if (cType 
!= Type 
|| string(URI
,0,ShortURI
.length()) != ShortURI
) 
 420          Out 
<< Buffer 
<< endl
; 
 425    // Just in case the file was empty 
 428       for (vector
<string
>::iterator I 
= List
.begin(); I 
!= List
.end(); I
++) 
 430          string::size_type Space 
= (*I
).find(' '); 
 431          if (Space 
== string::npos
) 
 432             return _error
->Error("Internal error"); 
 434          Out 
<< "deb \"cdrom:" << Name 
<< "/" << string(*I
,0,Space
) <<  
 435             "\" " << string(*I
,Space
+1) << endl
; 
 441    rename(File
.c_str(),string(File 
+ '~').c_str()); 
 442    if (rename(NewFile
.c_str(),File
.c_str()) != 0) 
 443       return _error
->Errno("rename","Failed to rename %s.new to %s", 
 444                            File
.c_str(),File
.c_str()); 
 450 // Prompt - Simple prompt                                               /*{{{*/ 
 451 // --------------------------------------------------------------------- 
 453 void Prompt(const char *Text
) 
 456    cout 
<< Text 
<< ' ' << flush
; 
 457    read(STDIN_FILENO
,&C
,1); 
 462 // PromptLine - Prompt for an input line                                /*{{{*/ 
 463 // --------------------------------------------------------------------- 
 465 string 
PromptLine(const char *Text
) 
 467    cout 
<< Text 
<< ':' << endl
; 
 475 // DoAdd - Add a new CDROM                                              /*{{{*/ 
 476 // --------------------------------------------------------------------- 
 477 /* This does the main add bit.. We show some status and things. The 
 478    sequence is to mount/umount the CD, Ident it then scan it for package  
 479    files and reduce that list. Then we copy over the package files and 
 480    verify them. Then rewrite the database files */ 
 481 bool DoAdd(CommandLine 
&) 
 484    string CDROM 
= _config
->FindDir("Acquire::cdrom::mount","/cdrom/"); 
 486       CDROM
= SafeGetCWD() + '/' + CDROM
; 
 488    cout 
<< "Using CD-ROM mount point " << CDROM 
<< endl
; 
 491    Configuration Database
; 
 492    string DFile 
= _config
->FindFile("Dir::State::cdroms"); 
 493    if (FileExists(DFile
) == true) 
 495       if (ReadConfigFile(Database
,DFile
) == false) 
 496          return _error
->Error("Unable to read the cdrom database %s", 
 500    // Unmount the CD and get the user to put in the one they want 
 501    if (_config
->FindB("APT::CDROM::NoMount",false) == false) 
 503       cout 
<< "Unmounting CD-ROM" << endl
; 
 506       // Mount the new CDROM 
 507       Prompt("Please insert a Disc in the drive and press enter"); 
 508       cout 
<< "Mounting CD-ROM" << endl
; 
 509       if (MountCdrom(CDROM
) == false) 
 510          return _error
->Error("Failed to mount the cdrom."); 
 513    // Hash the CD to get an ID 
 514    cout 
<< "Identifying.. " << flush
; 
 516    if (IdentCdrom(CDROM
,ID
) == false) 
 522    cout 
<< '[' << ID 
<< ']' << endl
; 
 524    cout 
<< "Scanning Disc for index files..  " << flush
; 
 525    // Get the CD structure 
 527    vector
<string
> sList
; 
 528    string StartDir 
= SafeGetCWD(); 
 530    if (FindPackages(CDROM
,List
,sList
,InfoDir
) == false) 
 536    chdir(StartDir
.c_str()); 
 538    if (_config
->FindB("Debug::aptcdrom",false) == true) 
 540       cout 
<< "I found:" << endl
; 
 541       for (vector
<string
>::iterator I 
= List
.begin(); I 
!= List
.end(); I
++) 
 548    DropBinaryArch(List
); 
 549    DropRepeats(List
,"Packages"); 
 550    DropRepeats(sList
,"Sources"); 
 551    cout 
<< "Found " << List
.size() << " package indexes and " << sList
.size() <<  
 552       " source indexes." << endl
; 
 554    if (List
.size() == 0) 
 555       return _error
->Error("Unable to locate any package files, perhaps this is not a Debian Disc"); 
 557    // Check if the CD is in the database 
 559    if (Database
.Exists("CD::" + ID
) == false || 
 560        _config
->FindB("APT::CDROM::Rename",false) == true) 
 562       // Try to use the CDs label if at all possible 
 563       if (InfoDir
.empty() == false && 
 564           FileExists(InfoDir 
+ "/info") == true) 
 566          ifstream 
F(string(InfoDir 
+ "/info").c_str()); 
 570          if (Name
.empty() == false) 
 572             cout 
<< "Found label '" << Name 
<< "'" << endl
; 
 573             Database
.Set("CD::" + ID 
+ "::Label",Name
); 
 577       if (_config
->FindB("APT::CDROM::Rename",false) == true || 
 578           Name
.empty() == true) 
 580          cout 
<< "Please provide a name for this Disc, such as 'Debian 2.1r1 Disk 1'"; 
 583             Name 
= PromptLine(""); 
 584             if (Name
.empty() == false && 
 585                 Name
.find('"') == string::npos 
&& 
 586                 Name
.find(':') == string::npos 
&& 
 587                 Name
.find('/') == string::npos
) 
 589             cout 
<< "That is not a valid name, try again " << endl
; 
 594       Name 
= Database
.Find("CD::" + ID
); 
 596    string::iterator J 
= Name
.begin(); 
 597    for (; J 
!= Name
.end(); J
++) 
 598       if (*J 
== '/' || *J 
== '"' || *J 
== ':') 
 601    Database
.Set("CD::" + ID
,Name
); 
 602    cout 
<< "This Disc is called '" << Name 
<< "'" << endl
; 
 604    // Copy the package files to the state directory 
 607    if (Copy
.CopyPackages(CDROM
,Name
,List
) == false || 
 608        SrcCopy
.CopyPackages(CDROM
,Name
,sList
) == false) 
 611    ReduceSourcelist(CDROM
,List
); 
 612    ReduceSourcelist(CDROM
,sList
); 
 614    // Write the database and sourcelist 
 615    if (_config
->FindB("APT::cdrom::NoAct",false) == false) 
 617       if (WriteDatabase(Database
) == false) 
 620       cout 
<< "Writing new source list" << endl
; 
 621       if (WriteSourceList(Name
,List
,false) == false || 
 622           WriteSourceList(Name
,sList
,true) == false) 
 626    // Print the sourcelist entries 
 627    cout 
<< "Source List entries for this Disc are:" << endl
; 
 628    for (vector
<string
>::iterator I 
= List
.begin(); I 
!= List
.end(); I
++) 
 630       string::size_type Space 
= (*I
).find(' '); 
 631       if (Space 
== string::npos
) 
 632          return _error
->Error("Internal error"); 
 634       cout 
<< "deb \"cdrom:" << Name 
<< "/" << string(*I
,0,Space
) <<  
 635          "\" " << string(*I
,Space
+1) << endl
; 
 638    for (vector
<string
>::iterator I 
= sList
.begin(); I 
!= sList
.end(); I
++) 
 640       string::size_type Space 
= (*I
).find(' '); 
 641       if (Space 
== string::npos
) 
 642          return _error
->Error("Internal error"); 
 644       cout 
<< "deb-src \"cdrom:" << Name 
<< "/" << string(*I
,0,Space
) <<  
 645          "\" " << string(*I
,Space
+1) << endl
; 
 648    cout 
<< "Repeat this process for the rest of the CDs in your set." << endl
; 
 653 // ShowHelp - Show the help screen                                      /*{{{*/ 
 654 // --------------------------------------------------------------------- 
 658    cout 
<< PACKAGE 
<< ' ' << VERSION 
<< " for " << ARCHITECTURE 
<< 
 659        " compiled on " << __DATE__ 
<< "  " << __TIME__ 
<< endl
; 
 660    if (_config
->FindB("version") == true) 
 663    cout 
<< "Usage: apt-cdrom [options] command" << endl
; 
 665    cout 
<< "apt-cdrom is a tool to add CDROM's to APT's source list. The " << endl
; 
 666    cout 
<< "CDROM mount point and device information is taken from apt.conf" << endl
; 
 667    cout 
<< "and /etc/fstab." << endl
; 
 669    cout 
<< "Commands:" << endl
; 
 670    cout 
<< "   add - Add a CDROM" << endl
; 
 672    cout 
<< "Options:" << endl
; 
 673    cout 
<< "  -h   This help text" << endl
; 
 674    cout 
<< "  -d   CD-ROM mount point" << endl
; 
 675    cout 
<< "  -r   Rename a recognized CD-ROM" << endl
; 
 676    cout 
<< "  -m   No mounting" << endl
; 
 677    cout 
<< "  -f   Fast mode, don't check package files" << endl
; 
 678    cout 
<< "  -a   Thorough scan mode" << endl
; 
 679    cout 
<< "  -c=? Read this configuration file" << endl
; 
 680    cout 
<< "  -o=? Set an arbitary configuration option, eg -o dir::cache=/tmp" << endl
; 
 681    cout 
<< "See fstab(5)" << endl
; 
 686 int main(int argc
,const char *argv
[]) 
 688    CommandLine::Args Args
[] = { 
 689       {'h',"help","help",0}, 
 690       {'v',"version","version",0}, 
 691       {'d',"cdrom","Acquire::cdrom::mount",CommandLine::HasArg
}, 
 692       {'r',"rename","APT::CDROM::Rename",0}, 
 693       {'m',"no-mount","APT::CDROM::NoMount",0}, 
 694       {'f',"fast","APT::CDROM::Fast",0}, 
 695       {'n',"just-print","APT::CDROM::NoAct",0}, 
 696       {'n',"recon","APT::CDROM::NoAct",0},       
 697       {'n',"no-act","APT::CDROM::NoAct",0}, 
 698       {'a',"thorough","APT::CDROM::Thorough",0}, 
 699       {'c',"config-file",0,CommandLine::ConfigFile
}, 
 700       {'o',"option",0,CommandLine::ArbItem
}, 
 702    CommandLine::Dispatch Cmds
[] = { 
 706    // Parse the command line and initialize the package library 
 707    CommandLine 
CmdL(Args
,_config
); 
 708    if (pkgInitialize(*_config
) == false || 
 709        CmdL
.Parse(argc
,argv
) == false) 
 711       _error
->DumpErrors(); 
 715    // See if the help should be shown 
 716    if (_config
->FindB("help") == true || 
 717        CmdL
.FileSize() == 0) 
 720    // Deal with stdout not being a tty 
 721    if (ttyname(STDOUT_FILENO
) == 0 && _config
->FindI("quiet",0) < 1) 
 722       _config
->Set("quiet","1"); 
 724    // Match the operation 
 725    CmdL
.DispatchArg(Cmds
); 
 727    // Print any errors or warnings found during parsing 
 728    if (_error
->empty() == false) 
 730       bool Errors 
= _error
->PendingError(); 
 731       _error
->DumpErrors(); 
 732       return Errors 
== true?100:0;