]>
git.saurik.com Git - apt.git/blob - methods/rred.cc
9abb1b89c6699e026f7f0566e46b0541a67a22aa
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 copyLinesFromFileToFile(FILE *fin
, FILE *fout
, unsigned int lines
,
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 unsigned int data_length
= 0;
127 if (mode
== MODE_CHANGED
|| mode
== MODE_ADDED
) {
129 ignoreLineInFile(ed_cmds
, buffer
);
132 while (strncmp(buffer
, ".", 1) != 0);
133 data_length
--; // the dot should not be copied
136 // do the recursive call - the last command is the one we need to execute at first
137 const State child
= applyFile(ed_cmds
, in_file
, out_file
, line
, buffer
, hash
);
138 if (child
!= ED_OK
) {
142 // change and delete are working on "line" - add is done after "line"
143 if (mode
!= MODE_ADDED
)
146 // first wind to the current position and copy over all unchanged lines
147 if (line
< startline
) {
148 copyLinesFromFileToFile(in_file
, out_file
, (startline
- line
), hash
, buffer
);
152 if (mode
!= MODE_ADDED
)
155 // include data from ed script
156 if (mode
== MODE_CHANGED
|| mode
== MODE_ADDED
) {
157 fseek(ed_cmds
, pos
, SEEK_SET
);
158 copyLinesFromFileToFile(ed_cmds
, out_file
, data_length
, hash
, buffer
);
161 // ignore the corresponding number of lines from input
162 if (mode
== MODE_CHANGED
|| mode
== MODE_DELETED
) {
163 while (line
< stopline
) {
164 ignoreLineInFile(in_file
, buffer
);
171 void RredMethod::copyLinesFromFileToFile(FILE *fin
, FILE *fout
, unsigned int lines
,/*{{{*/
172 Hashes
*hash
, char *buffer
) const {
173 while (0 < lines
--) {
175 fgets(buffer
, BUF_SIZE
, fin
);
176 size_t const written
= fwrite(buffer
, 1, strlen(buffer
), fout
);
177 hash
->Add((unsigned char*)buffer
, written
);
178 } while (strlen(buffer
) == (BUF_SIZE
- 1) &&
179 buffer
[BUF_SIZE
- 2] != '\n');
183 void RredMethod::ignoreLineInFile(FILE *fin
, char *buffer
) const { /*{{{*/
184 fgets(buffer
, BUF_SIZE
, fin
);
185 while (strlen(buffer
) == (BUF_SIZE
- 1) &&
186 buffer
[BUF_SIZE
- 2] != '\n') {
187 fgets(buffer
, BUF_SIZE
, fin
);
192 RredMethod::State
RredMethod::patchFile(FILE *ed_cmds
, FILE *in_file
, FILE *out_file
, /*{{{*/
193 Hashes
*hash
) const {
194 char buffer
[BUF_SIZE
];
196 /* we do a tail recursion to read the commands in the right order */
197 unsigned long line
= -1; // assign highest possible value
198 State result
= applyFile(ed_cmds
, in_file
, out_file
, line
, buffer
, hash
);
200 /* read the rest from infile */
201 if (result
== ED_OK
) {
202 while (fgets(buffer
, BUF_SIZE
, in_file
) != NULL
) {
203 size_t const written
= fwrite(buffer
, 1, strlen(buffer
), out_file
);
204 hash
->Add((unsigned char*)buffer
, written
);
210 bool RredMethod::Fetch(FetchItem
*Itm
) /*{{{*/
212 Debug
= _config
->FindB("Debug::pkgAcquire::RRed", false);
214 string Path
= Get
.Host
+ Get
.Path
; // To account for relative paths
217 Res
.Filename
= Itm
->DestFile
;
218 if (Itm
->Uri
.empty() == true) {
219 Path
= Itm
->DestFile
;
220 Itm
->DestFile
.append(".result");
225 std::clog
<< "Patching " << Path
<< " with " << Path
226 << ".ed and putting result into " << Itm
->DestFile
<< std::endl
;
227 // Open the source and destination files (the d'tor of FileFd will do
228 // the cleanup/closing of the fds)
229 FileFd
From(Path
,FileFd::ReadOnly
);
230 FileFd
Patch(Path
+".ed",FileFd::ReadOnly
);
231 FileFd
To(Itm
->DestFile
,FileFd::WriteEmpty
);
233 if (_error
->PendingError() == true)
237 FILE* fFrom
= fdopen(From
.Fd(), "r");
238 FILE* fPatch
= fdopen(Patch
.Fd(), "r");
239 FILE* fTo
= fdopen(To
.Fd(), "w");
240 // now do the actual patching
241 if (patchFile(fPatch
, fFrom
, fTo
, &Hash
) != ED_OK
) {
242 _error
->Errno("rred", _("Could not patch file"));
246 // write out the result
254 // Transfer the modification times
256 if (stat(Path
.c_str(),&Buf
) != 0)
257 return _error
->Errno("stat",_("Failed to stat"));
259 struct utimbuf TimeBuf
;
260 TimeBuf
.actime
= Buf
.st_atime
;
261 TimeBuf
.modtime
= Buf
.st_mtime
;
262 if (utime(Itm
->DestFile
.c_str(),&TimeBuf
) != 0)
263 return _error
->Errno("utime",_("Failed to set modification time"));
265 if (stat(Itm
->DestFile
.c_str(),&Buf
) != 0)
266 return _error
->Errno("stat",_("Failed to stat"));
269 if (Itm
->Uri
.empty() == true) {
270 Res
.LastModified
= Buf
.st_mtime
;
271 Res
.Size
= Buf
.st_size
;
272 Res
.TakeHashes(Hash
);
279 /** \brief Wrapper class for testing rred */ /*{{{*/
280 class TestRredMethod
: public RredMethod
{
282 /** \brief Run rred in debug test mode
284 * This method can be used to run the rred method outside
285 * of the "normal" acquire environment for easier testing.
287 * \param base basename of all files involved in this rred test
289 bool Run(char const *base
) {
290 _config
->CndSet("Debug::pkgAcquire::RRed", "true");
291 FetchItem
*test
= new FetchItem
;
292 test
->DestFile
= base
;
297 /** \brief Starter for the rred method (or its test method) {{{
299 * Used without parameters is the normal behavior for methods for
300 * the APT acquire system. While this works great for the acquire system
301 * it is very hard to test the method and therefore the method also
302 * accepts one parameter which will switch it directly to debug test mode:
303 * The test mode expects that if "Testfile" is given as parameter
304 * the file "Testfile" should be ed-style patched with "Testfile.ed"
305 * and will write the result to "Testfile.result".
307 int main(int argc
, char *argv
[]) {
313 bool result
= Mth
.Run(argv
[1]);
314 _error
->DumpErrors();