rred.cc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. // Includes /*{{{*/
  2. #include <apt-pkg/fileutl.h>
  3. #include <apt-pkg/mmap.h>
  4. #include <apt-pkg/error.h>
  5. #include <apt-pkg/acquire-method.h>
  6. #include <apt-pkg/strutl.h>
  7. #include <apt-pkg/hashes.h>
  8. #include <sys/stat.h>
  9. #include <sys/uio.h>
  10. #include <unistd.h>
  11. #include <utime.h>
  12. #include <stdio.h>
  13. #include <errno.h>
  14. #include <apti18n.h>
  15. /*}}}*/
  16. /** \brief RredMethod - ed-style incremential patch method {{{
  17. *
  18. * This method implements a patch functionality similar to "patch --ed" that is
  19. * used by the "tiffany" incremental packages download stuff. It differs from
  20. * "ed" insofar that it is way more restricted (and therefore secure).
  21. * The currently supported ed commands are "<em>c</em>hange", "<em>a</em>dd" and
  22. * "<em>d</em>elete" (diff doesn't output any other).
  23. * Additionally the records must be reverse sorted by line number and
  24. * may not overlap (diff *seems* to produce this kind of output).
  25. * */
  26. class RredMethod : public pkgAcqMethod {
  27. bool Debug;
  28. // the size of this doesn't really matter (except for performance)
  29. const static int BUF_SIZE = 1024;
  30. // the supported ed commands
  31. enum Mode {MODE_CHANGED='c', MODE_DELETED='d', MODE_ADDED='a'};
  32. // return values
  33. enum State {ED_OK, ED_ORDERING, ED_PARSER, ED_FAILURE, MMAP_FAILED};
  34. State applyFile(FILE *ed_cmds, FILE *in_file, FILE *out_file,
  35. unsigned long &line, char *buffer, Hashes *hash) const;
  36. void ignoreLineInFile(FILE *fin, char *buffer) const;
  37. void copyLinesFromFileToFile(FILE *fin, FILE *fout, unsigned int lines,
  38. Hashes *hash, char *buffer) const;
  39. State patchFile(FileFd &Patch, FileFd &From, FileFd &out_file, Hashes *hash) const;
  40. State patchMMap(FileFd &Patch, FileFd &From, FileFd &out_file, Hashes *hash) const;
  41. protected:
  42. // the methods main method
  43. virtual bool Fetch(FetchItem *Itm);
  44. public:
  45. RredMethod() : pkgAcqMethod("1.1",SingleInstance | SendConfig) {};
  46. };
  47. /*}}}*/
  48. /** \brief applyFile - in reverse order with a tail recursion {{{
  49. *
  50. * As it is expected that the commands are in reversed order in the patch file
  51. * we check in the first half if the command is valid, but doesn't execute it
  52. * and move a step deeper. After reaching the end of the file we apply the
  53. * patches in the correct order: last found command first.
  54. *
  55. * \param ed_cmds patch file to apply
  56. * \param in_file base file we want to patch
  57. * \param out_file file to write the patched result to
  58. * \param line of command operation
  59. * \param buffer internal used read/write buffer
  60. * \param hash the created file for correctness
  61. * \return the success State of the ed command executor
  62. */
  63. RredMethod::State RredMethod::applyFile(FILE *ed_cmds, FILE *in_file, FILE *out_file,
  64. unsigned long &line, char *buffer, Hashes *hash) const {
  65. // get the current command and parse it
  66. if (fgets(buffer, BUF_SIZE, ed_cmds) == NULL) {
  67. if (Debug == true)
  68. std::clog << "rred: encounter end of file - we can start patching now." << std::endl;
  69. line = 0;
  70. return ED_OK;
  71. }
  72. // parse in the effected linenumbers
  73. char* idx;
  74. errno=0;
  75. unsigned long const startline = strtol(buffer, &idx, 10);
  76. if (errno == ERANGE || errno == EINVAL) {
  77. _error->Errno("rred", "startline is an invalid number");
  78. return ED_PARSER;
  79. }
  80. if (startline > line) {
  81. _error->Error("rred: The start line (%lu) of the next command is higher than the last line (%lu). This is not allowed.", startline, line);
  82. return ED_ORDERING;
  83. }
  84. unsigned long stopline;
  85. if (*idx == ',') {
  86. idx++;
  87. errno=0;
  88. stopline = strtol(idx, &idx, 10);
  89. if (errno == ERANGE || errno == EINVAL) {
  90. _error->Errno("rred", "stopline is an invalid number");
  91. return ED_PARSER;
  92. }
  93. }
  94. else {
  95. stopline = startline;
  96. }
  97. line = startline;
  98. // which command to execute on this line(s)?
  99. switch (*idx) {
  100. case MODE_CHANGED:
  101. if (Debug == true)
  102. std::clog << "Change from line " << startline << " to " << stopline << std::endl;
  103. break;
  104. case MODE_ADDED:
  105. if (Debug == true)
  106. std::clog << "Insert after line " << startline << std::endl;
  107. break;
  108. case MODE_DELETED:
  109. if (Debug == true)
  110. std::clog << "Delete from line " << startline << " to " << stopline << std::endl;
  111. break;
  112. default:
  113. _error->Error("rred: Unknown ed command '%c'. Abort.", *idx);
  114. return ED_PARSER;
  115. }
  116. unsigned char mode = *idx;
  117. // save the current position
  118. unsigned const long pos = ftell(ed_cmds);
  119. // if this is add or change then go to the next full stop
  120. unsigned int data_length = 0;
  121. if (mode == MODE_CHANGED || mode == MODE_ADDED) {
  122. do {
  123. ignoreLineInFile(ed_cmds, buffer);
  124. data_length++;
  125. }
  126. while (strncmp(buffer, ".", 1) != 0);
  127. data_length--; // the dot should not be copied
  128. }
  129. // do the recursive call - the last command is the one we need to execute at first
  130. const State child = applyFile(ed_cmds, in_file, out_file, line, buffer, hash);
  131. if (child != ED_OK) {
  132. return child;
  133. }
  134. // change and delete are working on "line" - add is done after "line"
  135. if (mode != MODE_ADDED)
  136. line++;
  137. // first wind to the current position and copy over all unchanged lines
  138. if (line < startline) {
  139. copyLinesFromFileToFile(in_file, out_file, (startline - line), hash, buffer);
  140. line = startline;
  141. }
  142. if (mode != MODE_ADDED)
  143. line--;
  144. // include data from ed script
  145. if (mode == MODE_CHANGED || mode == MODE_ADDED) {
  146. fseek(ed_cmds, pos, SEEK_SET);
  147. copyLinesFromFileToFile(ed_cmds, out_file, data_length, hash, buffer);
  148. }
  149. // ignore the corresponding number of lines from input
  150. if (mode == MODE_CHANGED || mode == MODE_DELETED) {
  151. while (line < stopline) {
  152. ignoreLineInFile(in_file, buffer);
  153. line++;
  154. }
  155. }
  156. return ED_OK;
  157. }
  158. /*}}}*/
  159. void RredMethod::copyLinesFromFileToFile(FILE *fin, FILE *fout, unsigned int lines,/*{{{*/
  160. Hashes *hash, char *buffer) const {
  161. while (0 < lines--) {
  162. do {
  163. fgets(buffer, BUF_SIZE, fin);
  164. size_t const written = fwrite(buffer, 1, strlen(buffer), fout);
  165. hash->Add((unsigned char*)buffer, written);
  166. } while (strlen(buffer) == (BUF_SIZE - 1) &&
  167. buffer[BUF_SIZE - 2] != '\n');
  168. }
  169. }
  170. /*}}}*/
  171. void RredMethod::ignoreLineInFile(FILE *fin, char *buffer) const { /*{{{*/
  172. fgets(buffer, BUF_SIZE, fin);
  173. while (strlen(buffer) == (BUF_SIZE - 1) &&
  174. buffer[BUF_SIZE - 2] != '\n') {
  175. fgets(buffer, BUF_SIZE, fin);
  176. buffer[0] = ' ';
  177. }
  178. }
  179. /*}}}*/
  180. RredMethod::State RredMethod::patchFile(FileFd &Patch, FileFd &From, /*{{{*/
  181. FileFd &out_file, Hashes *hash) const {
  182. char buffer[BUF_SIZE];
  183. FILE* fFrom = fdopen(From.Fd(), "r");
  184. FILE* fPatch = fdopen(Patch.Fd(), "r");
  185. FILE* fTo = fdopen(out_file.Fd(), "w");
  186. /* we do a tail recursion to read the commands in the right order */
  187. unsigned long line = -1; // assign highest possible value
  188. State const result = applyFile(fPatch, fFrom, fTo, line, buffer, hash);
  189. /* read the rest from infile */
  190. if (result == ED_OK) {
  191. while (fgets(buffer, BUF_SIZE, fFrom) != NULL) {
  192. size_t const written = fwrite(buffer, 1, strlen(buffer), fTo);
  193. hash->Add((unsigned char*)buffer, written);
  194. }
  195. fflush(fTo);
  196. }
  197. return result;
  198. }
  199. /*}}}*/
  200. struct EdCommand { /*{{{*/
  201. size_t data_start;
  202. size_t data_end;
  203. size_t data_lines;
  204. size_t first_line;
  205. size_t last_line;
  206. char type;
  207. };
  208. #define IOV_COUNT 1024 /* Don't really want IOV_MAX since it can be arbitrarily large */
  209. /*}}}*/
  210. RredMethod::State RredMethod::patchMMap(FileFd &Patch, FileFd &From, /*{{{*/
  211. FileFd &out_file, Hashes *hash) const {
  212. #ifdef _POSIX_MAPPED_FILES
  213. MMap ed_cmds(Patch, MMap::ReadOnly);
  214. MMap in_file(From, MMap::ReadOnly);
  215. if (ed_cmds.Size() == 0 || in_file.Size() == 0)
  216. return MMAP_FAILED;
  217. EdCommand* commands = 0;
  218. size_t command_count = 0;
  219. size_t command_alloc = 0;
  220. const char* begin = (char*) ed_cmds.Data();
  221. const char* end = begin;
  222. const char* ed_end = (char*) ed_cmds.Data() + ed_cmds.Size();
  223. const char* input = (char*) in_file.Data();
  224. const char* input_end = (char*) in_file.Data() + in_file.Size();
  225. size_t i;
  226. /* 1. Parse entire script. It is executed in reverse order, so we cather it
  227. * in the `commands' buffer first
  228. */
  229. for(;;) {
  230. EdCommand cmd;
  231. cmd.data_start = 0;
  232. cmd.data_end = 0;
  233. while(begin != ed_end && *begin == '\n')
  234. ++begin;
  235. while(end != ed_end && *end != '\n')
  236. ++end;
  237. if(end == ed_end && begin == end)
  238. break;
  239. /* Determine command range */
  240. const char* tmp = begin;
  241. for(;;) {
  242. /* atoll is safe despite lacking NUL-termination; we know there's an
  243. * alphabetic character at end[-1]
  244. */
  245. if(tmp == end) {
  246. cmd.first_line = atol(begin);
  247. cmd.last_line = cmd.first_line;
  248. break;
  249. }
  250. if(*tmp == ',') {
  251. cmd.first_line = atol(begin);
  252. cmd.last_line = atol(tmp + 1);
  253. break;
  254. }
  255. ++tmp;
  256. }
  257. // which command to execute on this line(s)?
  258. switch (end[-1]) {
  259. case MODE_CHANGED:
  260. if (Debug == true)
  261. std::clog << "Change from line " << cmd.first_line << " to " << cmd.last_line << std::endl;
  262. break;
  263. case MODE_ADDED:
  264. if (Debug == true)
  265. std::clog << "Insert after line " << cmd.first_line << std::endl;
  266. break;
  267. case MODE_DELETED:
  268. if (Debug == true)
  269. std::clog << "Delete from line " << cmd.first_line << " to " << cmd.last_line << std::endl;
  270. break;
  271. default:
  272. _error->Error("rred: Unknown ed command '%c'. Abort.", end[-1]);
  273. free(commands);
  274. return ED_PARSER;
  275. }
  276. cmd.type = end[-1];
  277. /* Determine the size of the inserted text, so we don't have to scan this
  278. * text again later.
  279. */
  280. begin = end + 1;
  281. end = begin;
  282. cmd.data_lines = 0;
  283. if(cmd.type == MODE_ADDED || cmd.type == MODE_CHANGED) {
  284. cmd.data_start = begin - (char*) ed_cmds.Data();
  285. while(end != ed_end) {
  286. if(*end == '\n') {
  287. if(end[-1] == '.' && end[-2] == '\n')
  288. break;
  289. ++cmd.data_lines;
  290. }
  291. ++end;
  292. }
  293. cmd.data_end = end - (char*) ed_cmds.Data() - 1;
  294. begin = end + 1;
  295. end = begin;
  296. }
  297. if(command_count == command_alloc) {
  298. command_alloc = (command_alloc + 64) * 3 / 2;
  299. commands = (EdCommand*) realloc(commands, command_alloc * sizeof(EdCommand));
  300. }
  301. commands[command_count++] = cmd;
  302. }
  303. struct iovec* iov = new struct iovec[IOV_COUNT];
  304. size_t iov_size = 0;
  305. size_t amount, remaining;
  306. size_t line = 1;
  307. EdCommand* cmd;
  308. /* 2. Execute script. We gather writes in a `struct iov' array, and flush
  309. * using writev to minimize the number of system calls. Data is read
  310. * directly from the memory mappings of the input file and the script.
  311. */
  312. for(i = command_count; i-- > 0; ) {
  313. cmd = &commands[i];
  314. if(cmd->type == MODE_ADDED)
  315. amount = cmd->first_line + 1;
  316. else
  317. amount = cmd->first_line;
  318. if(line < amount) {
  319. begin = input;
  320. while(line != amount) {
  321. input = (const char*) memchr(input, '\n', input_end - input);
  322. if(!input)
  323. break;
  324. ++line;
  325. ++input;
  326. }
  327. iov[iov_size].iov_base = (void*) begin;
  328. iov[iov_size].iov_len = input - begin;
  329. hash->Add((const unsigned char*) begin, input - begin);
  330. if(++iov_size == IOV_COUNT) {
  331. writev(out_file.Fd(), iov, IOV_COUNT);
  332. iov_size = 0;
  333. }
  334. }
  335. if(cmd->type == MODE_DELETED || cmd->type == MODE_CHANGED) {
  336. remaining = (cmd->last_line - cmd->first_line) + 1;
  337. line += remaining;
  338. while(remaining) {
  339. input = (const char*) memchr(input, '\n', input_end - input);
  340. if(!input)
  341. break;
  342. --remaining;
  343. ++input;
  344. }
  345. }
  346. if(cmd->type == MODE_CHANGED || cmd->type == MODE_ADDED) {
  347. if(cmd->data_end != cmd->data_start) {
  348. iov[iov_size].iov_base = (void*) ((char*)ed_cmds.Data() + cmd->data_start);
  349. iov[iov_size].iov_len = cmd->data_end - cmd->data_start;
  350. hash->Add((const unsigned char*) ((char*)ed_cmds.Data() + cmd->data_start),
  351. iov[iov_size].iov_len);
  352. if(++iov_size == IOV_COUNT) {
  353. writev(out_file.Fd(), iov, IOV_COUNT);
  354. iov_size = 0;
  355. }
  356. }
  357. }
  358. }
  359. if(input != input_end) {
  360. iov[iov_size].iov_base = (void*) input;
  361. iov[iov_size].iov_len = input_end - input;
  362. hash->Add((const unsigned char*) input, input_end - input);
  363. ++iov_size;
  364. }
  365. if(iov_size) {
  366. writev(out_file.Fd(), iov, iov_size);
  367. iov_size = 0;
  368. }
  369. for(i = 0; i < iov_size; i += IOV_COUNT) {
  370. if(iov_size - i < IOV_COUNT)
  371. writev(out_file.Fd(), iov + i, iov_size - i);
  372. else
  373. writev(out_file.Fd(), iov + i, IOV_COUNT);
  374. }
  375. delete [] iov;
  376. free(commands);
  377. return ED_OK;
  378. #else
  379. return MMAP_FAILED;
  380. #endif
  381. }
  382. /*}}}*/
  383. bool RredMethod::Fetch(FetchItem *Itm) /*{{{*/
  384. {
  385. Debug = _config->FindB("Debug::pkgAcquire::RRed", false);
  386. URI Get = Itm->Uri;
  387. string Path = Get.Host + Get.Path; // To account for relative paths
  388. FetchResult Res;
  389. Res.Filename = Itm->DestFile;
  390. if (Itm->Uri.empty() == true) {
  391. Path = Itm->DestFile;
  392. Itm->DestFile.append(".result");
  393. } else
  394. URIStart(Res);
  395. if (Debug == true)
  396. std::clog << "Patching " << Path << " with " << Path
  397. << ".ed and putting result into " << Itm->DestFile << std::endl;
  398. // Open the source and destination files (the d'tor of FileFd will do
  399. // the cleanup/closing of the fds)
  400. FileFd From(Path,FileFd::ReadOnly);
  401. FileFd Patch(Path+".ed",FileFd::ReadOnly);
  402. FileFd To(Itm->DestFile,FileFd::WriteEmpty);
  403. To.EraseOnFailure();
  404. if (_error->PendingError() == true)
  405. return false;
  406. Hashes Hash;
  407. // now do the actual patching
  408. State const result = patchMMap(Patch, From, To, &Hash);
  409. if (result == MMAP_FAILED) {
  410. // retry with patchFile
  411. lseek(Patch.Fd(), 0, SEEK_SET);
  412. lseek(From.Fd(), 0, SEEK_SET);
  413. To.Open(Itm->DestFile,FileFd::WriteEmpty);
  414. if (_error->PendingError() == true)
  415. return false;
  416. if (patchFile(Patch, From, To, &Hash) != ED_OK) {
  417. return _error->WarningE("rred", _("Could not patch %s with mmap and with file operation usage - the patch seems to be corrupt."), Path.c_str());
  418. } else if (Debug == true) {
  419. std::clog << "rred: finished file patching of " << Path << " after mmap failed." << std::endl;
  420. }
  421. } else if (result != ED_OK) {
  422. return _error->Errno("rred", _("Could not patch %s with mmap (but no mmap specific fail) - the patch seems to be corrupt."), Path.c_str());
  423. } else if (Debug == true) {
  424. std::clog << "rred: finished mmap patching of " << Path << std::endl;
  425. }
  426. // write out the result
  427. From.Close();
  428. Patch.Close();
  429. To.Close();
  430. // Transfer the modification times
  431. struct stat Buf;
  432. if (stat(Path.c_str(),&Buf) != 0)
  433. return _error->Errno("stat",_("Failed to stat"));
  434. struct utimbuf TimeBuf;
  435. TimeBuf.actime = Buf.st_atime;
  436. TimeBuf.modtime = Buf.st_mtime;
  437. if (utime(Itm->DestFile.c_str(),&TimeBuf) != 0)
  438. return _error->Errno("utime",_("Failed to set modification time"));
  439. if (stat(Itm->DestFile.c_str(),&Buf) != 0)
  440. return _error->Errno("stat",_("Failed to stat"));
  441. // return done
  442. Res.LastModified = Buf.st_mtime;
  443. Res.Size = Buf.st_size;
  444. Res.TakeHashes(Hash);
  445. URIDone(Res);
  446. return true;
  447. }
  448. /*}}}*/
  449. /** \brief Wrapper class for testing rred */ /*{{{*/
  450. class TestRredMethod : public RredMethod {
  451. public:
  452. /** \brief Run rred in debug test mode
  453. *
  454. * This method can be used to run the rred method outside
  455. * of the "normal" acquire environment for easier testing.
  456. *
  457. * \param base basename of all files involved in this rred test
  458. */
  459. bool Run(char const *base) {
  460. _config->CndSet("Debug::pkgAcquire::RRed", "true");
  461. FetchItem *test = new FetchItem;
  462. test->DestFile = base;
  463. return Fetch(test);
  464. }
  465. };
  466. /*}}}*/
  467. /** \brief Starter for the rred method (or its test method) {{{
  468. *
  469. * Used without parameters is the normal behavior for methods for
  470. * the APT acquire system. While this works great for the acquire system
  471. * it is very hard to test the method and therefore the method also
  472. * accepts one parameter which will switch it directly to debug test mode:
  473. * The test mode expects that if "Testfile" is given as parameter
  474. * the file "Testfile" should be ed-style patched with "Testfile.ed"
  475. * and will write the result to "Testfile.result".
  476. */
  477. int main(int argc, char *argv[]) {
  478. if (argc <= 1) {
  479. RredMethod Mth;
  480. return Mth.Run();
  481. } else {
  482. TestRredMethod Mth;
  483. bool result = Mth.Run(argv[1]);
  484. _error->DumpErrors();
  485. return result;
  486. }
  487. }
  488. /*}}}*/