]> git.saurik.com Git - apt.git/blob - cmdline/apt-cdrom.cc
Added diff-only and tar-only options
[apt.git] / cmdline / apt-cdrom.cc
1 // -*- mode: cpp; mode: fold -*-
2 // Description /*{{{*/
3 // $Id: apt-cdrom.cc,v 1.32 1999/09/03 05:46:48 jgg Exp $
4 /* ######################################################################
5
6 APT CDROM - Tool for handling APT's CDROM database.
7
8 Currently the only option is 'add' which will take the current CD
9 in the drive and add it into the database.
10
11 ##################################################################### */
12 /*}}}*/
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>
21 #include <config.h>
22
23 #include "indexcopy.h"
24
25 #include <iostream>
26 #include <fstream>
27 #include <vector>
28 #include <algorithm>
29 #include <sys/stat.h>
30 #include <fcntl.h>
31 #include <dirent.h>
32 #include <unistd.h>
33 #include <stdio.h>
34 /*}}}*/
35
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
41 binary-* sub dirs. */
42 bool FindPackages(string CD,vector<string> &List,vector<string> &SList,
43 string &InfoDir,unsigned int Depth = 0)
44 {
45 static ino_t Inodes[9];
46 if (Depth >= 7)
47 return true;
48
49 if (CD[CD.length()-1] != '/')
50 CD += '/';
51
52 if (chdir(CD.c_str()) != 0)
53 return _error->Errno("chdir","Unable to change to %s",CD.c_str());
54
55 // Look for a .disk subdirectory
56 struct stat Buf;
57 if (stat(".disk",&Buf) == 0)
58 {
59 if (InfoDir.empty() == true)
60 InfoDir = CD + ".disk/";
61 }
62
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
65 anymore */
66 if (stat("Packages",&Buf) == 0 || stat("Packages.gz",&Buf) == 0)
67 {
68 List.push_back(CD);
69
70 // Continue down if thorough is given
71 if (_config->FindB("APT::CDROM::Thorough",false) == false)
72 return true;
73 }
74 if (stat("Sources.gz",&Buf) == 0 || stat("Sources",&Buf) == 0)
75 {
76 SList.push_back(CD);
77
78 // Continue down if thorough is given
79 if (_config->FindB("APT::CDROM::Thorough",false) == false)
80 return true;
81 }
82
83 DIR *D = opendir(".");
84 if (D == 0)
85 return _error->Errno("opendir","Unable to read %s",CD.c_str());
86
87 // Run over the directory
88 for (struct dirent *Dir = readdir(D); Dir != 0; Dir = readdir(D))
89 {
90 // Skip some files..
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,".disk") == 0 ||
95 strcmp(Dir->d_name,"experimental") == 0 ||
96 strcmp(Dir->d_name,"binary-all") == 0)
97 continue;
98
99 // See if the name is a sub directory
100 struct stat Buf;
101 if (stat(Dir->d_name,&Buf) != 0)
102 continue;
103
104 if (S_ISDIR(Buf.st_mode) == 0)
105 continue;
106
107 unsigned int I;
108 for (I = 0; I != Depth; I++)
109 if (Inodes[I] == Buf.st_ino)
110 break;
111 if (I != Depth)
112 continue;
113
114 // Store the inodes weve seen
115 Inodes[Depth] = Buf.st_ino;
116
117 // Descend
118 if (FindPackages(CD + Dir->d_name,List,SList,InfoDir,Depth+1) == false)
119 break;
120
121 if (chdir(CD.c_str()) != 0)
122 return _error->Errno("chdir","Unable to change to ",CD.c_str());
123 };
124
125 closedir(D);
126
127 return !_error->PendingError();
128 }
129 /*}}}*/
130 // DropBinaryArch - Dump dirs with a string like /binary-<foo>/ /*{{{*/
131 // ---------------------------------------------------------------------
132 /* Here we drop everything that is not this machines arch */
133 bool DropBinaryArch(vector<string> &List)
134 {
135 char S[300];
136 sprintf(S,"/binary-%s/",_config->Find("Apt::Architecture").c_str());
137
138 for (unsigned int I = 0; I < List.size(); I++)
139 {
140 const char *Str = List[I].c_str();
141
142 const char *Res;
143 if ((Res = strstr(Str,"/binary-")) == 0)
144 continue;
145
146 // Weird, remove it.
147 if (strlen(Res) < strlen(S))
148 {
149 List.erase(List.begin() + I);
150 I--;
151 continue;
152 }
153
154 // See if it is our arch
155 if (stringcmp(Res,Res + strlen(S),S) == 0)
156 continue;
157
158 // Erase it
159 List.erase(List.begin() + I);
160 I--;
161 }
162
163 return true;
164 }
165 /*}}}*/
166 // Score - We compute a 'score' for a path /*{{{*/
167 // ---------------------------------------------------------------------
168 /* Paths are scored based on how close they come to what I consider
169 normal. That is ones that have 'dist' 'stable' 'frozen' will score
170 higher than ones without. */
171 int Score(string Path)
172 {
173 int Res = 0;
174 if (Path.find("stable/") != string::npos)
175 Res += 2;
176 if (Path.find("/binary-") != string::npos)
177 Res += 2;
178 if (Path.find("frozen/") != string::npos)
179 Res += 2;
180 if (Path.find("/dists/") != string::npos)
181 Res += 4;
182 if (Path.find("/main/") != string::npos)
183 Res += 2;
184 if (Path.find("/contrib/") != string::npos)
185 Res += 2;
186 if (Path.find("/non-free/") != string::npos)
187 Res += 2;
188 if (Path.find("/non-US/") != string::npos)
189 Res += 2;
190 if (Path.find("/source/") != string::npos)
191 Res += 1;
192 return Res;
193 }
194 /*}}}*/
195 // DropRepeats - Drop repeated files resulting from symlinks /*{{{*/
196 // ---------------------------------------------------------------------
197 /* Here we go and stat every file that we found and strip dup inodes. */
198 bool DropRepeats(vector<string> &List,const char *Name)
199 {
200 // Get a list of all the inodes
201 ino_t *Inodes = new ino_t[List.size()];
202 for (unsigned int I = 0; I != List.size(); I++)
203 {
204 struct stat Buf;
205 if (stat((List[I] + Name).c_str(),&Buf) != 0 &&
206 stat((List[I] + Name + ".gz").c_str(),&Buf) != 0)
207 _error->Errno("stat","Failed to stat %s%s",List[I].c_str(),
208 Name);
209 Inodes[I] = Buf.st_ino;
210 }
211
212 if (_error->PendingError() == true)
213 return false;
214
215 // Look for dups
216 for (unsigned int I = 0; I != List.size(); I++)
217 {
218 for (unsigned int J = I+1; J < List.size(); J++)
219 {
220 // No match
221 if (Inodes[J] != Inodes[I])
222 continue;
223
224 // We score the two paths.. and erase one
225 int ScoreA = Score(List[I]);
226 int ScoreB = Score(List[J]);
227 if (ScoreA < ScoreB)
228 {
229 List[I] = string();
230 break;
231 }
232
233 List[J] = string();
234 }
235 }
236
237 // Wipe erased entries
238 for (unsigned int I = 0; I < List.size();)
239 {
240 if (List[I].empty() == false)
241 I++;
242 else
243 List.erase(List.begin()+I);
244 }
245
246 return true;
247 }
248 /*}}}*/
249
250 // ReduceSourceList - Takes the path list and reduces it /*{{{*/
251 // ---------------------------------------------------------------------
252 /* This takes the list of source list expressed entires and collects
253 similar ones to form a single entry for each dist */
254 bool ReduceSourcelist(string CD,vector<string> &List)
255 {
256 sort(List.begin(),List.end());
257
258 // Collect similar entries
259 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
260 {
261 // Find a space..
262 string::size_type Space = (*I).find(' ');
263 if (Space == string::npos)
264 continue;
265 string::size_type SSpace = (*I).find(' ',Space + 1);
266 if (SSpace == string::npos)
267 continue;
268
269 string Word1 = string(*I,Space,SSpace-Space);
270 for (vector<string>::iterator J = List.begin(); J != I; J++)
271 {
272 // Find a space..
273 string::size_type Space2 = (*J).find(' ');
274 if (Space2 == string::npos)
275 continue;
276 string::size_type SSpace2 = (*J).find(' ',Space2 + 1);
277 if (SSpace2 == string::npos)
278 continue;
279
280 if (string(*J,Space2,SSpace2-Space2) != Word1)
281 continue;
282
283 *J += string(*I,SSpace);
284 *I = string();
285 }
286 }
287
288 // Wipe erased entries
289 for (unsigned int I = 0; I < List.size();)
290 {
291 if (List[I].empty() == false)
292 I++;
293 else
294 List.erase(List.begin()+I);
295 }
296 }
297 /*}}}*/
298 // WriteDatabase - Write the CDROM Database file /*{{{*/
299 // ---------------------------------------------------------------------
300 /* We rewrite the configuration class associated with the cdrom database. */
301 bool WriteDatabase(Configuration &Cnf)
302 {
303 string DFile = _config->FindFile("Dir::State::cdroms");
304 string NewFile = DFile + ".new";
305
306 unlink(NewFile.c_str());
307 ofstream Out(NewFile.c_str());
308 if (!Out)
309 return _error->Errno("ofstream::ofstream",
310 "Failed to open %s.new",DFile.c_str());
311
312 /* Write out all of the configuration directives by walking the
313 configuration tree */
314 const Configuration::Item *Top = Cnf.Tree(0);
315 for (; Top != 0;)
316 {
317 // Print the config entry
318 if (Top->Value.empty() == false)
319 Out << Top->FullTag() + " \"" << Top->Value << "\";" << endl;
320
321 if (Top->Child != 0)
322 {
323 Top = Top->Child;
324 continue;
325 }
326
327 while (Top != 0 && Top->Next == 0)
328 Top = Top->Parent;
329 if (Top != 0)
330 Top = Top->Next;
331 }
332
333 Out.close();
334
335 rename(DFile.c_str(),string(DFile + '~').c_str());
336 if (rename(NewFile.c_str(),DFile.c_str()) != 0)
337 return _error->Errno("rename","Failed to rename %s.new to %s",
338 DFile.c_str(),DFile.c_str());
339
340 return true;
341 }
342 /*}}}*/
343 // WriteSourceList - Write an updated sourcelist /*{{{*/
344 // ---------------------------------------------------------------------
345 /* This reads the old source list and copies it into the new one. It
346 appends the new CDROM entires just after the first block of comments.
347 This places them first in the file. It also removes any old entries
348 that were the same. */
349 bool WriteSourceList(string Name,vector<string> &List,bool Source)
350 {
351 if (List.size() == 0)
352 return true;
353
354 string File = _config->FindFile("Dir::Etc::sourcelist");
355
356 // Open the stream for reading
357 ifstream F(File.c_str(),ios::in | ios::nocreate);
358 if (!F != 0)
359 return _error->Errno("ifstream::ifstream","Opening %s",File.c_str());
360
361 string NewFile = File + ".new";
362 unlink(NewFile.c_str());
363 ofstream Out(NewFile.c_str());
364 if (!Out)
365 return _error->Errno("ofstream::ofstream",
366 "Failed to open %s.new",File.c_str());
367
368 // Create a short uri without the path
369 string ShortURI = "cdrom:" + Name + "/";
370
371 const char *Type;
372 if (Source == true)
373 Type = "deb-src";
374 else
375 Type = "deb";
376
377 char Buffer[300];
378 int CurLine = 0;
379 bool First = true;
380 while (F.eof() == false)
381 {
382 F.getline(Buffer,sizeof(Buffer));
383 CurLine++;
384 _strtabexpand(Buffer,sizeof(Buffer));
385 _strstrip(Buffer);
386
387 // Comment or blank
388 if (Buffer[0] == '#' || Buffer[0] == 0)
389 {
390 Out << Buffer << endl;
391 continue;
392 }
393
394 if (First == true)
395 {
396 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
397 {
398 string::size_type Space = (*I).find(' ');
399 if (Space == string::npos)
400 return _error->Error("Internal error");
401 Out << Type << " \"cdrom:" << Name << "/" << string(*I,0,Space) <<
402 "\" " << string(*I,Space+1) << endl;
403 }
404 }
405 First = false;
406
407 // Grok it
408 string cType;
409 string URI;
410 const char *C = Buffer;
411 if (ParseQuoteWord(C,cType) == false ||
412 ParseQuoteWord(C,URI) == false)
413 {
414 Out << Buffer << endl;
415 continue;
416 }
417
418 // Emit lines like this one
419 if (cType != Type || string(URI,0,ShortURI.length()) != ShortURI)
420 {
421 Out << Buffer << endl;
422 continue;
423 }
424 }
425
426 // Just in case the file was empty
427 if (First == true)
428 {
429 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
430 {
431 string::size_type Space = (*I).find(' ');
432 if (Space == string::npos)
433 return _error->Error("Internal error");
434
435 Out << "deb \"cdrom:" << Name << "/" << string(*I,0,Space) <<
436 "\" " << string(*I,Space+1) << endl;
437 }
438 }
439
440 Out.close();
441
442 rename(File.c_str(),string(File + '~').c_str());
443 if (rename(NewFile.c_str(),File.c_str()) != 0)
444 return _error->Errno("rename","Failed to rename %s.new to %s",
445 File.c_str(),File.c_str());
446
447 return true;
448 }
449 /*}}}*/
450
451 // Prompt - Simple prompt /*{{{*/
452 // ---------------------------------------------------------------------
453 /* */
454 void Prompt(const char *Text)
455 {
456 char C;
457 cout << Text << ' ' << flush;
458 read(STDIN_FILENO,&C,1);
459 if (C != '\n')
460 cout << endl;
461 }
462 /*}}}*/
463 // PromptLine - Prompt for an input line /*{{{*/
464 // ---------------------------------------------------------------------
465 /* */
466 string PromptLine(const char *Text)
467 {
468 cout << Text << ':' << endl;
469
470 string Res;
471 getline(cin,Res);
472 return Res;
473 }
474 /*}}}*/
475
476 // DoAdd - Add a new CDROM /*{{{*/
477 // ---------------------------------------------------------------------
478 /* This does the main add bit.. We show some status and things. The
479 sequence is to mount/umount the CD, Ident it then scan it for package
480 files and reduce that list. Then we copy over the package files and
481 verify them. Then rewrite the database files */
482 bool DoAdd(CommandLine &)
483 {
484 // Startup
485 string CDROM = _config->FindDir("Acquire::cdrom::mount","/cdrom/");
486 if (CDROM[0] == '.')
487 CDROM= SafeGetCWD() + '/' + CDROM;
488
489 cout << "Using CD-ROM mount point " << CDROM << endl;
490
491 // Read the database
492 Configuration Database;
493 string DFile = _config->FindFile("Dir::State::cdroms");
494 if (FileExists(DFile) == true)
495 {
496 if (ReadConfigFile(Database,DFile) == false)
497 return _error->Error("Unable to read the cdrom database %s",
498 DFile.c_str());
499 }
500
501 // Unmount the CD and get the user to put in the one they want
502 if (_config->FindB("APT::CDROM::NoMount",false) == false)
503 {
504 cout << "Unmounting CD-ROM" << endl;
505 UnmountCdrom(CDROM);
506
507 // Mount the new CDROM
508 Prompt("Please insert a Disc in the drive and press enter");
509 cout << "Mounting CD-ROM" << endl;
510 if (MountCdrom(CDROM) == false)
511 return _error->Error("Failed to mount the cdrom.");
512 }
513
514 // Hash the CD to get an ID
515 cout << "Identifying.. " << flush;
516 string ID;
517 if (IdentCdrom(CDROM,ID) == false)
518 {
519 cout << endl;
520 return false;
521 }
522
523 cout << '[' << ID << ']' << endl;
524
525 cout << "Scanning Disc for index files.. " << flush;
526 // Get the CD structure
527 vector<string> List;
528 vector<string> sList;
529 string StartDir = SafeGetCWD();
530 string InfoDir;
531 if (FindPackages(CDROM,List,sList,InfoDir) == false)
532 {
533 cout << endl;
534 return false;
535 }
536
537 chdir(StartDir.c_str());
538
539 if (_config->FindB("Debug::aptcdrom",false) == true)
540 {
541 cout << "I found (binary):" << endl;
542 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
543 cout << *I << endl;
544 cout << "I found (source):" << endl;
545 for (vector<string>::iterator I = sList.begin(); I != sList.end(); I++)
546 cout << *I << endl;
547 }
548
549 // Fix up the list
550 DropBinaryArch(List);
551 DropRepeats(List,"Packages");
552 DropRepeats(sList,"Sources");
553 cout << "Found " << List.size() << " package indexes and " << sList.size() <<
554 " source indexes." << endl;
555
556 if (List.size() == 0)
557 return _error->Error("Unable to locate any package files, perhaps this is not a Debian Disc");
558
559 // Check if the CD is in the database
560 string Name;
561 if (Database.Exists("CD::" + ID) == false ||
562 _config->FindB("APT::CDROM::Rename",false) == true)
563 {
564 // Try to use the CDs label if at all possible
565 if (InfoDir.empty() == false &&
566 FileExists(InfoDir + "/info") == true)
567 {
568 ifstream F(string(InfoDir + "/info").c_str());
569 if (!F == 0)
570 getline(F,Name);
571
572 if (Name.empty() == false)
573 {
574 cout << "Found label '" << Name << "'" << endl;
575 Database.Set("CD::" + ID + "::Label",Name);
576 }
577 }
578
579 if (_config->FindB("APT::CDROM::Rename",false) == true ||
580 Name.empty() == true)
581 {
582 cout << "Please provide a name for this Disc, such as 'Debian 2.1r1 Disk 1'";
583 while (1)
584 {
585 Name = PromptLine("");
586 if (Name.empty() == false &&
587 Name.find('"') == string::npos &&
588 Name.find(':') == string::npos &&
589 Name.find('/') == string::npos)
590 break;
591 cout << "That is not a valid name, try again " << endl;
592 }
593 }
594 }
595 else
596 Name = Database.Find("CD::" + ID);
597
598 string::iterator J = Name.begin();
599 for (; J != Name.end(); J++)
600 if (*J == '/' || *J == '"' || *J == ':')
601 *J = '_';
602
603 Database.Set("CD::" + ID,Name);
604 cout << "This Disc is called '" << Name << "'" << endl;
605
606 // Copy the package files to the state directory
607 PackageCopy Copy;
608 SourceCopy SrcCopy;
609 if (Copy.CopyPackages(CDROM,Name,List) == false ||
610 SrcCopy.CopyPackages(CDROM,Name,sList) == false)
611 return false;
612
613 ReduceSourcelist(CDROM,List);
614 ReduceSourcelist(CDROM,sList);
615
616 // Write the database and sourcelist
617 if (_config->FindB("APT::cdrom::NoAct",false) == false)
618 {
619 if (WriteDatabase(Database) == false)
620 return false;
621
622 cout << "Writing new source list" << endl;
623 if (WriteSourceList(Name,List,false) == false ||
624 WriteSourceList(Name,sList,true) == false)
625 return false;
626 }
627
628 // Print the sourcelist entries
629 cout << "Source List entries for this Disc are:" << endl;
630 for (vector<string>::iterator I = List.begin(); I != List.end(); I++)
631 {
632 string::size_type Space = (*I).find(' ');
633 if (Space == string::npos)
634 return _error->Error("Internal error");
635
636 cout << "deb \"cdrom:" << Name << "/" << string(*I,0,Space) <<
637 "\" " << string(*I,Space+1) << endl;
638 }
639
640 for (vector<string>::iterator I = sList.begin(); I != sList.end(); I++)
641 {
642 string::size_type Space = (*I).find(' ');
643 if (Space == string::npos)
644 return _error->Error("Internal error");
645
646 cout << "deb-src \"cdrom:" << Name << "/" << string(*I,0,Space) <<
647 "\" " << string(*I,Space+1) << endl;
648 }
649
650 cout << "Repeat this process for the rest of the CDs in your set." << endl;
651 return true;
652 }
653 /*}}}*/
654
655 // ShowHelp - Show the help screen /*{{{*/
656 // ---------------------------------------------------------------------
657 /* */
658 int ShowHelp()
659 {
660 cout << PACKAGE << ' ' << VERSION << " for " << ARCHITECTURE <<
661 " compiled on " << __DATE__ << " " << __TIME__ << endl;
662 if (_config->FindB("version") == true)
663 return 100;
664
665 cout << "Usage: apt-cdrom [options] command" << endl;
666 cout << endl;
667 cout << "apt-cdrom is a tool to add CDROM's to APT's source list. The " << endl;
668 cout << "CDROM mount point and device information is taken from apt.conf" << endl;
669 cout << "and /etc/fstab." << endl;
670 cout << endl;
671 cout << "Commands:" << endl;
672 cout << " add - Add a CDROM" << endl;
673 cout << endl;
674 cout << "Options:" << endl;
675 cout << " -h This help text" << endl;
676 cout << " -d CD-ROM mount point" << endl;
677 cout << " -r Rename a recognized CD-ROM" << endl;
678 cout << " -m No mounting" << endl;
679 cout << " -f Fast mode, don't check package files" << endl;
680 cout << " -a Thorough scan mode" << endl;
681 cout << " -c=? Read this configuration file" << endl;
682 cout << " -o=? Set an arbitary configuration option, eg -o dir::cache=/tmp" << endl;
683 cout << "See fstab(5)" << endl;
684 return 100;
685 }
686 /*}}}*/
687
688 int main(int argc,const char *argv[])
689 {
690 CommandLine::Args Args[] = {
691 {'h',"help","help",0},
692 {'v',"version","version",0},
693 {'d',"cdrom","Acquire::cdrom::mount",CommandLine::HasArg},
694 {'r',"rename","APT::CDROM::Rename",0},
695 {'m',"no-mount","APT::CDROM::NoMount",0},
696 {'f',"fast","APT::CDROM::Fast",0},
697 {'n',"just-print","APT::CDROM::NoAct",0},
698 {'n',"recon","APT::CDROM::NoAct",0},
699 {'n',"no-act","APT::CDROM::NoAct",0},
700 {'a',"thorough","APT::CDROM::Thorough",0},
701 {'c',"config-file",0,CommandLine::ConfigFile},
702 {'o',"option",0,CommandLine::ArbItem},
703 {0,0,0,0}};
704 CommandLine::Dispatch Cmds[] = {
705 {"add",&DoAdd},
706 {0,0}};
707
708 // Parse the command line and initialize the package library
709 CommandLine CmdL(Args,_config);
710 if (pkgInitialize(*_config) == false ||
711 CmdL.Parse(argc,argv) == false)
712 {
713 _error->DumpErrors();
714 return 100;
715 }
716
717 // See if the help should be shown
718 if (_config->FindB("help") == true ||
719 CmdL.FileSize() == 0)
720 return ShowHelp();
721
722 // Deal with stdout not being a tty
723 if (ttyname(STDOUT_FILENO) == 0 && _config->FindI("quiet",0) < 1)
724 _config->Set("quiet","1");
725
726 // Match the operation
727 CmdL.DispatchArg(Cmds);
728
729 // Print any errors or warnings found during parsing
730 if (_error->empty() == false)
731 {
732 bool Errors = _error->PendingError();
733 _error->DumpErrors();
734 return Errors == true?100:0;
735 }
736
737 return 0;
738 }