patch.c 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. /* vi: set sw=4 ts=4: */
  2. /*
  3. * busybox patch applet to handle the unified diff format.
  4. * Copyright (C) 2003 Glenn McGrath <bug1@iinet.net.au>
  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 <getopt.h>
  22. #include "libbb.h"
  23. static unsigned int copy_lines(FILE *src_stream, FILE *dest_stream, const unsigned int lines_count)
  24. {
  25. unsigned int i = 0;
  26. while (src_stream && (i < lines_count)) {
  27. char *line;
  28. line = xmalloc_fgets(src_stream);
  29. if (line == NULL) {
  30. break;
  31. }
  32. if (fputs(line, dest_stream) == EOF) {
  33. bb_perror_msg_and_die("error writing to new file");
  34. }
  35. free(line);
  36. i++;
  37. }
  38. return i;
  39. }
  40. /* If patch_level is -1 it will remove all directory names
  41. * char *line must be greater than 4 chars
  42. * returns NULL if the file doesnt exist or error
  43. * returns malloc'ed filename
  44. */
  45. static char *extract_filename(char *line, int patch_level)
  46. {
  47. char *temp, *filename_start_ptr = line + 4;
  48. int i;
  49. /* Terminate string at end of source filename */
  50. temp = strchrnul(filename_start_ptr, '\t');
  51. *temp = '\0';
  52. /* Skip over (patch_level) number of leading directories */
  53. if (patch_level == -1)
  54. patch_level = INT_MAX;
  55. for (i = 0; i < patch_level; i++) {
  56. temp = strchr(filename_start_ptr, '/');
  57. if (!temp)
  58. break;
  59. filename_start_ptr = temp + 1;
  60. }
  61. return xstrdup(filename_start_ptr);
  62. }
  63. static int file_doesnt_exist(const char *filename)
  64. {
  65. struct stat statbuf;
  66. return stat(filename, &statbuf);
  67. }
  68. int patch_main(int argc, char **argv);
  69. int patch_main(int argc, char **argv)
  70. {
  71. int patch_level = -1;
  72. char *patch_line;
  73. int ret;
  74. FILE *patch_file = NULL;
  75. {
  76. char *p, *i;
  77. ret = getopt32(argv, "p:i:", &p, &i);
  78. if (ret & 1)
  79. patch_level = xatol_range(p, -1, USHRT_MAX);
  80. if (ret & 2) {
  81. patch_file = xfopen(i, "r");
  82. } else {
  83. patch_file = stdin;
  84. }
  85. ret = 0;
  86. }
  87. patch_line = xmalloc_getline(patch_file);
  88. while (patch_line) {
  89. FILE *src_stream;
  90. FILE *dst_stream;
  91. char *original_filename;
  92. char *new_filename;
  93. char *backup_filename;
  94. unsigned int src_cur_line = 1;
  95. unsigned int dest_cur_line = 0;
  96. unsigned int dest_beg_line;
  97. unsigned int bad_hunk_count = 0;
  98. unsigned int hunk_count = 0;
  99. char copy_trailing_lines_flag = 0;
  100. /* Skip everything upto the "---" marker
  101. * No need to parse the lines "Only in <dir>", and "diff <args>"
  102. */
  103. while (patch_line && strncmp(patch_line, "--- ", 4) != 0) {
  104. free(patch_line);
  105. patch_line = xmalloc_getline(patch_file);
  106. }
  107. /* FIXME: patch_line NULL check?? */
  108. /* Extract the filename used before the patch was generated */
  109. original_filename = extract_filename(patch_line, patch_level);
  110. free(patch_line);
  111. patch_line = xmalloc_getline(patch_file);
  112. /* FIXME: NULL check?? */
  113. if (strncmp(patch_line, "+++ ", 4) != 0) {
  114. ret = 2;
  115. bb_error_msg("invalid patch");
  116. continue;
  117. }
  118. new_filename = extract_filename(patch_line, patch_level);
  119. free(patch_line);
  120. if (file_doesnt_exist(new_filename)) {
  121. char *line_ptr;
  122. /* Create leading directories */
  123. line_ptr = strrchr(new_filename, '/');
  124. if (line_ptr) {
  125. *line_ptr = '\0';
  126. bb_make_directory(new_filename, -1, FILEUTILS_RECUR);
  127. *line_ptr = '/';
  128. }
  129. dst_stream = xfopen(new_filename, "w+");
  130. backup_filename = NULL;
  131. } else {
  132. backup_filename = xmalloc(strlen(new_filename) + 6);
  133. strcpy(backup_filename, new_filename);
  134. strcat(backup_filename, ".orig");
  135. if (rename(new_filename, backup_filename) == -1) {
  136. bb_perror_msg_and_die("cannot create file %s",
  137. backup_filename);
  138. }
  139. dst_stream = xfopen(new_filename, "w");
  140. }
  141. if ((backup_filename == NULL) || file_doesnt_exist(original_filename)) {
  142. src_stream = NULL;
  143. } else {
  144. if (strcmp(original_filename, new_filename) == 0) {
  145. src_stream = xfopen(backup_filename, "r");
  146. } else {
  147. src_stream = xfopen(original_filename, "r");
  148. }
  149. }
  150. printf("patching file %s\n", new_filename);
  151. /* Handle each hunk */
  152. patch_line = xmalloc_fgets(patch_file);
  153. while (patch_line) {
  154. unsigned int count;
  155. unsigned int src_beg_line;
  156. unsigned int unused;
  157. unsigned int hunk_offset_start = 0;
  158. int hunk_error = 0;
  159. /* This bit should be improved */
  160. if ((sscanf(patch_line, "@@ -%d,%d +%d,%d @@", &src_beg_line, &unused, &dest_beg_line, &unused) != 4) &&
  161. (sscanf(patch_line, "@@ -%d,%d +%d @@", &src_beg_line, &unused, &dest_beg_line) != 3) &&
  162. (sscanf(patch_line, "@@ -%d +%d,%d @@", &src_beg_line, &dest_beg_line, &unused) != 3)) {
  163. /* No more hunks for this file */
  164. break;
  165. }
  166. free(patch_line);
  167. hunk_count++;
  168. if (src_beg_line && dest_beg_line) {
  169. /* Copy unmodified lines upto start of hunk */
  170. /* src_beg_line will be 0 if its a new file */
  171. count = src_beg_line - src_cur_line;
  172. if (copy_lines(src_stream, dst_stream, count) != count) {
  173. bb_error_msg_and_die("bad src file");
  174. }
  175. src_cur_line += count;
  176. dest_cur_line += count;
  177. copy_trailing_lines_flag = 1;
  178. }
  179. hunk_offset_start = src_cur_line;
  180. while ((patch_line = xmalloc_fgets(patch_file)) != NULL) {
  181. if ((*patch_line == '-') || (*patch_line == ' ')) {
  182. char *src_line = NULL;
  183. if (src_stream) {
  184. src_line = xmalloc_fgets(src_stream);
  185. if (!src_line) {
  186. hunk_error++;
  187. break;
  188. } else {
  189. src_cur_line++;
  190. }
  191. if (strcmp(src_line, patch_line + 1) != 0) {
  192. bb_error_msg("hunk #%d FAILED at %d", hunk_count, hunk_offset_start);
  193. hunk_error++;
  194. free(patch_line);
  195. /* Probably need to find next hunk, etc... */
  196. /* but for now we just bail out */
  197. patch_line = NULL;
  198. break;
  199. }
  200. free(src_line);
  201. }
  202. if (*patch_line == ' ') {
  203. fputs(patch_line + 1, dst_stream);
  204. dest_cur_line++;
  205. }
  206. } else if (*patch_line == '+') {
  207. fputs(patch_line + 1, dst_stream);
  208. dest_cur_line++;
  209. } else {
  210. break;
  211. }
  212. free(patch_line);
  213. }
  214. if (hunk_error) {
  215. bad_hunk_count++;
  216. }
  217. }
  218. /* Cleanup last patched file */
  219. if (copy_trailing_lines_flag) {
  220. copy_lines(src_stream, dst_stream, -1);
  221. }
  222. if (src_stream) {
  223. fclose(src_stream);
  224. }
  225. if (dst_stream) {
  226. fclose(dst_stream);
  227. }
  228. if (bad_hunk_count) {
  229. if (!ret) {
  230. ret = 1;
  231. }
  232. bb_error_msg("%d out of %d hunk FAILED", bad_hunk_count, hunk_count);
  233. } else {
  234. /* It worked, we can remove the backup */
  235. if (backup_filename) {
  236. unlink(backup_filename);
  237. }
  238. if ((dest_cur_line == 0) || (dest_beg_line == 0)) {
  239. /* The new patched file is empty, remove it */
  240. xunlink(new_filename);
  241. if (strcmp(new_filename, original_filename) != 0)
  242. xunlink(original_filename);
  243. }
  244. }
  245. }
  246. /* 0 = SUCCESS
  247. * 1 = Some hunks failed
  248. * 2 = More serious problems
  249. */
  250. return ret;
  251. }