]>
git.saurik.com Git - apt.git/blob - ftparchive/multicompress.cc
1 // -*- mode: cpp; mode: fold -*-
3 // $Id: multicompress.cc,v 1.3 2001/05/29 03:48:27 jgg 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"
23 #include <apt-pkg/strutl.h>
24 #include <apt-pkg/error.h>
25 #include <apt-pkg/md5.h>
27 #include <sys/types.h>
36 const MultiCompress::CompType
MultiCompress::Compressors
[] =
38 {"gzip",".gz","gzip","-9n","-d",2},
39 {"bzip2",".bz2","bzip2","-9","-d",3},
42 // MultiCompress::MultiCompress - Constructor /*{{{*/
43 // ---------------------------------------------------------------------
44 /* Setup the file outputs, compression modes and fork the writer child */
45 MultiCompress::MultiCompress(string Output
,string Compress
,
46 mode_t Permissions
,bool Write
)
52 this->Permissions
= Permissions
;
54 /* Parse the compression string, a space separated lists of compresison
56 string::const_iterator I
= Compress
.begin();
57 for (; I
!= Compress
.end();)
59 for (; I
!= Compress
.end() && isspace(*I
); I
++);
62 string::const_iterator Start
= I
;
63 for (; I
!= Compress
.end() && !isspace(*I
); I
++);
65 // Find the matching compressor
66 const CompType
*Comp
= Compressors
;
67 for (; Comp
->Name
!= 0; Comp
++)
68 if (stringcmp(Start
,I
,Comp
->Name
) == 0)
74 _error
->Warning("Unknown Compresison Algorithm '%s'",string(Start
,I
).c_str());
78 // Create and link in a new output
79 Files
*NewOut
= new Files
;
80 NewOut
->Next
= Outputs
;
82 NewOut
->CompressProg
= Comp
;
83 NewOut
->Output
= Output
+Comp
->Extension
;
86 if (stat(NewOut
->Output
.c_str(),&St
) == 0)
87 NewOut
->OldMTime
= St
.st_mtime
;
95 /* Open all the temp files now so we can report any errors. File is
96 made unreable to prevent people from touching it during creating. */
97 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
98 I
->TmpFile
.Open(I
->Output
+ ".new",FileFd::WriteEmpty
,0600);
99 if (_error
->PendingError() == true)
104 _error
->Error("Compressed output %s needs a compression set",Output
.c_str());
111 // MultiCompress::~MultiCompress - Destructor /*{{{*/
112 // ---------------------------------------------------------------------
113 /* Just erase the file linked list. */
114 MultiCompress::~MultiCompress()
118 for (; Outputs
!= 0;)
120 Files
*Tmp
= Outputs
->Next
;
126 // MultiCompress::GetStat - Get stat information for compressed files /*{{{*/
127 // ---------------------------------------------------------------------
128 /* This checks each compressed file to make sure it exists and returns
129 stat information for a random file from the collection. False means
130 one or more of the files is missing. */
131 bool MultiCompress::GetStat(string Output
,string Compress
,struct stat
&St
)
133 /* Parse the compression string, a space separated lists of compresison
135 string::const_iterator I
= Compress
.begin();
136 bool DidStat
= false;
137 for (; I
!= Compress
.end();)
139 for (; I
!= Compress
.end() && isspace(*I
); I
++);
142 string::const_iterator Start
= I
;
143 for (; I
!= Compress
.end() && !isspace(*I
); I
++);
145 // Find the matching compressor
146 const CompType
*Comp
= Compressors
;
147 for (; Comp
->Name
!= 0; Comp
++)
148 if (stringcmp(Start
,I
,Comp
->Name
) == 0)
155 string Name
= Output
+Comp
->Extension
;
156 if (stat(Name
.c_str(),&St
) != 0)
163 // MultiCompress::Start - Start up the writer child /*{{{*/
164 // ---------------------------------------------------------------------
165 /* Fork a child and setup the communication pipe. */
166 bool MultiCompress::Start()
168 // Create a data pipe
169 int Pipe
[2] = {-1,-1};
171 return _error
->Errno("pipe","Failed to create IPC pipe to subprocess");
172 for (int I
= 0; I
!= 2; I
++)
173 SetCloseExec(Pipe
[I
],true);
181 if (_error
->PendingError() == true)
183 _error
->DumpErrors();
189 /* Tidy up the temp files, we open them in the constructor so as to
190 get proper error reporting. Close them now. */
191 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
195 Input
= fdopen(Pipe
[1],"w");
197 return _error
->Errno("fdopen","Failed to create FILE*");
200 return _error
->Errno("fork","Failed to fork");
204 // MultiCompress::Die - Clean up the writer /*{{{*/
205 // ---------------------------------------------------------------------
207 bool MultiCompress::Die()
214 bool Res
= ExecWait(Outputter
,"Compress Child",false);
219 // MultiCompress::Finalize - Finish up writing /*{{{*/
220 // ---------------------------------------------------------------------
221 /* This is only necessary for statistics reporting. */
222 bool MultiCompress::Finalize(unsigned long &OutSize
)
225 if (Input
== 0 || Die() == false)
231 // Check the mtimes to see if the files were replaced.
232 bool Changed
= false;
233 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
236 if (stat(I
->Output
.c_str(),&St
) != 0)
237 return _error
->Error("Internal Error, Failed to create %s",
240 if (I
->OldMTime
!= St
.st_mtime
)
244 // Update the mtime if necessary
245 if (UpdateMTime
> 0 &&
246 (Now
- St
.st_mtime
> (signed)UpdateMTime
|| St
.st_mtime
> Now
))
249 Buf
.actime
= Buf
.modtime
= Now
;
250 utime(I
->Output
.c_str(),&Buf
);
255 // Force the file permissions
256 if (St
.st_mode
!= Permissions
)
257 chmod(I
->Output
.c_str(),Permissions
);
259 OutSize
+= St
.st_size
;
262 if (Changed
== false)
268 // MultiCompress::OpenCompress - Open the compressor /*{{{*/
269 // ---------------------------------------------------------------------
270 /* This opens the compressor, either in compress mode or decompress
271 mode. FileFd is always the compressor input/output file,
272 OutFd is the created pipe, Input for Compress, Output for Decompress. */
273 bool MultiCompress::OpenCompress(const CompType
*Prog
,int &Pid
,int FileFd
,
274 int &OutFd
,bool Comp
)
279 if (Prog
->Binary
== 0)
285 // Create a data pipe
286 int Pipe
[2] = {-1,-1};
288 return _error
->Errno("pipe","Failed to create subprocess IPC");
289 for (int J
= 0; J
!= 2; J
++)
290 SetCloseExec(Pipe
[J
],true);
303 dup2(FileFd
,STDOUT_FILENO
);
304 dup2(Pipe
[0],STDIN_FILENO
);
308 dup2(FileFd
,STDIN_FILENO
);
309 dup2(Pipe
[1],STDOUT_FILENO
);
312 SetCloseExec(STDOUT_FILENO
,false);
313 SetCloseExec(STDIN_FILENO
,false);
316 Args
[0] = Prog
->Binary
;
318 Args
[1] = Prog
->CompArgs
;
320 Args
[1] = Prog
->UnCompArgs
;
322 execvp(Args
[0],(char **)Args
);
323 cerr
<< "Failed to exec compressor " << Args
[0] << endl
;
333 // MultiCompress::OpenOld - Open an old file /*{{{*/
334 // ---------------------------------------------------------------------
335 /* This opens one of the original output files, possibly decompressing it. */
336 bool MultiCompress::OpenOld(int &Fd
,int &Proc
)
338 Files
*Best
= Outputs
;
339 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
340 if (Best
->CompressProg
->Cost
> I
->CompressProg
->Cost
)
344 FileFd
F(Best
->Output
,FileFd::ReadOnly
);
345 if (_error
->PendingError() == true)
348 // Decompress the file so we can read it
349 if (OpenCompress(Best
->CompressProg
,Proc
,F
.Fd(),Fd
,false) == false)
355 // MultiCompress::CloseOld - Close the old file /*{{{*/
356 // ---------------------------------------------------------------------
358 bool MultiCompress::CloseOld(int Fd
,int Proc
)
362 if (ExecWait(Proc
,"decompressor",false) == false)
367 // MultiCompress::Child - The writer child /*{{{*/
368 // ---------------------------------------------------------------------
369 /* The child process forks a bunch of compression children and takes
370 input on FD and passes it to all the compressor childer. On the way it
371 computes the MD5 of the raw data. After this the raw data in the
372 original files is compared to see if this data is new. If the data
373 is new then the temp files are renamed, otherwise they are erased. */
374 bool MultiCompress::Child(int FD
)
376 // Start the compression children.
377 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
379 if (OpenCompress(I
->CompressProg
,I
->CompressProc
,I
->TmpFile
.Fd(),
380 I
->Fd
,true) == false)
384 /* Okay, now we just feed data from FD to all the other FDs. Also
385 stash a hash of the data to use later. */
386 SetNonBlock(FD
,false);
387 unsigned char Buffer
[32*1024];
388 unsigned long FileSize
= 0;
393 int Res
= read(FD
,Buffer
,sizeof(Buffer
));
401 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
403 if (write(I
->Fd
,Buffer
,Res
) != Res
)
405 _error
->Errno("write","IO to subprocess/file failed");
411 // Close all the writers
412 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
415 // Wait for the compressors to exit
416 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
418 if (I
->CompressProc
!= -1)
419 ExecWait(I
->CompressProc
,I
->CompressProg
->Binary
,false);
422 if (_error
->PendingError() == true)
425 /* Now we have to copy the files over, or erase them if they
426 have not changed. First find the cheapest decompressor */
427 bool Missing
= false;
428 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
430 if (I
->OldMTime
== 0)
437 // Check the MD5 of the lowest cost entity.
438 while (Missing
== false)
442 if (OpenOld(CompFd
,Proc
) == false)
450 unsigned long NewFileSize
= 0;
453 int Res
= read(CompFd
,Buffer
,sizeof(Buffer
));
457 return _error
->Errno("read","Failed to read while computing MD5");
459 OldMD5
.Add(Buffer
,Res
);
462 // Tidy the compressor
463 if (CloseOld(CompFd
,Proc
) == false)
467 if (OldMD5
.Result() == MD5
.Result() &&
468 FileSize
== NewFileSize
)
470 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
473 if (unlink(I
->TmpFile
.Name().c_str()) != 0)
474 _error
->Errno("unlink","Problem unlinking %s",
475 I
->TmpFile
.Name().c_str());
477 return !_error
->PendingError();
483 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
485 // Set the correct file modes
486 fchmod(I
->TmpFile
.Fd(),Permissions
);
488 if (rename(I
->TmpFile
.Name().c_str(),I
->Output
.c_str()) != 0)
489 _error
->Errno("rename","Failed to rename %s to %s",
490 I
->TmpFile
.Name().c_str(),I
->Output
.c_str());
494 return !_error
->PendingError();