rred.cc 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. // Includes /*{{{*/
  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>
  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. /** \brief RredMethod - ed-style incremential patch method {{{
  15. *
  16. * This method implements a patch functionality similar to "patch --ed" that is
  17. * used by the "tiffany" incremental packages download stuff. It differs from
  18. * "ed" insofar that it is way more restricted (and therefore secure).
  19. * The currently supported ed commands are "<em>c</em>hange", "<em>a</em>dd" and
  20. * "<em>d</em>elete" (diff doesn't output any other).
  21. * Additionally the records must be reverse sorted by line number and
  22. * may not overlap (diff *seems* to produce this kind of output).
  23. * */
  24. class RredMethod : public pkgAcqMethod {
  25. bool Debug;
  26. // the size of this doesn't really matter (except for performance)
  27. const static int BUF_SIZE = 1024;
  28. // the supported ed commands
  29. enum Mode {MODE_CHANGED='c', MODE_DELETED='d', MODE_ADDED='a'};
  30. // return values
  31. enum State {ED_OK=0, ED_ORDERING=1, ED_PARSER=2, ED_FAILURE=3};
  32. State applyFile(FILE *ed_cmds, FILE *in_file, FILE *out_file,
  33. unsigned long &line, char *buffer, Hashes *hash) const;
  34. void ignoreLineInFile(FILE *fin, char *buffer) const;
  35. void copyLinesFromFileToFile(FILE *fin, FILE *fout, unsigned int lines,
  36. Hashes *hash, char *buffer) const;
  37. State patchFile(FILE *ed_cmds, FILE *in_file, FILE *out_file, Hashes *hash) const;
  38. protected:
  39. // the methods main method
  40. virtual bool Fetch(FetchItem *Itm);
  41. public:
  42. RredMethod() : pkgAcqMethod("1.1",SingleInstance | SendConfig) {};
  43. };
  44. /*}}}*/
  45. /** \brief applyFile - in reverse order with a tail recursion {{{
  46. *
  47. * As it is expected that the commands are in reversed order in the patch file
  48. * we check in the first half if the command is valid, but doesn't execute it
  49. * and move a step deeper. After reaching the end of the file we apply the
  50. * patches in the correct order: last found command first.
  51. *
  52. * \param ed_cmds patch file to apply
  53. * \param in_file base file we want to patch
  54. * \param out_file file to write the patched result to
  55. * \param line of command operation
  56. * \param buffer internal used read/write buffer
  57. * \param hash the created file for correctness
  58. * \return the success State of the ed command executor
  59. */
  60. RredMethod::State RredMethod::applyFile(FILE *ed_cmds, FILE *in_file, FILE *out_file,
  61. unsigned long &line, char *buffer, Hashes *hash) const {
  62. // get the current command and parse it
  63. if (fgets(buffer, BUF_SIZE, ed_cmds) == NULL) {
  64. if (Debug == true)
  65. std::clog << "rred: encounter end of file - we can start patching now.";
  66. line = 0;
  67. return ED_OK;
  68. }
  69. // parse in the effected linenumbers
  70. char* idx;
  71. errno=0;
  72. unsigned long const startline = strtol(buffer, &idx, 10);
  73. if (errno == ERANGE || errno == EINVAL) {
  74. _error->Errno("rred", "startline is an invalid number");
  75. return ED_PARSER;
  76. }
  77. if (startline > line) {
  78. _error->Error("rred: The start line (%lu) of the next command is higher than the last line (%lu). This is not allowed.", startline, line);
  79. return ED_ORDERING;
  80. }
  81. unsigned long stopline;
  82. if (*idx == ',') {
  83. idx++;
  84. errno=0;
  85. stopline = strtol(idx, &idx, 10);
  86. if (errno == ERANGE || errno == EINVAL) {
  87. _error->Errno("rred", "stopline is an invalid number");
  88. return ED_PARSER;
  89. }
  90. }
  91. else {
  92. stopline = startline;
  93. }
  94. line = startline;
  95. // which command to execute on this line(s)?
  96. switch (*idx) {
  97. case MODE_CHANGED:
  98. if (Debug == true)
  99. std::clog << "Change from line " << startline << " to " << stopline << std::endl;
  100. break;
  101. case MODE_ADDED:
  102. if (Debug == true)
  103. std::clog << "Insert after line " << startline << std::endl;
  104. break;
  105. case MODE_DELETED:
  106. if (Debug == true)
  107. std::clog << "Delete from line " << startline << " to " << stopline << std::endl;
  108. break;
  109. default:
  110. _error->Error("rred: Unknown ed command '%c'. Abort.", *idx);
  111. return ED_PARSER;
  112. }
  113. unsigned char mode = *idx;
  114. // save the current position
  115. unsigned const long pos = ftell(ed_cmds);
  116. // if this is add or change then go to the next full stop
  117. unsigned int data_length = 0;
  118. if (mode == MODE_CHANGED || mode == MODE_ADDED) {
  119. do {
  120. ignoreLineInFile(ed_cmds, buffer);
  121. data_length++;
  122. }
  123. while (strncmp(buffer, ".", 1) != 0);
  124. data_length--; // the dot should not be copied
  125. }
  126. // do the recursive call - the last command is the one we need to execute at first
  127. const State child = applyFile(ed_cmds, in_file, out_file, line, buffer, hash);
  128. if (child != ED_OK) {
  129. return child;
  130. }
  131. // change and delete are working on "line" - add is done after "line"
  132. if (mode != MODE_ADDED)
  133. line++;
  134. // first wind to the current position and copy over all unchanged lines
  135. if (line < startline) {
  136. copyLinesFromFileToFile(in_file, out_file, (startline - line), hash, buffer);
  137. line = startline;
  138. }
  139. if (mode != MODE_ADDED)
  140. line--;
  141. // include data from ed script
  142. if (mode == MODE_CHANGED || mode == MODE_ADDED) {
  143. fseek(ed_cmds, pos, SEEK_SET);
  144. copyLinesFromFileToFile(ed_cmds, out_file, data_length, hash, buffer);
  145. }
  146. // ignore the corresponding number of lines from input
  147. if (mode == MODE_CHANGED || mode == MODE_DELETED) {
  148. while (line < stopline) {
  149. ignoreLineInFile(in_file, buffer);
  150. line++;
  151. }
  152. }
  153. return ED_OK;
  154. }
  155. /*}}}*/
  156. void RredMethod::copyLinesFromFileToFile(FILE *fin, FILE *fout, unsigned int lines,/*{{{*/
  157. Hashes *hash, char *buffer) const {
  158. while (0 < lines--) {
  159. do {
  160. fgets(buffer, BUF_SIZE, fin);
  161. size_t const written = fwrite(buffer, 1, strlen(buffer), fout);
  162. hash->Add((unsigned char*)buffer, written);
  163. } while (strlen(buffer) == (BUF_SIZE - 1) &&
  164. buffer[BUF_SIZE - 2] != '\n');
  165. }
  166. }
  167. /*}}}*/
  168. void RredMethod::ignoreLineInFile(FILE *fin, char *buffer) const { /*{{{*/
  169. fgets(buffer, BUF_SIZE, fin);
  170. while (strlen(buffer) == (BUF_SIZE - 1) &&
  171. buffer[BUF_SIZE - 2] != '\n') {
  172. fgets(buffer, BUF_SIZE, fin);
  173. buffer[0] = ' ';
  174. }
  175. }
  176. /*}}}*/
  177. RredMethod::State RredMethod::patchFile(FILE *ed_cmds, FILE *in_file, FILE *out_file, /*{{{*/
  178. Hashes *hash) const {
  179. char buffer[BUF_SIZE];
  180. /* we do a tail recursion to read the commands in the right order */
  181. unsigned long line = -1; // assign highest possible value
  182. State result = applyFile(ed_cmds, in_file, out_file, line, buffer, hash);
  183. /* read the rest from infile */
  184. if (result == ED_OK) {
  185. while (fgets(buffer, BUF_SIZE, in_file) != NULL) {
  186. size_t const written = fwrite(buffer, 1, strlen(buffer), out_file);
  187. hash->Add((unsigned char*)buffer, written);
  188. }
  189. }
  190. return result;
  191. }
  192. /*}}}*/
  193. bool RredMethod::Fetch(FetchItem *Itm) /*{{{*/
  194. {
  195. Debug = _config->FindB("Debug::pkgAcquire::RRed", false);
  196. URI Get = Itm->Uri;
  197. string Path = Get.Host + Get.Path; // To account for relative paths
  198. FetchResult Res;
  199. Res.Filename = Itm->DestFile;
  200. if (Itm->Uri.empty() == true) {
  201. Path = Itm->DestFile;
  202. Itm->DestFile.append(".result");
  203. } else
  204. URIStart(Res);
  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. Hashes Hash;
  217. FILE* fFrom = fdopen(From.Fd(), "r");
  218. FILE* fPatch = fdopen(Patch.Fd(), "r");
  219. FILE* fTo = fdopen(To.Fd(), "w");
  220. // now do the actual patching
  221. if (patchFile(fPatch, fFrom, fTo, &Hash) != ED_OK) {
  222. _error->Errno("rred", _("Could not patch file"));
  223. return false;
  224. }
  225. // write out the result
  226. fflush(fFrom);
  227. fflush(fPatch);
  228. fflush(fTo);
  229. From.Close();
  230. Patch.Close();
  231. To.Close();
  232. // Transfer the modification times
  233. struct stat Buf;
  234. if (stat(Path.c_str(),&Buf) != 0)
  235. return _error->Errno("stat",_("Failed to stat"));
  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. if (stat(Itm->DestFile.c_str(),&Buf) != 0)
  242. return _error->Errno("stat",_("Failed to stat"));
  243. // return done
  244. if (Itm->Uri.empty() == true) {
  245. Res.LastModified = Buf.st_mtime;
  246. Res.Size = Buf.st_size;
  247. Res.TakeHashes(Hash);
  248. URIDone(Res);
  249. }
  250. return true;
  251. }
  252. /*}}}*/
  253. /** \brief Wrapper class for testing rred */ /*{{{*/
  254. class TestRredMethod : public RredMethod {
  255. public:
  256. /** \brief Run rred in debug test mode
  257. *
  258. * This method can be used to run the rred method outside
  259. * of the "normal" acquire environment for easier testing.
  260. *
  261. * \param base basename of all files involved in this rred test
  262. */
  263. bool Run(char const *base) {
  264. _config->CndSet("Debug::pkgAcquire::RRed", "true");
  265. FetchItem *test = new FetchItem;
  266. test->DestFile = base;
  267. return Fetch(test);
  268. }
  269. };
  270. /*}}}*/
  271. /** \brief Starter for the rred method (or its test method) {{{
  272. *
  273. * Used without parameters is the normal behavior for methods for
  274. * the APT acquire system. While this works great for the acquire system
  275. * it is very hard to test the method and therefore the method also
  276. * accepts one parameter which will switch it directly to debug test mode:
  277. * The test mode expects that if "Testfile" is given as parameter
  278. * the file "Testfile" should be ed-style patched with "Testfile.ed"
  279. * and will write the result to "Testfile.result".
  280. */
  281. int main(int argc, char *argv[]) {
  282. if (argc == 0) {
  283. RredMethod Mth;
  284. return Mth.Run();
  285. } else {
  286. TestRredMethod Mth;
  287. bool result = Mth.Run(argv[1]);
  288. _error->DumpErrors();
  289. return result;
  290. }
  291. }
  292. /*}}}*/