]>
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::WriteAtomic
);
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::WriteAtomic
);
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 from the patch file
481 to be able to see in which state the file should be
482 and use the access time from the "old" file */
483 struct stat BufBase
, BufPatch
;
484 if (stat(Path
.c_str(),&BufBase
) != 0 ||
485 stat(string(Path
+".ed").c_str(),&BufPatch
) != 0)
486 return _error
->Errno("stat",_("Failed to stat"));
488 struct utimbuf TimeBuf
;
489 TimeBuf
.actime
= BufBase
.st_atime
;
490 TimeBuf
.modtime
= BufPatch
.st_mtime
;
491 if (utime(Itm
->DestFile
.c_str(),&TimeBuf
) != 0)
492 return _error
->Errno("utime",_("Failed to set modification time"));
494 if (stat(Itm
->DestFile
.c_str(),&BufBase
) != 0)
495 return _error
->Errno("stat",_("Failed to stat"));
498 Res
.LastModified
= BufBase
.st_mtime
;
499 Res
.Size
= BufBase
.st_size
;
500 Res
.TakeHashes(Hash
);
506 /** \brief Wrapper class for testing rred */ /*{{{*/
507 class TestRredMethod
: public RredMethod
{
509 /** \brief Run rred in debug test mode
511 * This method can be used to run the rred method outside
512 * of the "normal" acquire environment for easier testing.
514 * \param base basename of all files involved in this rred test
516 bool Run(char const *base
) {
517 _config
->CndSet("Debug::pkgAcquire::RRed", "true");
518 FetchItem
*test
= new FetchItem
;
519 test
->DestFile
= base
;
524 /** \brief Starter for the rred method (or its test method) {{{
526 * Used without parameters is the normal behavior for methods for
527 * the APT acquire system. While this works great for the acquire system
528 * it is very hard to test the method and therefore the method also
529 * accepts one parameter which will switch it directly to debug test mode:
530 * The test mode expects that if "Testfile" is given as parameter
531 * the file "Testfile" should be ed-style patched with "Testfile.ed"
532 * and will write the result to "Testfile.result".
534 int main(int argc
, char *argv
[]) {
540 bool result
= Mth
.Run(argv
[1]);
541 _error
->DumpErrors();