]>
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 /*{{{*/
19 #include <apt-pkg/fileutl.h>
20 #include <apt-pkg/strutl.h>
21 #include <apt-pkg/error.h>
22 #include <apt-pkg/md5.h>
23 #include <apt-pkg/aptconfiguration.h>
24 #include <apt-pkg/hashsum_template.h>
28 #include <sys/types.h>
33 #include "multicompress.h"
40 // MultiCompress::MultiCompress - Constructor /*{{{*/
41 // ---------------------------------------------------------------------
42 /* Setup the file outputs, compression modes and fork the writer child */
43 MultiCompress::MultiCompress(string
const &Output
,string
const &Compress
,
44 mode_t
const &Permissions
,bool const &Write
) :
45 Permissions(Permissions
)
51 /* Parse the compression string, a space separated lists of compresison
53 string::const_iterator I
= Compress
.begin();
54 for (; I
!= Compress
.end();)
56 for (; I
!= Compress
.end() && isspace(*I
); ++I
);
59 string::const_iterator Start
= I
;
60 for (; I
!= Compress
.end() && !isspace(*I
); ++I
);
62 // Find the matching compressor
63 std::vector
<APT::Configuration::Compressor
> Compressors
= APT::Configuration::getCompressors();
64 std::vector
<APT::Configuration::Compressor
>::const_iterator Comp
= Compressors
.begin();
65 for (; Comp
!= Compressors
.end(); ++Comp
)
66 if (stringcmp(Start
,I
,Comp
->Name
.c_str()) == 0)
70 if (Comp
== Compressors
.end())
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::WriteOnly
| FileFd::Create
| FileFd::Empty
, FileFd::Extension
, 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
const &Output
,string
const &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 std::vector
<APT::Configuration::Compressor
> Compressors
= APT::Configuration::getCompressors();
145 std::vector
<APT::Configuration::Compressor
>::const_iterator Comp
= Compressors
.begin();
146 for (; Comp
!= Compressors
.end(); ++Comp
)
147 if (stringcmp(Start
,I
,Comp
->Name
.c_str()) == 0)
151 if (Comp
== Compressors
.end())
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();
189 if (Input
.OpenDescriptor(Pipe
[1], FileFd::WriteOnly
, true) == false)
193 return _error
->Errno("fork",_("Failed to fork"));
197 // MultiCompress::Die - Clean up the writer /*{{{*/
198 // ---------------------------------------------------------------------
200 bool MultiCompress::Die()
202 if (Input
.IsOpen() == false)
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
.IsOpen() == false || 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
))
240 utimes(I
->Output
.c_str(), NULL
);
245 // Force the file permissions
246 if (St
.st_mode
!= Permissions
)
247 chmod(I
->Output
.c_str(),Permissions
);
249 OutSize
+= St
.st_size
;
252 if (Changed
== false)
258 // MultiCompress::OpenOld - Open an old file /*{{{*/
259 // ---------------------------------------------------------------------
260 /* This opens one of the original output files, possibly decompressing it. */
261 bool MultiCompress::OpenOld(FileFd
&Fd
)
263 Files
*Best
= Outputs
;
264 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
265 if (Best
->CompressProg
.Cost
> I
->CompressProg
.Cost
)
269 return Fd
.Open(Best
->Output
, FileFd::ReadOnly
, FileFd::Extension
);
272 // MultiCompress::Child - The writer child /*{{{*/
273 // ---------------------------------------------------------------------
274 /* The child process forks a bunch of compression children and takes
275 input on FD and passes it to all the compressor child. On the way it
276 computes the MD5 of the raw data. After this the raw data in the
277 original files is compared to see if this data is new. If the data
278 is new then the temp files are renamed, otherwise they are erased. */
279 bool MultiCompress::Child(int const &FD
)
281 /* Okay, now we just feed data from FD to all the other FDs. Also
282 stash a hash of the data to use later. */
283 SetNonBlock(FD
,false);
284 unsigned char Buffer
[32*1024];
285 unsigned long long FileSize
= 0;
290 int Res
= read(FD
,Buffer
,sizeof(Buffer
));
298 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
300 if (I
->TmpFile
.Write(Buffer
, Res
) == false)
302 _error
->Errno("write",_("IO to subprocess/file failed"));
308 if (_error
->PendingError() == true)
311 /* Now we have to copy the files over, or erase them if they
312 have not changed. First find the cheapest decompressor */
313 bool Missing
= false;
314 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
316 if (I
->OldMTime
== 0)
323 // Check the MD5 of the lowest cost entity.
324 while (Missing
== false)
327 if (OpenOld(CompFd
) == false)
335 unsigned long long NewFileSize
= 0;
338 unsigned long long Res
= 0;
339 if (CompFd
.Read(Buffer
,sizeof(Buffer
), &Res
) == false)
340 return _error
->Errno("read",_("Failed to read while computing MD5"));
344 OldMD5
.Add(Buffer
,Res
);
349 if (OldMD5
.Result() == MD5
.Result() &&
350 FileSize
== NewFileSize
)
352 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
355 RemoveFile("MultiCompress::Child", I
->TmpFile
.Name());
357 return !_error
->PendingError();
363 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
365 // Set the correct file modes
366 chmod(I
->TmpFile
.Name().c_str(),Permissions
);
368 if (rename(I
->TmpFile
.Name().c_str(),I
->Output
.c_str()) != 0)
369 _error
->Errno("rename",_("Failed to rename %s to %s"),
370 I
->TmpFile
.Name().c_str(),I
->Output
.c_str());
374 return !_error
->PendingError();