]>
git.saurik.com Git - apt.git/blob - ftparchive/multicompress.cc
2a930ca6b9c2a1e8f949f24a46686fda39302474
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/fileutl.h>
20 #include <apt-pkg/strutl.h>
21 #include <apt-pkg/error.h>
22 #include <apt-pkg/md5.h>
24 #include <sys/types.h>
30 #include "multicompress.h"
37 // MultiCompress::MultiCompress - Constructor /*{{{*/
38 // ---------------------------------------------------------------------
39 /* Setup the file outputs, compression modes and fork the writer child */
40 MultiCompress::MultiCompress(string
const &Output
,string
const &Compress
,
41 mode_t
const &Permissions
,bool const &Write
) :
42 Permissions(Permissions
)
49 /* Parse the compression string, a space separated lists of compresison
51 string::const_iterator I
= Compress
.begin();
52 for (; I
!= Compress
.end();)
54 for (; I
!= Compress
.end() && isspace(*I
); ++I
);
57 string::const_iterator Start
= I
;
58 for (; I
!= Compress
.end() && !isspace(*I
); ++I
);
60 // Find the matching compressor
61 std::vector
<APT::Configuration::Compressor
> Compressors
= APT::Configuration::getCompressors();
62 std::vector
<APT::Configuration::Compressor
>::const_iterator Comp
= Compressors
.begin();
63 for (; Comp
!= Compressors
.end(); ++Comp
)
64 if (stringcmp(Start
,I
,Comp
->Name
.c_str()) == 0)
68 if (Comp
== Compressors
.end())
70 _error
->Warning(_("Unknown compression algorithm '%s'"),string(Start
,I
).c_str());
74 // Create and link in a new output
75 Files
*NewOut
= new Files
;
76 NewOut
->Next
= Outputs
;
78 NewOut
->CompressProg
= *Comp
;
79 NewOut
->Output
= Output
+Comp
->Extension
;
82 if (stat(NewOut
->Output
.c_str(),&St
) == 0)
83 NewOut
->OldMTime
= St
.st_mtime
;
91 /* Open all the temp files now so we can report any errors. File is
92 made unreable to prevent people from touching it during creating. */
93 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
94 I
->TmpFile
.Open(I
->Output
+ ".new", FileFd::WriteOnly
| FileFd::Create
| FileFd::Empty
, FileFd::Extension
, 0600);
95 if (_error
->PendingError() == true)
100 _error
->Error(_("Compressed output %s needs a compression set"),Output
.c_str());
107 // MultiCompress::~MultiCompress - Destructor /*{{{*/
108 // ---------------------------------------------------------------------
109 /* Just erase the file linked list. */
110 MultiCompress::~MultiCompress()
114 for (; Outputs
!= 0;)
116 Files
*Tmp
= Outputs
->Next
;
122 // MultiCompress::GetStat - Get stat information for compressed files /*{{{*/
123 // ---------------------------------------------------------------------
124 /* This checks each compressed file to make sure it exists and returns
125 stat information for a random file from the collection. False means
126 one or more of the files is missing. */
127 bool MultiCompress::GetStat(string
const &Output
,string
const &Compress
,struct stat
&St
)
129 /* Parse the compression string, a space separated lists of compresison
131 string::const_iterator I
= Compress
.begin();
132 bool DidStat
= false;
133 for (; I
!= Compress
.end();)
135 for (; I
!= Compress
.end() && isspace(*I
); ++I
);
138 string::const_iterator Start
= I
;
139 for (; I
!= Compress
.end() && !isspace(*I
); ++I
);
141 // Find the matching compressor
142 std::vector
<APT::Configuration::Compressor
> Compressors
= APT::Configuration::getCompressors();
143 std::vector
<APT::Configuration::Compressor
>::const_iterator Comp
= Compressors
.begin();
144 for (; Comp
!= Compressors
.end(); ++Comp
)
145 if (stringcmp(Start
,I
,Comp
->Name
.c_str()) == 0)
149 if (Comp
== Compressors
.end())
152 string Name
= Output
+Comp
->Extension
;
153 if (stat(Name
.c_str(),&St
) != 0)
160 // MultiCompress::Start - Start up the writer child /*{{{*/
161 // ---------------------------------------------------------------------
162 /* Fork a child and setup the communication pipe. */
163 bool MultiCompress::Start()
165 // Create a data pipe
166 int Pipe
[2] = {-1,-1};
168 return _error
->Errno("pipe",_("Failed to create IPC pipe to subprocess"));
169 for (int I
= 0; I
!= 2; I
++)
170 SetCloseExec(Pipe
[I
],true);
178 if (_error
->PendingError() == true)
180 _error
->DumpErrors();
187 Input
= fdopen(Pipe
[1],"w");
189 return _error
->Errno("fdopen",_("Failed to create FILE*"));
192 return _error
->Errno("fork",_("Failed to fork"));
196 // MultiCompress::Die - Clean up the writer /*{{{*/
197 // ---------------------------------------------------------------------
199 bool MultiCompress::Die()
206 bool Res
= ExecWait(Outputter
,_("Compress child"),false);
211 // MultiCompress::Finalize - Finish up writing /*{{{*/
212 // ---------------------------------------------------------------------
213 /* This is only necessary for statistics reporting. */
214 bool MultiCompress::Finalize(unsigned long long &OutSize
)
217 if (Input
== 0 || Die() == false)
223 // Check the mtimes to see if the files were replaced.
224 bool Changed
= false;
225 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
228 if (stat(I
->Output
.c_str(),&St
) != 0)
229 return _error
->Error(_("Internal error, failed to create %s"),
232 if (I
->OldMTime
!= St
.st_mtime
)
236 // Update the mtime if necessary
237 if (UpdateMTime
> 0 &&
238 (Now
- St
.st_mtime
> (signed)UpdateMTime
|| St
.st_mtime
> Now
))
241 Buf
.actime
= Buf
.modtime
= Now
;
242 utime(I
->Output
.c_str(),&Buf
);
247 // Force the file permissions
248 if (St
.st_mode
!= Permissions
)
249 chmod(I
->Output
.c_str(),Permissions
);
251 OutSize
+= St
.st_size
;
254 if (Changed
== false)
260 // MultiCompress::OpenOld - Open an old file /*{{{*/
261 // ---------------------------------------------------------------------
262 /* This opens one of the original output files, possibly decompressing it. */
263 bool MultiCompress::OpenOld(int &Fd
,pid_t
&Proc
)
265 Files
*Best
= Outputs
;
266 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
267 if (Best
->CompressProg
.Cost
> I
->CompressProg
.Cost
)
271 FileFd
F(Best
->Output
,FileFd::ReadOnly
);
272 if (_error
->PendingError() == true)
275 // Decompress the file so we can read it
276 if (ExecCompressor(Best
->CompressProg
,&Proc
,F
.Fd(),Fd
,false) == false)
282 // MultiCompress::CloseOld - Close the old file /*{{{*/
283 // ---------------------------------------------------------------------
285 bool MultiCompress::CloseOld(int Fd
,pid_t Proc
)
289 if (ExecWait(Proc
,_("decompressor"),false) == false)
294 // MultiCompress::Child - The writer child /*{{{*/
295 // ---------------------------------------------------------------------
296 /* The child process forks a bunch of compression children and takes
297 input on FD and passes it to all the compressor child. On the way it
298 computes the MD5 of the raw data. After this the raw data in the
299 original files is compared to see if this data is new. If the data
300 is new then the temp files are renamed, otherwise they are erased. */
301 bool MultiCompress::Child(int const &FD
)
303 /* Okay, now we just feed data from FD to all the other FDs. Also
304 stash a hash of the data to use later. */
305 SetNonBlock(FD
,false);
306 unsigned char Buffer
[32*1024];
307 unsigned long long FileSize
= 0;
312 int Res
= read(FD
,Buffer
,sizeof(Buffer
));
320 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
322 if (I
->TmpFile
.Write(Buffer
, Res
) == false)
324 _error
->Errno("write",_("IO to subprocess/file failed"));
330 if (_error
->PendingError() == true)
333 /* Now we have to copy the files over, or erase them if they
334 have not changed. First find the cheapest decompressor */
335 bool Missing
= false;
336 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
338 if (I
->OldMTime
== 0)
345 // Check the MD5 of the lowest cost entity.
346 while (Missing
== false)
350 if (OpenOld(CompFd
,Proc
) == false)
358 unsigned long long NewFileSize
= 0;
361 int Res
= read(CompFd
,Buffer
,sizeof(Buffer
));
365 return _error
->Errno("read",_("Failed to read while computing MD5"));
367 OldMD5
.Add(Buffer
,Res
);
370 // Tidy the compressor
371 if (CloseOld(CompFd
,Proc
) == false)
375 if (OldMD5
.Result() == MD5
.Result() &&
376 FileSize
== NewFileSize
)
378 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
381 if (unlink(I
->TmpFile
.Name().c_str()) != 0)
382 _error
->Errno("unlink",_("Problem unlinking %s"),
383 I
->TmpFile
.Name().c_str());
385 return !_error
->PendingError();
391 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
393 // Set the correct file modes
394 fchmod(I
->TmpFile
.Fd(),Permissions
);
396 if (rename(I
->TmpFile
.Name().c_str(),I
->Output
.c_str()) != 0)
397 _error
->Errno("rename",_("Failed to rename %s to %s"),
398 I
->TmpFile
.Name().c_str(),I
->Output
.c_str());
402 return !_error
->PendingError();