]> git.saurik.com Git - apt.git/blame - ftparchive/multicompress.cc
move fd duplication closer to the gz/bz2 open calls
[apt.git] / ftparchive / multicompress.cc
CommitLineData
b2e465d6
AL
1// -*- mode: cpp; mode: fold -*-
2// Description /*{{{*/
dc738e7a 3// $Id: multicompress.cc,v 1.4 2003/02/10 07:34:41 doogie Exp $
b2e465d6
AL
4/* ######################################################################
5
6 MultiCompressor
7
8 This class is very complicated in order to optimize for the common
9 case of its use, writing a large set of compressed files that are
10 different from the old set. It spawns off compressors in parallel
11 to maximize compression throughput and has a separate task managing
12 the data going into the compressors.
13
14 ##################################################################### */
15 /*}}}*/
16// Include Files /*{{{*/
ea542140
DK
17#include <config.h>
18
699b209e 19#include <apt-pkg/fileutl.h>
b2e465d6
AL
20#include <apt-pkg/strutl.h>
21#include <apt-pkg/error.h>
22#include <apt-pkg/md5.h>
453b82a3
DK
23#include <apt-pkg/aptconfiguration.h>
24#include <apt-pkg/hashsum_template.h>
ea542140 25
453b82a3
DK
26#include <ctype.h>
27#include <vector>
b2e465d6
AL
28#include <sys/types.h>
29#include <sys/stat.h>
246bbb61 30#include <sys/time.h>
b2e465d6 31#include <unistd.h>
ea542140
DK
32
33#include "multicompress.h"
34#include <apti18n.h>
b2e465d6
AL
35 /*}}}*/
36
812f4169
AL
37using namespace std;
38
b2e465d6
AL
39
40// MultiCompress::MultiCompress - Constructor /*{{{*/
41// ---------------------------------------------------------------------
42/* Setup the file outputs, compression modes and fork the writer child */
9209ec47
DK
43MultiCompress::MultiCompress(string const &Output,string const &Compress,
44 mode_t const &Permissions,bool const &Write) :
45 Permissions(Permissions)
b2e465d6
AL
46{
47 Outputs = 0;
48 Outputter = -1;
49 Input = 0;
50 UpdateMTime = 0;
03bef784 51
b2e465d6
AL
52 /* Parse the compression string, a space separated lists of compresison
53 types */
54 string::const_iterator I = Compress.begin();
55 for (; I != Compress.end();)
56 {
f7f0d6c7 57 for (; I != Compress.end() && isspace(*I); ++I);
b2e465d6
AL
58
59 // Grab a word
60 string::const_iterator Start = I;
f7f0d6c7 61 for (; I != Compress.end() && !isspace(*I); ++I);
b2e465d6
AL
62
63 // Find the matching compressor
03bef784
DK
64 std::vector<APT::Configuration::Compressor> Compressors = APT::Configuration::getCompressors();
65 std::vector<APT::Configuration::Compressor>::const_iterator Comp = Compressors.begin();
66 for (; Comp != Compressors.end(); ++Comp)
67 if (stringcmp(Start,I,Comp->Name.c_str()) == 0)
b2e465d6
AL
68 break;
69
70 // Hmm.. unknown.
03bef784 71 if (Comp == Compressors.end())
b2e465d6 72 {
db0db9fe 73 _error->Warning(_("Unknown compression algorithm '%s'"),string(Start,I).c_str());
b2e465d6
AL
74 continue;
75 }
76
77 // Create and link in a new output
78 Files *NewOut = new Files;
79 NewOut->Next = Outputs;
80 Outputs = NewOut;
03bef784 81 NewOut->CompressProg = *Comp;
b2e465d6
AL
82 NewOut->Output = Output+Comp->Extension;
83
84 struct stat St;
85 if (stat(NewOut->Output.c_str(),&St) == 0)
86 NewOut->OldMTime = St.st_mtime;
87 else
88 NewOut->OldMTime = 0;
89 }
90
91 if (Write == false)
92 return;
93
94 /* Open all the temp files now so we can report any errors. File is
95 made unreable to prevent people from touching it during creating. */
96 for (Files *I = Outputs; I != 0; I = I->Next)
52b47296 97 I->TmpFile.Open(I->Output + ".new", FileFd::WriteOnly | FileFd::Create | FileFd::Empty, FileFd::Extension, 0600);
b2e465d6
AL
98 if (_error->PendingError() == true)
99 return;
100
101 if (Outputs == 0)
102 {
dc738e7a 103 _error->Error(_("Compressed output %s needs a compression set"),Output.c_str());
b2e465d6
AL
104 return;
105 }
106
107 Start();
108}
109 /*}}}*/
110// MultiCompress::~MultiCompress - Destructor /*{{{*/
111// ---------------------------------------------------------------------
112/* Just erase the file linked list. */
113MultiCompress::~MultiCompress()
114{
115 Die();
116
117 for (; Outputs != 0;)
118 {
119 Files *Tmp = Outputs->Next;
120 delete Outputs;
121 Outputs = Tmp;
122 }
123}
124 /*}}}*/
125// MultiCompress::GetStat - Get stat information for compressed files /*{{{*/
126// ---------------------------------------------------------------------
127/* This checks each compressed file to make sure it exists and returns
128 stat information for a random file from the collection. False means
129 one or more of the files is missing. */
9209ec47 130bool MultiCompress::GetStat(string const &Output,string const &Compress,struct stat &St)
b2e465d6
AL
131{
132 /* Parse the compression string, a space separated lists of compresison
133 types */
134 string::const_iterator I = Compress.begin();
135 bool DidStat = false;
136 for (; I != Compress.end();)
137 {
f7f0d6c7 138 for (; I != Compress.end() && isspace(*I); ++I);
b2e465d6
AL
139
140 // Grab a word
141 string::const_iterator Start = I;
f7f0d6c7 142 for (; I != Compress.end() && !isspace(*I); ++I);
b2e465d6
AL
143
144 // Find the matching compressor
03bef784
DK
145 std::vector<APT::Configuration::Compressor> Compressors = APT::Configuration::getCompressors();
146 std::vector<APT::Configuration::Compressor>::const_iterator Comp = Compressors.begin();
147 for (; Comp != Compressors.end(); ++Comp)
148 if (stringcmp(Start,I,Comp->Name.c_str()) == 0)
b2e465d6
AL
149 break;
150
151 // Hmm.. unknown.
03bef784 152 if (Comp == Compressors.end())
b2e465d6
AL
153 continue;
154
155 string Name = Output+Comp->Extension;
156 if (stat(Name.c_str(),&St) != 0)
157 return false;
158 DidStat = true;
159 }
160 return DidStat;
161}
162 /*}}}*/
163// MultiCompress::Start - Start up the writer child /*{{{*/
164// ---------------------------------------------------------------------
165/* Fork a child and setup the communication pipe. */
166bool MultiCompress::Start()
167{
168 // Create a data pipe
169 int Pipe[2] = {-1,-1};
170 if (pipe(Pipe) != 0)
dc738e7a 171 return _error->Errno("pipe",_("Failed to create IPC pipe to subprocess"));
b2e465d6
AL
172 for (int I = 0; I != 2; I++)
173 SetCloseExec(Pipe[I],true);
174
175 // The child..
176 Outputter = fork();
177 if (Outputter == 0)
178 {
179 close(Pipe[1]);
180 Child(Pipe[0]);
181 if (_error->PendingError() == true)
182 {
183 _error->DumpErrors();
184 _exit(100);
185 }
186 _exit(0);
187 };
188
b2e465d6
AL
189 close(Pipe[0]);
190 Input = fdopen(Pipe[1],"w");
191 if (Input == 0)
dc738e7a 192 return _error->Errno("fdopen",_("Failed to create FILE*"));
b2e465d6
AL
193
194 if (Outputter == -1)
dc738e7a 195 return _error->Errno("fork",_("Failed to fork"));
b2e465d6
AL
196 return true;
197}
198 /*}}}*/
199// MultiCompress::Die - Clean up the writer /*{{{*/
200// ---------------------------------------------------------------------
201/* */
202bool MultiCompress::Die()
203{
204 if (Input == 0)
205 return true;
206
207 fclose(Input);
208 Input = 0;
db0db9fe 209 bool Res = ExecWait(Outputter,_("Compress child"),false);
b2e465d6
AL
210 Outputter = -1;
211 return Res;
212}
213 /*}}}*/
214// MultiCompress::Finalize - Finish up writing /*{{{*/
215// ---------------------------------------------------------------------
216/* This is only necessary for statistics reporting. */
650faab0 217bool MultiCompress::Finalize(unsigned long long &OutSize)
b2e465d6
AL
218{
219 OutSize = 0;
220 if (Input == 0 || Die() == false)
221 return false;
222
223 time_t Now;
224 time(&Now);
225
226 // Check the mtimes to see if the files were replaced.
227 bool Changed = false;
228 for (Files *I = Outputs; I != 0; I = I->Next)
229 {
230 struct stat St;
231 if (stat(I->Output.c_str(),&St) != 0)
db0db9fe 232 return _error->Error(_("Internal error, failed to create %s"),
b2e465d6
AL
233 I->Output.c_str());
234
235 if (I->OldMTime != St.st_mtime)
236 Changed = true;
237 else
238 {
239 // Update the mtime if necessary
9ce3cfc9 240 if (UpdateMTime > 0 &&
b2e465d6
AL
241 (Now - St.st_mtime > (signed)UpdateMTime || St.st_mtime > Now))
242 {
246bbb61 243 utimes(I->Output.c_str(), NULL);
b2e465d6 244 Changed = true;
9ce3cfc9 245 }
b2e465d6
AL
246 }
247
248 // Force the file permissions
249 if (St.st_mode != Permissions)
250 chmod(I->Output.c_str(),Permissions);
251
252 OutSize += St.st_size;
253 }
254
255 if (Changed == false)
256 OutSize = 0;
257
258 return true;
259}
260 /*}}}*/
b2e465d6
AL
261// MultiCompress::OpenOld - Open an old file /*{{{*/
262// ---------------------------------------------------------------------
263/* This opens one of the original output files, possibly decompressing it. */
12d1f5b3 264bool MultiCompress::OpenOld(FileFd &Fd)
b2e465d6
AL
265{
266 Files *Best = Outputs;
267 for (Files *I = Outputs; I != 0; I = I->Next)
03bef784 268 if (Best->CompressProg.Cost > I->CompressProg.Cost)
b2e465d6
AL
269 Best = I;
270
271 // Open the file
12d1f5b3 272 return Fd.Open(Best->Output, FileFd::ReadOnly, FileFd::Extension);
b2e465d6
AL
273}
274 /*}}}*/
b2e465d6
AL
275// MultiCompress::Child - The writer child /*{{{*/
276// ---------------------------------------------------------------------
277/* The child process forks a bunch of compression children and takes
c6474fb6 278 input on FD and passes it to all the compressor child. On the way it
b2e465d6
AL
279 computes the MD5 of the raw data. After this the raw data in the
280 original files is compared to see if this data is new. If the data
281 is new then the temp files are renamed, otherwise they are erased. */
9209ec47 282bool MultiCompress::Child(int const &FD)
b2e465d6 283{
b2e465d6
AL
284 /* Okay, now we just feed data from FD to all the other FDs. Also
285 stash a hash of the data to use later. */
286 SetNonBlock(FD,false);
287 unsigned char Buffer[32*1024];
650faab0 288 unsigned long long FileSize = 0;
b2e465d6
AL
289 MD5Summation MD5;
290 while (1)
291 {
292 WaitFd(FD,false);
293 int Res = read(FD,Buffer,sizeof(Buffer));
294 if (Res == 0)
295 break;
296 if (Res < 0)
297 continue;
298
299 MD5.Add(Buffer,Res);
300 FileSize += Res;
301 for (Files *I = Outputs; I != 0; I = I->Next)
302 {
52b47296 303 if (I->TmpFile.Write(Buffer, Res) == false)
b2e465d6 304 {
dc738e7a 305 _error->Errno("write",_("IO to subprocess/file failed"));
b2e465d6
AL
306 break;
307 }
308 }
309 }
52b47296 310
b2e465d6
AL
311 if (_error->PendingError() == true)
312 return false;
313
314 /* Now we have to copy the files over, or erase them if they
315 have not changed. First find the cheapest decompressor */
316 bool Missing = false;
317 for (Files *I = Outputs; I != 0; I = I->Next)
318 {
319 if (I->OldMTime == 0)
320 {
321 Missing = true;
322 break;
323 }
324 }
325
326 // Check the MD5 of the lowest cost entity.
327 while (Missing == false)
328 {
12d1f5b3
DK
329 FileFd CompFd;
330 if (OpenOld(CompFd) == false)
b2e465d6
AL
331 {
332 _error->Discard();
333 break;
334 }
12d1f5b3 335
b2e465d6
AL
336 // Compute the hash
337 MD5Summation OldMD5;
650faab0 338 unsigned long long NewFileSize = 0;
b2e465d6
AL
339 while (1)
340 {
12d1f5b3
DK
341 unsigned long long Res = 0;
342 if (CompFd.Read(Buffer,sizeof(Buffer), &Res) == false)
343 return _error->Errno("read",_("Failed to read while computing MD5"));
b2e465d6
AL
344 if (Res == 0)
345 break;
b2e465d6
AL
346 NewFileSize += Res;
347 OldMD5.Add(Buffer,Res);
348 }
12d1f5b3 349 CompFd.Close();
b2e465d6
AL
350
351 // Check the hash
352 if (OldMD5.Result() == MD5.Result() &&
353 FileSize == NewFileSize)
354 {
355 for (Files *I = Outputs; I != 0; I = I->Next)
356 {
357 I->TmpFile.Close();
358 if (unlink(I->TmpFile.Name().c_str()) != 0)
dc738e7a 359 _error->Errno("unlink",_("Problem unlinking %s"),
b2e465d6
AL
360 I->TmpFile.Name().c_str());
361 }
362 return !_error->PendingError();
363 }
364 break;
365 }
366
367 // Finalize
368 for (Files *I = Outputs; I != 0; I = I->Next)
369 {
370 // Set the correct file modes
cc130d71 371 chmod(I->TmpFile.Name().c_str(),Permissions);
b2e465d6
AL
372
373 if (rename(I->TmpFile.Name().c_str(),I->Output.c_str()) != 0)
dc738e7a 374 _error->Errno("rename",_("Failed to rename %s to %s"),
b2e465d6
AL
375 I->TmpFile.Name().c_str(),I->Output.c_str());
376 I->TmpFile.Close();
377 }
378
379 return !_error->PendingError();
380}
381 /*}}}*/
382