]>
git.saurik.com Git - apt.git/blob - methods/rred.cc
4 #include <apt-pkg/fileutl.h>
5 #include <apt-pkg/mmap.h>
6 #include <apt-pkg/error.h>
7 #include <apt-pkg/acquire-method.h>
8 #include <apt-pkg/strutl.h>
9 #include <apt-pkg/hashes.h>
10 #include <apt-pkg/configuration.h>
21 /** \brief RredMethod - ed-style incremential patch method {{{
23 * This method implements a patch functionality similar to "patch --ed" that is
24 * used by the "tiffany" incremental packages download stuff. It differs from
25 * "ed" insofar that it is way more restricted (and therefore secure).
26 * The currently supported ed commands are "<em>c</em>hange", "<em>a</em>dd" and
27 * "<em>d</em>elete" (diff doesn't output any other).
28 * Additionally the records must be reverse sorted by line number and
29 * may not overlap (diff *seems* to produce this kind of output).
31 class RredMethod
: public pkgAcqMethod
{
33 // the size of this doesn't really matter (except for performance)
34 const static int BUF_SIZE
= 1024;
35 // the supported ed commands
36 enum Mode
{MODE_CHANGED
='c', MODE_DELETED
='d', MODE_ADDED
='a'};
38 enum State
{ED_OK
, ED_ORDERING
, ED_PARSER
, ED_FAILURE
, MMAP_FAILED
};
40 State
applyFile(FileFd
&ed_cmds
, FileFd
&in_file
, FileFd
&out_file
,
41 unsigned long &line
, char *buffer
, Hashes
*hash
) const;
42 void ignoreLineInFile(FileFd
&fin
, char *buffer
) const;
43 void copyLinesFromFileToFile(FileFd
&fin
, FileFd
&fout
, unsigned int lines
,
44 Hashes
*hash
, char *buffer
) const;
46 State
patchFile(FileFd
&Patch
, FileFd
&From
, FileFd
&out_file
, Hashes
*hash
) const;
47 State
patchMMap(FileFd
&Patch
, FileFd
&From
, FileFd
&out_file
, Hashes
*hash
) const;
50 // the methods main method
51 virtual bool Fetch(FetchItem
*Itm
);
54 RredMethod() : pkgAcqMethod("1.1",SingleInstance
| SendConfig
), Debug(false) {};
57 /** \brief applyFile - in reverse order with a tail recursion {{{
59 * As it is expected that the commands are in reversed order in the patch file
60 * we check in the first half if the command is valid, but doesn't execute it
61 * and move a step deeper. After reaching the end of the file we apply the
62 * patches in the correct order: last found command first.
64 * \param ed_cmds patch file to apply
65 * \param in_file base file we want to patch
66 * \param out_file file to write the patched result to
67 * \param line of command operation
68 * \param buffer internal used read/write buffer
69 * \param hash the created file for correctness
70 * \return the success State of the ed command executor
72 RredMethod::State
RredMethod::applyFile(FileFd
&ed_cmds
, FileFd
&in_file
, FileFd
&out_file
,
73 unsigned long &line
, char *buffer
, Hashes
*hash
) const {
74 // get the current command and parse it
75 if (ed_cmds
.ReadLine(buffer
, BUF_SIZE
) == NULL
) {
77 std::clog
<< "rred: encounter end of file - we can start patching now." << std::endl
;
82 // parse in the effected linenumbers
85 unsigned long const startline
= strtol(buffer
, &idx
, 10);
86 if (errno
== ERANGE
|| errno
== EINVAL
) {
87 _error
->Errno("rred", "startline is an invalid number");
90 if (startline
> line
) {
91 _error
->Error("rred: The start line (%lu) of the next command is higher than the last line (%lu). This is not allowed.", startline
, line
);
94 unsigned long stopline
;
98 stopline
= strtol(idx
, &idx
, 10);
99 if (errno
== ERANGE
|| errno
== EINVAL
) {
100 _error
->Errno("rred", "stopline is an invalid number");
105 stopline
= startline
;
109 // which command to execute on this line(s)?
113 std::clog
<< "Change from line " << startline
<< " to " << stopline
<< std::endl
;
117 std::clog
<< "Insert after line " << startline
<< std::endl
;
121 std::clog
<< "Delete from line " << startline
<< " to " << stopline
<< std::endl
;
124 _error
->Error("rred: Unknown ed command '%c'. Abort.", *idx
);
127 unsigned char mode
= *idx
;
129 // save the current position
130 unsigned const long long pos
= ed_cmds
.Tell();
132 // if this is add or change then go to the next full stop
133 unsigned int data_length
= 0;
134 if (mode
== MODE_CHANGED
|| mode
== MODE_ADDED
) {
136 ignoreLineInFile(ed_cmds
, buffer
);
139 while (strncmp(buffer
, ".", 1) != 0);
140 data_length
--; // the dot should not be copied
143 // do the recursive call - the last command is the one we need to execute at first
144 const State child
= applyFile(ed_cmds
, in_file
, out_file
, line
, buffer
, hash
);
145 if (child
!= ED_OK
) {
149 // change and delete are working on "line" - add is done after "line"
150 if (mode
!= MODE_ADDED
)
153 // first wind to the current position and copy over all unchanged lines
154 if (line
< startline
) {
155 copyLinesFromFileToFile(in_file
, out_file
, (startline
- line
), hash
, buffer
);
159 if (mode
!= MODE_ADDED
)
162 // include data from ed script
163 if (mode
== MODE_CHANGED
|| mode
== MODE_ADDED
) {
165 copyLinesFromFileToFile(ed_cmds
, out_file
, data_length
, hash
, buffer
);
168 // ignore the corresponding number of lines from input
169 if (mode
== MODE_CHANGED
|| mode
== MODE_DELETED
) {
170 while (line
< stopline
) {
171 ignoreLineInFile(in_file
, buffer
);
178 void RredMethod::copyLinesFromFileToFile(FileFd
&fin
, FileFd
&fout
, unsigned int lines
,/*{{{*/
179 Hashes
*hash
, char *buffer
) const {
180 while (0 < lines
--) {
182 fin
.ReadLine(buffer
, BUF_SIZE
);
183 unsigned long long const towrite
= strlen(buffer
);
184 fout
.Write(buffer
, towrite
);
185 hash
->Add((unsigned char*)buffer
, towrite
);
186 } while (strlen(buffer
) == (BUF_SIZE
- 1) &&
187 buffer
[BUF_SIZE
- 2] != '\n');
191 void RredMethod::ignoreLineInFile(FileFd
&fin
, char *buffer
) const { /*{{{*/
192 fin
.ReadLine(buffer
, BUF_SIZE
);
193 while (strlen(buffer
) == (BUF_SIZE
- 1) &&
194 buffer
[BUF_SIZE
- 2] != '\n') {
195 fin
.ReadLine(buffer
, BUF_SIZE
);
200 RredMethod::State
RredMethod::patchFile(FileFd
&Patch
, FileFd
&From
, /*{{{*/
201 FileFd
&out_file
, Hashes
*hash
) const {
202 char buffer
[BUF_SIZE
];
204 /* we do a tail recursion to read the commands in the right order */
205 unsigned long line
= -1; // assign highest possible value
206 State
const result
= applyFile(Patch
, From
, out_file
, line
, buffer
, hash
);
208 /* read the rest from infile */
209 if (result
== ED_OK
) {
210 while (From
.ReadLine(buffer
, BUF_SIZE
) != NULL
) {
211 unsigned long long const towrite
= strlen(buffer
);
212 out_file
.Write(buffer
, towrite
);
213 hash
->Add((unsigned char*)buffer
, towrite
);
219 /* struct EdCommand {{{*/
220 #ifdef _POSIX_MAPPED_FILES
229 #define IOV_COUNT 1024 /* Don't really want IOV_MAX since it can be arbitrarily large */
230 static ssize_t
retry_writev(int fd
, const struct iovec
*iov
, int iovcnt
) {
235 Res
= writev(fd
, iov
+ i
, iovcnt
);
236 if (Res
< 0 && errno
== EINTR
)
239 return _error
->Errno("writev",_("Write error"));
242 } while (Res
> 0 && iovcnt
> 0);
247 RredMethod::State
RredMethod::patchMMap(FileFd
&Patch
, FileFd
&From
, /*{{{*/
248 FileFd
&out_file
, Hashes
*hash
) const {
249 #ifdef _POSIX_MAPPED_FILES
250 MMap
ed_cmds(Patch
, MMap::ReadOnly
);
251 MMap
in_file(From
, MMap::ReadOnly
);
253 unsigned long long const ed_size
= ed_cmds
.Size();
254 unsigned long long const in_size
= in_file
.Size();
255 if (ed_size
== 0 || in_size
== 0)
258 EdCommand
* commands
= 0;
259 size_t command_count
= 0;
260 size_t command_alloc
= 0;
262 const char* begin
= (char*) ed_cmds
.Data();
263 const char* end
= begin
;
264 const char* ed_end
= (char*) ed_cmds
.Data() + ed_size
;
266 const char* input
= (char*) in_file
.Data();
267 const char* input_end
= (char*) in_file
.Data() + in_size
;
271 /* 1. Parse entire script. It is executed in reverse order, so we cather it
272 * in the `commands' buffer first
280 while(begin
!= ed_end
&& *begin
== '\n')
282 while(end
!= ed_end
&& *end
!= '\n')
284 if(end
== ed_end
&& begin
== end
)
287 /* Determine command range */
288 const char* tmp
= begin
;
291 /* atoll is safe despite lacking NUL-termination; we know there's an
292 * alphabetic character at end[-1]
295 cmd
.first_line
= atol(begin
);
296 cmd
.last_line
= cmd
.first_line
;
300 cmd
.first_line
= atol(begin
);
301 cmd
.last_line
= atol(tmp
+ 1);
307 // which command to execute on this line(s)?
311 std::clog
<< "Change from line " << cmd
.first_line
<< " to " << cmd
.last_line
<< std::endl
;
315 std::clog
<< "Insert after line " << cmd
.first_line
<< std::endl
;
319 std::clog
<< "Delete from line " << cmd
.first_line
<< " to " << cmd
.last_line
<< std::endl
;
322 _error
->Error("rred: Unknown ed command '%c'. Abort.", end
[-1]);
328 /* Determine the size of the inserted text, so we don't have to scan this
335 if(cmd
.type
== MODE_ADDED
|| cmd
.type
== MODE_CHANGED
) {
336 cmd
.data_start
= begin
- (char*) ed_cmds
.Data();
337 while(end
!= ed_end
) {
339 if(end
[-1] == '.' && end
[-2] == '\n')
345 cmd
.data_end
= end
- (char*) ed_cmds
.Data() - 1;
349 if(command_count
== command_alloc
) {
350 command_alloc
= (command_alloc
+ 64) * 3 / 2;
351 EdCommand
* newCommands
= (EdCommand
*) realloc(commands
, command_alloc
* sizeof(EdCommand
));
352 if (newCommands
== NULL
) {
356 commands
= newCommands
;
358 commands
[command_count
++] = cmd
;
361 struct iovec
* iov
= new struct iovec
[IOV_COUNT
];
364 size_t amount
, remaining
;
368 /* 2. Execute script. We gather writes in a `struct iov' array, and flush
369 * using writev to minimize the number of system calls. Data is read
370 * directly from the memory mappings of the input file and the script.
373 for(i
= command_count
; i
-- > 0; ) {
375 if(cmd
->type
== MODE_ADDED
)
376 amount
= cmd
->first_line
+ 1;
378 amount
= cmd
->first_line
;
382 while(line
!= amount
) {
383 input
= (const char*) memchr(input
, '\n', input_end
- input
);
390 iov
[iov_size
].iov_base
= (void*) begin
;
391 iov
[iov_size
].iov_len
= input
- begin
;
392 hash
->Add((const unsigned char*) begin
, input
- begin
);
394 if(++iov_size
== IOV_COUNT
) {
395 retry_writev(out_file
.Fd(), iov
, IOV_COUNT
);
400 if(cmd
->type
== MODE_DELETED
|| cmd
->type
== MODE_CHANGED
) {
401 remaining
= (cmd
->last_line
- cmd
->first_line
) + 1;
404 input
= (const char*) memchr(input
, '\n', input_end
- input
);
412 if(cmd
->type
== MODE_CHANGED
|| cmd
->type
== MODE_ADDED
) {
413 if(cmd
->data_end
!= cmd
->data_start
) {
414 iov
[iov_size
].iov_base
= (void*) ((char*)ed_cmds
.Data() + cmd
->data_start
);
415 iov
[iov_size
].iov_len
= cmd
->data_end
- cmd
->data_start
;
416 hash
->Add((const unsigned char*) ((char*)ed_cmds
.Data() + cmd
->data_start
),
417 iov
[iov_size
].iov_len
);
419 if(++iov_size
== IOV_COUNT
) {
420 retry_writev(out_file
.Fd(), iov
, IOV_COUNT
);
427 if(input
!= input_end
) {
428 iov
[iov_size
].iov_base
= (void*) input
;
429 iov
[iov_size
].iov_len
= input_end
- input
;
430 hash
->Add((const unsigned char*) input
, input_end
- input
);
435 retry_writev(out_file
.Fd(), iov
, iov_size
);
439 for(i
= 0; i
< iov_size
; i
+= IOV_COUNT
) {
440 if(iov_size
- i
< IOV_COUNT
)
441 retry_writev(out_file
.Fd(), iov
+ i
, iov_size
- i
);
443 retry_writev(out_file
.Fd(), iov
+ i
, IOV_COUNT
);
455 bool RredMethod::Fetch(FetchItem
*Itm
) /*{{{*/
457 Debug
= _config
->FindB("Debug::pkgAcquire::RRed", false);
459 std::string Path
= Get
.Host
+ Get
.Path
; // To account for relative paths
462 Res
.Filename
= Itm
->DestFile
;
463 if (Itm
->Uri
.empty() == true) {
464 Path
= Itm
->DestFile
;
465 Itm
->DestFile
.append(".result");
470 std::clog
<< "Patching " << Path
<< " with " << Path
471 << ".ed and putting result into " << Itm
->DestFile
<< std::endl
;
472 // Open the source and destination files (the d'tor of FileFd will do
473 // the cleanup/closing of the fds)
474 FileFd
From(Path
,FileFd::ReadOnly
);
475 FileFd
Patch(Path
+".ed",FileFd::ReadOnly
, FileFd::Gzip
);
476 FileFd
To(Itm
->DestFile
,FileFd::WriteAtomic
);
478 if (_error
->PendingError() == true)
482 // now do the actual patching
483 State
const result
= patchMMap(Patch
, From
, To
, &Hash
);
484 if (result
== MMAP_FAILED
) {
485 // retry with patchFile
488 To
.Open(Itm
->DestFile
,FileFd::WriteAtomic
);
489 if (_error
->PendingError() == true)
491 if (patchFile(Patch
, From
, To
, &Hash
) != ED_OK
) {
492 return _error
->WarningE("rred", _("Could not patch %s with mmap and with file operation usage - the patch seems to be corrupt."), Path
.c_str());
493 } else if (Debug
== true) {
494 std::clog
<< "rred: finished file patching of " << Path
<< " after mmap failed." << std::endl
;
496 } else if (result
!= ED_OK
) {
497 return _error
->Errno("rred", _("Could not patch %s with mmap (but no mmap specific fail) - the patch seems to be corrupt."), Path
.c_str());
498 } else if (Debug
== true) {
499 std::clog
<< "rred: finished mmap patching of " << Path
<< std::endl
;
502 // write out the result
507 /* Transfer the modification times from the patch file
508 to be able to see in which state the file should be
509 and use the access time from the "old" file */
510 struct stat BufBase
, BufPatch
;
511 if (stat(Path
.c_str(),&BufBase
) != 0 ||
512 stat(std::string(Path
+".ed").c_str(),&BufPatch
) != 0)
513 return _error
->Errno("stat",_("Failed to stat"));
515 struct utimbuf TimeBuf
;
516 TimeBuf
.actime
= BufBase
.st_atime
;
517 TimeBuf
.modtime
= BufPatch
.st_mtime
;
518 if (utime(Itm
->DestFile
.c_str(),&TimeBuf
) != 0)
519 return _error
->Errno("utime",_("Failed to set modification time"));
521 if (stat(Itm
->DestFile
.c_str(),&BufBase
) != 0)
522 return _error
->Errno("stat",_("Failed to stat"));
525 Res
.LastModified
= BufBase
.st_mtime
;
526 Res
.Size
= BufBase
.st_size
;
527 Res
.TakeHashes(Hash
);
533 /** \brief Wrapper class for testing rred */ /*{{{*/
534 class TestRredMethod
: public RredMethod
{
536 /** \brief Run rred in debug test mode
538 * This method can be used to run the rred method outside
539 * of the "normal" acquire environment for easier testing.
541 * \param base basename of all files involved in this rred test
543 bool Run(char const *base
) {
544 _config
->CndSet("Debug::pkgAcquire::RRed", "true");
545 FetchItem
*test
= new FetchItem
;
546 test
->DestFile
= base
;
551 /** \brief Starter for the rred method (or its test method) {{{
553 * Used without parameters is the normal behavior for methods for
554 * the APT acquire system. While this works great for the acquire system
555 * it is very hard to test the method and therefore the method also
556 * accepts one parameter which will switch it directly to debug test mode:
557 * The test mode expects that if "Testfile" is given as parameter
558 * the file "Testfile" should be ed-style patched with "Testfile.ed"
559 * and will write the result to "Testfile.result".
561 int main(int argc
, char *argv
[]) {
567 bool result
= Mth
.Run(argv
[1]);
568 _error
->DumpErrors();