]>
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},
38 {"xz",".xz","xz","-6","-d",5},
41 // MultiCompress::MultiCompress - Constructor /*{{{*/
42 // ---------------------------------------------------------------------
43 /* Setup the file outputs, compression modes and fork the writer child */
44 MultiCompress::MultiCompress(string
const &Output
,string
const &Compress
,
45 mode_t
const &Permissions
,bool const &Write
) :
46 Permissions(Permissions
)
53 /* Parse the compression string, a space separated lists of compresison
55 string::const_iterator I
= Compress
.begin();
56 for (; I
!= Compress
.end();)
58 for (; I
!= Compress
.end() && isspace(*I
); I
++);
61 string::const_iterator Start
= I
;
62 for (; I
!= Compress
.end() && !isspace(*I
); I
++);
64 // Find the matching compressor
65 const CompType
*Comp
= Compressors
;
66 for (; Comp
->Name
!= 0; Comp
++)
67 if (stringcmp(Start
,I
,Comp
->Name
) == 0)
73 _error
->Warning(_("Unknown compression algorithm '%s'"),string(Start
,I
).c_str());
77 // Create and link in a new output
78 Files
*NewOut
= new Files
;
79 NewOut
->Next
= Outputs
;
81 NewOut
->CompressProg
= Comp
;
82 NewOut
->Output
= Output
+Comp
->Extension
;
85 if (stat(NewOut
->Output
.c_str(),&St
) == 0)
86 NewOut
->OldMTime
= St
.st_mtime
;
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
)
97 I
->TmpFile
.Open(I
->Output
+ ".new",FileFd::WriteEmpty
,0600);
98 if (_error
->PendingError() == true)
103 _error
->Error(_("Compressed output %s needs a compression set"),Output
.c_str());
110 // MultiCompress::~MultiCompress - Destructor /*{{{*/
111 // ---------------------------------------------------------------------
112 /* Just erase the file linked list. */
113 MultiCompress::~MultiCompress()
117 for (; Outputs
!= 0;)
119 Files
*Tmp
= Outputs
->Next
;
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. */
130 bool MultiCompress::GetStat(string
const &Output
,string
const &Compress
,struct stat
&St
)
132 /* Parse the compression string, a space separated lists of compresison
134 string::const_iterator I
= Compress
.begin();
135 bool DidStat
= false;
136 for (; I
!= Compress
.end();)
138 for (; I
!= Compress
.end() && isspace(*I
); I
++);
141 string::const_iterator Start
= I
;
142 for (; I
!= Compress
.end() && !isspace(*I
); I
++);
144 // Find the matching compressor
145 const CompType
*Comp
= Compressors
;
146 for (; Comp
->Name
!= 0; Comp
++)
147 if (stringcmp(Start
,I
,Comp
->Name
) == 0)
154 string Name
= Output
+Comp
->Extension
;
155 if (stat(Name
.c_str(),&St
) != 0)
162 // MultiCompress::Start - Start up the writer child /*{{{*/
163 // ---------------------------------------------------------------------
164 /* Fork a child and setup the communication pipe. */
165 bool MultiCompress::Start()
167 // Create a data pipe
168 int Pipe
[2] = {-1,-1};
170 return _error
->Errno("pipe",_("Failed to create IPC pipe to subprocess"));
171 for (int I
= 0; I
!= 2; I
++)
172 SetCloseExec(Pipe
[I
],true);
180 if (_error
->PendingError() == true)
182 _error
->DumpErrors();
188 /* Tidy up the temp files, we open them in the constructor so as to
189 get proper error reporting. Close them now. */
190 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
194 Input
= fdopen(Pipe
[1],"w");
196 return _error
->Errno("fdopen",_("Failed to create FILE*"));
199 return _error
->Errno("fork",_("Failed to fork"));
203 // MultiCompress::Die - Clean up the writer /*{{{*/
204 // ---------------------------------------------------------------------
206 bool MultiCompress::Die()
213 bool Res
= ExecWait(Outputter
,_("Compress child"),false);
218 // MultiCompress::Finalize - Finish up writing /*{{{*/
219 // ---------------------------------------------------------------------
220 /* This is only necessary for statistics reporting. */
221 bool MultiCompress::Finalize(unsigned long &OutSize
)
224 if (Input
== 0 || Die() == false)
230 // Check the mtimes to see if the files were replaced.
231 bool Changed
= false;
232 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
235 if (stat(I
->Output
.c_str(),&St
) != 0)
236 return _error
->Error(_("Internal error, failed to create %s"),
239 if (I
->OldMTime
!= St
.st_mtime
)
243 // Update the mtime if necessary
244 if (UpdateMTime
> 0 &&
245 (Now
- St
.st_mtime
> (signed)UpdateMTime
|| St
.st_mtime
> Now
))
248 Buf
.actime
= Buf
.modtime
= Now
;
249 utime(I
->Output
.c_str(),&Buf
);
254 // Force the file permissions
255 if (St
.st_mode
!= Permissions
)
256 chmod(I
->Output
.c_str(),Permissions
);
258 OutSize
+= St
.st_size
;
261 if (Changed
== false)
267 // MultiCompress::OpenCompress - Open the compressor /*{{{*/
268 // ---------------------------------------------------------------------
269 /* This opens the compressor, either in compress mode or decompress
270 mode. FileFd is always the compressor input/output file,
271 OutFd is the created pipe, Input for Compress, Output for Decompress. */
272 bool MultiCompress::OpenCompress(const CompType
*Prog
,pid_t
&Pid
,int const &FileFd
,
273 int &OutFd
,bool const &Comp
)
278 if (Prog
->Binary
== 0)
284 // Create a data pipe
285 int Pipe
[2] = {-1,-1};
287 return _error
->Errno("pipe",_("Failed to create subprocess IPC"));
288 for (int J
= 0; J
!= 2; J
++)
289 SetCloseExec(Pipe
[J
],true);
302 dup2(FileFd
,STDOUT_FILENO
);
303 dup2(Pipe
[0],STDIN_FILENO
);
307 dup2(FileFd
,STDIN_FILENO
);
308 dup2(Pipe
[1],STDOUT_FILENO
);
311 SetCloseExec(STDOUT_FILENO
,false);
312 SetCloseExec(STDIN_FILENO
,false);
315 Args
[0] = Prog
->Binary
;
317 Args
[1] = Prog
->CompArgs
;
319 Args
[1] = Prog
->UnCompArgs
;
321 execvp(Args
[0],(char **)Args
);
322 cerr
<< _("Failed to exec compressor ") << Args
[0] << endl
;
332 // MultiCompress::OpenOld - Open an old file /*{{{*/
333 // ---------------------------------------------------------------------
334 /* This opens one of the original output files, possibly decompressing it. */
335 bool MultiCompress::OpenOld(int &Fd
,pid_t
&Proc
)
337 Files
*Best
= Outputs
;
338 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
339 if (Best
->CompressProg
->Cost
> I
->CompressProg
->Cost
)
343 FileFd
F(Best
->Output
,FileFd::ReadOnly
);
344 if (_error
->PendingError() == true)
347 // Decompress the file so we can read it
348 if (OpenCompress(Best
->CompressProg
,Proc
,F
.Fd(),Fd
,false) == false)
354 // MultiCompress::CloseOld - Close the old file /*{{{*/
355 // ---------------------------------------------------------------------
357 bool MultiCompress::CloseOld(int Fd
,pid_t Proc
)
361 if (ExecWait(Proc
,_("decompressor"),false) == false)
366 // MultiCompress::Child - The writer child /*{{{*/
367 // ---------------------------------------------------------------------
368 /* The child process forks a bunch of compression children and takes
369 input on FD and passes it to all the compressor child. On the way it
370 computes the MD5 of the raw data. After this the raw data in the
371 original files is compared to see if this data is new. If the data
372 is new then the temp files are renamed, otherwise they are erased. */
373 bool MultiCompress::Child(int const &FD
)
375 // Start the compression children.
376 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
378 if (OpenCompress(I
->CompressProg
,I
->CompressProc
,I
->TmpFile
.Fd(),
379 I
->Fd
,true) == false)
383 /* Okay, now we just feed data from FD to all the other FDs. Also
384 stash a hash of the data to use later. */
385 SetNonBlock(FD
,false);
386 unsigned char Buffer
[32*1024];
387 unsigned long FileSize
= 0;
392 int Res
= read(FD
,Buffer
,sizeof(Buffer
));
400 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
402 if (write(I
->Fd
,Buffer
,Res
) != Res
)
404 _error
->Errno("write",_("IO to subprocess/file failed"));
410 // Close all the writers
411 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
414 // Wait for the compressors to exit
415 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
417 if (I
->CompressProc
!= -1)
418 ExecWait(I
->CompressProc
,I
->CompressProg
->Binary
,false);
421 if (_error
->PendingError() == true)
424 /* Now we have to copy the files over, or erase them if they
425 have not changed. First find the cheapest decompressor */
426 bool Missing
= false;
427 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
429 if (I
->OldMTime
== 0)
436 // Check the MD5 of the lowest cost entity.
437 while (Missing
== false)
441 if (OpenOld(CompFd
,Proc
) == false)
449 unsigned long NewFileSize
= 0;
452 int Res
= read(CompFd
,Buffer
,sizeof(Buffer
));
456 return _error
->Errno("read",_("Failed to read while computing MD5"));
458 OldMD5
.Add(Buffer
,Res
);
461 // Tidy the compressor
462 if (CloseOld(CompFd
,Proc
) == false)
466 if (OldMD5
.Result() == MD5
.Result() &&
467 FileSize
== NewFileSize
)
469 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
472 if (unlink(I
->TmpFile
.Name().c_str()) != 0)
473 _error
->Errno("unlink",_("Problem unlinking %s"),
474 I
->TmpFile
.Name().c_str());
476 return !_error
->PendingError();
482 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
484 // Set the correct file modes
485 fchmod(I
->TmpFile
.Fd(),Permissions
);
487 if (rename(I
->TmpFile
.Name().c_str(),I
->Output
.c_str()) != 0)
488 _error
->Errno("rename",_("Failed to rename %s to %s"),
489 I
->TmpFile
.Name().c_str(),I
->Output
.c_str());
493 return !_error
->PendingError();