summaryrefslogtreecommitdiff
path: root/cmdline/apt-cdrom.cc
diff options
context:
space:
mode:
authorArch Librarian <arch@canonical.com>2004-09-20 16:51:42 +0000
committerArch Librarian <arch@canonical.com>2004-09-20 16:51:42 +0000
commit83d89a9f6a641adccce898eb0bc675d94bc46465 (patch)
treec4f3eb7e5469264794c6e2e87cef54da9513ca2a /cmdline/apt-cdrom.cc
parenta05599f12fd30388ee972ed2535d5297afe0c20b (diff)
apt-cdrom
Author: jgg Date: 1998-11-27 01:52:53 GMT apt-cdrom
Diffstat (limited to 'cmdline/apt-cdrom.cc')
-rw-r--r--cmdline/apt-cdrom.cc706
1 files changed, 706 insertions, 0 deletions
diff --git a/cmdline/apt-cdrom.cc b/cmdline/apt-cdrom.cc
new file mode 100644
index 000000000..1839fdab4
--- /dev/null
+++ b/cmdline/apt-cdrom.cc
@@ -0,0 +1,706 @@
+// -*- mode: cpp; mode: fold -*-
+// Description /*{{{*/
+// $Id: apt-cdrom.cc,v 1.1 1998/11/27 01:52:56 jgg Exp $
+/* ######################################################################
+
+
+ ##################################################################### */
+ /*}}}*/
+// Include Files /*{{{*/
+#include <apt-pkg/cmndline.h>
+#include <apt-pkg/error.h>
+#include <apt-pkg/init.h>
+#include <apt-pkg/md5.h>
+#include <apt-pkg/fileutl.h>
+#include <apt-pkg/progress.h>
+#include <apt-pkg/tagfile.h>
+#include <strutl.h>
+#include <config.h>
+
+#include <iostream>
+#include <vector>
+#include <algorithm>
+#include <sys/wait.h>
+#include <sys/errno.h>
+#include <sys/vfs.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <stdio.h>
+ /*}}}*/
+
+// UnmountCdrom - Unmount a cdrom /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool UnmountCdrom(string Path)
+{
+ int Child = fork();
+ if (Child < -1)
+ return _error->Errno("fork","Failed to fork");
+
+ // The child
+ if (Child == 0)
+ {
+ // Make all the fds /dev/null
+ for (int I = 0; I != 10;)
+ close(I);
+ for (int I = 0; I != 3;)
+ dup2(open("/dev/null",O_RDWR),I);
+
+ const char *Args[10];
+ Args[0] = "umount";
+ Args[1] = Path.c_str();
+ Args[2] = 0;
+ execvp(Args[0],(char **)Args);
+ exit(100);
+ }
+
+ // Wait for mount
+ int Status = 0;
+ while (waitpid(Child,&Status,0) != Child)
+ {
+ if (errno == EINTR)
+ continue;
+ return _error->Errno("waitpid","Couldn't wait for subprocess");
+ }
+
+ // Check for an error code.
+ if (WIFEXITED(Status) == 0 || WEXITSTATUS(Status) != 0)
+ return false;
+ return true;
+}
+ /*}}}*/
+// MountCdrom - Mount a cdrom /*{{{*/
+// ---------------------------------------------------------------------
+/* We fork mount.. */
+bool MountCdrom(string Path)
+{
+ int Child = fork();
+ if (Child < -1)
+ return _error->Errno("fork","Failed to fork");
+
+ // The child
+ if (Child == 0)
+ {
+ // Make all the fds /dev/null
+ for (int I = 0; I != 10;)
+ close(I);
+ for (int I = 0; I != 3;)
+ dup2(open("/dev/null",O_RDWR),I);
+
+ const char *Args[10];
+ Args[0] = "mount";
+ Args[1] = Path.c_str();
+ Args[2] = 0;
+ execvp(Args[0],(char **)Args);
+ exit(100);
+ }
+
+ // Wait for mount
+ int Status = 0;
+ while (waitpid(Child,&Status,0) != Child)
+ {
+ if (errno == EINTR)
+ continue;
+ return _error->Errno("waitpid","Couldn't wait for subprocess");
+ }
+
+ // Check for an error code.
+ if (WIFEXITED(Status) == 0 || WEXITSTATUS(Status) != 0)
+ return false;
+ return true;
+}
+ /*}}}*/
+// IdentCdrom - Generate a unique string for this CD /*{{{*/
+// ---------------------------------------------------------------------
+/* We convert everything we hash into a string, this prevents byte size/order
+ from effecting the outcome. */
+bool IdentCdrom(string CD,string &Res)
+{
+ MD5Summation Hash;
+
+ string StartDir = SafeGetCWD();
+ if (chdir(CD.c_str()) != 0)
+ return _error->Errno("chdir","Unable to change to %s",CD.c_str());
+
+ DIR *D = opendir(".");
+ if (D == 0)
+ return _error->Errno("opendir","Unable to read %s",CD.c_str());
+
+ // Run over the directory
+ char S[300];
+ for (struct dirent *Dir = readdir(D); Dir != 0; Dir = readdir(D))
+ {
+ // Skip some files..
+ if (strcmp(Dir->d_name,".") == 0 ||
+ strcmp(Dir->d_name,"..") == 0)
+ continue;
+
+ sprintf(S,"%lu",Dir->d_ino);
+ Hash.Add(S);
+ Hash.Add(Dir->d_name);
+ };
+
+ chdir(StartDir.c_str());
+ closedir(D);
+
+ // Some stats from the fsys
+ struct statfs Buf;
+ if (statfs(CD.c_str(),&Buf) != 0)
+ return _error->Errno("statfs","Failed to stat the cdrom");
+
+ sprintf(S,"%u %u",Buf.f_blocks,Buf.f_bfree);
+ Hash.Add(S);
+
+ Res = Hash.Result().Value();
+ return true;
+}
+ /*}}}*/
+
+// FindPackage - Find the package files on the CDROM /*{{{*/
+// ---------------------------------------------------------------------
+/* We look over the cdrom for package files. This is a recursive
+ search that short circuits when it his a package file in the dir.
+ This speeds it up greatly as the majority of the size is in the
+ binary-* sub dirs. */
+bool FindPackages(string CD,vector<string> &List, int Depth = 0)
+{
+ if (Depth >= 5)
+ return true;
+
+ if (CD[CD.length()-1] != '/')
+ CD += '/';
+
+ if (chdir(CD.c_str()) != 0)
+ return _error->Errno("chdir","Unable to change to %s",CD.c_str());
+
+ /* Aha! We found some package files. We assume that everything under
+ this dir is controlled by those package files so we don't look down
+ anymore */
+ struct stat Buf;
+ if (stat("Packages",&Buf) == 0 ||
+ stat("Packages.gz",&Buf) == 0)
+ {
+ List.push_back(CD);
+ return true;
+ }
+
+ DIR *D = opendir(".");
+ if (D == 0)
+ return _error->Errno("opendir","Unable to read %s",CD.c_str());
+
+ // Run over the directory
+ for (struct dirent *Dir = readdir(D); Dir != 0; Dir = readdir(D))
+ {
+ // Skip some files..
+ if (strcmp(Dir->d_name,".") == 0 ||
+ strcmp(Dir->d_name,"..") == 0 ||
+ strcmp(Dir->d_name,"source") == 0 ||
+ strcmp(Dir->d_name,"experimental") == 0 ||
+ strcmp(Dir->d_name,"binary-all") == 0)
+ continue;
+
+ // See if the name is a sub directory
+ struct stat Buf;
+ if (stat(Dir->d_name,&Buf) != 0)
+ {
+ _error->Errno("Stat","Stat failed for %s",Dir->d_name);
+ break;
+ }
+
+ if (S_ISDIR(Buf.st_mode) == 0)
+ continue;
+
+ // Descend
+ if (FindPackages(CD + Dir->d_name,List,Depth+1) == false)
+ break;
+
+ if (chdir(CD.c_str()) != 0)
+ return _error->Errno("chdir","Unable to change to ",CD.c_str());
+ };
+
+ closedir(D);
+
+ return !_error->PendingError();
+}
+ /*}}}*/
+// CopyPackages - Copy the package files from the CD /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool CopyPackages(string CDROM,string Name,vector<string> &List)
+{
+ OpTextProgress Progress;
+
+ bool NoStat = _config->FindB("APT::CDROM::Fast",false);
+
+ // Prepare the progress indicator
+ unsigned long TotalSize = 0;
+ for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
+ {
+ struct stat Buf;
+ if (stat(string(*I + "Packages").c_str(),&Buf) != 0)
+ return _error->Errno("stat","Stat failed for %s",
+ string(*I + "Packages").c_str());
+ TotalSize += Buf.st_size;
+ }
+
+ unsigned long CurrentSize = 0;
+ unsigned int NotFound = 0;
+ unsigned int WrongSize = 0;
+ unsigned int Packages = 0;
+ for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
+ {
+ // Open the package file
+ FileFd Pkg(*I + "Packages",FileFd::ReadOnly);
+ pkgTagFile Parser(Pkg);
+ if (_error->PendingError() == true)
+ return false;
+
+ // Open the output file
+ char S[400];
+ sprintf(S,"cdrom:%s/%sPackages",Name.c_str(),(*I).c_str() + CDROM.length());
+ string TargetF = _config->FindDir("Dir::State::lists") + "partial/";
+ FileFd Target(TargetF + URItoFileName(S),FileFd::WriteEmpty);
+ if (_error->PendingError() == true)
+ return false;
+
+ // Setup the progress meter
+ Progress.OverallProgress(CurrentSize,TotalSize,Pkg.Size(),
+ "Reading Package Lists");
+
+ // Parse
+ Progress.SubProgress(Pkg.Size());
+ pkgTagSection Section;
+ while (Parser.Step(Section) == true)
+ {
+ Progress.Progress(Parser.Offset());
+
+ string File = Section.FindS("Filename");
+ unsigned long Size = Section.FindI("Size");
+ if (File.empty() || Size == 0)
+ return _error->Error("Cannot find filename or size tag");
+
+ // See if the file exists
+ if (NoStat == false)
+ {
+ struct stat Buf;
+ File = CDROM + File;
+ if (stat(File.c_str(),&Buf) != 0)
+ {
+ NotFound++;
+ continue;
+ }
+
+ // Size match
+ if ((unsigned)Buf.st_size != Size)
+ {
+ WrongSize++;
+ continue;
+ }
+ }
+
+ Packages++;
+
+ // Copy it to the target package file
+ const char *Start;
+ const char *Stop;
+ Section.GetSection(Start,Stop);
+ if (Target.Write(Start,Stop-Start) == false)
+ return false;
+ }
+
+ CurrentSize += Pkg.Size();
+ }
+ Progress.Done();
+
+ // Some stats
+ cout << "Wrote " << Packages << " package records" ;
+ if (NotFound != 0)
+ cout << " with " << NotFound << " missing files";
+ if (NotFound != 0 && WrongSize != 0)
+ cout << " and";
+ if (WrongSize != 0)
+ cout << " with " << WrongSize << " mismatched files";
+ cout << '.' << endl;
+ if (NotFound + WrongSize > 10)
+ cout << "Alot of package entires were discarded, perhaps this CD is funny?" << endl;
+}
+ /*}}}*/
+// DropBinaryArch - Dump dirs with a string like /binary-<foo>/ /*{{{*/
+// ---------------------------------------------------------------------
+/* Here we drop everything that is not this machines arch */
+bool DropBinaryArch(vector<string> &List)
+{
+ char S[300];
+ sprintf(S,"/binary-%s/",_config->Find("Apt::Architecture").c_str());
+
+ for (unsigned int I = 0; I < List.size(); I++)
+ {
+ const char *Str = List[I].c_str();
+
+ const char *Res;
+ if ((Res = strstr(Str,"/binary-")) == 0)
+ continue;
+
+ // Weird, remove it.
+ if (strlen(Res) < strlen(S))
+ {
+ List.erase(List.begin() + I);
+ I--;
+ continue;
+ }
+
+ // See if it is our arch
+ if (stringcmp(Res,Res + strlen(S),S) == 0)
+ continue;
+
+ // Erase it
+ List.erase(List.begin() + I);
+ I--;
+ }
+
+ return true;
+}
+ /*}}}*/
+// Score - We compute a 'score' for a path /*{{{*/
+// ---------------------------------------------------------------------
+/* Paths are scored based on how close they come to what I consider
+ normal. That is ones that have 'dist' 'stable' 'frozen' will score
+ higher than ones without. */
+int Score(string Path)
+{
+ int Res = 0;
+ if (Path.find("stable/") != string::npos)
+ Res += 2;
+ if (Path.find("frozen/") != string::npos)
+ Res += 2;
+ if (Path.find("/dists/") != string::npos)
+ Res += 4;
+ if (Path.find("/main/") != string::npos)
+ Res += 2;
+ if (Path.find("/contrib/") != string::npos)
+ Res += 2;
+ if (Path.find("/non-free/") != string::npos)
+ Res += 2;
+ if (Path.find("/non-US/") != string::npos)
+ Res += 2;
+ return Res;
+}
+ /*}}}*/
+// DropRepeats - Drop repeated files resulting from symlinks /*{{{*/
+// ---------------------------------------------------------------------
+/* Here we go and stat every file that we found and strip dup inodes. */
+bool DropRepeats(vector<string> &List)
+{
+ // Get a list of all the inodes
+ ino_t *Inodes = new ino_t[List.size()];
+ for (unsigned int I = 0; I != List.size(); I++)
+ {
+ struct stat Buf;
+ if (stat(List[I].c_str(),&Buf) != 0)
+ _error->Errno("stat","Failed to stat %s",List[I].c_str());
+ Inodes[I] = Buf.st_ino;
+ }
+
+ // Look for dups
+ for (unsigned int I = 0; I != List.size(); I++)
+ {
+ for (unsigned int J = I+1; J < List.size(); J++)
+ {
+ // No match
+ if (Inodes[J] != Inodes[I])
+ continue;
+
+ // We score the two paths.. and erase one
+ int ScoreA = Score(List[I]);
+ int ScoreB = Score(List[J]);
+ if (ScoreA < ScoreB)
+ {
+ List[I] = string();
+ break;
+ }
+
+ List[J] = string();
+ }
+ }
+
+ // Wipe erased entries
+ for (unsigned int I = 0; I < List.size();)
+ {
+ if (List[I].empty() == false)
+ I++;
+ else
+ List.erase(List.begin()+I);
+ }
+
+ return true;
+}
+ /*}}}*/
+// ConvertToSourceList - Takes the path list and converts it /*{{{*/
+// ---------------------------------------------------------------------
+/* This looks at each element and decides if it can be expressed using
+ dists/ form or if it requires an absolute specficiation. It also
+ strips the leading CDROM path from the paths. */
+bool ConvertToSourcelist(string CD,vector<string> &List)
+{
+ char S[300];
+ sprintf(S,"binary-%s",_config->Find("Apt::Architecture").c_str());
+
+ sort(List.begin(),List.end());
+
+ // Convert to source list notation
+ for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
+ {
+ // Strip the cdrom base path
+ *I = string(*I,CD.length());
+
+ // Too short to be a dists/ type
+ if ((*I).length() < strlen("dists/"))
+ continue;
+
+ // Not a dists type.
+ if (stringcmp((*I).begin(),(*I).begin()+strlen("dists/"),"dists/") != 0)
+ continue;
+
+ // Isolate the dist
+ string::size_type Slash = strlen("dists/");
+ string::size_type Slash2 = (*I).find('/',Slash + 1);
+ if (Slash2 == string::npos || Slash2 + 2 >= (*I).length())
+ continue;
+ string Dist = string(*I,Slash,Slash2 - Slash);
+
+ // Isolate the component
+ Slash = (*I).find('/',Slash2+1);
+ if (Slash == string::npos || Slash + 2 >= (*I).length())
+ continue;
+ string Comp = string(*I,Slash2+1,Slash - Slash2-1);
+
+ // Verify the trailing binar - bit
+ Slash2 = (*I).find('/',Slash + 1);
+ if (Slash == string::npos)
+ continue;
+ string Binary = string(*I,Slash+1,Slash2 - Slash-1);
+
+ if (Binary != S)
+ continue;
+
+ *I = Dist + ' ' + Comp;
+ }
+
+ // Collect similar entries
+ for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
+ {
+ // Find a space..
+ string::size_type Space = (*I).find(' ');
+ if (Space == string::npos)
+ continue;
+
+ string Word1 = string(*I,0,Space);
+ for (vector<string>::iterator J = List.begin(); J != I; J++)
+ {
+ // Find a space..
+ string::size_type Space2 = (*J).find(' ');
+ if (Space2 == string::npos)
+ continue;
+
+ if (string(*J,0,Space2) != Word1)
+ continue;
+
+ *J += string(*I,Space);
+ *I = string();
+ }
+ }
+
+ // Wipe erased entries
+ for (unsigned int I = 0; I < List.size();)
+ {
+ if (List[I].empty() == false)
+ I++;
+ else
+ List.erase(List.begin()+I);
+ }
+}
+ /*}}}*/
+
+// Prompt - Simple prompt /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+void Prompt(const char *Text)
+{
+ char C;
+ cout << Text << ' ' << flush;
+ read(STDIN_FILENO,&C,1);
+ if (C != '\n')
+ cout << endl;
+}
+ /*}}}*/
+// PromptLine - Prompt for an input line /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+string PromptLine(const char *Text)
+{
+ cout << Text << ':' << endl;
+
+ string Res;
+ getline(cin,Res);
+ return Res;
+}
+ /*}}}*/
+
+// DoAdd - Add a new CDROM /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+bool DoAdd(CommandLine &)
+{
+ // Startup
+ string CDROM = _config->FindDir("Acquire::cdrom::mount","/cdrom/");
+ cout << "Using CD-ROM mount point " << CDROM << endl;
+
+ // Read the database
+ Configuration Database;
+ string DFile = _config->FindFile("Dir::State::cdroms");
+ if (FileExists(DFile) == true)
+ {
+ if (ReadConfigFile(Database,DFile) == false)
+ return _error->Error("Unable to read the cdrom database %s",
+ DFile.c_str());
+ }
+
+ // Unmount the CD and get the user to put in the one they want
+ if (_config->FindB("APT::CDROM::NoMount",false) == false)
+ {
+ cout << "Unmounting CD-ROM" << endl;
+ UnmountCdrom(CDROM);
+
+ // Mount the new CDROM
+ Prompt("Please insert a CD-ROM and press any key");
+ cout << "Mounting CD-ROM" << endl;
+ if (MountCdrom(CDROM) == false)
+ {
+ cout << "Failed to mount the cdrom." << endl;
+ return false;
+ }
+ }
+
+ // Hash the CD to get an ID
+ cout << "Indentifying.. " << flush;
+ string ID;
+ if (IdentCdrom(CDROM,ID) == false)
+ return false;
+ cout << '[' << ID << ']' << endl;
+
+ cout << "Scanning Disc for index files.. " << flush;
+ // Get the CD structure
+ vector<string> List;
+ string StartDir = SafeGetCWD();
+ if (FindPackages(CDROM,List) == false)
+ return false;
+ chdir(StartDir.c_str());
+
+ // Fix up the list
+ DropBinaryArch(List);
+ DropRepeats(List);
+ cout << "Found " << List.size() << " package index files." << endl;
+
+ if (List.size() == 0)
+ return _error->Error("Unable to locate any package files, perhaps this is not a debian CD-ROM");
+
+ // Check if the CD is in the database
+ string Name;
+ if (Database.Exists("CD::" + ID) == false ||
+ _config->FindB("APT::CDROM::Rename",false) == true)
+ {
+ cout << "Please provide a name for this CD-ROM, such as 'Debian 2.1r1 Disk 1'";
+ Name = PromptLine("");
+ }
+ else
+ Name = Database.Find("CD::" + ID);
+ cout << "This Disc is called '" << Name << "'" << endl;
+
+ // Copy the package files to the state directory
+ if (CopyPackages(CDROM,Name,List) == false)
+ return false;
+
+ ConvertToSourcelist(CDROM,List);
+
+ // Print the sourcelist entries
+ cout << "Source List entires for this Disc are:" << endl;
+ for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
+ cout << "deb \"cdrom:" << Name << "/\" " << *I << endl;
+
+ return true;
+}
+ /*}}}*/
+
+// ShowHelp - Show the help screen /*{{{*/
+// ---------------------------------------------------------------------
+/* */
+int ShowHelp()
+{
+ cout << PACKAGE << ' ' << VERSION << " for " << ARCHITECTURE <<
+ " compiled on " << __DATE__ << " " << __TIME__ << endl;
+
+ cout << "Usage: apt-cdrom [options] command" << endl;
+ cout << endl;
+ cout << "apt-cdrom is a tool to add CDROM's to APT's source list. The " << endl;
+ cout << "CDROM mount point and device information is taken from apt.conf" << endl;
+ cout << "and /etc/fstab." << endl;
+ cout << endl;
+ cout << "Commands:" << endl;
+ cout << " add - Add a CDROM" << endl;
+ cout << endl;
+ cout << "Options:" << endl;
+ cout << " -h This help text" << endl;
+ cout << " -d CD-ROM mount point" << endl;
+ cout << " -r Rename a recognized CD-ROM" << endl;
+ cout << " -m No mounting" << endl;
+ cout << " -c=? Read this configuration file" << endl;
+ cout << " -o=? Set an arbitary configuration option, ie -o dir::cache=/tmp" << endl;
+ cout << "See fstab(5)" << endl;
+ return 100;
+}
+ /*}}}*/
+
+int main(int argc,const char *argv[])
+{
+ CommandLine::Args Args[] = {
+ {'h',"help","help",0},
+ {'d',"cdrom","Acquire::cdrom::mount",CommandLine::HasArg},
+ {'r',"rename","APT::CDROM::Rename",0},
+ {'m',"no-mount","APT::CDROM::NoMount",0},
+ {'f',"fast","APT::CDROM::Fast",0},
+ {'c',"config-file",0,CommandLine::ConfigFile},
+ {'o',"option",0,CommandLine::ArbItem},
+ {0,0,0,0}};
+ CommandLine::Dispatch Cmds[] = {
+ {"add",&DoAdd},
+ {0,0}};
+
+ // Parse the command line and initialize the package library
+ CommandLine CmdL(Args,_config);
+ if (pkgInitialize(*_config) == false ||
+ CmdL.Parse(argc,argv) == false)
+ {
+ _error->DumpErrors();
+ return 100;
+ }
+
+ // See if the help should be shown
+ if (_config->FindB("help") == true ||
+ CmdL.FileSize() == 0)
+ return ShowHelp();
+
+ // Match the operation
+ CmdL.DispatchArg(Cmds);
+
+ // Print any errors or warnings found during parsing
+ if (_error->empty() == false)
+ {
+ bool Errors = _error->PendingError();
+ _error->DumpErrors();
+ return Errors == true?100:0;
+ }
+
+ return 0;
+}