]>
git.saurik.com Git - apt.git/blob - cmdline/apt-cdrom.cc
fe7d4d55ade9b15e453e0a9d48196fb7b5fbf750
1 // -*- mode: cpp; mode: fold -*-
3 // $Id: apt-cdrom.cc,v 1.2 1998/11/27 04:49:42 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/md5.h>
18 #include <apt-pkg/fileutl.h>
19 #include <apt-pkg/progress.h>
20 #include <apt-pkg/tagfile.h>
29 #include <sys/errno.h>
38 // UnmountCdrom - Unmount a cdrom /*{{{*/
39 // ---------------------------------------------------------------------
41 bool UnmountCdrom(string Path
)
45 return _error
->Errno("fork","Failed to fork");
50 // Make all the fds /dev/null
51 for (int I
= 0; I
!= 10; I
++)
53 for (int I
= 0; I
!= 3; I
++)
54 dup2(open("/dev/null",O_RDWR
),I
);
58 Args
[1] = Path
.c_str();
60 execvp(Args
[0],(char **)Args
);
66 while (waitpid(Child
,&Status
,0) != Child
)
70 return _error
->Errno("waitpid","Couldn't wait for subprocess");
73 // Check for an error code.
74 if (WIFEXITED(Status
) == 0 || WEXITSTATUS(Status
) != 0)
79 // MountCdrom - Mount a cdrom /*{{{*/
80 // ---------------------------------------------------------------------
82 bool MountCdrom(string Path
)
86 return _error
->Errno("fork","Failed to fork");
91 // Make all the fds /dev/null
92 for (int I
= 0; I
!= 10; I
++)
94 for (int I
= 0; I
!= 3; I
++)
95 dup2(open("/dev/null",O_RDWR
),I
);
99 Args
[1] = Path
.c_str();
101 execvp(Args
[0],(char **)Args
);
107 while (waitpid(Child
,&Status
,0) != Child
)
111 return _error
->Errno("waitpid","Couldn't wait for subprocess");
114 // Check for an error code.
115 if (WIFEXITED(Status
) == 0 || WEXITSTATUS(Status
) != 0)
120 // IdentCdrom - Generate a unique string for this CD /*{{{*/
121 // ---------------------------------------------------------------------
122 /* We convert everything we hash into a string, this prevents byte size/order
123 from effecting the outcome. */
124 bool IdentCdrom(string CD
,string
&Res
)
128 string StartDir
= SafeGetCWD();
129 if (chdir(CD
.c_str()) != 0)
130 return _error
->Errno("chdir","Unable to change to %s",CD
.c_str());
132 DIR *D
= opendir(".");
134 return _error
->Errno("opendir","Unable to read %s",CD
.c_str());
136 // Run over the directory
138 for (struct dirent
*Dir
= readdir(D
); Dir
!= 0; Dir
= readdir(D
))
141 if (strcmp(Dir
->d_name
,".") == 0 ||
142 strcmp(Dir
->d_name
,"..") == 0)
145 sprintf(S
,"%lu",Dir
->d_ino
);
147 Hash
.Add(Dir
->d_name
);
150 chdir(StartDir
.c_str());
153 // Some stats from the fsys
155 if (statfs(CD
.c_str(),&Buf
) != 0)
156 return _error
->Errno("statfs","Failed to stat the cdrom");
158 sprintf(S
,"%u %u",Buf
.f_blocks
,Buf
.f_bfree
);
161 Res
= Hash
.Result().Value();
166 // FindPackage - Find the package files on the CDROM /*{{{*/
167 // ---------------------------------------------------------------------
168 /* We look over the cdrom for package files. This is a recursive
169 search that short circuits when it his a package file in the dir.
170 This speeds it up greatly as the majority of the size is in the
171 binary-* sub dirs. */
172 bool FindPackages(string CD
,vector
<string
> &List
, int Depth
= 0)
177 if (CD
[CD
.length()-1] != '/')
180 if (chdir(CD
.c_str()) != 0)
181 return _error
->Errno("chdir","Unable to change to %s",CD
.c_str());
183 /* Aha! We found some package files. We assume that everything under
184 this dir is controlled by those package files so we don't look down
187 if (stat("Packages",&Buf
) == 0 ||
188 stat("Packages.gz",&Buf
) == 0)
194 DIR *D
= opendir(".");
196 return _error
->Errno("opendir","Unable to read %s",CD
.c_str());
198 // Run over the directory
199 for (struct dirent
*Dir
= readdir(D
); Dir
!= 0; Dir
= readdir(D
))
202 if (strcmp(Dir
->d_name
,".") == 0 ||
203 strcmp(Dir
->d_name
,"..") == 0 ||
204 strcmp(Dir
->d_name
,"source") == 0 ||
205 strcmp(Dir
->d_name
,"experimental") == 0 ||
206 strcmp(Dir
->d_name
,"binary-all") == 0)
209 // See if the name is a sub directory
211 if (stat(Dir
->d_name
,&Buf
) != 0)
213 _error
->Errno("Stat","Stat failed for %s",Dir
->d_name
);
217 if (S_ISDIR(Buf
.st_mode
) == 0)
221 if (FindPackages(CD
+ Dir
->d_name
,List
,Depth
+1) == false)
224 if (chdir(CD
.c_str()) != 0)
225 return _error
->Errno("chdir","Unable to change to ",CD
.c_str());
230 return !_error
->PendingError();
233 // CopyPackages - Copy the package files from the CD /*{{{*/
234 // ---------------------------------------------------------------------
236 bool CopyPackages(string CDROM
,string Name
,vector
<string
> &List
)
238 OpTextProgress Progress
;
240 bool NoStat
= _config
->FindB("APT::CDROM::Fast",false);
242 // Prepare the progress indicator
243 unsigned long TotalSize
= 0;
244 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
247 if (stat(string(*I
+ "Packages").c_str(),&Buf
) != 0)
248 return _error
->Errno("stat","Stat failed for %s",
249 string(*I
+ "Packages").c_str());
250 TotalSize
+= Buf
.st_size
;
253 unsigned long CurrentSize
= 0;
254 unsigned int NotFound
= 0;
255 unsigned int WrongSize
= 0;
256 unsigned int Packages
= 0;
257 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
259 // Open the package file
260 FileFd
Pkg(*I
+ "Packages",FileFd::ReadOnly
);
261 pkgTagFile
Parser(Pkg
);
262 if (_error
->PendingError() == true)
265 // Open the output file
267 sprintf(S
,"cdrom:%s/%sPackages",Name
.c_str(),(*I
).c_str() + CDROM
.length());
268 string TargetF
= _config
->FindDir("Dir::State::lists") + "partial/";
269 TargetF
+= URItoFileName(S
);
270 if (_config
->FindB("APT::CDROM::NoAct",false) == true)
271 TargetF
= "/dev/null";
272 FileFd
Target(TargetF
,FileFd::WriteEmpty
);
273 if (_error
->PendingError() == true)
276 // Setup the progress meter
277 Progress
.OverallProgress(CurrentSize
,TotalSize
,Pkg
.Size(),
278 "Reading Package Lists");
281 Progress
.SubProgress(Pkg
.Size());
282 pkgTagSection Section
;
283 while (Parser
.Step(Section
) == true)
285 Progress
.Progress(Parser
.Offset());
287 string File
= Section
.FindS("Filename");
288 unsigned long Size
= Section
.FindI("Size");
289 if (File
.empty() || Size
== 0)
290 return _error
->Error("Cannot find filename or size tag");
292 // See if the file exists
297 if (stat(File
.c_str(),&Buf
) != 0)
304 if ((unsigned)Buf
.st_size
!= Size
)
313 // Copy it to the target package file
316 Section
.GetSection(Start
,Stop
);
317 if (Target
.Write(Start
,Stop
-Start
) == false)
321 CurrentSize
+= Pkg
.Size();
326 cout
<< "Wrote " << Packages
<< " package records" ;
328 cout
<< " with " << NotFound
<< " missing files";
329 if (NotFound
!= 0 && WrongSize
!= 0)
332 cout
<< " with " << WrongSize
<< " mismatched files";
334 if (NotFound
+ WrongSize
> 10)
335 cout
<< "Alot of package entires were discarded, perhaps this CD is funny?" << endl
;
338 // DropBinaryArch - Dump dirs with a string like /binary-<foo>/ /*{{{*/
339 // ---------------------------------------------------------------------
340 /* Here we drop everything that is not this machines arch */
341 bool DropBinaryArch(vector
<string
> &List
)
344 sprintf(S
,"/binary-%s/",_config
->Find("Apt::Architecture").c_str());
346 for (unsigned int I
= 0; I
< List
.size(); I
++)
348 const char *Str
= List
[I
].c_str();
351 if ((Res
= strstr(Str
,"/binary-")) == 0)
355 if (strlen(Res
) < strlen(S
))
357 List
.erase(List
.begin() + I
);
362 // See if it is our arch
363 if (stringcmp(Res
,Res
+ strlen(S
),S
) == 0)
367 List
.erase(List
.begin() + I
);
374 // Score - We compute a 'score' for a path /*{{{*/
375 // ---------------------------------------------------------------------
376 /* Paths are scored based on how close they come to what I consider
377 normal. That is ones that have 'dist' 'stable' 'frozen' will score
378 higher than ones without. */
379 int Score(string Path
)
382 if (Path
.find("stable/") != string::npos
)
384 if (Path
.find("frozen/") != string::npos
)
386 if (Path
.find("/dists/") != string::npos
)
388 if (Path
.find("/main/") != string::npos
)
390 if (Path
.find("/contrib/") != string::npos
)
392 if (Path
.find("/non-free/") != string::npos
)
394 if (Path
.find("/non-US/") != string::npos
)
399 // DropRepeats - Drop repeated files resulting from symlinks /*{{{*/
400 // ---------------------------------------------------------------------
401 /* Here we go and stat every file that we found and strip dup inodes. */
402 bool DropRepeats(vector
<string
> &List
)
404 // Get a list of all the inodes
405 ino_t
*Inodes
= new ino_t
[List
.size()];
406 for (unsigned int I
= 0; I
!= List
.size(); I
++)
409 if (stat(List
[I
].c_str(),&Buf
) != 0)
410 _error
->Errno("stat","Failed to stat %s",List
[I
].c_str());
411 Inodes
[I
] = Buf
.st_ino
;
415 for (unsigned int I
= 0; I
!= List
.size(); I
++)
417 for (unsigned int J
= I
+1; J
< List
.size(); J
++)
420 if (Inodes
[J
] != Inodes
[I
])
423 // We score the two paths.. and erase one
424 int ScoreA
= Score(List
[I
]);
425 int ScoreB
= Score(List
[J
]);
436 // Wipe erased entries
437 for (unsigned int I
= 0; I
< List
.size();)
439 if (List
[I
].empty() == false)
442 List
.erase(List
.begin()+I
);
448 // ConvertToSourceList - Takes the path list and converts it /*{{{*/
449 // ---------------------------------------------------------------------
450 /* This looks at each element and decides if it can be expressed using
451 dists/ form or if it requires an absolute specficiation. It also
452 strips the leading CDROM path from the paths. */
453 bool ConvertToSourcelist(string CD
,vector
<string
> &List
)
456 sprintf(S
,"binary-%s",_config
->Find("Apt::Architecture").c_str());
458 sort(List
.begin(),List
.end());
460 // Convert to source list notation
461 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
463 // Strip the cdrom base path
464 *I
= string(*I
,CD
.length());
466 // Too short to be a dists/ type
467 if ((*I
).length() < strlen("dists/"))
471 if (stringcmp((*I
).begin(),(*I
).begin()+strlen("dists/"),"dists/") != 0)
475 string::size_type Slash
= strlen("dists/");
476 string::size_type Slash2
= (*I
).find('/',Slash
+ 1);
477 if (Slash2
== string::npos
|| Slash2
+ 2 >= (*I
).length())
479 string Dist
= string(*I
,Slash
,Slash2
- Slash
);
481 // Isolate the component
482 Slash
= (*I
).find('/',Slash2
+1);
483 if (Slash
== string::npos
|| Slash
+ 2 >= (*I
).length())
485 string Comp
= string(*I
,Slash2
+1,Slash
- Slash2
-1);
487 // Verify the trailing binar - bit
488 Slash2
= (*I
).find('/',Slash
+ 1);
489 if (Slash
== string::npos
)
491 string Binary
= string(*I
,Slash
+1,Slash2
- Slash
-1);
496 *I
= Dist
+ ' ' + Comp
;
499 // Collect similar entries
500 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
503 string::size_type Space
= (*I
).find(' ');
504 if (Space
== string::npos
)
507 string Word1
= string(*I
,0,Space
);
508 for (vector
<string
>::iterator J
= List
.begin(); J
!= I
; J
++)
511 string::size_type Space2
= (*J
).find(' ');
512 if (Space2
== string::npos
)
515 if (string(*J
,0,Space2
) != Word1
)
518 *J
+= string(*I
,Space
);
523 // Wipe erased entries
524 for (unsigned int I
= 0; I
< List
.size();)
526 if (List
[I
].empty() == false)
529 List
.erase(List
.begin()+I
);
533 // WriteDatabase - Write the CDROM Database file /*{{{*/
534 // ---------------------------------------------------------------------
536 bool WriteDatabase(Configuration
&Cnf
)
538 string DFile
= _config
->FindFile("Dir::State::cdroms");
540 ofstream
Out(string(DFile
+ ".new").c_str());
542 return _error
->Error("Failed to open %s.new",DFile
.c_str());
544 /* Write out all of the configuration directives by walking the
545 configuration tree */
546 const Configuration::Item
*Top
= Cnf
.Tree(0);
549 // Print the config entry
550 if (Top
->Value
.empty() == false)
551 Out
<< Top
->FullTag() + " \"" << Top
->Value
<< "\";" << endl
;
559 while (Top
!= 0 && Top
->Next
== 0)
567 rename(DFile
.c_str(),string(DFile
+ '~').c_str());
568 if (rename(string(DFile
+ ".new").c_str(),DFile
.c_str()) != 0)
569 return _error
->Errno("rename","Failed to rename %s.new to %s",
570 DFile
.c_str(),DFile
.c_str());
575 // WriteSourceList - Write an updated sourcelist /*{{{*/
576 // ---------------------------------------------------------------------
578 bool WriteSourceList(string Name
,vector
<string
> &List
)
584 // Prompt - Simple prompt /*{{{*/
585 // ---------------------------------------------------------------------
587 void Prompt(const char *Text
)
590 cout
<< Text
<< ' ' << flush
;
591 read(STDIN_FILENO
,&C
,1);
596 // PromptLine - Prompt for an input line /*{{{*/
597 // ---------------------------------------------------------------------
599 string
PromptLine(const char *Text
)
601 cout
<< Text
<< ':' << endl
;
609 // DoAdd - Add a new CDROM /*{{{*/
610 // ---------------------------------------------------------------------
612 bool DoAdd(CommandLine
&)
615 string CDROM
= _config
->FindDir("Acquire::cdrom::mount","/cdrom/");
616 cout
<< "Using CD-ROM mount point " << CDROM
<< endl
;
619 Configuration Database
;
620 string DFile
= _config
->FindFile("Dir::State::cdroms");
621 if (FileExists(DFile
) == true)
623 if (ReadConfigFile(Database
,DFile
) == false)
624 return _error
->Error("Unable to read the cdrom database %s",
628 // Unmount the CD and get the user to put in the one they want
629 if (_config
->FindB("APT::CDROM::NoMount",false) == false)
631 cout
<< "Unmounting CD-ROM" << endl
;
634 // Mount the new CDROM
635 Prompt("Please insert a Disc in the drive and press any key");
636 cout
<< "Mounting CD-ROM" << endl
;
637 if (MountCdrom(CDROM
) == false)
639 cout
<< "Failed to mount the cdrom." << endl
;
644 // Hash the CD to get an ID
645 cout
<< "Identifying.. " << flush
;
647 if (IdentCdrom(CDROM
,ID
) == false)
649 cout
<< '[' << ID
<< ']' << endl
;
651 cout
<< "Scanning Disc for index files.. " << flush
;
652 // Get the CD structure
654 string StartDir
= SafeGetCWD();
655 if (FindPackages(CDROM
,List
) == false)
657 chdir(StartDir
.c_str());
660 DropBinaryArch(List
);
662 cout
<< "Found " << List
.size() << " package index files." << endl
;
664 if (List
.size() == 0)
665 return _error
->Error("Unable to locate any package files, perhaps this is not a debian CD-ROM");
667 // Check if the CD is in the database
669 if (Database
.Exists("CD::" + ID
) == false ||
670 _config
->FindB("APT::CDROM::Rename",false) == true)
672 cout
<< "Please provide a name for this CD-ROM, such as 'Debian 2.1r1 Disk 1'";
673 Name
= PromptLine("");
676 Name
= Database
.Find("CD::" + ID
);
677 Database
.Set("CD::" + ID
,Name
);
678 cout
<< "This Disc is called '" << Name
<< "'" << endl
;
680 // Copy the package files to the state directory
681 if (CopyPackages(CDROM
,Name
,List
) == false)
684 ConvertToSourcelist(CDROM
,List
);
686 // Print the sourcelist entries
687 cout
<< "Source List entires for this Disc are:" << endl
;
688 for (vector
<string
>::iterator I
= List
.begin(); I
!= List
.end(); I
++)
689 cout
<< "deb \"cdrom:" << Name
<< "/\" " << *I
<< endl
;
691 // Write the database and sourcelist
692 if (_config
->FindB("APT::cdrom::NoAct",false) == false)
694 if (WriteDatabase(Database
) == false)
702 // ShowHelp - Show the help screen /*{{{*/
703 // ---------------------------------------------------------------------
707 cout
<< PACKAGE
<< ' ' << VERSION
<< " for " << ARCHITECTURE
<<
708 " compiled on " << __DATE__
<< " " << __TIME__
<< endl
;
710 cout
<< "Usage: apt-cdrom [options] command" << endl
;
712 cout
<< "apt-cdrom is a tool to add CDROM's to APT's source list. The " << endl
;
713 cout
<< "CDROM mount point and device information is taken from apt.conf" << endl
;
714 cout
<< "and /etc/fstab." << endl
;
716 cout
<< "Commands:" << endl
;
717 cout
<< " add - Add a CDROM" << endl
;
719 cout
<< "Options:" << endl
;
720 cout
<< " -h This help text" << endl
;
721 cout
<< " -d CD-ROM mount point" << endl
;
722 cout
<< " -r Rename a recognized CD-ROM" << endl
;
723 cout
<< " -m No mounting" << endl
;
724 cout
<< " -f Fast mode, don't check package files" << endl
;
725 cout
<< " -c=? Read this configuration file" << endl
;
726 cout
<< " -o=? Set an arbitary configuration option, ie -o dir::cache=/tmp" << endl
;
727 cout
<< "See fstab(5)" << endl
;
732 int main(int argc
,const char *argv
[])
734 CommandLine::Args Args
[] = {
735 {'h',"help","help",0},
736 {'d',"cdrom","Acquire::cdrom::mount",CommandLine::HasArg
},
737 {'r',"rename","APT::CDROM::Rename",0},
738 {'m',"no-mount","APT::CDROM::NoMount",0},
739 {'f',"fast","APT::CDROM::Fast",0},
740 {'n',"just-print","APT::CDROM::NoAct",0},
741 {'n',"recon","APT::CDROM::NoAct",0},
742 {'n',"no-act","APT::CDROM::NoAct",0},
743 {'c',"config-file",0,CommandLine::ConfigFile
},
744 {'o',"option",0,CommandLine::ArbItem
},
746 CommandLine::Dispatch Cmds
[] = {
750 // Parse the command line and initialize the package library
751 CommandLine
CmdL(Args
,_config
);
752 if (pkgInitialize(*_config
) == false ||
753 CmdL
.Parse(argc
,argv
) == false)
755 _error
->DumpErrors();
759 // See if the help should be shown
760 if (_config
->FindB("help") == true ||
761 CmdL
.FileSize() == 0)
764 // Match the operation
765 CmdL
.DispatchArg(Cmds
);
767 // Print any errors or warnings found during parsing
768 if (_error
->empty() == false)
770 bool Errors
= _error
->PendingError();
771 _error
->DumpErrors();
772 return Errors
== true?100:0;