Fixes for file opening
[ntk/apt.git] / cmdline / apt-cdrom.cc
CommitLineData
83d89a9f
AL
1// -*- mode: cpp; mode: fold -*-
2// Description /*{{{*/
3// $Id: apt-cdrom.cc,v 1.1 1998/11/27 01:52:56 jgg Exp $
4/* ######################################################################
5
6
7 ##################################################################### */
8 /*}}}*/
9// Include Files /*{{{*/
10#include <apt-pkg/cmndline.h>
11#include <apt-pkg/error.h>
12#include <apt-pkg/init.h>
13#include <apt-pkg/md5.h>
14#include <apt-pkg/fileutl.h>
15#include <apt-pkg/progress.h>
16#include <apt-pkg/tagfile.h>
17#include <strutl.h>
18#include <config.h>
19
20#include <iostream>
21#include <vector>
22#include <algorithm>
23#include <sys/wait.h>
24#include <sys/errno.h>
25#include <sys/vfs.h>
26#include <sys/stat.h>
27#include <fcntl.h>
28#include <dirent.h>
29#include <unistd.h>
30#include <stdio.h>
31 /*}}}*/
32
33// UnmountCdrom - Unmount a cdrom /*{{{*/
34// ---------------------------------------------------------------------
35/* */
36bool UnmountCdrom(string Path)
37{
38 int Child = fork();
39 if (Child < -1)
40 return _error->Errno("fork","Failed to fork");
41
42 // The child
43 if (Child == 0)
44 {
45 // Make all the fds /dev/null
46 for (int I = 0; I != 10;)
47 close(I);
48 for (int I = 0; I != 3;)
49 dup2(open("/dev/null",O_RDWR),I);
50
51 const char *Args[10];
52 Args[0] = "umount";
53 Args[1] = Path.c_str();
54 Args[2] = 0;
55 execvp(Args[0],(char **)Args);
56 exit(100);
57 }
58
59 // Wait for mount
60 int Status = 0;
61 while (waitpid(Child,&Status,0) != Child)
62 {
63 if (errno == EINTR)
64 continue;
65 return _error->Errno("waitpid","Couldn't wait for subprocess");
66 }
67
68 // Check for an error code.
69 if (WIFEXITED(Status) == 0 || WEXITSTATUS(Status) != 0)
70 return false;
71 return true;
72}
73 /*}}}*/
74// MountCdrom - Mount a cdrom /*{{{*/
75// ---------------------------------------------------------------------
76/* We fork mount.. */
77bool MountCdrom(string Path)
78{
79 int Child = fork();
80 if (Child < -1)
81 return _error->Errno("fork","Failed to fork");
82
83 // The child
84 if (Child == 0)
85 {
86 // Make all the fds /dev/null
87 for (int I = 0; I != 10;)
88 close(I);
89 for (int I = 0; I != 3;)
90 dup2(open("/dev/null",O_RDWR),I);
91
92 const char *Args[10];
93 Args[0] = "mount";
94 Args[1] = Path.c_str();
95 Args[2] = 0;
96 execvp(Args[0],(char **)Args);
97 exit(100);
98 }
99
100 // Wait for mount
101 int Status = 0;
102 while (waitpid(Child,&Status,0) != Child)
103 {
104 if (errno == EINTR)
105 continue;
106 return _error->Errno("waitpid","Couldn't wait for subprocess");
107 }
108
109 // Check for an error code.
110 if (WIFEXITED(Status) == 0 || WEXITSTATUS(Status) != 0)
111 return false;
112 return true;
113}
114 /*}}}*/
115// IdentCdrom - Generate a unique string for this CD /*{{{*/
116// ---------------------------------------------------------------------
117/* We convert everything we hash into a string, this prevents byte size/order
118 from effecting the outcome. */
119bool IdentCdrom(string CD,string &Res)
120{
121 MD5Summation Hash;
122
123 string StartDir = SafeGetCWD();
124 if (chdir(CD.c_str()) != 0)
125 return _error->Errno("chdir","Unable to change to %s",CD.c_str());
126
127 DIR *D = opendir(".");
128 if (D == 0)
129 return _error->Errno("opendir","Unable to read %s",CD.c_str());
130
131 // Run over the directory
132 char S[300];
133 for (struct dirent *Dir = readdir(D); Dir != 0; Dir = readdir(D))
134 {
135 // Skip some files..
136 if (strcmp(Dir->d_name,".") == 0 ||
137 strcmp(Dir->d_name,"..") == 0)
138 continue;
139
140 sprintf(S,"%lu",Dir->d_ino);
141 Hash.Add(S);
142 Hash.Add(Dir->d_name);
143 };
144
145 chdir(StartDir.c_str());
146 closedir(D);
147
148 // Some stats from the fsys
149 struct statfs Buf;
150 if (statfs(CD.c_str(),&Buf) != 0)
151 return _error->Errno("statfs","Failed to stat the cdrom");
152
153 sprintf(S,"%u %u",Buf.f_blocks,Buf.f_bfree);
154 Hash.Add(S);
155
156 Res = Hash.Result().Value();
157 return true;
158}
159 /*}}}*/
160
161// FindPackage - Find the package files on the CDROM /*{{{*/
162// ---------------------------------------------------------------------
163/* We look over the cdrom for package files. This is a recursive
164 search that short circuits when it his a package file in the dir.
165 This speeds it up greatly as the majority of the size is in the
166 binary-* sub dirs. */
167bool FindPackages(string CD,vector<string> &List, int Depth = 0)
168{
169 if (Depth >= 5)
170 return true;
171
172 if (CD[CD.length()-1] != '/')
173 CD += '/';
174
175 if (chdir(CD.c_str()) != 0)
176 return _error->Errno("chdir","Unable to change to %s",CD.c_str());
177
178 /* Aha! We found some package files. We assume that everything under
179 this dir is controlled by those package files so we don't look down
180 anymore */
181 struct stat Buf;
182 if (stat("Packages",&Buf) == 0 ||
183 stat("Packages.gz",&Buf) == 0)
184 {
185 List.push_back(CD);
186 return true;
187 }
188
189 DIR *D = opendir(".");
190 if (D == 0)
191 return _error->Errno("opendir","Unable to read %s",CD.c_str());
192
193 // Run over the directory
194 for (struct dirent *Dir = readdir(D); Dir != 0; Dir = readdir(D))
195 {
196 // Skip some files..
197 if (strcmp(Dir->d_name,".") == 0 ||
198 strcmp(Dir->d_name,"..") == 0 ||
199 strcmp(Dir->d_name,"source") == 0 ||
200 strcmp(Dir->d_name,"experimental") == 0 ||
201 strcmp(Dir->d_name,"binary-all") == 0)
202 continue;
203
204 // See if the name is a sub directory
205 struct stat Buf;
206 if (stat(Dir->d_name,&Buf) != 0)
207 {
208 _error->Errno("Stat","Stat failed for %s",Dir->d_name);
209 break;
210 }
211
212 if (S_ISDIR(Buf.st_mode) == 0)
213 continue;
214
215 // Descend
216 if (FindPackages(CD + Dir->d_name,List,Depth+1) == false)
217 break;
218
219 if (chdir(CD.c_str()) != 0)
220 return _error->Errno("chdir","Unable to change to ",CD.c_str());
221 };
222
223 closedir(D);
224
225 return !_error->PendingError();
226}
227 /*}}}*/
228// CopyPackages - Copy the package files from the CD /*{{{*/
229// ---------------------------------------------------------------------
230/* */
231bool CopyPackages(string CDROM,string Name,vector<string> &List)
232{
233 OpTextProgress Progress;
234
235 bool NoStat = _config->FindB("APT::CDROM::Fast",false);
236
237 // Prepare the progress indicator
238 unsigned long TotalSize = 0;
239 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
240 {
241 struct stat Buf;
242 if (stat(string(*I + "Packages").c_str(),&Buf) != 0)
243 return _error->Errno("stat","Stat failed for %s",
244 string(*I + "Packages").c_str());
245 TotalSize += Buf.st_size;
246 }
247
248 unsigned long CurrentSize = 0;
249 unsigned int NotFound = 0;
250 unsigned int WrongSize = 0;
251 unsigned int Packages = 0;
252 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
253 {
254 // Open the package file
255 FileFd Pkg(*I + "Packages",FileFd::ReadOnly);
256 pkgTagFile Parser(Pkg);
257 if (_error->PendingError() == true)
258 return false;
259
260 // Open the output file
261 char S[400];
262 sprintf(S,"cdrom:%s/%sPackages",Name.c_str(),(*I).c_str() + CDROM.length());
263 string TargetF = _config->FindDir("Dir::State::lists") + "partial/";
264 FileFd Target(TargetF + URItoFileName(S),FileFd::WriteEmpty);
265 if (_error->PendingError() == true)
266 return false;
267
268 // Setup the progress meter
269 Progress.OverallProgress(CurrentSize,TotalSize,Pkg.Size(),
270 "Reading Package Lists");
271
272 // Parse
273 Progress.SubProgress(Pkg.Size());
274 pkgTagSection Section;
275 while (Parser.Step(Section) == true)
276 {
277 Progress.Progress(Parser.Offset());
278
279 string File = Section.FindS("Filename");
280 unsigned long Size = Section.FindI("Size");
281 if (File.empty() || Size == 0)
282 return _error->Error("Cannot find filename or size tag");
283
284 // See if the file exists
285 if (NoStat == false)
286 {
287 struct stat Buf;
288 File = CDROM + File;
289 if (stat(File.c_str(),&Buf) != 0)
290 {
291 NotFound++;
292 continue;
293 }
294
295 // Size match
296 if ((unsigned)Buf.st_size != Size)
297 {
298 WrongSize++;
299 continue;
300 }
301 }
302
303 Packages++;
304
305 // Copy it to the target package file
306 const char *Start;
307 const char *Stop;
308 Section.GetSection(Start,Stop);
309 if (Target.Write(Start,Stop-Start) == false)
310 return false;
311 }
312
313 CurrentSize += Pkg.Size();
314 }
315 Progress.Done();
316
317 // Some stats
318 cout << "Wrote " << Packages << " package records" ;
319 if (NotFound != 0)
320 cout << " with " << NotFound << " missing files";
321 if (NotFound != 0 && WrongSize != 0)
322 cout << " and";
323 if (WrongSize != 0)
324 cout << " with " << WrongSize << " mismatched files";
325 cout << '.' << endl;
326 if (NotFound + WrongSize > 10)
327 cout << "Alot of package entires were discarded, perhaps this CD is funny?" << endl;
328}
329 /*}}}*/
330// DropBinaryArch - Dump dirs with a string like /binary-<foo>/ /*{{{*/
331// ---------------------------------------------------------------------
332/* Here we drop everything that is not this machines arch */
333bool DropBinaryArch(vector<string> &List)
334{
335 char S[300];
336 sprintf(S,"/binary-%s/",_config->Find("Apt::Architecture").c_str());
337
338 for (unsigned int I = 0; I < List.size(); I++)
339 {
340 const char *Str = List[I].c_str();
341
342 const char *Res;
343 if ((Res = strstr(Str,"/binary-")) == 0)
344 continue;
345
346 // Weird, remove it.
347 if (strlen(Res) < strlen(S))
348 {
349 List.erase(List.begin() + I);
350 I--;
351 continue;
352 }
353
354 // See if it is our arch
355 if (stringcmp(Res,Res + strlen(S),S) == 0)
356 continue;
357
358 // Erase it
359 List.erase(List.begin() + I);
360 I--;
361 }
362
363 return true;
364}
365 /*}}}*/
366// Score - We compute a 'score' for a path /*{{{*/
367// ---------------------------------------------------------------------
368/* Paths are scored based on how close they come to what I consider
369 normal. That is ones that have 'dist' 'stable' 'frozen' will score
370 higher than ones without. */
371int Score(string Path)
372{
373 int Res = 0;
374 if (Path.find("stable/") != string::npos)
375 Res += 2;
376 if (Path.find("frozen/") != string::npos)
377 Res += 2;
378 if (Path.find("/dists/") != string::npos)
379 Res += 4;
380 if (Path.find("/main/") != string::npos)
381 Res += 2;
382 if (Path.find("/contrib/") != string::npos)
383 Res += 2;
384 if (Path.find("/non-free/") != string::npos)
385 Res += 2;
386 if (Path.find("/non-US/") != string::npos)
387 Res += 2;
388 return Res;
389}
390 /*}}}*/
391// DropRepeats - Drop repeated files resulting from symlinks /*{{{*/
392// ---------------------------------------------------------------------
393/* Here we go and stat every file that we found and strip dup inodes. */
394bool DropRepeats(vector<string> &List)
395{
396 // Get a list of all the inodes
397 ino_t *Inodes = new ino_t[List.size()];
398 for (unsigned int I = 0; I != List.size(); I++)
399 {
400 struct stat Buf;
401 if (stat(List[I].c_str(),&Buf) != 0)
402 _error->Errno("stat","Failed to stat %s",List[I].c_str());
403 Inodes[I] = Buf.st_ino;
404 }
405
406 // Look for dups
407 for (unsigned int I = 0; I != List.size(); I++)
408 {
409 for (unsigned int J = I+1; J < List.size(); J++)
410 {
411 // No match
412 if (Inodes[J] != Inodes[I])
413 continue;
414
415 // We score the two paths.. and erase one
416 int ScoreA = Score(List[I]);
417 int ScoreB = Score(List[J]);
418 if (ScoreA < ScoreB)
419 {
420 List[I] = string();
421 break;
422 }
423
424 List[J] = string();
425 }
426 }
427
428 // Wipe erased entries
429 for (unsigned int I = 0; I < List.size();)
430 {
431 if (List[I].empty() == false)
432 I++;
433 else
434 List.erase(List.begin()+I);
435 }
436
437 return true;
438}
439 /*}}}*/
440// ConvertToSourceList - Takes the path list and converts it /*{{{*/
441// ---------------------------------------------------------------------
442/* This looks at each element and decides if it can be expressed using
443 dists/ form or if it requires an absolute specficiation. It also
444 strips the leading CDROM path from the paths. */
445bool ConvertToSourcelist(string CD,vector<string> &List)
446{
447 char S[300];
448 sprintf(S,"binary-%s",_config->Find("Apt::Architecture").c_str());
449
450 sort(List.begin(),List.end());
451
452 // Convert to source list notation
453 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
454 {
455 // Strip the cdrom base path
456 *I = string(*I,CD.length());
457
458 // Too short to be a dists/ type
459 if ((*I).length() < strlen("dists/"))
460 continue;
461
462 // Not a dists type.
463 if (stringcmp((*I).begin(),(*I).begin()+strlen("dists/"),"dists/") != 0)
464 continue;
465
466 // Isolate the dist
467 string::size_type Slash = strlen("dists/");
468 string::size_type Slash2 = (*I).find('/',Slash + 1);
469 if (Slash2 == string::npos || Slash2 + 2 >= (*I).length())
470 continue;
471 string Dist = string(*I,Slash,Slash2 - Slash);
472
473 // Isolate the component
474 Slash = (*I).find('/',Slash2+1);
475 if (Slash == string::npos || Slash + 2 >= (*I).length())
476 continue;
477 string Comp = string(*I,Slash2+1,Slash - Slash2-1);
478
479 // Verify the trailing binar - bit
480 Slash2 = (*I).find('/',Slash + 1);
481 if (Slash == string::npos)
482 continue;
483 string Binary = string(*I,Slash+1,Slash2 - Slash-1);
484
485 if (Binary != S)
486 continue;
487
488 *I = Dist + ' ' + Comp;
489 }
490
491 // Collect similar entries
492 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
493 {
494 // Find a space..
495 string::size_type Space = (*I).find(' ');
496 if (Space == string::npos)
497 continue;
498
499 string Word1 = string(*I,0,Space);
500 for (vector<string>::iterator J = List.begin(); J != I; J++)
501 {
502 // Find a space..
503 string::size_type Space2 = (*J).find(' ');
504 if (Space2 == string::npos)
505 continue;
506
507 if (string(*J,0,Space2) != Word1)
508 continue;
509
510 *J += string(*I,Space);
511 *I = string();
512 }
513 }
514
515 // Wipe erased entries
516 for (unsigned int I = 0; I < List.size();)
517 {
518 if (List[I].empty() == false)
519 I++;
520 else
521 List.erase(List.begin()+I);
522 }
523}
524 /*}}}*/
525
526// Prompt - Simple prompt /*{{{*/
527// ---------------------------------------------------------------------
528/* */
529void Prompt(const char *Text)
530{
531 char C;
532 cout << Text << ' ' << flush;
533 read(STDIN_FILENO,&C,1);
534 if (C != '\n')
535 cout << endl;
536}
537 /*}}}*/
538// PromptLine - Prompt for an input line /*{{{*/
539// ---------------------------------------------------------------------
540/* */
541string PromptLine(const char *Text)
542{
543 cout << Text << ':' << endl;
544
545 string Res;
546 getline(cin,Res);
547 return Res;
548}
549 /*}}}*/
550
551// DoAdd - Add a new CDROM /*{{{*/
552// ---------------------------------------------------------------------
553/* */
554bool DoAdd(CommandLine &)
555{
556 // Startup
557 string CDROM = _config->FindDir("Acquire::cdrom::mount","/cdrom/");
558 cout << "Using CD-ROM mount point " << CDROM << endl;
559
560 // Read the database
561 Configuration Database;
562 string DFile = _config->FindFile("Dir::State::cdroms");
563 if (FileExists(DFile) == true)
564 {
565 if (ReadConfigFile(Database,DFile) == false)
566 return _error->Error("Unable to read the cdrom database %s",
567 DFile.c_str());
568 }
569
570 // Unmount the CD and get the user to put in the one they want
571 if (_config->FindB("APT::CDROM::NoMount",false) == false)
572 {
573 cout << "Unmounting CD-ROM" << endl;
574 UnmountCdrom(CDROM);
575
576 // Mount the new CDROM
577 Prompt("Please insert a CD-ROM and press any key");
578 cout << "Mounting CD-ROM" << endl;
579 if (MountCdrom(CDROM) == false)
580 {
581 cout << "Failed to mount the cdrom." << endl;
582 return false;
583 }
584 }
585
586 // Hash the CD to get an ID
587 cout << "Indentifying.. " << flush;
588 string ID;
589 if (IdentCdrom(CDROM,ID) == false)
590 return false;
591 cout << '[' << ID << ']' << endl;
592
593 cout << "Scanning Disc for index files.. " << flush;
594 // Get the CD structure
595 vector<string> List;
596 string StartDir = SafeGetCWD();
597 if (FindPackages(CDROM,List) == false)
598 return false;
599 chdir(StartDir.c_str());
600
601 // Fix up the list
602 DropBinaryArch(List);
603 DropRepeats(List);
604 cout << "Found " << List.size() << " package index files." << endl;
605
606 if (List.size() == 0)
607 return _error->Error("Unable to locate any package files, perhaps this is not a debian CD-ROM");
608
609 // Check if the CD is in the database
610 string Name;
611 if (Database.Exists("CD::" + ID) == false ||
612 _config->FindB("APT::CDROM::Rename",false) == true)
613 {
614 cout << "Please provide a name for this CD-ROM, such as 'Debian 2.1r1 Disk 1'";
615 Name = PromptLine("");
616 }
617 else
618 Name = Database.Find("CD::" + ID);
619 cout << "This Disc is called '" << Name << "'" << endl;
620
621 // Copy the package files to the state directory
622 if (CopyPackages(CDROM,Name,List) == false)
623 return false;
624
625 ConvertToSourcelist(CDROM,List);
626
627 // Print the sourcelist entries
628 cout << "Source List entires for this Disc are:" << endl;
629 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
630 cout << "deb \"cdrom:" << Name << "/\" " << *I << endl;
631
632 return true;
633}
634 /*}}}*/
635
636// ShowHelp - Show the help screen /*{{{*/
637// ---------------------------------------------------------------------
638/* */
639int ShowHelp()
640{
641 cout << PACKAGE << ' ' << VERSION << " for " << ARCHITECTURE <<
642 " compiled on " << __DATE__ << " " << __TIME__ << endl;
643
644 cout << "Usage: apt-cdrom [options] command" << endl;
645 cout << endl;
646 cout << "apt-cdrom is a tool to add CDROM's to APT's source list. The " << endl;
647 cout << "CDROM mount point and device information is taken from apt.conf" << endl;
648 cout << "and /etc/fstab." << endl;
649 cout << endl;
650 cout << "Commands:" << endl;
651 cout << " add - Add a CDROM" << endl;
652 cout << endl;
653 cout << "Options:" << endl;
654 cout << " -h This help text" << endl;
655 cout << " -d CD-ROM mount point" << endl;
656 cout << " -r Rename a recognized CD-ROM" << endl;
657 cout << " -m No mounting" << endl;
658 cout << " -c=? Read this configuration file" << endl;
659 cout << " -o=? Set an arbitary configuration option, ie -o dir::cache=/tmp" << endl;
660 cout << "See fstab(5)" << endl;
661 return 100;
662}
663 /*}}}*/
664
665int main(int argc,const char *argv[])
666{
667 CommandLine::Args Args[] = {
668 {'h',"help","help",0},
669 {'d',"cdrom","Acquire::cdrom::mount",CommandLine::HasArg},
670 {'r',"rename","APT::CDROM::Rename",0},
671 {'m',"no-mount","APT::CDROM::NoMount",0},
672 {'f',"fast","APT::CDROM::Fast",0},
673 {'c',"config-file",0,CommandLine::ConfigFile},
674 {'o',"option",0,CommandLine::ArbItem},
675 {0,0,0,0}};
676 CommandLine::Dispatch Cmds[] = {
677 {"add",&DoAdd},
678 {0,0}};
679
680 // Parse the command line and initialize the package library
681 CommandLine CmdL(Args,_config);
682 if (pkgInitialize(*_config) == false ||
683 CmdL.Parse(argc,argv) == false)
684 {
685 _error->DumpErrors();
686 return 100;
687 }
688
689 // See if the help should be shown
690 if (_config->FindB("help") == true ||
691 CmdL.FileSize() == 0)
692 return ShowHelp();
693
694 // Match the operation
695 CmdL.DispatchArg(Cmds);
696
697 // Print any errors or warnings found during parsing
698 if (_error->empty() == false)
699 {
700 bool Errors = _error->PendingError();
701 _error->DumpErrors();
702 return Errors == true?100:0;
703 }
704
705 return 0;
706}