rred.cc 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  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. #include <sys/stat.h>
  7. #include <unistd.h>
  8. #include <utime.h>
  9. #include <stdio.h>
  10. #include <errno.h>
  11. #include <apti18n.h>
  12. /* this method implements a patch functionality similar to "patch --ed" that is
  13. * used by the "tiffany" incremental packages download stuff. it differs from
  14. * "ed" insofar that it is way more restricted (and therefore secure). in the
  15. * moment only the "c", "a" and "d" commands of ed are implemented (diff
  16. * doesn't output any other). additionally the records must be reverse sorted
  17. * by line number and may not overlap (diff *seems* to produce this kind of
  18. * output).
  19. * */
  20. const char *Prog;
  21. class RredMethod : public pkgAcqMethod
  22. {
  23. bool Debug;
  24. // the size of this doesn't really matter (except for performance)
  25. const static int BUF_SIZE = 1024;
  26. // the ed commands
  27. enum Mode {MODE_CHANGED, MODE_DELETED, MODE_ADDED};
  28. // return values
  29. enum State {ED_OK, ED_ORDERING, ED_PARSER, ED_FAILURE};
  30. // this applies a single hunk, it uses a tail recursion to
  31. // reverse the hunks in the file
  32. int ed_rec(FILE *ed_cmds, FILE *in_file, FILE *out_file, int line,
  33. char *buffer, unsigned int bufsize, Hashes *hash);
  34. // apply a patch file
  35. int ed_file(FILE *ed_cmds, FILE *in_file, FILE *out_file, Hashes *hash);
  36. protected:
  37. // the methods main method
  38. virtual bool Fetch(FetchItem *Itm);
  39. public:
  40. RredMethod() : pkgAcqMethod("1.1",SingleInstance | SendConfig) {};
  41. };
  42. int RredMethod::ed_rec(FILE *ed_cmds, FILE *in_file, FILE *out_file, int line,
  43. char *buffer, unsigned int bufsize, Hashes *hash) {
  44. int pos;
  45. int startline;
  46. int stopline;
  47. int mode;
  48. int written;
  49. char *idx;
  50. /* get the current command and parse it*/
  51. if (fgets(buffer, bufsize, ed_cmds) == NULL) {
  52. return line;
  53. }
  54. startline = strtol(buffer, &idx, 10);
  55. if (startline < line) {
  56. return ED_ORDERING;
  57. }
  58. if (*idx == ',') {
  59. idx++;
  60. stopline = strtol(idx, &idx, 10);
  61. }
  62. else {
  63. stopline = startline;
  64. }
  65. if (*idx == 'c') {
  66. mode = MODE_CHANGED;
  67. if (Debug == true) {
  68. std::clog << "changing from line " << startline
  69. << " to " << stopline << std::endl;
  70. }
  71. }
  72. else if (*idx == 'a') {
  73. mode = MODE_ADDED;
  74. if (Debug == true) {
  75. std::clog << "adding after line " << startline << std::endl;
  76. }
  77. }
  78. else if (*idx == 'd') {
  79. mode = MODE_DELETED;
  80. if (Debug == true) {
  81. std::clog << "deleting from line " << startline
  82. << " to " << stopline << std::endl;
  83. }
  84. }
  85. else {
  86. return ED_PARSER;
  87. }
  88. /* get the current position */
  89. pos = ftell(ed_cmds);
  90. /* if this is add or change then go to the next full stop */
  91. if ((mode == MODE_CHANGED) || (mode == MODE_ADDED)) {
  92. do {
  93. fgets(buffer, bufsize, ed_cmds);
  94. while ((strlen(buffer) == (bufsize - 1))
  95. && (buffer[bufsize - 2] != '\n')) {
  96. fgets(buffer, bufsize, ed_cmds);
  97. buffer[0] = ' ';
  98. }
  99. } while (strncmp(buffer, ".", 1) != 0);
  100. }
  101. /* do the recursive call */
  102. line = ed_rec(ed_cmds, in_file, out_file, line, buffer, bufsize,
  103. hash);
  104. /* pass on errors */
  105. if (line < 0) {
  106. return line;
  107. }
  108. /* apply our hunk */
  109. fseek(ed_cmds, pos, SEEK_SET);
  110. /* first wind to the current position */
  111. if (mode != MODE_ADDED) {
  112. startline -= 1;
  113. }
  114. while (line < startline) {
  115. fgets(buffer, bufsize, in_file);
  116. written = fwrite(buffer, 1, strlen(buffer), out_file);
  117. hash->Add((unsigned char*)buffer, written);
  118. while ((strlen(buffer) == (bufsize - 1))
  119. && (buffer[bufsize - 2] != '\n')) {
  120. fgets(buffer, bufsize, in_file);
  121. written = fwrite(buffer, 1, strlen(buffer), out_file);
  122. hash->Add((unsigned char*)buffer, written);
  123. }
  124. line++;
  125. }
  126. /* include from ed script */
  127. if ((mode == MODE_ADDED) || (mode == MODE_CHANGED)) {
  128. do {
  129. fgets(buffer, bufsize, ed_cmds);
  130. if (strncmp(buffer, ".", 1) != 0) {
  131. written = fwrite(buffer, 1, strlen(buffer), out_file);
  132. hash->Add((unsigned char*)buffer, written);
  133. while ((strlen(buffer) == (bufsize - 1))
  134. && (buffer[bufsize - 2] != '\n')) {
  135. fgets(buffer, bufsize, ed_cmds);
  136. written = fwrite(buffer, 1, strlen(buffer), out_file);
  137. hash->Add((unsigned char*)buffer, written);
  138. }
  139. }
  140. else {
  141. break;
  142. }
  143. } while (1);
  144. }
  145. /* ignore the corresponding number of lines from input */
  146. if ((mode == MODE_DELETED) || (mode == MODE_CHANGED)) {
  147. while (line < stopline) {
  148. fgets(buffer, bufsize, in_file);
  149. while ((strlen(buffer) == (bufsize - 1))
  150. && (buffer[bufsize - 2] != '\n')) {
  151. fgets(buffer, bufsize, in_file);
  152. }
  153. line++;
  154. }
  155. }
  156. return line;
  157. }
  158. int RredMethod::ed_file(FILE *ed_cmds, FILE *in_file, FILE *out_file,
  159. Hashes *hash) {
  160. char buffer[BUF_SIZE];
  161. int result;
  162. int written;
  163. /* we do a tail recursion to read the commands in the right order */
  164. result = ed_rec(ed_cmds, in_file, out_file, 0, buffer, BUF_SIZE,
  165. hash);
  166. /* read the rest from infile */
  167. if (result >= 0) {
  168. while (fgets(buffer, BUF_SIZE, in_file) != NULL) {
  169. written = fwrite(buffer, 1, strlen(buffer), out_file);
  170. hash->Add((unsigned char*)buffer, written);
  171. }
  172. }
  173. else {
  174. return ED_FAILURE;
  175. }
  176. return ED_OK;
  177. }
  178. bool RredMethod::Fetch(FetchItem *Itm)
  179. {
  180. Debug = _config->FindB("Debug::pkgAcquire::RRed", false);
  181. URI Get = Itm->Uri;
  182. string Path = Get.Host + Get.Path; // To account for relative paths
  183. FetchResult Res;
  184. Res.Filename = Itm->DestFile;
  185. if (Itm->Uri.empty() == true) {
  186. Path = Itm->DestFile;
  187. Itm->DestFile.append(".result");
  188. } else
  189. URIStart(Res);
  190. if (Debug == true)
  191. std::clog << "Patching " << Path << " with " << Path
  192. << ".ed and putting result into " << Itm->DestFile << std::endl;
  193. // Open the source and destination files (the d'tor of FileFd will do
  194. // the cleanup/closing of the fds)
  195. FileFd From(Path,FileFd::ReadOnly);
  196. FileFd Patch(Path+".ed",FileFd::ReadOnly);
  197. FileFd To(Itm->DestFile,FileFd::WriteEmpty);
  198. To.EraseOnFailure();
  199. if (_error->PendingError() == true)
  200. return false;
  201. Hashes Hash;
  202. FILE* fFrom = fdopen(From.Fd(), "r");
  203. FILE* fPatch = fdopen(Patch.Fd(), "r");
  204. FILE* fTo = fdopen(To.Fd(), "w");
  205. // now do the actual patching
  206. if (ed_file(fPatch, fFrom, fTo, &Hash) != ED_OK) {
  207. _error->Errno("rred", _("Could not patch file"));
  208. return false;
  209. }
  210. // write out the result
  211. fflush(fFrom);
  212. fflush(fPatch);
  213. fflush(fTo);
  214. From.Close();
  215. Patch.Close();
  216. To.Close();
  217. // Transfer the modification times
  218. struct stat Buf;
  219. if (stat(Path.c_str(),&Buf) != 0)
  220. return _error->Errno("stat",_("Failed to stat"));
  221. struct utimbuf TimeBuf;
  222. TimeBuf.actime = Buf.st_atime;
  223. TimeBuf.modtime = Buf.st_mtime;
  224. if (utime(Itm->DestFile.c_str(),&TimeBuf) != 0)
  225. return _error->Errno("utime",_("Failed to set modification time"));
  226. if (stat(Itm->DestFile.c_str(),&Buf) != 0)
  227. return _error->Errno("stat",_("Failed to stat"));
  228. // return done
  229. if (Itm->Uri.empty() == true) {
  230. Res.LastModified = Buf.st_mtime;
  231. Res.Size = Buf.st_size;
  232. Res.TakeHashes(Hash);
  233. URIDone(Res);
  234. }
  235. return true;
  236. }
  237. /**
  238. * \brief Wrapper class for testing rred
  239. */
  240. class TestRredMethod : public RredMethod {
  241. public:
  242. /** \brief Run rred in debug test mode
  243. *
  244. * This method can be used to run the rred method outside
  245. * of the "normal" acquire environment for easier testing.
  246. *
  247. * \param base basename of all files involved in this rred test
  248. */
  249. bool Run(char const *base) {
  250. _config->CndSet("Debug::pkgAcquire::RRed", "true");
  251. FetchItem *test = new FetchItem;
  252. test->DestFile = base;
  253. return Fetch(test);
  254. }
  255. };
  256. /**
  257. * \brief Starter for the rred method (or its test method)
  258. *
  259. * Used without parameters is the normal behavior for methods for
  260. * the APT acquire system. While this works great for the acquire system
  261. * it is very hard to test the method and therefore the method also
  262. * accepts one parameter which will switch it directly to debug test mode:
  263. * The test mode expects that if "Testfile" is given as parameter
  264. * the file "Testfile" should be ed-style patched with "Testfile.ed"
  265. * and will write the result to "Testfile.result".
  266. */
  267. int main(int argc, char *argv[]) {
  268. Prog = strrchr(argv[0],'/');
  269. Prog++;
  270. if (argc == 0) {
  271. RredMethod Mth;
  272. return Mth.Run();
  273. } else {
  274. TestRredMethod Mth;
  275. bool result = Mth.Run(argv[1]);
  276. _error->DumpErrors();
  277. return result;
  278. }
  279. }