]>
git.saurik.com Git - apt.git/blob - ftparchive/multicompress.cc
04560f4ab0e87a2fec5487a1fa2973167a7318ed
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 /*{{{*/
19 #include <apt-pkg/strutl.h>
20 #include <apt-pkg/error.h>
21 #include <apt-pkg/md5.h>
23 #include <sys/types.h>
29 #include "multicompress.h"
36 // MultiCompress::MultiCompress - Constructor /*{{{*/
37 // ---------------------------------------------------------------------
38 /* Setup the file outputs, compression modes and fork the writer child */
39 MultiCompress::MultiCompress(string
const &Output
,string
const &Compress
,
40 mode_t
const &Permissions
,bool const &Write
) :
41 Permissions(Permissions
)
48 /* Parse the compression string, a space separated lists of compresison
50 string::const_iterator I
= Compress
.begin();
51 for (; I
!= Compress
.end();)
53 for (; I
!= Compress
.end() && isspace(*I
); I
++);
56 string::const_iterator Start
= I
;
57 for (; I
!= Compress
.end() && !isspace(*I
); I
++);
59 // Find the matching compressor
60 std::vector
<APT::Configuration::Compressor
> Compressors
= APT::Configuration::getCompressors();
61 std::vector
<APT::Configuration::Compressor
>::const_iterator Comp
= Compressors
.begin();
62 for (; Comp
!= Compressors
.end(); ++Comp
)
63 if (stringcmp(Start
,I
,Comp
->Name
.c_str()) == 0)
67 if (Comp
== Compressors
.end())
69 _error
->Warning(_("Unknown compression algorithm '%s'"),string(Start
,I
).c_str());
73 // Create and link in a new output
74 Files
*NewOut
= new Files
;
75 NewOut
->Next
= Outputs
;
77 NewOut
->CompressProg
= *Comp
;
78 NewOut
->Output
= Output
+Comp
->Extension
;
81 if (stat(NewOut
->Output
.c_str(),&St
) == 0)
82 NewOut
->OldMTime
= St
.st_mtime
;
90 /* Open all the temp files now so we can report any errors. File is
91 made unreable to prevent people from touching it during creating. */
92 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
93 I
->TmpFile
.Open(I
->Output
+ ".new",FileFd::WriteEmpty
,0600);
94 if (_error
->PendingError() == true)
99 _error
->Error(_("Compressed output %s needs a compression set"),Output
.c_str());
106 // MultiCompress::~MultiCompress - Destructor /*{{{*/
107 // ---------------------------------------------------------------------
108 /* Just erase the file linked list. */
109 MultiCompress::~MultiCompress()
113 for (; Outputs
!= 0;)
115 Files
*Tmp
= Outputs
->Next
;
121 // MultiCompress::GetStat - Get stat information for compressed files /*{{{*/
122 // ---------------------------------------------------------------------
123 /* This checks each compressed file to make sure it exists and returns
124 stat information for a random file from the collection. False means
125 one or more of the files is missing. */
126 bool MultiCompress::GetStat(string
const &Output
,string
const &Compress
,struct stat
&St
)
128 /* Parse the compression string, a space separated lists of compresison
130 string::const_iterator I
= Compress
.begin();
131 bool DidStat
= false;
132 for (; I
!= Compress
.end();)
134 for (; I
!= Compress
.end() && isspace(*I
); I
++);
137 string::const_iterator Start
= I
;
138 for (; I
!= Compress
.end() && !isspace(*I
); I
++);
140 // Find the matching compressor
141 std::vector
<APT::Configuration::Compressor
> Compressors
= APT::Configuration::getCompressors();
142 std::vector
<APT::Configuration::Compressor
>::const_iterator Comp
= Compressors
.begin();
143 for (; Comp
!= Compressors
.end(); ++Comp
)
144 if (stringcmp(Start
,I
,Comp
->Name
.c_str()) == 0)
148 if (Comp
== Compressors
.end())
151 string Name
= Output
+Comp
->Extension
;
152 if (stat(Name
.c_str(),&St
) != 0)
159 // MultiCompress::Start - Start up the writer child /*{{{*/
160 // ---------------------------------------------------------------------
161 /* Fork a child and setup the communication pipe. */
162 bool MultiCompress::Start()
164 // Create a data pipe
165 int Pipe
[2] = {-1,-1};
167 return _error
->Errno("pipe",_("Failed to create IPC pipe to subprocess"));
168 for (int I
= 0; I
!= 2; I
++)
169 SetCloseExec(Pipe
[I
],true);
177 if (_error
->PendingError() == true)
179 _error
->DumpErrors();
185 /* Tidy up the temp files, we open them in the constructor so as to
186 get proper error reporting. Close them now. */
187 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
191 Input
= fdopen(Pipe
[1],"w");
193 return _error
->Errno("fdopen",_("Failed to create FILE*"));
196 return _error
->Errno("fork",_("Failed to fork"));
200 // MultiCompress::Die - Clean up the writer /*{{{*/
201 // ---------------------------------------------------------------------
203 bool MultiCompress::Die()
210 bool Res
= ExecWait(Outputter
,_("Compress child"),false);
215 // MultiCompress::Finalize - Finish up writing /*{{{*/
216 // ---------------------------------------------------------------------
217 /* This is only necessary for statistics reporting. */
218 bool MultiCompress::Finalize(unsigned long &OutSize
)
221 if (Input
== 0 || Die() == false)
227 // Check the mtimes to see if the files were replaced.
228 bool Changed
= false;
229 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
232 if (stat(I
->Output
.c_str(),&St
) != 0)
233 return _error
->Error(_("Internal error, failed to create %s"),
236 if (I
->OldMTime
!= St
.st_mtime
)
240 // Update the mtime if necessary
241 if (UpdateMTime
> 0 &&
242 (Now
- St
.st_mtime
> (signed)UpdateMTime
|| St
.st_mtime
> Now
))
245 Buf
.actime
= Buf
.modtime
= Now
;
246 utime(I
->Output
.c_str(),&Buf
);
251 // Force the file permissions
252 if (St
.st_mode
!= Permissions
)
253 chmod(I
->Output
.c_str(),Permissions
);
255 OutSize
+= St
.st_size
;
258 if (Changed
== false)
264 // MultiCompress::OpenCompress - Open the compressor /*{{{*/
265 // ---------------------------------------------------------------------
266 /* This opens the compressor, either in compress mode or decompress
267 mode. FileFd is always the compressor input/output file,
268 OutFd is the created pipe, Input for Compress, Output for Decompress. */
269 bool MultiCompress::OpenCompress(APT::Configuration::Compressor
const &Prog
,
270 pid_t
&Pid
,int const &FileFd
,int &OutFd
,bool const &Comp
)
275 if (Prog
.Binary
.empty() == true)
281 // Create a data pipe
282 int Pipe
[2] = {-1,-1};
284 return _error
->Errno("pipe",_("Failed to create subprocess IPC"));
285 for (int J
= 0; J
!= 2; J
++)
286 SetCloseExec(Pipe
[J
],true);
299 dup2(FileFd
,STDOUT_FILENO
);
300 dup2(Pipe
[0],STDIN_FILENO
);
304 dup2(FileFd
,STDIN_FILENO
);
305 dup2(Pipe
[1],STDOUT_FILENO
);
308 SetCloseExec(STDOUT_FILENO
,false);
309 SetCloseExec(STDIN_FILENO
,false);
311 std::vector
<char const*> Args
;
312 Args
.push_back(Prog
.Binary
.c_str());
313 std::vector
<std::string
> const * const addArgs
=
314 (Comp
== true) ? &(Prog
.CompressArgs
) : &(Prog
.UncompressArgs
);
315 for (std::vector
<std::string
>::const_iterator a
= addArgs
->begin();
316 a
!= addArgs
->end(); ++a
)
317 Args
.push_back(a
->c_str());
318 Args
.push_back(NULL
);
320 execvp(Args
[0],(char **)&Args
[0]);
321 cerr
<< _("Failed to exec compressor ") << Args
[0] << endl
;
331 // MultiCompress::OpenOld - Open an old file /*{{{*/
332 // ---------------------------------------------------------------------
333 /* This opens one of the original output files, possibly decompressing it. */
334 bool MultiCompress::OpenOld(int &Fd
,pid_t
&Proc
)
336 Files
*Best
= Outputs
;
337 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
338 if (Best
->CompressProg
.Cost
> I
->CompressProg
.Cost
)
342 FileFd
F(Best
->Output
,FileFd::ReadOnly
);
343 if (_error
->PendingError() == true)
346 // Decompress the file so we can read it
347 if (OpenCompress(Best
->CompressProg
,Proc
,F
.Fd(),Fd
,false) == false)
353 // MultiCompress::CloseOld - Close the old file /*{{{*/
354 // ---------------------------------------------------------------------
356 bool MultiCompress::CloseOld(int Fd
,pid_t Proc
)
360 if (ExecWait(Proc
,_("decompressor"),false) == false)
365 // MultiCompress::Child - The writer child /*{{{*/
366 // ---------------------------------------------------------------------
367 /* The child process forks a bunch of compression children and takes
368 input on FD and passes it to all the compressor child. On the way it
369 computes the MD5 of the raw data. After this the raw data in the
370 original files is compared to see if this data is new. If the data
371 is new then the temp files are renamed, otherwise they are erased. */
372 bool MultiCompress::Child(int const &FD
)
374 // Start the compression children.
375 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
377 if (OpenCompress(I
->CompressProg
,I
->CompressProc
,I
->TmpFile
.Fd(),
378 I
->Fd
,true) == false)
382 /* Okay, now we just feed data from FD to all the other FDs. Also
383 stash a hash of the data to use later. */
384 SetNonBlock(FD
,false);
385 unsigned char Buffer
[32*1024];
386 unsigned long FileSize
= 0;
391 int Res
= read(FD
,Buffer
,sizeof(Buffer
));
399 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
401 if (write(I
->Fd
,Buffer
,Res
) != Res
)
403 _error
->Errno("write",_("IO to subprocess/file failed"));
409 // Close all the writers
410 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
413 // Wait for the compressors to exit
414 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
416 if (I
->CompressProc
!= -1)
417 ExecWait(I
->CompressProc
, I
->CompressProg
.Binary
.c_str(), false);
420 if (_error
->PendingError() == true)
423 /* Now we have to copy the files over, or erase them if they
424 have not changed. First find the cheapest decompressor */
425 bool Missing
= false;
426 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
428 if (I
->OldMTime
== 0)
435 // Check the MD5 of the lowest cost entity.
436 while (Missing
== false)
440 if (OpenOld(CompFd
,Proc
) == false)
448 unsigned long NewFileSize
= 0;
451 int Res
= read(CompFd
,Buffer
,sizeof(Buffer
));
455 return _error
->Errno("read",_("Failed to read while computing MD5"));
457 OldMD5
.Add(Buffer
,Res
);
460 // Tidy the compressor
461 if (CloseOld(CompFd
,Proc
) == false)
465 if (OldMD5
.Result() == MD5
.Result() &&
466 FileSize
== NewFileSize
)
468 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
471 if (unlink(I
->TmpFile
.Name().c_str()) != 0)
472 _error
->Errno("unlink",_("Problem unlinking %s"),
473 I
->TmpFile
.Name().c_str());
475 return !_error
->PendingError();
481 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
483 // Set the correct file modes
484 fchmod(I
->TmpFile
.Fd(),Permissions
);
486 if (rename(I
->TmpFile
.Name().c_str(),I
->Output
.c_str()) != 0)
487 _error
->Errno("rename",_("Failed to rename %s to %s"),
488 I
->TmpFile
.Name().c_str(),I
->Output
.c_str());
492 return !_error
->PendingError();