]>
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>
20 /** \brief RredMethod - ed-style incremential patch method {{{
22 * This method implements a patch functionality similar to "patch --ed" that is
23 * used by the "tiffany" incremental packages download stuff. It differs from
24 * "ed" insofar that it is way more restricted (and therefore secure).
25 * The currently supported ed commands are "<em>c</em>hange", "<em>a</em>dd" and
26 * "<em>d</em>elete" (diff doesn't output any other).
27 * Additionally the records must be reverse sorted by line number and
28 * may not overlap (diff *seems* to produce this kind of output).
30 class RredMethod
: public pkgAcqMethod
{
32 // the size of this doesn't really matter (except for performance)
33 const static int BUF_SIZE
= 1024;
34 // the supported ed commands
35 enum Mode
{MODE_CHANGED
='c', MODE_DELETED
='d', MODE_ADDED
='a'};
37 enum State
{ED_OK
, ED_ORDERING
, ED_PARSER
, ED_FAILURE
, MMAP_FAILED
};
39 State
applyFile(FileFd
&ed_cmds
, FileFd
&in_file
, FileFd
&out_file
,
40 unsigned long &line
, char *buffer
, Hashes
*hash
) const;
41 void ignoreLineInFile(FileFd
&fin
, char *buffer
) const;
42 void copyLinesFromFileToFile(FileFd
&fin
, FileFd
&fout
, unsigned int lines
,
43 Hashes
*hash
, char *buffer
) const;
45 State
patchFile(FileFd
&Patch
, FileFd
&From
, FileFd
&out_file
, Hashes
*hash
) const;
46 State
patchMMap(FileFd
&Patch
, FileFd
&From
, FileFd
&out_file
, Hashes
*hash
) const;
49 // the methods main method
50 virtual bool Fetch(FetchItem
*Itm
);
53 RredMethod() : pkgAcqMethod("1.1",SingleInstance
| SendConfig
), Debug(false) {};
56 /** \brief applyFile - in reverse order with a tail recursion {{{
58 * As it is expected that the commands are in reversed order in the patch file
59 * we check in the first half if the command is valid, but doesn't execute it
60 * and move a step deeper. After reaching the end of the file we apply the
61 * patches in the correct order: last found command first.
63 * \param ed_cmds patch file to apply
64 * \param in_file base file we want to patch
65 * \param out_file file to write the patched result to
66 * \param line of command operation
67 * \param buffer internal used read/write buffer
68 * \param hash the created file for correctness
69 * \return the success State of the ed command executor
71 RredMethod::State
RredMethod::applyFile(FileFd
&ed_cmds
, FileFd
&in_file
, FileFd
&out_file
,
72 unsigned long &line
, char *buffer
, Hashes
*hash
) const {
73 // get the current command and parse it
74 if (ed_cmds
.ReadLine(buffer
, BUF_SIZE
) == NULL
) {
76 std::clog
<< "rred: encounter end of file - we can start patching now." << std::endl
;
81 // parse in the effected linenumbers
84 unsigned long const startline
= strtol(buffer
, &idx
, 10);
85 if (errno
== ERANGE
|| errno
== EINVAL
) {
86 _error
->Errno("rred", "startline is an invalid number");
89 if (startline
> line
) {
90 _error
->Error("rred: The start line (%lu) of the next command is higher than the last line (%lu). This is not allowed.", startline
, line
);
93 unsigned long stopline
;
97 stopline
= strtol(idx
, &idx
, 10);
98 if (errno
== ERANGE
|| errno
== EINVAL
) {
99 _error
->Errno("rred", "stopline is an invalid number");
104 stopline
= startline
;
108 // which command to execute on this line(s)?
112 std::clog
<< "Change from line " << startline
<< " to " << stopline
<< std::endl
;
116 std::clog
<< "Insert after line " << startline
<< std::endl
;
120 std::clog
<< "Delete from line " << startline
<< " to " << stopline
<< std::endl
;
123 _error
->Error("rred: Unknown ed command '%c'. Abort.", *idx
);
126 unsigned char mode
= *idx
;
128 // save the current position
129 unsigned const long long pos
= ed_cmds
.Tell();
131 // if this is add or change then go to the next full stop
132 unsigned int data_length
= 0;
133 if (mode
== MODE_CHANGED
|| mode
== MODE_ADDED
) {
135 ignoreLineInFile(ed_cmds
, buffer
);
138 while (strncmp(buffer
, ".", 1) != 0);
139 data_length
--; // the dot should not be copied
142 // do the recursive call - the last command is the one we need to execute at first
143 const State child
= applyFile(ed_cmds
, in_file
, out_file
, line
, buffer
, hash
);
144 if (child
!= ED_OK
) {
148 // change and delete are working on "line" - add is done after "line"
149 if (mode
!= MODE_ADDED
)
152 // first wind to the current position and copy over all unchanged lines
153 if (line
< startline
) {
154 copyLinesFromFileToFile(in_file
, out_file
, (startline
- line
), hash
, buffer
);
158 if (mode
!= MODE_ADDED
)
161 // include data from ed script
162 if (mode
== MODE_CHANGED
|| mode
== MODE_ADDED
) {
164 copyLinesFromFileToFile(ed_cmds
, out_file
, data_length
, hash
, buffer
);
167 // ignore the corresponding number of lines from input
168 if (mode
== MODE_CHANGED
|| mode
== MODE_DELETED
) {
169 while (line
< stopline
) {
170 ignoreLineInFile(in_file
, buffer
);
177 void RredMethod::copyLinesFromFileToFile(FileFd
&fin
, FileFd
&fout
, unsigned int lines
,/*{{{*/
178 Hashes
*hash
, char *buffer
) const {
179 while (0 < lines
--) {
181 fin
.ReadLine(buffer
, BUF_SIZE
);
182 unsigned long long const towrite
= strlen(buffer
);
183 fout
.Write(buffer
, towrite
);
184 hash
->Add((unsigned char*)buffer
, towrite
);
185 } while (strlen(buffer
) == (BUF_SIZE
- 1) &&
186 buffer
[BUF_SIZE
- 2] != '\n');
190 void RredMethod::ignoreLineInFile(FileFd
&fin
, char *buffer
) const { /*{{{*/
191 fin
.ReadLine(buffer
, BUF_SIZE
);
192 while (strlen(buffer
) == (BUF_SIZE
- 1) &&
193 buffer
[BUF_SIZE
- 2] != '\n') {
194 fin
.ReadLine(buffer
, BUF_SIZE
);
199 RredMethod::State
RredMethod::patchFile(FileFd
&Patch
, FileFd
&From
, /*{{{*/
200 FileFd
&out_file
, Hashes
*hash
) const {
201 char buffer
[BUF_SIZE
];
203 /* we do a tail recursion to read the commands in the right order */
204 unsigned long line
= -1; // assign highest possible value
205 State
const result
= applyFile(Patch
, From
, out_file
, line
, buffer
, hash
);
207 /* read the rest from infile */
208 if (result
== ED_OK
) {
209 while (From
.ReadLine(buffer
, BUF_SIZE
) != NULL
) {
210 unsigned long long const towrite
= strlen(buffer
);
211 out_file
.Write(buffer
, towrite
);
212 hash
->Add((unsigned char*)buffer
, towrite
);
218 /* struct EdCommand {{{*/
219 #ifdef _POSIX_MAPPED_FILES
228 #define IOV_COUNT 1024 /* Don't really want IOV_MAX since it can be arbitrarily large */
229 static ssize_t
retry_writev(int fd
, const struct iovec
*iov
, int iovcnt
) {
234 Res
= writev(fd
, iov
+ i
, iovcnt
);
235 if (Res
< 0 && errno
== EINTR
)
238 return _error
->Errno("writev",_("Write error"));
241 } while (Res
> 0 && iovcnt
> 0);
246 RredMethod::State
RredMethod::patchMMap(FileFd
&Patch
, FileFd
&From
, /*{{{*/
247 FileFd
&out_file
, Hashes
*hash
) const {
248 #ifdef _POSIX_MAPPED_FILES
249 MMap
ed_cmds(Patch
, MMap::ReadOnly
);
250 MMap
in_file(From
, MMap::ReadOnly
);
252 unsigned long long const ed_size
= ed_cmds
.Size();
253 unsigned long long const in_size
= in_file
.Size();
254 if (ed_size
== 0 || in_size
== 0)
257 EdCommand
* commands
= 0;
258 size_t command_count
= 0;
259 size_t command_alloc
= 0;
261 const char* begin
= (char*) ed_cmds
.Data();
262 const char* end
= begin
;
263 const char* ed_end
= (char*) ed_cmds
.Data() + ed_size
;
265 const char* input
= (char*) in_file
.Data();
266 const char* input_end
= (char*) in_file
.Data() + in_size
;
270 /* 1. Parse entire script. It is executed in reverse order, so we cather it
271 * in the `commands' buffer first
279 while(begin
!= ed_end
&& *begin
== '\n')
281 while(end
!= ed_end
&& *end
!= '\n')
283 if(end
== ed_end
&& begin
== end
)
286 /* Determine command range */
287 const char* tmp
= begin
;
290 /* atoll is safe despite lacking NUL-termination; we know there's an
291 * alphabetic character at end[-1]
294 cmd
.first_line
= atol(begin
);
295 cmd
.last_line
= cmd
.first_line
;
299 cmd
.first_line
= atol(begin
);
300 cmd
.last_line
= atol(tmp
+ 1);
306 // which command to execute on this line(s)?
310 std::clog
<< "Change from line " << cmd
.first_line
<< " to " << cmd
.last_line
<< std::endl
;
314 std::clog
<< "Insert after line " << cmd
.first_line
<< std::endl
;
318 std::clog
<< "Delete from line " << cmd
.first_line
<< " to " << cmd
.last_line
<< std::endl
;
321 _error
->Error("rred: Unknown ed command '%c'. Abort.", end
[-1]);
327 /* Determine the size of the inserted text, so we don't have to scan this
334 if(cmd
.type
== MODE_ADDED
|| cmd
.type
== MODE_CHANGED
) {
335 cmd
.data_start
= begin
- (char*) ed_cmds
.Data();
336 while(end
!= ed_end
) {
338 if(end
[-1] == '.' && end
[-2] == '\n')
344 cmd
.data_end
= end
- (char*) ed_cmds
.Data() - 1;
348 if(command_count
== command_alloc
) {
349 command_alloc
= (command_alloc
+ 64) * 3 / 2;
350 EdCommand
* newCommands
= (EdCommand
*) realloc(commands
, command_alloc
* sizeof(EdCommand
));
351 if (newCommands
== NULL
) {
355 commands
= newCommands
;
357 commands
[command_count
++] = cmd
;
360 struct iovec
* iov
= new struct iovec
[IOV_COUNT
];
363 size_t amount
, remaining
;
367 /* 2. Execute script. We gather writes in a `struct iov' array, and flush
368 * using writev to minimize the number of system calls. Data is read
369 * directly from the memory mappings of the input file and the script.
372 for(i
= command_count
; i
-- > 0; ) {
374 if(cmd
->type
== MODE_ADDED
)
375 amount
= cmd
->first_line
+ 1;
377 amount
= cmd
->first_line
;
381 while(line
!= amount
) {
382 input
= (const char*) memchr(input
, '\n', input_end
- input
);
389 iov
[iov_size
].iov_base
= (void*) begin
;
390 iov
[iov_size
].iov_len
= input
- begin
;
391 hash
->Add((const unsigned char*) begin
, input
- begin
);
393 if(++iov_size
== IOV_COUNT
) {
394 retry_writev(out_file
.Fd(), iov
, IOV_COUNT
);
399 if(cmd
->type
== MODE_DELETED
|| cmd
->type
== MODE_CHANGED
) {
400 remaining
= (cmd
->last_line
- cmd
->first_line
) + 1;
403 input
= (const char*) memchr(input
, '\n', input_end
- input
);
411 if(cmd
->type
== MODE_CHANGED
|| cmd
->type
== MODE_ADDED
) {
412 if(cmd
->data_end
!= cmd
->data_start
) {
413 iov
[iov_size
].iov_base
= (void*) ((char*)ed_cmds
.Data() + cmd
->data_start
);
414 iov
[iov_size
].iov_len
= cmd
->data_end
- cmd
->data_start
;
415 hash
->Add((const unsigned char*) ((char*)ed_cmds
.Data() + cmd
->data_start
),
416 iov
[iov_size
].iov_len
);
418 if(++iov_size
== IOV_COUNT
) {
419 retry_writev(out_file
.Fd(), iov
, IOV_COUNT
);
426 if(input
!= input_end
) {
427 iov
[iov_size
].iov_base
= (void*) input
;
428 iov
[iov_size
].iov_len
= input_end
- input
;
429 hash
->Add((const unsigned char*) input
, input_end
- input
);
434 retry_writev(out_file
.Fd(), iov
, iov_size
);
438 for(i
= 0; i
< iov_size
; i
+= IOV_COUNT
) {
439 if(iov_size
- i
< IOV_COUNT
)
440 retry_writev(out_file
.Fd(), iov
+ i
, iov_size
- i
);
442 retry_writev(out_file
.Fd(), iov
+ i
, IOV_COUNT
);
454 bool RredMethod::Fetch(FetchItem
*Itm
) /*{{{*/
456 Debug
= _config
->FindB("Debug::pkgAcquire::RRed", false);
458 std::string Path
= Get
.Host
+ Get
.Path
; // To account for relative paths
461 Res
.Filename
= Itm
->DestFile
;
462 if (Itm
->Uri
.empty() == true) {
463 Path
= Itm
->DestFile
;
464 Itm
->DestFile
.append(".result");
469 std::clog
<< "Patching " << Path
<< " with " << Path
470 << ".ed and putting result into " << Itm
->DestFile
<< std::endl
;
471 // Open the source and destination files (the d'tor of FileFd will do
472 // the cleanup/closing of the fds)
473 FileFd
From(Path
,FileFd::ReadOnly
);
474 FileFd
Patch(Path
+".ed",FileFd::ReadOnly
, FileFd::Gzip
);
475 FileFd
To(Itm
->DestFile
,FileFd::WriteAtomic
);
477 if (_error
->PendingError() == true)
481 // now do the actual patching
482 State
const result
= patchMMap(Patch
, From
, To
, &Hash
);
483 if (result
== MMAP_FAILED
) {
484 // retry with patchFile
487 To
.Open(Itm
->DestFile
,FileFd::WriteAtomic
);
488 if (_error
->PendingError() == true)
490 if (patchFile(Patch
, From
, To
, &Hash
) != ED_OK
) {
491 return _error
->WarningE("rred", _("Could not patch %s with mmap and with file operation usage - the patch seems to be corrupt."), Path
.c_str());
492 } else if (Debug
== true) {
493 std::clog
<< "rred: finished file patching of " << Path
<< " after mmap failed." << std::endl
;
495 } else if (result
!= ED_OK
) {
496 return _error
->Errno("rred", _("Could not patch %s with mmap (but no mmap specific fail) - the patch seems to be corrupt."), Path
.c_str());
497 } else if (Debug
== true) {
498 std::clog
<< "rred: finished mmap patching of " << Path
<< std::endl
;
501 // write out the result
506 /* Transfer the modification times from the patch file
507 to be able to see in which state the file should be
508 and use the access time from the "old" file */
509 struct stat BufBase
, BufPatch
;
510 if (stat(Path
.c_str(),&BufBase
) != 0 ||
511 stat(std::string(Path
+".ed").c_str(),&BufPatch
) != 0)
512 return _error
->Errno("stat",_("Failed to stat"));
514 struct utimbuf TimeBuf
;
515 TimeBuf
.actime
= BufBase
.st_atime
;
516 TimeBuf
.modtime
= BufPatch
.st_mtime
;
517 if (utime(Itm
->DestFile
.c_str(),&TimeBuf
) != 0)
518 return _error
->Errno("utime",_("Failed to set modification time"));
520 if (stat(Itm
->DestFile
.c_str(),&BufBase
) != 0)
521 return _error
->Errno("stat",_("Failed to stat"));
524 Res
.LastModified
= BufBase
.st_mtime
;
525 Res
.Size
= BufBase
.st_size
;
526 Res
.TakeHashes(Hash
);
532 /** \brief Wrapper class for testing rred */ /*{{{*/
533 class TestRredMethod
: public RredMethod
{
535 /** \brief Run rred in debug test mode
537 * This method can be used to run the rred method outside
538 * of the "normal" acquire environment for easier testing.
540 * \param base basename of all files involved in this rred test
542 bool Run(char const *base
) {
543 _config
->CndSet("Debug::pkgAcquire::RRed", "true");
544 FetchItem
*test
= new FetchItem
;
545 test
->DestFile
= base
;
550 /** \brief Starter for the rred method (or its test method) {{{
552 * Used without parameters is the normal behavior for methods for
553 * the APT acquire system. While this works great for the acquire system
554 * it is very hard to test the method and therefore the method also
555 * accepts one parameter which will switch it directly to debug test mode:
556 * The test mode expects that if "Testfile" is given as parameter
557 * the file "Testfile" should be ed-style patched with "Testfile.ed"
558 * and will write the result to "Testfile.result".
560 int main(int argc
, char *argv
[]) {
566 bool result
= Mth
.Run(argv
[1]);
567 _error
->DumpErrors();