]>
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 /*{{{*/
18 #pragma implementation "multicompress.h"
21 #include "multicompress.h"
24 #include <apt-pkg/strutl.h>
25 #include <apt-pkg/error.h>
26 #include <apt-pkg/md5.h>
28 #include <sys/types.h>
37 const MultiCompress::CompType
MultiCompress::Compressors
[] =
39 {"gzip",".gz","gzip","-9n","-d",2},
40 {"bzip2",".bz2","bzip2","-9","-d",3},
43 // MultiCompress::MultiCompress - Constructor /*{{{*/
44 // ---------------------------------------------------------------------
45 /* Setup the file outputs, compression modes and fork the writer child */
46 MultiCompress::MultiCompress(string Output
,string Compress
,
47 mode_t Permissions
,bool Write
)
53 this->Permissions
= Permissions
;
55 /* Parse the compression string, a space separated lists of compresison
57 string::const_iterator I
= Compress
.begin();
58 for (; I
!= Compress
.end();)
60 for (; I
!= Compress
.end() && isspace(*I
); I
++);
63 string::const_iterator Start
= I
;
64 for (; I
!= Compress
.end() && !isspace(*I
); I
++);
66 // Find the matching compressor
67 const CompType
*Comp
= Compressors
;
68 for (; Comp
->Name
!= 0; Comp
++)
69 if (stringcmp(Start
,I
,Comp
->Name
) == 0)
75 _error
->Warning(_("Unknown Compresison Algorithm '%s'"),string(Start
,I
).c_str());
79 // Create and link in a new output
80 Files
*NewOut
= new Files
;
81 NewOut
->Next
= Outputs
;
83 NewOut
->CompressProg
= Comp
;
84 NewOut
->Output
= Output
+Comp
->Extension
;
87 if (stat(NewOut
->Output
.c_str(),&St
) == 0)
88 NewOut
->OldMTime
= St
.st_mtime
;
96 /* Open all the temp files now so we can report any errors. File is
97 made unreable to prevent people from touching it during creating. */
98 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
99 I
->TmpFile
.Open(I
->Output
+ ".new",FileFd::WriteEmpty
,0600);
100 if (_error
->PendingError() == true)
105 _error
->Error(_("Compressed output %s needs a compression set"),Output
.c_str());
112 // MultiCompress::~MultiCompress - Destructor /*{{{*/
113 // ---------------------------------------------------------------------
114 /* Just erase the file linked list. */
115 MultiCompress::~MultiCompress()
119 for (; Outputs
!= 0;)
121 Files
*Tmp
= Outputs
->Next
;
127 // MultiCompress::GetStat - Get stat information for compressed files /*{{{*/
128 // ---------------------------------------------------------------------
129 /* This checks each compressed file to make sure it exists and returns
130 stat information for a random file from the collection. False means
131 one or more of the files is missing. */
132 bool MultiCompress::GetStat(string Output
,string Compress
,struct stat
&St
)
134 /* Parse the compression string, a space separated lists of compresison
136 string::const_iterator I
= Compress
.begin();
137 bool DidStat
= false;
138 for (; I
!= Compress
.end();)
140 for (; I
!= Compress
.end() && isspace(*I
); I
++);
143 string::const_iterator Start
= I
;
144 for (; I
!= Compress
.end() && !isspace(*I
); I
++);
146 // Find the matching compressor
147 const CompType
*Comp
= Compressors
;
148 for (; Comp
->Name
!= 0; Comp
++)
149 if (stringcmp(Start
,I
,Comp
->Name
) == 0)
156 string Name
= Output
+Comp
->Extension
;
157 if (stat(Name
.c_str(),&St
) != 0)
164 // MultiCompress::Start - Start up the writer child /*{{{*/
165 // ---------------------------------------------------------------------
166 /* Fork a child and setup the communication pipe. */
167 bool MultiCompress::Start()
169 // Create a data pipe
170 int Pipe
[2] = {-1,-1};
172 return _error
->Errno("pipe",_("Failed to create IPC pipe to subprocess"));
173 for (int I
= 0; I
!= 2; I
++)
174 SetCloseExec(Pipe
[I
],true);
182 if (_error
->PendingError() == true)
184 _error
->DumpErrors();
190 /* Tidy up the temp files, we open them in the constructor so as to
191 get proper error reporting. Close them now. */
192 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
196 Input
= fdopen(Pipe
[1],"w");
198 return _error
->Errno("fdopen",_("Failed to create FILE*"));
201 return _error
->Errno("fork",_("Failed to fork"));
205 // MultiCompress::Die - Clean up the writer /*{{{*/
206 // ---------------------------------------------------------------------
208 bool MultiCompress::Die()
215 bool Res
= ExecWait(Outputter
,_("Compress Child"),false);
220 // MultiCompress::Finalize - Finish up writing /*{{{*/
221 // ---------------------------------------------------------------------
222 /* This is only necessary for statistics reporting. */
223 bool MultiCompress::Finalize(unsigned long &OutSize
)
226 if (Input
== 0 || Die() == false)
232 // Check the mtimes to see if the files were replaced.
233 bool Changed
= false;
234 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
237 if (stat(I
->Output
.c_str(),&St
) != 0)
238 return _error
->Error(_("Internal Error, Failed to create %s"),
241 if (I
->OldMTime
!= St
.st_mtime
)
245 // Update the mtime if necessary
246 if (UpdateMTime
> 0 &&
247 (Now
- St
.st_mtime
> (signed)UpdateMTime
|| St
.st_mtime
> Now
))
250 Buf
.actime
= Buf
.modtime
= Now
;
251 utime(I
->Output
.c_str(),&Buf
);
256 // Force the file permissions
257 if (St
.st_mode
!= Permissions
)
258 chmod(I
->Output
.c_str(),Permissions
);
260 OutSize
+= St
.st_size
;
263 if (Changed
== false)
269 // MultiCompress::OpenCompress - Open the compressor /*{{{*/
270 // ---------------------------------------------------------------------
271 /* This opens the compressor, either in compress mode or decompress
272 mode. FileFd is always the compressor input/output file,
273 OutFd is the created pipe, Input for Compress, Output for Decompress. */
274 bool MultiCompress::OpenCompress(const CompType
*Prog
,pid_t
&Pid
,int FileFd
,
275 int &OutFd
,bool Comp
)
280 if (Prog
->Binary
== 0)
286 // Create a data pipe
287 int Pipe
[2] = {-1,-1};
289 return _error
->Errno("pipe",_("Failed to create subprocess IPC"));
290 for (int J
= 0; J
!= 2; J
++)
291 SetCloseExec(Pipe
[J
],true);
304 dup2(FileFd
,STDOUT_FILENO
);
305 dup2(Pipe
[0],STDIN_FILENO
);
309 dup2(FileFd
,STDIN_FILENO
);
310 dup2(Pipe
[1],STDOUT_FILENO
);
313 SetCloseExec(STDOUT_FILENO
,false);
314 SetCloseExec(STDIN_FILENO
,false);
317 Args
[0] = Prog
->Binary
;
319 Args
[1] = Prog
->CompArgs
;
321 Args
[1] = Prog
->UnCompArgs
;
323 execvp(Args
[0],(char **)Args
);
324 cerr
<< _("Failed to exec compressor ") << Args
[0] << endl
;
334 // MultiCompress::OpenOld - Open an old file /*{{{*/
335 // ---------------------------------------------------------------------
336 /* This opens one of the original output files, possibly decompressing it. */
337 bool MultiCompress::OpenOld(int &Fd
,pid_t
&Proc
)
339 Files
*Best
= Outputs
;
340 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
341 if (Best
->CompressProg
->Cost
> I
->CompressProg
->Cost
)
345 FileFd
F(Best
->Output
,FileFd::ReadOnly
);
346 if (_error
->PendingError() == true)
349 // Decompress the file so we can read it
350 if (OpenCompress(Best
->CompressProg
,Proc
,F
.Fd(),Fd
,false) == false)
356 // MultiCompress::CloseOld - Close the old file /*{{{*/
357 // ---------------------------------------------------------------------
359 bool MultiCompress::CloseOld(int Fd
,pid_t Proc
)
363 if (ExecWait(Proc
,_("decompressor"),false) == false)
368 // MultiCompress::Child - The writer child /*{{{*/
369 // ---------------------------------------------------------------------
370 /* The child process forks a bunch of compression children and takes
371 input on FD and passes it to all the compressor childer. On the way it
372 computes the MD5 of the raw data. After this the raw data in the
373 original files is compared to see if this data is new. If the data
374 is new then the temp files are renamed, otherwise they are erased. */
375 bool MultiCompress::Child(int FD
)
377 // Start the compression children.
378 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
380 if (OpenCompress(I
->CompressProg
,I
->CompressProc
,I
->TmpFile
.Fd(),
381 I
->Fd
,true) == false)
385 /* Okay, now we just feed data from FD to all the other FDs. Also
386 stash a hash of the data to use later. */
387 SetNonBlock(FD
,false);
388 unsigned char Buffer
[32*1024];
389 unsigned long FileSize
= 0;
394 int Res
= read(FD
,Buffer
,sizeof(Buffer
));
402 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
404 if (write(I
->Fd
,Buffer
,Res
) != Res
)
406 _error
->Errno("write",_("IO to subprocess/file failed"));
412 // Close all the writers
413 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
416 // Wait for the compressors to exit
417 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
419 if (I
->CompressProc
!= -1)
420 ExecWait(I
->CompressProc
,I
->CompressProg
->Binary
,false);
423 if (_error
->PendingError() == true)
426 /* Now we have to copy the files over, or erase them if they
427 have not changed. First find the cheapest decompressor */
428 bool Missing
= false;
429 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
431 if (I
->OldMTime
== 0)
438 // Check the MD5 of the lowest cost entity.
439 while (Missing
== false)
443 if (OpenOld(CompFd
,Proc
) == false)
451 unsigned long NewFileSize
= 0;
454 int Res
= read(CompFd
,Buffer
,sizeof(Buffer
));
458 return _error
->Errno("read",_("Failed to read while computing MD5"));
460 OldMD5
.Add(Buffer
,Res
);
463 // Tidy the compressor
464 if (CloseOld(CompFd
,Proc
) == false)
468 if (OldMD5
.Result() == MD5
.Result() &&
469 FileSize
== NewFileSize
)
471 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
474 if (unlink(I
->TmpFile
.Name().c_str()) != 0)
475 _error
->Errno("unlink",_("Problem unlinking %s"),
476 I
->TmpFile
.Name().c_str());
478 return !_error
->PendingError();
484 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
486 // Set the correct file modes
487 fchmod(I
->TmpFile
.Fd(),Permissions
);
489 if (rename(I
->TmpFile
.Name().c_str(),I
->Output
.c_str()) != 0)
490 _error
->Errno("rename",_("Failed to rename %s to %s"),
491 I
->TmpFile
.Name().c_str(),I
->Output
.c_str());
495 return !_error
->PendingError();