]>
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>
34 // MultiCompress::MultiCompress - Constructor /*{{{*/
35 // ---------------------------------------------------------------------
36 /* Setup the file outputs, compression modes and fork the writer child */
37 MultiCompress::MultiCompress(string
const &Output
,string
const &Compress
,
38 mode_t
const &Permissions
,bool const &Write
) :
39 Permissions(Permissions
)
46 /* Parse the compression string, a space separated lists of compresison
48 string::const_iterator I
= Compress
.begin();
49 for (; I
!= Compress
.end();)
51 for (; I
!= Compress
.end() && isspace(*I
); ++I
);
54 string::const_iterator Start
= I
;
55 for (; I
!= Compress
.end() && !isspace(*I
); ++I
);
57 // Find the matching compressor
58 std::vector
<APT::Configuration::Compressor
> Compressors
= APT::Configuration::getCompressors();
59 std::vector
<APT::Configuration::Compressor
>::const_iterator Comp
= Compressors
.begin();
60 for (; Comp
!= Compressors
.end(); ++Comp
)
61 if (stringcmp(Start
,I
,Comp
->Name
.c_str()) == 0)
65 if (Comp
== Compressors
.end())
67 _error
->Warning(_("Unknown compression algorithm '%s'"),string(Start
,I
).c_str());
71 // Create and link in a new output
72 Files
*NewOut
= new Files
;
73 NewOut
->Next
= Outputs
;
75 NewOut
->CompressProg
= *Comp
;
76 NewOut
->Output
= Output
+Comp
->Extension
;
79 if (stat(NewOut
->Output
.c_str(),&St
) == 0)
80 NewOut
->OldMTime
= St
.st_mtime
;
88 /* Open all the temp files now so we can report any errors. File is
89 made unreable to prevent people from touching it during creating. */
90 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
91 I
->TmpFile
.Open(I
->Output
+ ".new",FileFd::WriteEmpty
,0600);
92 if (_error
->PendingError() == true)
97 _error
->Error(_("Compressed output %s needs a compression set"),Output
.c_str());
104 // MultiCompress::~MultiCompress - Destructor /*{{{*/
105 // ---------------------------------------------------------------------
106 /* Just erase the file linked list. */
107 MultiCompress::~MultiCompress()
111 for (; Outputs
!= 0;)
113 Files
*Tmp
= Outputs
->Next
;
119 // MultiCompress::GetStat - Get stat information for compressed files /*{{{*/
120 // ---------------------------------------------------------------------
121 /* This checks each compressed file to make sure it exists and returns
122 stat information for a random file from the collection. False means
123 one or more of the files is missing. */
124 bool MultiCompress::GetStat(string
const &Output
,string
const &Compress
,struct stat
&St
)
126 /* Parse the compression string, a space separated lists of compresison
128 string::const_iterator I
= Compress
.begin();
129 bool DidStat
= false;
130 for (; I
!= Compress
.end();)
132 for (; I
!= Compress
.end() && isspace(*I
); ++I
);
135 string::const_iterator Start
= I
;
136 for (; I
!= Compress
.end() && !isspace(*I
); ++I
);
138 // Find the matching compressor
139 std::vector
<APT::Configuration::Compressor
> Compressors
= APT::Configuration::getCompressors();
140 std::vector
<APT::Configuration::Compressor
>::const_iterator Comp
= Compressors
.begin();
141 for (; Comp
!= Compressors
.end(); ++Comp
)
142 if (stringcmp(Start
,I
,Comp
->Name
.c_str()) == 0)
146 if (Comp
== Compressors
.end())
149 string Name
= Output
+Comp
->Extension
;
150 if (stat(Name
.c_str(),&St
) != 0)
157 // MultiCompress::Start - Start up the writer child /*{{{*/
158 // ---------------------------------------------------------------------
159 /* Fork a child and setup the communication pipe. */
160 bool MultiCompress::Start()
162 // Create a data pipe
163 int Pipe
[2] = {-1,-1};
165 return _error
->Errno("pipe",_("Failed to create IPC pipe to subprocess"));
166 for (int I
= 0; I
!= 2; I
++)
167 SetCloseExec(Pipe
[I
],true);
175 if (_error
->PendingError() == true)
177 _error
->DumpErrors();
183 /* Tidy up the temp files, we open them in the constructor so as to
184 get proper error reporting. Close them now. */
185 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
189 Input
= fdopen(Pipe
[1],"w");
191 return _error
->Errno("fdopen",_("Failed to create FILE*"));
194 return _error
->Errno("fork",_("Failed to fork"));
198 // MultiCompress::Die - Clean up the writer /*{{{*/
199 // ---------------------------------------------------------------------
201 bool MultiCompress::Die()
208 bool Res
= ExecWait(Outputter
,_("Compress child"),false);
213 // MultiCompress::Finalize - Finish up writing /*{{{*/
214 // ---------------------------------------------------------------------
215 /* This is only necessary for statistics reporting. */
216 bool MultiCompress::Finalize(unsigned long &OutSize
)
219 if (Input
== 0 || Die() == false)
225 // Check the mtimes to see if the files were replaced.
226 bool Changed
= false;
227 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
230 if (stat(I
->Output
.c_str(),&St
) != 0)
231 return _error
->Error(_("Internal error, failed to create %s"),
234 if (I
->OldMTime
!= St
.st_mtime
)
238 // Update the mtime if necessary
239 if (UpdateMTime
> 0 &&
240 (Now
- St
.st_mtime
> (signed)UpdateMTime
|| St
.st_mtime
> Now
))
243 Buf
.actime
= Buf
.modtime
= Now
;
244 utime(I
->Output
.c_str(),&Buf
);
249 // Force the file permissions
250 if (St
.st_mode
!= Permissions
)
251 chmod(I
->Output
.c_str(),Permissions
);
253 OutSize
+= St
.st_size
;
256 if (Changed
== false)
262 // MultiCompress::OpenCompress - Open the compressor /*{{{*/
263 // ---------------------------------------------------------------------
264 /* This opens the compressor, either in compress mode or decompress
265 mode. FileFd is always the compressor input/output file,
266 OutFd is the created pipe, Input for Compress, Output for Decompress. */
267 bool MultiCompress::OpenCompress(APT::Configuration::Compressor
const &Prog
,
268 pid_t
&Pid
,int const &FileFd
,int &OutFd
,bool const &Comp
)
273 if (Prog
.Binary
.empty() == true)
279 // Create a data pipe
280 int Pipe
[2] = {-1,-1};
282 return _error
->Errno("pipe",_("Failed to create subprocess IPC"));
283 for (int J
= 0; J
!= 2; J
++)
284 SetCloseExec(Pipe
[J
],true);
297 dup2(FileFd
,STDOUT_FILENO
);
298 dup2(Pipe
[0],STDIN_FILENO
);
302 dup2(FileFd
,STDIN_FILENO
);
303 dup2(Pipe
[1],STDOUT_FILENO
);
306 SetCloseExec(STDOUT_FILENO
,false);
307 SetCloseExec(STDIN_FILENO
,false);
309 std::vector
<char const*> Args
;
310 Args
.push_back(Prog
.Binary
.c_str());
311 std::vector
<std::string
> const * const addArgs
=
312 (Comp
== true) ? &(Prog
.CompressArgs
) : &(Prog
.UncompressArgs
);
313 for (std::vector
<std::string
>::const_iterator a
= addArgs
->begin();
314 a
!= addArgs
->end(); ++a
)
315 Args
.push_back(a
->c_str());
316 Args
.push_back(NULL
);
318 execvp(Args
[0],(char **)&Args
[0]);
319 cerr
<< _("Failed to exec compressor ") << Args
[0] << endl
;
329 // MultiCompress::OpenOld - Open an old file /*{{{*/
330 // ---------------------------------------------------------------------
331 /* This opens one of the original output files, possibly decompressing it. */
332 bool MultiCompress::OpenOld(int &Fd
,pid_t
&Proc
)
334 Files
*Best
= Outputs
;
335 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
336 if (Best
->CompressProg
.Cost
> I
->CompressProg
.Cost
)
340 FileFd
F(Best
->Output
,FileFd::ReadOnly
);
341 if (_error
->PendingError() == true)
344 // Decompress the file so we can read it
345 if (OpenCompress(Best
->CompressProg
,Proc
,F
.Fd(),Fd
,false) == false)
351 // MultiCompress::CloseOld - Close the old file /*{{{*/
352 // ---------------------------------------------------------------------
354 bool MultiCompress::CloseOld(int Fd
,pid_t Proc
)
358 if (ExecWait(Proc
,_("decompressor"),false) == false)
363 // MultiCompress::Child - The writer child /*{{{*/
364 // ---------------------------------------------------------------------
365 /* The child process forks a bunch of compression children and takes
366 input on FD and passes it to all the compressor child. On the way it
367 computes the MD5 of the raw data. After this the raw data in the
368 original files is compared to see if this data is new. If the data
369 is new then the temp files are renamed, otherwise they are erased. */
370 bool MultiCompress::Child(int const &FD
)
372 // Start the compression children.
373 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
375 if (OpenCompress(I
->CompressProg
,I
->CompressProc
,I
->TmpFile
.Fd(),
376 I
->Fd
,true) == false)
380 /* Okay, now we just feed data from FD to all the other FDs. Also
381 stash a hash of the data to use later. */
382 SetNonBlock(FD
,false);
383 unsigned char Buffer
[32*1024];
384 unsigned long FileSize
= 0;
389 int Res
= read(FD
,Buffer
,sizeof(Buffer
));
397 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
399 if (write(I
->Fd
,Buffer
,Res
) != Res
)
401 _error
->Errno("write",_("IO to subprocess/file failed"));
407 // Close all the writers
408 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
411 // Wait for the compressors to exit
412 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
414 if (I
->CompressProc
!= -1)
415 ExecWait(I
->CompressProc
, I
->CompressProg
.Binary
.c_str(), false);
418 if (_error
->PendingError() == true)
421 /* Now we have to copy the files over, or erase them if they
422 have not changed. First find the cheapest decompressor */
423 bool Missing
= false;
424 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
426 if (I
->OldMTime
== 0)
433 // Check the MD5 of the lowest cost entity.
434 while (Missing
== false)
438 if (OpenOld(CompFd
,Proc
) == false)
446 unsigned long NewFileSize
= 0;
449 int Res
= read(CompFd
,Buffer
,sizeof(Buffer
));
453 return _error
->Errno("read",_("Failed to read while computing MD5"));
455 OldMD5
.Add(Buffer
,Res
);
458 // Tidy the compressor
459 if (CloseOld(CompFd
,Proc
) == false)
463 if (OldMD5
.Result() == MD5
.Result() &&
464 FileSize
== NewFileSize
)
466 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
469 if (unlink(I
->TmpFile
.Name().c_str()) != 0)
470 _error
->Errno("unlink",_("Problem unlinking %s"),
471 I
->TmpFile
.Name().c_str());
473 return !_error
->PendingError();
479 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
481 // Set the correct file modes
482 fchmod(I
->TmpFile
.Fd(),Permissions
);
484 if (rename(I
->TmpFile
.Name().c_str(),I
->Output
.c_str()) != 0)
485 _error
->Errno("rename",_("Failed to rename %s to %s"),
486 I
->TmpFile
.Name().c_str(),I
->Output
.c_str());
490 return !_error
->PendingError();