]> git.saurik.com Git - apt.git/blob - methods/rred.cc
add a debug test mode to the rred method for easier testing
[apt.git] / 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>
6
7 #include <sys/stat.h>
8 #include <unistd.h>
9 #include <utime.h>
10 #include <stdio.h>
11 #include <errno.h>
12 #include <apti18n.h>
13
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
20 * output).
21 * */
22
23 const char *Prog;
24
25 class RredMethod : public pkgAcqMethod
26 {
27 bool Debug;
28 // the size of this doesn't really matter (except for performance)
29 const static int BUF_SIZE = 1024;
30 // the ed commands
31 enum Mode {MODE_CHANGED, MODE_DELETED, MODE_ADDED};
32 // return values
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);
38 // apply a patch file
39 int ed_file(FILE *ed_cmds, FILE *in_file, FILE *out_file, Hashes *hash);
40
41 protected:
42 // the methods main method
43 virtual bool Fetch(FetchItem *Itm);
44
45 public:
46
47 RredMethod() : pkgAcqMethod("1.1",SingleInstance | SendConfig) {};
48 };
49
50 int RredMethod::ed_rec(FILE *ed_cmds, FILE *in_file, FILE *out_file, int line,
51 char *buffer, unsigned int bufsize, Hashes *hash) {
52 int pos;
53 int startline;
54 int stopline;
55 int mode;
56 int written;
57 char *idx;
58
59 /* get the current command and parse it*/
60 if (fgets(buffer, bufsize, ed_cmds) == NULL) {
61 return line;
62 }
63 startline = strtol(buffer, &idx, 10);
64 if (startline < line) {
65 return ED_ORDERING;
66 }
67 if (*idx == ',') {
68 idx++;
69 stopline = strtol(idx, &idx, 10);
70 }
71 else {
72 stopline = startline;
73 }
74 if (*idx == 'c') {
75 mode = MODE_CHANGED;
76 if (Debug == true) {
77 std::clog << "changing from line " << startline
78 << " to " << stopline << std::endl;
79 }
80 }
81 else if (*idx == 'a') {
82 mode = MODE_ADDED;
83 if (Debug == true) {
84 std::clog << "adding after line " << startline << std::endl;
85 }
86 }
87 else if (*idx == 'd') {
88 mode = MODE_DELETED;
89 if (Debug == true) {
90 std::clog << "deleting from line " << startline
91 << " to " << stopline << std::endl;
92 }
93 }
94 else {
95 return ED_PARSER;
96 }
97 /* get the current position */
98 pos = ftell(ed_cmds);
99 /* if this is add or change then go to the next full stop */
100 if ((mode == MODE_CHANGED) || (mode == MODE_ADDED)) {
101 do {
102 fgets(buffer, bufsize, ed_cmds);
103 while ((strlen(buffer) == (bufsize - 1))
104 && (buffer[bufsize - 2] != '\n')) {
105 fgets(buffer, bufsize, ed_cmds);
106 buffer[0] = ' ';
107 }
108 } while (strncmp(buffer, ".", 1) != 0);
109 }
110 /* do the recursive call */
111 line = ed_rec(ed_cmds, in_file, out_file, line, buffer, bufsize,
112 hash);
113 /* pass on errors */
114 if (line < 0) {
115 return line;
116 }
117 /* apply our hunk */
118 fseek(ed_cmds, pos, SEEK_SET);
119 /* first wind to the current position */
120 if (mode != MODE_ADDED) {
121 startline -= 1;
122 }
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);
132 }
133 line++;
134 }
135 /* include from ed script */
136 if ((mode == MODE_ADDED) || (mode == MODE_CHANGED)) {
137 do {
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);
147 }
148 }
149 else {
150 break;
151 }
152 } while (1);
153 }
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);
161 }
162 line++;
163 }
164 }
165 return line;
166 }
167
168 int RredMethod::ed_file(FILE *ed_cmds, FILE *in_file, FILE *out_file,
169 Hashes *hash) {
170 char buffer[BUF_SIZE];
171 int result;
172 int written;
173
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,
176 hash);
177
178 /* read the rest from infile */
179 if (result >= 0) {
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);
183 }
184 }
185 else {
186 return ED_FAILURE;
187 }
188 return ED_OK;
189 }
190
191 bool RredMethod::Fetch(FetchItem *Itm)
192 {
193 Debug = _config->FindB("Debug::pkgAcquire::RRed", false);
194 URI Get = Itm->Uri;
195 string Path = Get.Host + Get.Path; // To account for relative paths
196
197 FetchResult Res;
198 Res.Filename = Itm->DestFile;
199 if (Itm->Uri.empty() == true) {
200 Path = Itm->DestFile;
201 Itm->DestFile.append(".result");
202 } else
203 URIStart(Res);
204
205 if (Debug == true)
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);
213 To.EraseOnFailure();
214 if (_error->PendingError() == true)
215 return false;
216
217 Hashes Hash;
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"));
224 return false;
225 }
226
227 // write out the result
228 fflush(fFrom);
229 fflush(fPatch);
230 fflush(fTo);
231 From.Close();
232 Patch.Close();
233 To.Close();
234
235 // Transfer the modification times
236 struct stat Buf;
237 if (stat(Path.c_str(),&Buf) != 0)
238 return _error->Errno("stat",_("Failed to stat"));
239
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"));
245
246 if (stat(Itm->DestFile.c_str(),&Buf) != 0)
247 return _error->Errno("stat",_("Failed to stat"));
248
249 // return done
250 if (Itm->Uri.empty() == true) {
251 Res.LastModified = Buf.st_mtime;
252 Res.Size = Buf.st_size;
253 Res.TakeHashes(Hash);
254 URIDone(Res);
255 }
256
257 return true;
258 }
259
260 /**
261 * \brief Wrapper class for testing rred
262 */
263 class TestRredMethod : public RredMethod {
264 public:
265 /** \brief Run rred in debug test mode
266 *
267 * This method can be used to run the rred method outside
268 * of the "normal" acquire environment for easier testing.
269 *
270 * \param base basename of all files involved in this rred test
271 */
272 bool Run(char const *base) {
273 _config->CndSet("Debug::pkgAcquire::RRed", "true");
274 FetchItem *test = new FetchItem;
275 test->DestFile = base;
276 return Fetch(test);
277 }
278 };
279
280 /**
281 * \brief Starter for the rred method (or its test method)
282 *
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".
290 */
291 int main(int argc, char *argv[]) {
292 Prog = strrchr(argv[0],'/');
293 Prog++;
294
295 if (argc == 0) {
296 RredMethod Mth;
297 return Mth.Run();
298 } else {
299 TestRredMethod Mth;
300 bool result = Mth.Run(argv[1]);
301 _error->DumpErrors();
302 return result;
303 }
304 }