]>
git.saurik.com Git - apt.git/blob - methods/rred.cc
2 #include <apt-pkg/fileutl.h>
3 #include <apt-pkg/error.h>
4 #include <apt-pkg/acquire-method.h>
5 #include <apt-pkg/strutl.h>
6 #include <apt-pkg/hashes.h>
15 /** \brief RredMethod - ed-style incremential patch method {{{
17 * This method implements a patch functionality similar to "patch --ed" that is
18 * used by the "tiffany" incremental packages download stuff. It differs from
19 * "ed" insofar that it is way more restricted (and therefore secure).
20 * The currently supported ed commands are "<em>c</em>hange", "<em>a</em>dd" and
21 * "<em>d</em>elete" (diff doesn't output any other).
22 * Additionally the records must be reverse sorted by line number and
23 * may not overlap (diff *seems* to produce this kind of output).
25 class RredMethod
: public pkgAcqMethod
{
27 // the size of this doesn't really matter (except for performance)
28 const static int BUF_SIZE
= 1024;
29 // the supported ed commands
30 enum Mode
{MODE_CHANGED
='c', MODE_DELETED
='d', MODE_ADDED
='a'};
32 enum State
{ED_OK
=0, ED_ORDERING
=1, ED_PARSER
=2, ED_FAILURE
=3};
34 State
applyFile(FILE *ed_cmds
, FILE *in_file
, FILE *out_file
,
35 unsigned long &line
, char *buffer
, Hashes
*hash
) const;
36 void ignoreLineInFile(FILE *fin
, char *buffer
) const;
37 void copyLineFromFileToFile(FILE *fin
, FILE *fout
,
38 Hashes
*hash
, char *buffer
) const;
40 State
patchFile(FILE *ed_cmds
, FILE *in_file
, FILE *out_file
, Hashes
*hash
) const;
43 // the methods main method
44 virtual bool Fetch(FetchItem
*Itm
);
47 RredMethod() : pkgAcqMethod("1.1",SingleInstance
| SendConfig
) {};
50 /** \brief applyFile - in reverse order with a tail recursion {{{
52 * As it is expected that the commands are in reversed order in the patch file
53 * we check in the first half if the command is valid, but doesn't execute it
54 * and move a step deeper. After reaching the end of the file we apply the
55 * patches in the correct order: last found command first.
57 * \param ed_cmds patch file to apply
58 * \param in_file base file we want to patch
59 * \param out_file file to write the patched result to
60 * \param line of command operation
61 * \param buffer internal used read/write buffer
62 * \param hash the created file for correctness
63 * \return the success State of the ed command executor
65 RredMethod::State
RredMethod::applyFile(FILE *ed_cmds
, FILE *in_file
, FILE *out_file
,
66 unsigned long &line
, char *buffer
, Hashes
*hash
) const {
67 // get the current command and parse it
68 if (fgets(buffer
, BUF_SIZE
, ed_cmds
) == NULL
) {
70 std::clog
<< "rred: encounter end of file - we can start patching now.";
75 // parse in the effected linenumbers
78 unsigned long const startline
= strtol(buffer
, &idx
, 10);
79 if (errno
== ERANGE
|| errno
== EINVAL
) {
80 _error
->Errno("rred", "startline is an invalid number");
83 if (startline
> line
) {
84 _error
->Error("rred: The start line (%lu) of the next command is higher than the last line (%lu). This is not allowed.", startline
, line
);
87 unsigned long stopline
;
91 stopline
= strtol(idx
, &idx
, 10);
92 if (errno
== ERANGE
|| errno
== EINVAL
) {
93 _error
->Errno("rred", "stopline is an invalid number");
102 // which command to execute on this line(s)?
106 std::clog
<< "Change from line " << startline
<< " to " << stopline
<< std::endl
;
110 std::clog
<< "Insert after line " << startline
<< std::endl
;
114 std::clog
<< "Delete from line " << startline
<< " to " << stopline
<< std::endl
;
117 _error
->Error("rred: Unknown ed command '%c'. Abort.", *idx
);
120 unsigned char mode
= *idx
;
122 // save the current position
123 unsigned const long pos
= ftell(ed_cmds
);
125 // if this is add or change then go to the next full stop
126 if (mode
== MODE_CHANGED
|| mode
== MODE_ADDED
) {
128 ignoreLineInFile(ed_cmds
, buffer
);
129 while (strncmp(buffer
, ".", 1) != 0);
132 // do the recursive call - the last command is the one we need to execute at first
133 const State child
= applyFile(ed_cmds
, in_file
, out_file
, line
, buffer
, hash
);
134 if (child
!= ED_OK
) {
138 // change and delete are working on "line" - add is done after "line"
139 if (mode
!= MODE_ADDED
)
142 // first wind to the current position and copy over all unchanged lines
143 while (line
< startline
) {
144 fgets(buffer
, BUF_SIZE
, in_file
);
145 copyLineFromFileToFile(in_file
, out_file
, hash
, buffer
);
149 if (mode
!= MODE_ADDED
)
152 // include data from ed script
153 if (mode
== MODE_CHANGED
|| mode
== MODE_ADDED
) {
154 fseek(ed_cmds
, pos
, SEEK_SET
);
155 while(fgets(buffer
, BUF_SIZE
, ed_cmds
) != NULL
) {
156 if (strncmp(buffer
, ".", 1) != 0)
157 copyLineFromFileToFile(ed_cmds
, out_file
, hash
, buffer
);
163 // ignore the corresponding number of lines from input
164 if (mode
== MODE_CHANGED
|| mode
== MODE_DELETED
) {
165 while (line
< stopline
) {
166 ignoreLineInFile(in_file
, buffer
);
173 void RredMethod::copyLineFromFileToFile(FILE *fin
, FILE *fout
, /*{{{*/
174 Hashes
*hash
, char *buffer
) const {
175 size_t written
= fwrite(buffer
, 1, strlen(buffer
), fout
);
176 hash
->Add((unsigned char*)buffer
, written
);
177 while (strlen(buffer
) == (BUF_SIZE
- 1) &&
178 buffer
[BUF_SIZE
- 2] != '\n') {
179 fgets(buffer
, BUF_SIZE
, fin
);
180 written
= fwrite(buffer
, 1, strlen(buffer
), fout
);
181 hash
->Add((unsigned char*)buffer
, written
);
185 void RredMethod::ignoreLineInFile(FILE *fin
, char *buffer
) const { /*{{{*/
186 fgets(buffer
, BUF_SIZE
, fin
);
187 while (strlen(buffer
) == (BUF_SIZE
- 1) &&
188 buffer
[BUF_SIZE
- 2] != '\n') {
189 fgets(buffer
, BUF_SIZE
, fin
);
194 RredMethod::State
RredMethod::patchFile(FILE *ed_cmds
, FILE *in_file
, FILE *out_file
, /*{{{*/
195 Hashes
*hash
) const {
196 char buffer
[BUF_SIZE
];
198 /* we do a tail recursion to read the commands in the right order */
199 unsigned long line
= -1; // assign highest possible value
200 State result
= applyFile(ed_cmds
, in_file
, out_file
, line
, buffer
, hash
);
202 /* read the rest from infile */
203 if (result
== ED_OK
) {
204 while (fgets(buffer
, BUF_SIZE
, in_file
) != NULL
) {
205 size_t const written
= fwrite(buffer
, 1, strlen(buffer
), out_file
);
206 hash
->Add((unsigned char*)buffer
, written
);
212 bool RredMethod::Fetch(FetchItem
*Itm
) /*{{{*/
214 Debug
= _config
->FindB("Debug::pkgAcquire::RRed", false);
216 string Path
= Get
.Host
+ Get
.Path
; // To account for relative paths
219 Res
.Filename
= Itm
->DestFile
;
220 if (Itm
->Uri
.empty() == true) {
221 Path
= Itm
->DestFile
;
222 Itm
->DestFile
.append(".result");
227 std::clog
<< "Patching " << Path
<< " with " << Path
228 << ".ed and putting result into " << Itm
->DestFile
<< std::endl
;
229 // Open the source and destination files (the d'tor of FileFd will do
230 // the cleanup/closing of the fds)
231 FileFd
From(Path
,FileFd::ReadOnly
);
232 FileFd
Patch(Path
+".ed",FileFd::ReadOnly
);
233 FileFd
To(Itm
->DestFile
,FileFd::WriteEmpty
);
235 if (_error
->PendingError() == true)
239 FILE* fFrom
= fdopen(From
.Fd(), "r");
240 FILE* fPatch
= fdopen(Patch
.Fd(), "r");
241 FILE* fTo
= fdopen(To
.Fd(), "w");
242 // now do the actual patching
243 if (patchFile(fPatch
, fFrom
, fTo
, &Hash
) != ED_OK
) {
244 _error
->Errno("rred", _("Could not patch file"));
248 // write out the result
256 // Transfer the modification times
258 if (stat(Path
.c_str(),&Buf
) != 0)
259 return _error
->Errno("stat",_("Failed to stat"));
261 struct utimbuf TimeBuf
;
262 TimeBuf
.actime
= Buf
.st_atime
;
263 TimeBuf
.modtime
= Buf
.st_mtime
;
264 if (utime(Itm
->DestFile
.c_str(),&TimeBuf
) != 0)
265 return _error
->Errno("utime",_("Failed to set modification time"));
267 if (stat(Itm
->DestFile
.c_str(),&Buf
) != 0)
268 return _error
->Errno("stat",_("Failed to stat"));
271 if (Itm
->Uri
.empty() == true) {
272 Res
.LastModified
= Buf
.st_mtime
;
273 Res
.Size
= Buf
.st_size
;
274 Res
.TakeHashes(Hash
);
281 /** \brief Wrapper class for testing rred */ /*{{{*/
282 class TestRredMethod
: public RredMethod
{
284 /** \brief Run rred in debug test mode
286 * This method can be used to run the rred method outside
287 * of the "normal" acquire environment for easier testing.
289 * \param base basename of all files involved in this rred test
291 bool Run(char const *base
) {
292 _config
->CndSet("Debug::pkgAcquire::RRed", "true");
293 FetchItem
*test
= new FetchItem
;
294 test
->DestFile
= base
;
299 /** \brief Starter for the rred method (or its test method) {{{
301 * Used without parameters is the normal behavior for methods for
302 * the APT acquire system. While this works great for the acquire system
303 * it is very hard to test the method and therefore the method also
304 * accepts one parameter which will switch it directly to debug test mode:
305 * The test mode expects that if "Testfile" is given as parameter
306 * the file "Testfile" should be ed-style patched with "Testfile.ed"
307 * and will write the result to "Testfile.result".
309 int main(int argc
, char *argv
[]) {
315 bool result
= Mth
.Run(argv
[1]);
316 _error
->DumpErrors();