patch.c 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. /* vi: set sw=4 ts=4: */
  2. /*
  3. * busybox patch applet to handle the unified diff format.
  4. * Copyright (C) 2003 Glenn McGrath
  5. *
  6. * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
  7. *
  8. * This applet is written to work with patches generated by GNU diff,
  9. * where there is equivalent functionality busybox patch shall behave
  10. * as per GNU patch.
  11. *
  12. * There is a SUSv3 specification for patch, however it looks to be
  13. * incomplete, it doesnt even mention unified diff format.
  14. * http://www.opengroup.org/onlinepubs/007904975/utilities/patch.html
  15. *
  16. * Issues
  17. * - Non-interactive
  18. * - Patches must apply cleanly or patch (not just one hunk) will fail.
  19. * - Reject file isnt saved
  20. */
  21. #include "libbb.h"
  22. static unsigned copy_lines(FILE *src_stream, FILE *dst_stream, unsigned lines_count)
  23. {
  24. while (src_stream && lines_count) {
  25. char *line;
  26. line = xmalloc_fgets(src_stream);
  27. if (line == NULL) {
  28. break;
  29. }
  30. if (fputs(line, dst_stream) == EOF) {
  31. bb_perror_msg_and_die("error writing to new file");
  32. }
  33. free(line);
  34. lines_count--;
  35. }
  36. return lines_count;
  37. }
  38. /* If patch_level is -1 it will remove all directory names
  39. * char *line must be greater than 4 chars
  40. * returns NULL if the file doesnt exist or error
  41. * returns malloc'ed filename
  42. * NB: frees 1st argument!
  43. */
  44. static char *extract_filename(char *line, int patch_level, const char *pat)
  45. {
  46. char *temp = NULL, *filename_start_ptr = line + 4;
  47. if (strncmp(line, pat, 4) == 0) {
  48. /* Terminate string at end of source filename */
  49. line[strcspn(line, "\t\n\r")] = '\0';
  50. /* Skip over (patch_level) number of leading directories */
  51. while (patch_level--) {
  52. temp = strchr(filename_start_ptr, '/');
  53. if (!temp)
  54. break;
  55. filename_start_ptr = temp + 1;
  56. }
  57. temp = xstrdup(filename_start_ptr);
  58. }
  59. free(line);
  60. return temp;
  61. }
  62. int patch_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
  63. int patch_main(int argc UNUSED_PARAM, char **argv)
  64. {
  65. struct stat saved_stat;
  66. char *patch_line;
  67. FILE *patch_file;
  68. int patch_level;
  69. int ret = 0;
  70. char plus = '+';
  71. unsigned opt;
  72. enum {
  73. OPT_R = (1 << 2),
  74. OPT_N = (1 << 3),
  75. };
  76. xfunc_error_retval = 2;
  77. {
  78. const char *p = "-1";
  79. const char *i = "-"; /* compat */
  80. opt = getopt32(argv, "p:i:RN", &p, &i);
  81. if (opt & OPT_R)
  82. plus = '-';
  83. patch_level = xatoi(p); /* can be negative! */
  84. patch_file = xfopen_stdin(i);
  85. }
  86. patch_line = xmalloc_fgetline(patch_file);
  87. while (patch_line) {
  88. FILE *src_stream;
  89. FILE *dst_stream;
  90. //char *old_filename;
  91. char *new_filename;
  92. char *backup_filename;
  93. unsigned src_cur_line = 1;
  94. unsigned dst_cur_line = 0;
  95. unsigned dst_beg_line;
  96. unsigned bad_hunk_count = 0;
  97. unsigned hunk_count = 0;
  98. smallint copy_trailing_lines_flag = 0;
  99. /* Skip everything upto the "---" marker
  100. * No need to parse the lines "Only in <dir>", and "diff <args>"
  101. */
  102. do {
  103. /* Extract the filename used before the patch was generated */
  104. new_filename = extract_filename(patch_line, patch_level, "--- ");
  105. // was old_filename above
  106. patch_line = xmalloc_fgetline(patch_file);
  107. if (!patch_line) goto quit;
  108. } while (!new_filename);
  109. free(new_filename); // "source" filename is irrelevant
  110. new_filename = extract_filename(patch_line, patch_level, "+++ ");
  111. if (!new_filename) {
  112. bb_error_msg_and_die("invalid patch");
  113. }
  114. /* Get access rights from the file to be patched */
  115. if (stat(new_filename, &saved_stat) != 0) {
  116. char *slash = strrchr(new_filename, '/');
  117. if (slash) {
  118. /* Create leading directories */
  119. *slash = '\0';
  120. bb_make_directory(new_filename, -1, FILEUTILS_RECUR);
  121. *slash = '/';
  122. }
  123. backup_filename = NULL;
  124. src_stream = NULL;
  125. saved_stat.st_mode = 0644;
  126. } else {
  127. backup_filename = xasprintf("%s.orig", new_filename);
  128. xrename(new_filename, backup_filename);
  129. src_stream = xfopen_for_read(backup_filename);
  130. }
  131. dst_stream = xfopen_for_write(new_filename);
  132. fchmod(fileno(dst_stream), saved_stat.st_mode);
  133. printf("patching file %s\n", new_filename);
  134. /* Handle all hunks for this file */
  135. patch_line = xmalloc_fgets(patch_file);
  136. while (patch_line) {
  137. unsigned count;
  138. unsigned src_beg_line;
  139. unsigned hunk_offset_start;
  140. unsigned src_last_line = 1;
  141. unsigned dst_last_line = 1;
  142. if ((sscanf(patch_line, "@@ -%d,%d +%d,%d", &src_beg_line, &src_last_line, &dst_beg_line, &dst_last_line) < 3)
  143. && (sscanf(patch_line, "@@ -%d +%d,%d", &src_beg_line, &dst_beg_line, &dst_last_line) < 2)
  144. ) {
  145. /* No more hunks for this file */
  146. break;
  147. }
  148. if (plus != '+') {
  149. /* reverse patch */
  150. unsigned tmp = src_last_line;
  151. src_last_line = dst_last_line;
  152. dst_last_line = tmp;
  153. tmp = src_beg_line;
  154. src_beg_line = dst_beg_line;
  155. dst_beg_line = tmp;
  156. }
  157. hunk_count++;
  158. if (src_beg_line && dst_beg_line) {
  159. /* Copy unmodified lines upto start of hunk */
  160. /* src_beg_line will be 0 if it's a new file */
  161. count = src_beg_line - src_cur_line;
  162. if (copy_lines(src_stream, dst_stream, count)) {
  163. bb_error_msg_and_die("bad src file");
  164. }
  165. src_cur_line += count;
  166. dst_cur_line += count;
  167. copy_trailing_lines_flag = 1;
  168. }
  169. src_last_line += hunk_offset_start = src_cur_line;
  170. dst_last_line += dst_cur_line;
  171. while (1) {
  172. free(patch_line);
  173. patch_line = xmalloc_fgets(patch_file);
  174. if (patch_line == NULL)
  175. break; /* EOF */
  176. if ((*patch_line != '-') && (*patch_line != '+')
  177. && (*patch_line != ' ')
  178. ) {
  179. break; /* End of hunk */
  180. }
  181. if (*patch_line != plus) { /* '-' or ' ' */
  182. char *src_line = NULL;
  183. if (src_cur_line == src_last_line)
  184. break;
  185. if (src_stream) {
  186. src_line = xmalloc_fgets(src_stream);
  187. if (src_line) {
  188. int diff = strcmp(src_line, patch_line + 1);
  189. src_cur_line++;
  190. free(src_line);
  191. if (diff)
  192. src_line = NULL;
  193. }
  194. }
  195. /* Do not patch an already patched hunk with -N */
  196. if (src_line == 0 && (opt & OPT_N)) {
  197. continue;
  198. }
  199. if (!src_line) {
  200. bb_error_msg("hunk #%u FAILED at %u", hunk_count, hunk_offset_start);
  201. bad_hunk_count++;
  202. break;
  203. }
  204. if (*patch_line != ' ') { /* '-' */
  205. continue;
  206. }
  207. }
  208. if (dst_cur_line == dst_last_line)
  209. break;
  210. fputs(patch_line + 1, dst_stream);
  211. dst_cur_line++;
  212. } /* end of while loop handling one hunk */
  213. } /* end of while loop handling one file */
  214. /* Cleanup last patched file */
  215. if (copy_trailing_lines_flag) {
  216. copy_lines(src_stream, dst_stream, (unsigned)(-1));
  217. }
  218. if (src_stream) {
  219. fclose(src_stream);
  220. }
  221. fclose(dst_stream);
  222. if (bad_hunk_count) {
  223. ret = 1;
  224. bb_error_msg("%u out of %u hunk FAILED", bad_hunk_count, hunk_count);
  225. } else {
  226. /* It worked, we can remove the backup */
  227. if (backup_filename) {
  228. unlink(backup_filename);
  229. }
  230. if ((dst_cur_line == 0) || (dst_beg_line == 0)) {
  231. /* The new patched file is empty, remove it */
  232. xunlink(new_filename);
  233. // /* old_filename and new_filename may be the same file */
  234. // unlink(old_filename);
  235. }
  236. }
  237. free(backup_filename);
  238. //free(old_filename);
  239. free(new_filename);
  240. } /* end of "while there are patch lines" */
  241. quit:
  242. /* 0 = SUCCESS
  243. * 1 = Some hunks failed
  244. * 2 = More serious problems (exited earlier)
  245. */
  246. return ret;
  247. }