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