]>
git.saurik.com Git - apt.git/blob - methods/rred.cc
1 #include <apt-pkg/fileutl.h>
2 #include <apt-pkg/error.h>
3 #include <apt-pkg/acquire-method.h>
4 #include <apt-pkg/strutl.h>
5 #include <apt-pkg/hashes.h>
14 /* this method implements a patch functionality similar to "patch --ed" that is
15 * used by the "tiffany" incremental packages download stuff. it differs from
16 * "ed" insofar that it is way more restricted (and therefore secure). in the
17 * moment only the "c", "a" and "d" commands of ed are implemented (diff
18 * doesn't output any other). additionally the records must be reverse sorted
19 * by line number and may not overlap (diff *seems* to produce this kind of
25 class RredMethod
: public pkgAcqMethod
28 // the size of this doesn't really matter (except for performance)
29 const static int BUF_SIZE
= 1024;
31 enum Mode
{MODE_CHANGED
, MODE_DELETED
, MODE_ADDED
};
33 enum State
{ED_OK
, ED_ORDERING
, ED_PARSER
, ED_FAILURE
};
34 // this applies a single hunk, it uses a tail recursion to
35 // reverse the hunks in the file
36 int ed_rec(FILE *ed_cmds
, FILE *in_file
, FILE *out_file
, int line
,
37 char *buffer
, unsigned int bufsize
, Hashes
*hash
);
39 int ed_file(FILE *ed_cmds
, FILE *in_file
, FILE *out_file
, Hashes
*hash
);
42 // the methods main method
43 virtual bool Fetch(FetchItem
*Itm
);
47 RredMethod() : pkgAcqMethod("1.1",SingleInstance
| SendConfig
) {};
50 int RredMethod::ed_rec(FILE *ed_cmds
, FILE *in_file
, FILE *out_file
, int line
,
51 char *buffer
, unsigned int bufsize
, Hashes
*hash
) {
59 /* get the current command and parse it*/
60 if (fgets(buffer
, bufsize
, ed_cmds
) == NULL
) {
63 startline
= strtol(buffer
, &idx
, 10);
64 if (startline
< line
) {
69 stopline
= strtol(idx
, &idx
, 10);
77 std::clog
<< "changing from line " << startline
78 << " to " << stopline
<< std::endl
;
81 else if (*idx
== 'a') {
84 std::clog
<< "adding after line " << startline
<< std::endl
;
87 else if (*idx
== 'd') {
90 std::clog
<< "deleting from line " << startline
91 << " to " << stopline
<< std::endl
;
97 /* get the current position */
99 /* if this is add or change then go to the next full stop */
100 if ((mode
== MODE_CHANGED
) || (mode
== MODE_ADDED
)) {
102 fgets(buffer
, bufsize
, ed_cmds
);
103 while ((strlen(buffer
) == (bufsize
- 1))
104 && (buffer
[bufsize
- 2] != '\n')) {
105 fgets(buffer
, bufsize
, ed_cmds
);
108 } while (strncmp(buffer
, ".", 1) != 0);
110 /* do the recursive call */
111 line
= ed_rec(ed_cmds
, in_file
, out_file
, line
, buffer
, bufsize
,
118 fseek(ed_cmds
, pos
, SEEK_SET
);
119 /* first wind to the current position */
120 if (mode
!= MODE_ADDED
) {
123 while (line
< startline
) {
124 fgets(buffer
, bufsize
, in_file
);
125 written
= fwrite(buffer
, 1, strlen(buffer
), out_file
);
126 hash
->Add((unsigned char*)buffer
, written
);
127 while ((strlen(buffer
) == (bufsize
- 1))
128 && (buffer
[bufsize
- 2] != '\n')) {
129 fgets(buffer
, bufsize
, in_file
);
130 written
= fwrite(buffer
, 1, strlen(buffer
), out_file
);
131 hash
->Add((unsigned char*)buffer
, written
);
135 /* include from ed script */
136 if ((mode
== MODE_ADDED
) || (mode
== MODE_CHANGED
)) {
138 fgets(buffer
, bufsize
, ed_cmds
);
139 if (strncmp(buffer
, ".", 1) != 0) {
140 written
= fwrite(buffer
, 1, strlen(buffer
), out_file
);
141 hash
->Add((unsigned char*)buffer
, written
);
142 while ((strlen(buffer
) == (bufsize
- 1))
143 && (buffer
[bufsize
- 2] != '\n')) {
144 fgets(buffer
, bufsize
, ed_cmds
);
145 written
= fwrite(buffer
, 1, strlen(buffer
), out_file
);
146 hash
->Add((unsigned char*)buffer
, written
);
154 /* ignore the corresponding number of lines from input */
155 if ((mode
== MODE_DELETED
) || (mode
== MODE_CHANGED
)) {
156 while (line
< stopline
) {
157 fgets(buffer
, bufsize
, in_file
);
158 while ((strlen(buffer
) == (bufsize
- 1))
159 && (buffer
[bufsize
- 2] != '\n')) {
160 fgets(buffer
, bufsize
, in_file
);
168 int RredMethod::ed_file(FILE *ed_cmds
, FILE *in_file
, FILE *out_file
,
170 char buffer
[BUF_SIZE
];
174 /* we do a tail recursion to read the commands in the right order */
175 result
= ed_rec(ed_cmds
, in_file
, out_file
, 0, buffer
, BUF_SIZE
,
178 /* read the rest from infile */
180 while (fgets(buffer
, BUF_SIZE
, in_file
) != NULL
) {
181 written
= fwrite(buffer
, 1, strlen(buffer
), out_file
);
182 hash
->Add((unsigned char*)buffer
, written
);
191 bool RredMethod::Fetch(FetchItem
*Itm
)
193 Debug
= _config
->FindB("Debug::pkgAcquire::RRed", false);
195 string Path
= Get
.Host
+ Get
.Path
; // To account for relative paths
198 Res
.Filename
= Itm
->DestFile
;
199 if (Itm
->Uri
.empty() == true) {
200 Path
= Itm
->DestFile
;
201 Itm
->DestFile
.append(".result");
206 std::clog
<< "Patching " << Path
<< " with " << Path
207 << ".ed and putting result into " << Itm
->DestFile
<< std::endl
;
208 // Open the source and destination files (the d'tor of FileFd will do
209 // the cleanup/closing of the fds)
210 FileFd
From(Path
,FileFd::ReadOnly
);
211 FileFd
Patch(Path
+".ed",FileFd::ReadOnly
);
212 FileFd
To(Itm
->DestFile
,FileFd::WriteEmpty
);
214 if (_error
->PendingError() == true)
218 FILE* fFrom
= fdopen(From
.Fd(), "r");
219 FILE* fPatch
= fdopen(Patch
.Fd(), "r");
220 FILE* fTo
= fdopen(To
.Fd(), "w");
221 // now do the actual patching
222 if (ed_file(fPatch
, fFrom
, fTo
, &Hash
) != ED_OK
) {
223 _error
->Errno("rred", _("Could not patch file"));
227 // write out the result
235 // Transfer the modification times
237 if (stat(Path
.c_str(),&Buf
) != 0)
238 return _error
->Errno("stat",_("Failed to stat"));
240 struct utimbuf TimeBuf
;
241 TimeBuf
.actime
= Buf
.st_atime
;
242 TimeBuf
.modtime
= Buf
.st_mtime
;
243 if (utime(Itm
->DestFile
.c_str(),&TimeBuf
) != 0)
244 return _error
->Errno("utime",_("Failed to set modification time"));
246 if (stat(Itm
->DestFile
.c_str(),&Buf
) != 0)
247 return _error
->Errno("stat",_("Failed to stat"));
250 if (Itm
->Uri
.empty() == true) {
251 Res
.LastModified
= Buf
.st_mtime
;
252 Res
.Size
= Buf
.st_size
;
253 Res
.TakeHashes(Hash
);
261 * \brief Wrapper class for testing rred
263 class TestRredMethod
: public RredMethod
{
265 /** \brief Run rred in debug test mode
267 * This method can be used to run the rred method outside
268 * of the "normal" acquire environment for easier testing.
270 * \param base basename of all files involved in this rred test
272 bool Run(char const *base
) {
273 _config
->CndSet("Debug::pkgAcquire::RRed", "true");
274 FetchItem
*test
= new FetchItem
;
275 test
->DestFile
= base
;
281 * \brief Starter for the rred method (or its test method)
283 * Used without parameters is the normal behavior for methods for
284 * the APT acquire system. While this works great for the acquire system
285 * it is very hard to test the method and therefore the method also
286 * accepts one parameter which will switch it directly to debug test mode:
287 * The test mode expects that if "Testfile" is given as parameter
288 * the file "Testfile" should be ed-style patched with "Testfile.ed"
289 * and will write the result to "Testfile.result".
291 int main(int argc
, char *argv
[]) {
292 Prog
= strrchr(argv
[0],'/');
300 bool result
= Mth
.Run(argv
[1]);
301 _error
->DumpErrors();