]>
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},
37 {"lzma",".lzma","lzma","-9","-d",4},
40 // MultiCompress::MultiCompress - Constructor /*{{{*/
41 // ---------------------------------------------------------------------
42 /* Setup the file outputs, compression modes and fork the writer child */
43 MultiCompress::MultiCompress(string Output
,string Compress
,
44 mode_t Permissions
,bool Write
)
50 this->Permissions
= Permissions
;
52 /* Parse the compression string, a space separated lists of compresison
54 string::const_iterator I
= Compress
.begin();
55 for (; I
!= Compress
.end();)
57 for (; I
!= Compress
.end() && isspace(*I
); I
++);
60 string::const_iterator Start
= I
;
61 for (; I
!= Compress
.end() && !isspace(*I
); I
++);
63 // Find the matching compressor
64 const CompType
*Comp
= Compressors
;
65 for (; Comp
->Name
!= 0; Comp
++)
66 if (stringcmp(Start
,I
,Comp
->Name
) == 0)
72 _error
->Warning(_("Unknown compression algorithm '%s'"),string(Start
,I
).c_str());
76 // Create and link in a new output
77 Files
*NewOut
= new Files
;
78 NewOut
->Next
= Outputs
;
80 NewOut
->CompressProg
= Comp
;
81 NewOut
->Output
= Output
+Comp
->Extension
;
84 if (stat(NewOut
->Output
.c_str(),&St
) == 0)
85 NewOut
->OldMTime
= St
.st_mtime
;
93 /* Open all the temp files now so we can report any errors. File is
94 made unreable to prevent people from touching it during creating. */
95 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
96 I
->TmpFile
.Open(I
->Output
+ ".new",FileFd::WriteEmpty
,0600);
97 if (_error
->PendingError() == true)
102 _error
->Error(_("Compressed output %s needs a compression set"),Output
.c_str());
109 // MultiCompress::~MultiCompress - Destructor /*{{{*/
110 // ---------------------------------------------------------------------
111 /* Just erase the file linked list. */
112 MultiCompress::~MultiCompress()
116 for (; Outputs
!= 0;)
118 Files
*Tmp
= Outputs
->Next
;
124 // MultiCompress::GetStat - Get stat information for compressed files /*{{{*/
125 // ---------------------------------------------------------------------
126 /* This checks each compressed file to make sure it exists and returns
127 stat information for a random file from the collection. False means
128 one or more of the files is missing. */
129 bool MultiCompress::GetStat(string Output
,string Compress
,struct stat
&St
)
131 /* Parse the compression string, a space separated lists of compresison
133 string::const_iterator I
= Compress
.begin();
134 bool DidStat
= false;
135 for (; I
!= Compress
.end();)
137 for (; I
!= Compress
.end() && isspace(*I
); I
++);
140 string::const_iterator Start
= I
;
141 for (; I
!= Compress
.end() && !isspace(*I
); I
++);
143 // Find the matching compressor
144 const CompType
*Comp
= Compressors
;
145 for (; Comp
->Name
!= 0; Comp
++)
146 if (stringcmp(Start
,I
,Comp
->Name
) == 0)
153 string Name
= Output
+Comp
->Extension
;
154 if (stat(Name
.c_str(),&St
) != 0)
161 // MultiCompress::Start - Start up the writer child /*{{{*/
162 // ---------------------------------------------------------------------
163 /* Fork a child and setup the communication pipe. */
164 bool MultiCompress::Start()
166 // Create a data pipe
167 int Pipe
[2] = {-1,-1};
169 return _error
->Errno("pipe",_("Failed to create IPC pipe to subprocess"));
170 for (int I
= 0; I
!= 2; I
++)
171 SetCloseExec(Pipe
[I
],true);
179 if (_error
->PendingError() == true)
181 _error
->DumpErrors();
187 /* Tidy up the temp files, we open them in the constructor so as to
188 get proper error reporting. Close them now. */
189 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
193 Input
= fdopen(Pipe
[1],"w");
195 return _error
->Errno("fdopen",_("Failed to create FILE*"));
198 return _error
->Errno("fork",_("Failed to fork"));
202 // MultiCompress::Die - Clean up the writer /*{{{*/
203 // ---------------------------------------------------------------------
205 bool MultiCompress::Die()
212 bool Res
= ExecWait(Outputter
,_("Compress child"),false);
217 // MultiCompress::Finalize - Finish up writing /*{{{*/
218 // ---------------------------------------------------------------------
219 /* This is only necessary for statistics reporting. */
220 bool MultiCompress::Finalize(unsigned long &OutSize
)
223 if (Input
== 0 || Die() == false)
229 // Check the mtimes to see if the files were replaced.
230 bool Changed
= false;
231 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
234 if (stat(I
->Output
.c_str(),&St
) != 0)
235 return _error
->Error(_("Internal error, failed to create %s"),
238 if (I
->OldMTime
!= St
.st_mtime
)
242 // Update the mtime if necessary
243 if (UpdateMTime
> 0 &&
244 (Now
- St
.st_mtime
> (signed)UpdateMTime
|| St
.st_mtime
> Now
))
247 Buf
.actime
= Buf
.modtime
= Now
;
248 utime(I
->Output
.c_str(),&Buf
);
253 // Force the file permissions
254 if (St
.st_mode
!= Permissions
)
255 chmod(I
->Output
.c_str(),Permissions
);
257 OutSize
+= St
.st_size
;
260 if (Changed
== false)
266 // MultiCompress::OpenCompress - Open the compressor /*{{{*/
267 // ---------------------------------------------------------------------
268 /* This opens the compressor, either in compress mode or decompress
269 mode. FileFd is always the compressor input/output file,
270 OutFd is the created pipe, Input for Compress, Output for Decompress. */
271 bool MultiCompress::OpenCompress(const CompType
*Prog
,pid_t
&Pid
,int FileFd
,
272 int &OutFd
,bool Comp
)
277 if (Prog
->Binary
== 0)
283 // Create a data pipe
284 int Pipe
[2] = {-1,-1};
286 return _error
->Errno("pipe",_("Failed to create subprocess IPC"));
287 for (int J
= 0; J
!= 2; J
++)
288 SetCloseExec(Pipe
[J
],true);
301 dup2(FileFd
,STDOUT_FILENO
);
302 dup2(Pipe
[0],STDIN_FILENO
);
306 dup2(FileFd
,STDIN_FILENO
);
307 dup2(Pipe
[1],STDOUT_FILENO
);
310 SetCloseExec(STDOUT_FILENO
,false);
311 SetCloseExec(STDIN_FILENO
,false);
314 Args
[0] = Prog
->Binary
;
316 Args
[1] = Prog
->CompArgs
;
318 Args
[1] = Prog
->UnCompArgs
;
320 execvp(Args
[0],(char **)Args
);
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 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
,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();