]>
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>
25 #include <sys/types.h>
31 #include "multicompress.h"
38 // MultiCompress::MultiCompress - Constructor /*{{{*/
39 // ---------------------------------------------------------------------
40 /* Setup the file outputs, compression modes and fork the writer child */
41 MultiCompress::MultiCompress(string
const &Output
,string
const &Compress
,
42 mode_t
const &Permissions
,bool const &Write
) :
43 Permissions(Permissions
)
50 /* Parse the compression string, a space separated lists of compresison
52 string::const_iterator I
= Compress
.begin();
53 for (; I
!= Compress
.end();)
55 for (; I
!= Compress
.end() && isspace(*I
); ++I
);
58 string::const_iterator Start
= I
;
59 for (; I
!= Compress
.end() && !isspace(*I
); ++I
);
61 // Find the matching compressor
62 std::vector
<APT::Configuration::Compressor
> Compressors
= APT::Configuration::getCompressors();
63 std::vector
<APT::Configuration::Compressor
>::const_iterator Comp
= Compressors
.begin();
64 for (; Comp
!= Compressors
.end(); ++Comp
)
65 if (stringcmp(Start
,I
,Comp
->Name
.c_str()) == 0)
69 if (Comp
== Compressors
.end())
71 _error
->Warning(_("Unknown compression algorithm '%s'"),string(Start
,I
).c_str());
75 // Create and link in a new output
76 Files
*NewOut
= new Files
;
77 NewOut
->Next
= Outputs
;
79 NewOut
->CompressProg
= *Comp
;
80 NewOut
->Output
= Output
+Comp
->Extension
;
83 if (stat(NewOut
->Output
.c_str(),&St
) == 0)
84 NewOut
->OldMTime
= St
.st_mtime
;
92 /* Open all the temp files now so we can report any errors. File is
93 made unreable to prevent people from touching it during creating. */
94 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
95 I
->TmpFile
.Open(I
->Output
+ ".new", FileFd::WriteOnly
| FileFd::Create
| FileFd::Empty
, FileFd::Extension
, 0600);
96 if (_error
->PendingError() == true)
101 _error
->Error(_("Compressed output %s needs a compression set"),Output
.c_str());
108 // MultiCompress::~MultiCompress - Destructor /*{{{*/
109 // ---------------------------------------------------------------------
110 /* Just erase the file linked list. */
111 MultiCompress::~MultiCompress()
115 for (; Outputs
!= 0;)
117 Files
*Tmp
= Outputs
->Next
;
123 // MultiCompress::GetStat - Get stat information for compressed files /*{{{*/
124 // ---------------------------------------------------------------------
125 /* This checks each compressed file to make sure it exists and returns
126 stat information for a random file from the collection. False means
127 one or more of the files is missing. */
128 bool MultiCompress::GetStat(string
const &Output
,string
const &Compress
,struct stat
&St
)
130 /* Parse the compression string, a space separated lists of compresison
132 string::const_iterator I
= Compress
.begin();
133 bool DidStat
= false;
134 for (; I
!= Compress
.end();)
136 for (; I
!= Compress
.end() && isspace(*I
); ++I
);
139 string::const_iterator Start
= I
;
140 for (; I
!= Compress
.end() && !isspace(*I
); ++I
);
142 // Find the matching compressor
143 std::vector
<APT::Configuration::Compressor
> Compressors
= APT::Configuration::getCompressors();
144 std::vector
<APT::Configuration::Compressor
>::const_iterator Comp
= Compressors
.begin();
145 for (; Comp
!= Compressors
.end(); ++Comp
)
146 if (stringcmp(Start
,I
,Comp
->Name
.c_str()) == 0)
150 if (Comp
== Compressors
.end())
153 string Name
= Output
+Comp
->Extension
;
154 if (stat(Name
.c_str(),&St
) != 0)
161 // MultiCompress::Start - Start up the writer child /*{{{*/
162 // ---------------------------------------------------------------------
163 /* Fork a child and setup the communication pipe. */
164 bool MultiCompress::Start()
166 // Create a data pipe
167 int Pipe
[2] = {-1,-1};
169 return _error
->Errno("pipe",_("Failed to create IPC pipe to subprocess"));
170 for (int I
= 0; I
!= 2; I
++)
171 SetCloseExec(Pipe
[I
],true);
179 if (_error
->PendingError() == true)
181 _error
->DumpErrors();
188 Input
= fdopen(Pipe
[1],"w");
190 return _error
->Errno("fdopen",_("Failed to create FILE*"));
193 return _error
->Errno("fork",_("Failed to fork"));
197 // MultiCompress::Die - Clean up the writer /*{{{*/
198 // ---------------------------------------------------------------------
200 bool MultiCompress::Die()
207 bool Res
= ExecWait(Outputter
,_("Compress child"),false);
212 // MultiCompress::Finalize - Finish up writing /*{{{*/
213 // ---------------------------------------------------------------------
214 /* This is only necessary for statistics reporting. */
215 bool MultiCompress::Finalize(unsigned long long &OutSize
)
218 if (Input
== 0 || Die() == false)
224 // Check the mtimes to see if the files were replaced.
225 bool Changed
= false;
226 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
229 if (stat(I
->Output
.c_str(),&St
) != 0)
230 return _error
->Error(_("Internal error, failed to create %s"),
233 if (I
->OldMTime
!= St
.st_mtime
)
237 // Update the mtime if necessary
238 if (UpdateMTime
> 0 &&
239 (Now
- St
.st_mtime
> (signed)UpdateMTime
|| St
.st_mtime
> Now
))
241 utimes(I
->Output
.c_str(), NULL
);
246 // Force the file permissions
247 if (St
.st_mode
!= Permissions
)
248 chmod(I
->Output
.c_str(),Permissions
);
250 OutSize
+= St
.st_size
;
253 if (Changed
== false)
259 // MultiCompress::OpenOld - Open an old file /*{{{*/
260 // ---------------------------------------------------------------------
261 /* This opens one of the original output files, possibly decompressing it. */
262 bool MultiCompress::OpenOld(FileFd
&Fd
)
264 Files
*Best
= Outputs
;
265 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
266 if (Best
->CompressProg
.Cost
> I
->CompressProg
.Cost
)
270 return Fd
.Open(Best
->Output
, FileFd::ReadOnly
, FileFd::Extension
);
273 // MultiCompress::Child - The writer child /*{{{*/
274 // ---------------------------------------------------------------------
275 /* The child process forks a bunch of compression children and takes
276 input on FD and passes it to all the compressor child. On the way it
277 computes the MD5 of the raw data. After this the raw data in the
278 original files is compared to see if this data is new. If the data
279 is new then the temp files are renamed, otherwise they are erased. */
280 bool MultiCompress::Child(int const &FD
)
282 /* Okay, now we just feed data from FD to all the other FDs. Also
283 stash a hash of the data to use later. */
284 SetNonBlock(FD
,false);
285 unsigned char Buffer
[32*1024];
286 unsigned long long FileSize
= 0;
291 int Res
= read(FD
,Buffer
,sizeof(Buffer
));
299 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
301 if (I
->TmpFile
.Write(Buffer
, Res
) == false)
303 _error
->Errno("write",_("IO to subprocess/file failed"));
309 if (_error
->PendingError() == true)
312 /* Now we have to copy the files over, or erase them if they
313 have not changed. First find the cheapest decompressor */
314 bool Missing
= false;
315 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
317 if (I
->OldMTime
== 0)
324 // Check the MD5 of the lowest cost entity.
325 while (Missing
== false)
328 if (OpenOld(CompFd
) == false)
336 unsigned long long NewFileSize
= 0;
339 unsigned long long Res
= 0;
340 if (CompFd
.Read(Buffer
,sizeof(Buffer
), &Res
) == false)
341 return _error
->Errno("read",_("Failed to read while computing MD5"));
345 OldMD5
.Add(Buffer
,Res
);
350 if (OldMD5
.Result() == MD5
.Result() &&
351 FileSize
== NewFileSize
)
353 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
356 if (unlink(I
->TmpFile
.Name().c_str()) != 0)
357 _error
->Errno("unlink",_("Problem unlinking %s"),
358 I
->TmpFile
.Name().c_str());
360 return !_error
->PendingError();
366 for (Files
*I
= Outputs
; I
!= 0; I
= I
->Next
)
368 // Set the correct file modes
369 chmod(I
->TmpFile
.Name().c_str(),Permissions
);
371 if (rename(I
->TmpFile
.Name().c_str(),I
->Output
.c_str()) != 0)
372 _error
->Errno("rename",_("Failed to rename %s to %s"),
373 I
->TmpFile
.Name().c_str(),I
->Output
.c_str());
377 return !_error
->PendingError();