]>
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>
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(FileFd
&Fd
)
265 Files
*Best
= Outputs
;
266 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
267 if (Best
->CompressProg
.Cost
> I
->CompressProg
.Cost
)
271 return Fd
.Open(Best
->Output
, FileFd::ReadOnly
, FileFd::Extension
);
274 // MultiCompress::Child - The writer child /*{{{*/
275 // ---------------------------------------------------------------------
276 /* The child process forks a bunch of compression children and takes
277 input on FD and passes it to all the compressor child. On the way it
278 computes the MD5 of the raw data. After this the raw data in the
279 original files is compared to see if this data is new. If the data
280 is new then the temp files are renamed, otherwise they are erased. */
281 bool MultiCompress::Child(int const &FD
)
283 /* Okay, now we just feed data from FD to all the other FDs. Also
284 stash a hash of the data to use later. */
285 SetNonBlock(FD
,false);
286 unsigned char Buffer
[32*1024];
287 unsigned long long FileSize
= 0;
292 int Res
= read(FD
,Buffer
,sizeof(Buffer
));
300 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
302 if (I
->TmpFile
.Write(Buffer
, Res
) == false)
304 _error
->Errno("write",_("IO to subprocess/file failed"));
310 if (_error
->PendingError() == true)
313 /* Now we have to copy the files over, or erase them if they
314 have not changed. First find the cheapest decompressor */
315 bool Missing
= false;
316 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
318 if (I
->OldMTime
== 0)
325 // Check the MD5 of the lowest cost entity.
326 while (Missing
== false)
329 if (OpenOld(CompFd
) == false)
337 unsigned long long NewFileSize
= 0;
340 unsigned long long Res
= 0;
341 if (CompFd
.Read(Buffer
,sizeof(Buffer
), &Res
) == false)
342 return _error
->Errno("read",_("Failed to read while computing MD5"));
346 OldMD5
.Add(Buffer
,Res
);
351 if (OldMD5
.Result() == MD5
.Result() &&
352 FileSize
== NewFileSize
)
354 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
357 if (unlink(I
->TmpFile
.Name().c_str()) != 0)
358 _error
->Errno("unlink",_("Problem unlinking %s"),
359 I
->TmpFile
.Name().c_str());
361 return !_error
->PendingError();
367 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
369 // Set the correct file modes
370 fchmod(I
->TmpFile
.Fd(),Permissions
);
372 if (rename(I
->TmpFile
.Name().c_str(),I
->Output
.c_str()) != 0)
373 _error
->Errno("rename",_("Failed to rename %s to %s"),
374 I
->TmpFile
.Name().c_str(),I
->Output
.c_str());
378 return !_error
->PendingError();