]>
git.saurik.com Git - apt.git/blob - ftparchive/multicompress.cc
1 // -*- mode: cpp; mode: fold -*-
3 // $Id: multicompress.cc,v 1.4 2003/02/10 07:34:41 doogie Exp $
4 /* ######################################################################
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.
14 ##################################################################### */
16 // Include Files /*{{{*/
17 #include "multicompress.h"
20 #include <apt-pkg/strutl.h>
21 #include <apt-pkg/error.h>
22 #include <apt-pkg/md5.h>
24 #include <sys/types.h>
33 const MultiCompress::CompType
MultiCompress::Compressors
[] =
35 {"gzip",".gz","gzip","-9n","-d",2},
36 {"bzip2",".bz2","bzip2","-9","-d",3},
39 // MultiCompress::MultiCompress - Constructor /*{{{*/
40 // ---------------------------------------------------------------------
41 /* Setup the file outputs, compression modes and fork the writer child */
42 MultiCompress::MultiCompress(string Output
,string Compress
,
43 mode_t Permissions
,bool Write
)
49 this->Permissions
= Permissions
;
51 /* Parse the compression string, a space separated lists of compresison
53 string::const_iterator I
= Compress
.begin();
54 for (; I
!= Compress
.end();)
56 for (; I
!= Compress
.end() && isspace(*I
); I
++);
59 string::const_iterator Start
= I
;
60 for (; I
!= Compress
.end() && !isspace(*I
); I
++);
62 // Find the matching compressor
63 const CompType
*Comp
= Compressors
;
64 for (; Comp
->Name
!= 0; Comp
++)
65 if (stringcmp(Start
,I
,Comp
->Name
) == 0)
71 _error
->Warning(_("Unknown compression algorithm '%s'"),string(Start
,I
).c_str());
75 // Create and link in a new output
76 Files
*NewOut
= new Files
;
77 NewOut
->Next
= Outputs
;
79 NewOut
->CompressProg
= Comp
;
80 NewOut
->Output
= Output
+Comp
->Extension
;
83 if (stat(NewOut
->Output
.c_str(),&St
) == 0)
84 NewOut
->OldMTime
= St
.st_mtime
;
92 /* Open all the temp files now so we can report any errors. File is
93 made unreable to prevent people from touching it during creating. */
94 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
95 I
->TmpFile
.Open(I
->Output
+ ".new",FileFd::WriteEmpty
,0600);
96 if (_error
->PendingError() == true)
101 _error
->Error(_("Compressed output %s needs a compression set"),Output
.c_str());
108 // MultiCompress::~MultiCompress - Destructor /*{{{*/
109 // ---------------------------------------------------------------------
110 /* Just erase the file linked list. */
111 MultiCompress::~MultiCompress()
115 for (; Outputs
!= 0;)
117 Files
*Tmp
= Outputs
->Next
;
123 // MultiCompress::GetStat - Get stat information for compressed files /*{{{*/
124 // ---------------------------------------------------------------------
125 /* This checks each compressed file to make sure it exists and returns
126 stat information for a random file from the collection. False means
127 one or more of the files is missing. */
128 bool MultiCompress::GetStat(string Output
,string Compress
,struct stat
&St
)
130 /* Parse the compression string, a space separated lists of compresison
132 string::const_iterator I
= Compress
.begin();
133 bool DidStat
= false;
134 for (; I
!= Compress
.end();)
136 for (; I
!= Compress
.end() && isspace(*I
); I
++);
139 string::const_iterator Start
= I
;
140 for (; I
!= Compress
.end() && !isspace(*I
); I
++);
142 // Find the matching compressor
143 const CompType
*Comp
= Compressors
;
144 for (; Comp
->Name
!= 0; Comp
++)
145 if (stringcmp(Start
,I
,Comp
->Name
) == 0)
152 string Name
= Output
+Comp
->Extension
;
153 if (stat(Name
.c_str(),&St
) != 0)
160 // MultiCompress::Start - Start up the writer child /*{{{*/
161 // ---------------------------------------------------------------------
162 /* Fork a child and setup the communication pipe. */
163 bool MultiCompress::Start()
165 // Create a data pipe
166 int Pipe
[2] = {-1,-1};
168 return _error
->Errno("pipe",_("Failed to create IPC pipe to subprocess"));
169 for (int I
= 0; I
!= 2; I
++)
170 SetCloseExec(Pipe
[I
],true);
178 if (_error
->PendingError() == true)
180 _error
->DumpErrors();
186 /* Tidy up the temp files, we open them in the constructor so as to
187 get proper error reporting. Close them now. */
188 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
192 Input
= fdopen(Pipe
[1],"w");
194 return _error
->Errno("fdopen",_("Failed to create FILE*"));
197 return _error
->Errno("fork",_("Failed to fork"));
201 // MultiCompress::Die - Clean up the writer /*{{{*/
202 // ---------------------------------------------------------------------
204 bool MultiCompress::Die()
211 bool Res
= ExecWait(Outputter
,_("Compress child"),false);
216 // MultiCompress::Finalize - Finish up writing /*{{{*/
217 // ---------------------------------------------------------------------
218 /* This is only necessary for statistics reporting. */
219 bool MultiCompress::Finalize(unsigned long &OutSize
)
222 if (Input
== 0 || Die() == false)
228 // Check the mtimes to see if the files were replaced.
229 bool Changed
= false;
230 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
233 if (stat(I
->Output
.c_str(),&St
) != 0)
234 return _error
->Error(_("Internal error, failed to create %s"),
237 if (I
->OldMTime
!= St
.st_mtime
)
241 // Update the mtime if necessary
242 if (UpdateMTime
> 0 &&
243 (Now
- St
.st_mtime
> (signed)UpdateMTime
|| St
.st_mtime
> Now
))
246 Buf
.actime
= Buf
.modtime
= Now
;
247 utime(I
->Output
.c_str(),&Buf
);
252 // Force the file permissions
253 if (St
.st_mode
!= Permissions
)
254 chmod(I
->Output
.c_str(),Permissions
);
256 OutSize
+= St
.st_size
;
259 if (Changed
== false)
265 // MultiCompress::OpenCompress - Open the compressor /*{{{*/
266 // ---------------------------------------------------------------------
267 /* This opens the compressor, either in compress mode or decompress
268 mode. FileFd is always the compressor input/output file,
269 OutFd is the created pipe, Input for Compress, Output for Decompress. */
270 bool MultiCompress::OpenCompress(const CompType
*Prog
,pid_t
&Pid
,int FileFd
,
271 int &OutFd
,bool Comp
)
276 if (Prog
->Binary
== 0)
282 // Create a data pipe
283 int Pipe
[2] = {-1,-1};
285 return _error
->Errno("pipe",_("Failed to create subprocess IPC"));
286 for (int J
= 0; J
!= 2; J
++)
287 SetCloseExec(Pipe
[J
],true);
300 dup2(FileFd
,STDOUT_FILENO
);
301 dup2(Pipe
[0],STDIN_FILENO
);
305 dup2(FileFd
,STDIN_FILENO
);
306 dup2(Pipe
[1],STDOUT_FILENO
);
309 SetCloseExec(STDOUT_FILENO
,false);
310 SetCloseExec(STDIN_FILENO
,false);
313 Args
[0] = Prog
->Binary
;
315 Args
[1] = Prog
->CompArgs
;
317 Args
[1] = Prog
->UnCompArgs
;
319 execvp(Args
[0],(char **)Args
);
320 cerr
<< _("Failed to exec compressor ") << Args
[0] << endl
;
330 // MultiCompress::OpenOld - Open an old file /*{{{*/
331 // ---------------------------------------------------------------------
332 /* This opens one of the original output files, possibly decompressing it. */
333 bool MultiCompress::OpenOld(int &Fd
,pid_t
&Proc
)
335 Files
*Best
= Outputs
;
336 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
337 if (Best
->CompressProg
->Cost
> I
->CompressProg
->Cost
)
341 FileFd
F(Best
->Output
,FileFd::ReadOnly
);
342 if (_error
->PendingError() == true)
345 // Decompress the file so we can read it
346 if (OpenCompress(Best
->CompressProg
,Proc
,F
.Fd(),Fd
,false) == false)
352 // MultiCompress::CloseOld - Close the old file /*{{{*/
353 // ---------------------------------------------------------------------
355 bool MultiCompress::CloseOld(int Fd
,pid_t Proc
)
359 if (ExecWait(Proc
,_("decompressor"),false) == false)
364 // MultiCompress::Child - The writer child /*{{{*/
365 // ---------------------------------------------------------------------
366 /* The child process forks a bunch of compression children and takes
367 input on FD and passes it to all the compressor childer. On the way it
368 computes the MD5 of the raw data. After this the raw data in the
369 original files is compared to see if this data is new. If the data
370 is new then the temp files are renamed, otherwise they are erased. */
371 bool MultiCompress::Child(int FD
)
373 // Start the compression children.
374 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
376 if (OpenCompress(I
->CompressProg
,I
->CompressProc
,I
->TmpFile
.Fd(),
377 I
->Fd
,true) == false)
381 /* Okay, now we just feed data from FD to all the other FDs. Also
382 stash a hash of the data to use later. */
383 SetNonBlock(FD
,false);
384 unsigned char Buffer
[32*1024];
385 unsigned long FileSize
= 0;
390 int Res
= read(FD
,Buffer
,sizeof(Buffer
));
398 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
400 if (write(I
->Fd
,Buffer
,Res
) != Res
)
402 _error
->Errno("write",_("IO to subprocess/file failed"));
408 // Close all the writers
409 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
412 // Wait for the compressors to exit
413 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
415 if (I
->CompressProc
!= -1)
416 ExecWait(I
->CompressProc
,I
->CompressProg
->Binary
,false);
419 if (_error
->PendingError() == true)
422 /* Now we have to copy the files over, or erase them if they
423 have not changed. First find the cheapest decompressor */
424 bool Missing
= false;
425 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
427 if (I
->OldMTime
== 0)
434 // Check the MD5 of the lowest cost entity.
435 while (Missing
== false)
439 if (OpenOld(CompFd
,Proc
) == false)
447 unsigned long NewFileSize
= 0;
450 int Res
= read(CompFd
,Buffer
,sizeof(Buffer
));
454 return _error
->Errno("read",_("Failed to read while computing MD5"));
456 OldMD5
.Add(Buffer
,Res
);
459 // Tidy the compressor
460 if (CloseOld(CompFd
,Proc
) == false)
464 if (OldMD5
.Result() == MD5
.Result() &&
465 FileSize
== NewFileSize
)
467 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
470 if (unlink(I
->TmpFile
.Name().c_str()) != 0)
471 _error
->Errno("unlink",_("Problem unlinking %s"),
472 I
->TmpFile
.Name().c_str());
474 return !_error
->PendingError();
480 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
482 // Set the correct file modes
483 fchmod(I
->TmpFile
.Fd(),Permissions
);
485 if (rename(I
->TmpFile
.Name().c_str(),I
->Output
.c_str()) != 0)
486 _error
->Errno("rename",_("Failed to rename %s to %s"),
487 I
->TmpFile
.Name().c_str(),I
->Output
.c_str());
491 return !_error
->PendingError();