]>
Commit | Line | Data |
---|---|---|
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 | // the methods main method | |
41 | virtual bool Fetch(FetchItem *Itm); | |
42 | ||
43 | public: | |
44 | ||
45 | RredMethod() : pkgAcqMethod("1.1",SingleInstance | SendConfig) {}; | |
46 | }; | |
47 | ||
48 | int RredMethod::ed_rec(FILE *ed_cmds, FILE *in_file, FILE *out_file, int line, | |
49 | char *buffer, unsigned int bufsize, Hashes *hash) { | |
50 | int pos; | |
51 | int startline; | |
52 | int stopline; | |
53 | int mode; | |
54 | int written; | |
55 | char *idx; | |
56 | ||
57 | /* get the current command and parse it*/ | |
58 | if (fgets(buffer, bufsize, ed_cmds) == NULL) { | |
59 | return line; | |
60 | } | |
61 | startline = strtol(buffer, &idx, 10); | |
62 | if (startline < line) { | |
63 | return ED_ORDERING; | |
64 | } | |
65 | if (*idx == ',') { | |
66 | idx++; | |
67 | stopline = strtol(idx, &idx, 10); | |
68 | } | |
69 | else { | |
70 | stopline = startline; | |
71 | } | |
72 | if (*idx == 'c') { | |
73 | mode = MODE_CHANGED; | |
74 | if (Debug == true) { | |
75 | std::clog << "changing from line " << startline | |
76 | << " to " << stopline << std::endl; | |
77 | } | |
78 | } | |
79 | else if (*idx == 'a') { | |
80 | mode = MODE_ADDED; | |
81 | if (Debug == true) { | |
82 | std::clog << "adding after line " << startline << std::endl; | |
83 | } | |
84 | } | |
85 | else if (*idx == 'd') { | |
86 | mode = MODE_DELETED; | |
87 | if (Debug == true) { | |
88 | std::clog << "deleting from line " << startline | |
89 | << " to " << stopline << std::endl; | |
90 | } | |
91 | } | |
92 | else { | |
93 | return ED_PARSER; | |
94 | } | |
95 | /* get the current position */ | |
96 | pos = ftell(ed_cmds); | |
97 | /* if this is add or change then go to the next full stop */ | |
98 | if ((mode == MODE_CHANGED) || (mode == MODE_ADDED)) { | |
99 | do { | |
100 | fgets(buffer, bufsize, ed_cmds); | |
101 | while ((strlen(buffer) == (bufsize - 1)) | |
102 | && (buffer[bufsize - 2] != '\n')) { | |
103 | fgets(buffer, bufsize, ed_cmds); | |
104 | buffer[0] = ' '; | |
105 | } | |
106 | } while (strncmp(buffer, ".", 1) != 0); | |
107 | } | |
108 | /* do the recursive call */ | |
109 | line = ed_rec(ed_cmds, in_file, out_file, line, buffer, bufsize, | |
110 | hash); | |
111 | /* pass on errors */ | |
112 | if (line < 0) { | |
113 | return line; | |
114 | } | |
115 | /* apply our hunk */ | |
116 | fseek(ed_cmds, pos, SEEK_SET); | |
117 | /* first wind to the current position */ | |
118 | if (mode != MODE_ADDED) { | |
119 | startline -= 1; | |
120 | } | |
121 | while (line < startline) { | |
122 | fgets(buffer, bufsize, in_file); | |
123 | written = fwrite(buffer, 1, strlen(buffer), out_file); | |
124 | hash->Add((unsigned char*)buffer, written); | |
125 | while ((strlen(buffer) == (bufsize - 1)) | |
126 | && (buffer[bufsize - 2] != '\n')) { | |
127 | fgets(buffer, bufsize, in_file); | |
128 | written = fwrite(buffer, 1, strlen(buffer), out_file); | |
129 | hash->Add((unsigned char*)buffer, written); | |
130 | } | |
131 | line++; | |
132 | } | |
133 | /* include from ed script */ | |
134 | if ((mode == MODE_ADDED) || (mode == MODE_CHANGED)) { | |
135 | do { | |
136 | fgets(buffer, bufsize, ed_cmds); | |
137 | if (strncmp(buffer, ".", 1) != 0) { | |
138 | written = fwrite(buffer, 1, strlen(buffer), out_file); | |
139 | hash->Add((unsigned char*)buffer, written); | |
140 | while ((strlen(buffer) == (bufsize - 1)) | |
141 | && (buffer[bufsize - 2] != '\n')) { | |
142 | fgets(buffer, bufsize, ed_cmds); | |
143 | written = fwrite(buffer, 1, strlen(buffer), out_file); | |
144 | hash->Add((unsigned char*)buffer, written); | |
145 | } | |
146 | } | |
147 | else { | |
148 | break; | |
149 | } | |
150 | } while (1); | |
151 | } | |
152 | /* ignore the corresponding number of lines from input */ | |
153 | if ((mode == MODE_DELETED) || (mode == MODE_CHANGED)) { | |
154 | while (line < stopline) { | |
155 | fgets(buffer, bufsize, in_file); | |
156 | while ((strlen(buffer) == (bufsize - 1)) | |
157 | && (buffer[bufsize - 2] != '\n')) { | |
158 | fgets(buffer, bufsize, in_file); | |
159 | } | |
160 | line++; | |
161 | } | |
162 | } | |
163 | return line; | |
164 | } | |
165 | ||
166 | int RredMethod::ed_file(FILE *ed_cmds, FILE *in_file, FILE *out_file, | |
167 | Hashes *hash) { | |
168 | char buffer[BUF_SIZE]; | |
169 | int result; | |
170 | int written; | |
171 | ||
172 | /* we do a tail recursion to read the commands in the right order */ | |
173 | result = ed_rec(ed_cmds, in_file, out_file, 0, buffer, BUF_SIZE, | |
174 | hash); | |
175 | ||
176 | /* read the rest from infile */ | |
177 | if (result > 0) { | |
178 | while (fgets(buffer, BUF_SIZE, in_file) != NULL) { | |
179 | written = fwrite(buffer, 1, strlen(buffer), out_file); | |
180 | hash->Add((unsigned char*)buffer, written); | |
181 | } | |
182 | } | |
183 | else { | |
184 | return ED_FAILURE; | |
185 | } | |
186 | return ED_OK; | |
187 | } | |
188 | ||
189 | ||
190 | bool RredMethod::Fetch(FetchItem *Itm) | |
191 | { | |
192 | Debug = _config->FindB("Debug::pkgAcquire::RRed",false); | |
193 | URI Get = Itm->Uri; | |
194 | string Path = Get.Host + Get.Path; // To account for relative paths | |
195 | // Path contains the filename to patch | |
196 | FetchResult Res; | |
197 | Res.Filename = Itm->DestFile; | |
198 | URIStart(Res); | |
199 | // Res.Filename the destination filename | |
200 | ||
201 | if (Debug == true) | |
202 | std::clog << "Patching " << Path << " with " << Path | |
203 | << ".ed and putting result into " << Itm->DestFile << std::endl; | |
204 | // Open the source and destination files (the d'tor of FileFd will do | |
205 | // the cleanup/closing of the fds) | |
206 | FileFd From(Path,FileFd::ReadOnly); | |
207 | FileFd Patch(Path+".ed",FileFd::ReadOnly); | |
208 | FileFd To(Itm->DestFile,FileFd::WriteEmpty); | |
209 | To.EraseOnFailure(); | |
210 | if (_error->PendingError() == true) | |
211 | return false; | |
212 | ||
213 | Hashes Hash; | |
214 | FILE* fFrom = fdopen(From.Fd(), "r"); | |
215 | FILE* fPatch = fdopen(Patch.Fd(), "r"); | |
216 | FILE* fTo = fdopen(To.Fd(), "w"); | |
217 | // now do the actual patching | |
218 | if (ed_file(fPatch, fFrom, fTo, &Hash) != ED_OK) { | |
219 | _error->Errno("rred", _("Could not patch file")); | |
220 | return false; | |
221 | } | |
222 | ||
223 | // write out the result | |
224 | fflush(fFrom); | |
225 | fflush(fPatch); | |
226 | fflush(fTo); | |
227 | From.Close(); | |
228 | Patch.Close(); | |
229 | To.Close(); | |
230 | ||
231 | // Transfer the modification times | |
232 | struct stat Buf; | |
233 | if (stat(Path.c_str(),&Buf) != 0) | |
234 | return _error->Errno("stat",_("Failed to stat")); | |
235 | ||
236 | struct utimbuf TimeBuf; | |
237 | TimeBuf.actime = Buf.st_atime; | |
238 | TimeBuf.modtime = Buf.st_mtime; | |
239 | if (utime(Itm->DestFile.c_str(),&TimeBuf) != 0) | |
240 | return _error->Errno("utime",_("Failed to set modification time")); | |
241 | ||
242 | if (stat(Itm->DestFile.c_str(),&Buf) != 0) | |
243 | return _error->Errno("stat",_("Failed to stat")); | |
244 | ||
245 | // return done | |
246 | Res.LastModified = Buf.st_mtime; | |
247 | Res.Size = Buf.st_size; | |
248 | Res.TakeHashes(Hash); | |
249 | URIDone(Res); | |
250 | ||
251 | return true; | |
252 | } | |
253 | ||
254 | int main(int argc, char *argv[]) | |
255 | { | |
256 | RredMethod Mth; | |
257 | ||
258 | Prog = strrchr(argv[0],'/'); | |
259 | Prog++; | |
260 | ||
261 | return Mth.Run(); | |
262 | } |