]>
git.saurik.com Git - apt.git/blob - methods/rred.cc
2 #include <apt-pkg/fileutl.h>
3 #include <apt-pkg/mmap.h>
4 #include <apt-pkg/error.h>
5 #include <apt-pkg/acquire-method.h>
6 #include <apt-pkg/strutl.h>
7 #include <apt-pkg/hashes.h>
17 /** \brief RredMethod - ed-style incremential patch method {{{
19 * This method implements a patch functionality similar to "patch --ed" that is
20 * used by the "tiffany" incremental packages download stuff. It differs from
21 * "ed" insofar that it is way more restricted (and therefore secure).
22 * The currently supported ed commands are "<em>c</em>hange", "<em>a</em>dd" and
23 * "<em>d</em>elete" (diff doesn't output any other).
24 * Additionally the records must be reverse sorted by line number and
25 * may not overlap (diff *seems* to produce this kind of output).
27 class RredMethod
: public pkgAcqMethod
{
29 // the size of this doesn't really matter (except for performance)
30 const static int BUF_SIZE
= 1024;
31 // the supported ed commands
32 enum Mode
{MODE_CHANGED
='c', MODE_DELETED
='d', MODE_ADDED
='a'};
34 enum State
{ED_OK
, ED_ORDERING
, ED_PARSER
, ED_FAILURE
, MMAP_FAILED
};
36 State
applyFile(FILE *ed_cmds
, FILE *in_file
, FILE *out_file
,
37 unsigned long &line
, char *buffer
, Hashes
*hash
) const;
38 void ignoreLineInFile(FILE *fin
, char *buffer
) const;
39 void copyLinesFromFileToFile(FILE *fin
, FILE *fout
, unsigned int lines
,
40 Hashes
*hash
, char *buffer
) const;
42 State
patchFile(FileFd
&Patch
, FileFd
&From
, FileFd
&out_file
, Hashes
*hash
) const;
43 State
patchMMap(FileFd
&Patch
, FileFd
&From
, FileFd
&out_file
, Hashes
*hash
) const;
46 // the methods main method
47 virtual bool Fetch(FetchItem
*Itm
);
50 RredMethod() : pkgAcqMethod("1.1",SingleInstance
| SendConfig
) {};
53 /** \brief applyFile - in reverse order with a tail recursion {{{
55 * As it is expected that the commands are in reversed order in the patch file
56 * we check in the first half if the command is valid, but doesn't execute it
57 * and move a step deeper. After reaching the end of the file we apply the
58 * patches in the correct order: last found command first.
60 * \param ed_cmds patch file to apply
61 * \param in_file base file we want to patch
62 * \param out_file file to write the patched result to
63 * \param line of command operation
64 * \param buffer internal used read/write buffer
65 * \param hash the created file for correctness
66 * \return the success State of the ed command executor
68 RredMethod::State
RredMethod::applyFile(FILE *ed_cmds
, FILE *in_file
, FILE *out_file
,
69 unsigned long &line
, char *buffer
, Hashes
*hash
) const {
70 // get the current command and parse it
71 if (fgets(buffer
, BUF_SIZE
, ed_cmds
) == NULL
) {
73 std::clog
<< "rred: encounter end of file - we can start patching now." << std::endl
;
78 // parse in the effected linenumbers
81 unsigned long const startline
= strtol(buffer
, &idx
, 10);
82 if (errno
== ERANGE
|| errno
== EINVAL
) {
83 _error
->Errno("rred", "startline is an invalid number");
86 if (startline
> line
) {
87 _error
->Error("rred: The start line (%lu) of the next command is higher than the last line (%lu). This is not allowed.", startline
, line
);
90 unsigned long stopline
;
94 stopline
= strtol(idx
, &idx
, 10);
95 if (errno
== ERANGE
|| errno
== EINVAL
) {
96 _error
->Errno("rred", "stopline is an invalid number");
101 stopline
= startline
;
105 // which command to execute on this line(s)?
109 std::clog
<< "Change from line " << startline
<< " to " << stopline
<< std::endl
;
113 std::clog
<< "Insert after line " << startline
<< std::endl
;
117 std::clog
<< "Delete from line " << startline
<< " to " << stopline
<< std::endl
;
120 _error
->Error("rred: Unknown ed command '%c'. Abort.", *idx
);
123 unsigned char mode
= *idx
;
125 // save the current position
126 unsigned const long pos
= ftell(ed_cmds
);
128 // if this is add or change then go to the next full stop
129 unsigned int data_length
= 0;
130 if (mode
== MODE_CHANGED
|| mode
== MODE_ADDED
) {
132 ignoreLineInFile(ed_cmds
, buffer
);
135 while (strncmp(buffer
, ".", 1) != 0);
136 data_length
--; // the dot should not be copied
139 // do the recursive call - the last command is the one we need to execute at first
140 const State child
= applyFile(ed_cmds
, in_file
, out_file
, line
, buffer
, hash
);
141 if (child
!= ED_OK
) {
145 // change and delete are working on "line" - add is done after "line"
146 if (mode
!= MODE_ADDED
)
149 // first wind to the current position and copy over all unchanged lines
150 if (line
< startline
) {
151 copyLinesFromFileToFile(in_file
, out_file
, (startline
- line
), hash
, buffer
);
155 if (mode
!= MODE_ADDED
)
158 // include data from ed script
159 if (mode
== MODE_CHANGED
|| mode
== MODE_ADDED
) {
160 fseek(ed_cmds
, pos
, SEEK_SET
);
161 copyLinesFromFileToFile(ed_cmds
, out_file
, data_length
, hash
, buffer
);
164 // ignore the corresponding number of lines from input
165 if (mode
== MODE_CHANGED
|| mode
== MODE_DELETED
) {
166 while (line
< stopline
) {
167 ignoreLineInFile(in_file
, buffer
);
174 void RredMethod::copyLinesFromFileToFile(FILE *fin
, FILE *fout
, unsigned int lines
,/*{{{*/
175 Hashes
*hash
, char *buffer
) const {
176 while (0 < lines
--) {
178 fgets(buffer
, BUF_SIZE
, fin
);
179 size_t const written
= fwrite(buffer
, 1, strlen(buffer
), fout
);
180 hash
->Add((unsigned char*)buffer
, written
);
181 } while (strlen(buffer
) == (BUF_SIZE
- 1) &&
182 buffer
[BUF_SIZE
- 2] != '\n');
186 void RredMethod::ignoreLineInFile(FILE *fin
, char *buffer
) const { /*{{{*/
187 fgets(buffer
, BUF_SIZE
, fin
);
188 while (strlen(buffer
) == (BUF_SIZE
- 1) &&
189 buffer
[BUF_SIZE
- 2] != '\n') {
190 fgets(buffer
, BUF_SIZE
, fin
);
195 RredMethod::State
RredMethod::patchFile(FileFd
&Patch
, FileFd
&From
, /*{{{*/
196 FileFd
&out_file
, Hashes
*hash
) const {
197 char buffer
[BUF_SIZE
];
198 FILE* fFrom
= fdopen(From
.Fd(), "r");
199 FILE* fPatch
= fdopen(Patch
.Fd(), "r");
200 FILE* fTo
= fdopen(out_file
.Fd(), "w");
202 /* we do a tail recursion to read the commands in the right order */
203 unsigned long line
= -1; // assign highest possible value
204 State
const result
= applyFile(fPatch
, fFrom
, fTo
, line
, buffer
, hash
);
206 /* read the rest from infile */
207 if (result
== ED_OK
) {
208 while (fgets(buffer
, BUF_SIZE
, fFrom
) != NULL
) {
209 size_t const written
= fwrite(buffer
, 1, strlen(buffer
), fTo
);
210 hash
->Add((unsigned char*)buffer
, written
);
217 struct EdCommand
{ /*{{{*/
225 #define IOV_COUNT 1024 /* Don't really want IOV_MAX since it can be arbitrarily large */
227 RredMethod::State
RredMethod::patchMMap(FileFd
&Patch
, FileFd
&From
, /*{{{*/
228 FileFd
&out_file
, Hashes
*hash
) const {
229 #ifdef _POSIX_MAPPED_FILES
230 MMap
ed_cmds(Patch
, MMap::ReadOnly
);
231 MMap
in_file(From
, MMap::ReadOnly
);
233 if (ed_cmds
.Size() == 0 || in_file
.Size() == 0)
236 EdCommand
* commands
= 0;
237 size_t command_count
= 0;
238 size_t command_alloc
= 0;
240 const char* begin
= (char*) ed_cmds
.Data();
241 const char* end
= begin
;
242 const char* ed_end
= (char*) ed_cmds
.Data() + ed_cmds
.Size();
244 const char* input
= (char*) in_file
.Data();
245 const char* input_end
= (char*) in_file
.Data() + in_file
.Size();
249 /* 1. Parse entire script. It is executed in reverse order, so we cather it
250 * in the `commands' buffer first
258 while(begin
!= ed_end
&& *begin
== '\n')
260 while(end
!= ed_end
&& *end
!= '\n')
262 if(end
== ed_end
&& begin
== end
)
265 /* Determine command range */
266 const char* tmp
= begin
;
269 /* atoll is safe despite lacking NUL-termination; we know there's an
270 * alphabetic character at end[-1]
273 cmd
.first_line
= atol(begin
);
274 cmd
.last_line
= cmd
.first_line
;
278 cmd
.first_line
= atol(begin
);
279 cmd
.last_line
= atol(tmp
+ 1);
285 // which command to execute on this line(s)?
289 std::clog
<< "Change from line " << cmd
.first_line
<< " to " << cmd
.last_line
<< std::endl
;
293 std::clog
<< "Insert after line " << cmd
.first_line
<< std::endl
;
297 std::clog
<< "Delete from line " << cmd
.first_line
<< " to " << cmd
.last_line
<< std::endl
;
300 _error
->Error("rred: Unknown ed command '%c'. Abort.", end
[-1]);
306 /* Determine the size of the inserted text, so we don't have to scan this
313 if(cmd
.type
== MODE_ADDED
|| cmd
.type
== MODE_CHANGED
) {
314 cmd
.data_start
= begin
- (char*) ed_cmds
.Data();
315 while(end
!= ed_end
) {
317 if(end
[-1] == '.' && end
[-2] == '\n')
323 cmd
.data_end
= end
- (char*) ed_cmds
.Data() - 1;
327 if(command_count
== command_alloc
) {
328 command_alloc
= (command_alloc
+ 64) * 3 / 2;
329 commands
= (EdCommand
*) realloc(commands
, command_alloc
* sizeof(EdCommand
));
331 commands
[command_count
++] = cmd
;
334 struct iovec
* iov
= new struct iovec
[IOV_COUNT
];
337 size_t amount
, remaining
;
341 /* 2. Execute script. We gather writes in a `struct iov' array, and flush
342 * using writev to minimize the number of system calls. Data is read
343 * directly from the memory mappings of the input file and the script.
346 for(i
= command_count
; i
-- > 0; ) {
348 if(cmd
->type
== MODE_ADDED
)
349 amount
= cmd
->first_line
+ 1;
351 amount
= cmd
->first_line
;
355 while(line
!= amount
) {
356 input
= (const char*) memchr(input
, '\n', input_end
- input
);
363 iov
[iov_size
].iov_base
= (void*) begin
;
364 iov
[iov_size
].iov_len
= input
- begin
;
365 hash
->Add((const unsigned char*) begin
, input
- begin
);
367 if(++iov_size
== IOV_COUNT
) {
368 writev(out_file
.Fd(), iov
, IOV_COUNT
);
373 if(cmd
->type
== MODE_DELETED
|| cmd
->type
== MODE_CHANGED
) {
374 remaining
= (cmd
->last_line
- cmd
->first_line
) + 1;
377 input
= (const char*) memchr(input
, '\n', input_end
- input
);
385 if(cmd
->type
== MODE_CHANGED
|| cmd
->type
== MODE_ADDED
) {
386 if(cmd
->data_end
!= cmd
->data_start
) {
387 iov
[iov_size
].iov_base
= (void*) ((char*)ed_cmds
.Data() + cmd
->data_start
);
388 iov
[iov_size
].iov_len
= cmd
->data_end
- cmd
->data_start
;
389 hash
->Add((const unsigned char*) ((char*)ed_cmds
.Data() + cmd
->data_start
),
390 iov
[iov_size
].iov_len
);
392 if(++iov_size
== IOV_COUNT
) {
393 writev(out_file
.Fd(), iov
, IOV_COUNT
);
400 if(input
!= input_end
) {
401 iov
[iov_size
].iov_base
= (void*) input
;
402 iov
[iov_size
].iov_len
= input_end
- input
;
403 hash
->Add((const unsigned char*) input
, input_end
- input
);
408 writev(out_file
.Fd(), iov
, iov_size
);
412 for(i
= 0; i
< iov_size
; i
+= IOV_COUNT
) {
413 if(iov_size
- i
< IOV_COUNT
)
414 writev(out_file
.Fd(), iov
+ i
, iov_size
- i
);
416 writev(out_file
.Fd(), iov
+ i
, IOV_COUNT
);
428 bool RredMethod::Fetch(FetchItem
*Itm
) /*{{{*/
430 Debug
= _config
->FindB("Debug::pkgAcquire::RRed", false);
432 string Path
= Get
.Host
+ Get
.Path
; // To account for relative paths
435 Res
.Filename
= Itm
->DestFile
;
436 if (Itm
->Uri
.empty() == true) {
437 Path
= Itm
->DestFile
;
438 Itm
->DestFile
.append(".result");
443 std::clog
<< "Patching " << Path
<< " with " << Path
444 << ".ed and putting result into " << Itm
->DestFile
<< std::endl
;
445 // Open the source and destination files (the d'tor of FileFd will do
446 // the cleanup/closing of the fds)
447 FileFd
From(Path
,FileFd::ReadOnly
);
448 FileFd
Patch(Path
+".ed",FileFd::ReadOnly
);
449 FileFd
To(Itm
->DestFile
,FileFd::WriteEmpty
);
451 if (_error
->PendingError() == true)
455 // now do the actual patching
456 State
const result
= patchMMap(Patch
, From
, To
, &Hash
);
457 if (result
== MMAP_FAILED
) {
458 // retry with patchFile
459 lseek(Patch
.Fd(), 0, SEEK_SET
);
460 lseek(From
.Fd(), 0, SEEK_SET
);
461 To
.Open(Itm
->DestFile
,FileFd::WriteEmpty
);
462 if (_error
->PendingError() == true)
464 if (patchFile(Patch
, From
, To
, &Hash
) != ED_OK
) {
465 return _error
->WarningE("rred", _("Could not patch %s with mmap and with file operation usage - the patch seems to be corrupt."), Path
.c_str());
466 } else if (Debug
== true) {
467 std::clog
<< "rred: finished file patching of " << Path
<< " after mmap failed." << std::endl
;
469 } else if (result
!= ED_OK
) {
470 return _error
->Errno("rred", _("Could not patch %s with mmap (but no mmap specific fail) - the patch seems to be corrupt."), Path
.c_str());
471 } else if (Debug
== true) {
472 std::clog
<< "rred: finished mmap patching of " << Path
<< std::endl
;
475 // write out the result
480 // Transfer the modification times
482 if (stat(Path
.c_str(),&Buf
) != 0)
483 return _error
->Errno("stat",_("Failed to stat"));
485 struct utimbuf TimeBuf
;
486 TimeBuf
.actime
= Buf
.st_atime
;
487 TimeBuf
.modtime
= Buf
.st_mtime
;
488 if (utime(Itm
->DestFile
.c_str(),&TimeBuf
) != 0)
489 return _error
->Errno("utime",_("Failed to set modification time"));
491 if (stat(Itm
->DestFile
.c_str(),&Buf
) != 0)
492 return _error
->Errno("stat",_("Failed to stat"));
495 Res
.LastModified
= Buf
.st_mtime
;
496 Res
.Size
= Buf
.st_size
;
497 Res
.TakeHashes(Hash
);
503 /** \brief Wrapper class for testing rred */ /*{{{*/
504 class TestRredMethod
: public RredMethod
{
506 /** \brief Run rred in debug test mode
508 * This method can be used to run the rred method outside
509 * of the "normal" acquire environment for easier testing.
511 * \param base basename of all files involved in this rred test
513 bool Run(char const *base
) {
514 _config
->CndSet("Debug::pkgAcquire::RRed", "true");
515 FetchItem
*test
= new FetchItem
;
516 test
->DestFile
= base
;
521 /** \brief Starter for the rred method (or its test method) {{{
523 * Used without parameters is the normal behavior for methods for
524 * the APT acquire system. While this works great for the acquire system
525 * it is very hard to test the method and therefore the method also
526 * accepts one parameter which will switch it directly to debug test mode:
527 * The test mode expects that if "Testfile" is given as parameter
528 * the file "Testfile" should be ed-style patched with "Testfile.ed"
529 * and will write the result to "Testfile.result".
531 int main(int argc
, char *argv
[]) {
537 bool result
= Mth
.Run(argv
[1]);
538 _error
->DumpErrors();