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 GPLv2 or later, see file LICENSE in this source tree. 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 "libbb.h" 23 24static unsigned copy_lines(FILE *src_stream, FILE *dst_stream, unsigned lines_count) 25{ 26 while (src_stream && lines_count) { 27 char *line; 28 line = xmalloc_fgets(src_stream); 29 if (line == NULL) { 30 break; 31 } 32 if (fputs(line, dst_stream) == EOF) { 33 bb_perror_msg_and_die("error writing to new file"); 34 } 35 free(line); 36 lines_count--; 37 } 38 return lines_count; 39} 40 41/* If patch_level is -1 it will remove all directory names 42 * char *line must be greater than 4 chars 43 * returns NULL if the file doesnt exist or error 44 * returns malloc'ed filename 45 * NB: frees 1st argument! 46 */ 47static char *extract_filename(char *line, int patch_level, const char *pat) 48{ 49 char *temp = NULL, *filename_start_ptr = line + 4; 50 51 if (strncmp(line, pat, 4) == 0) { 52 /* Terminate string at end of source filename */ 53 line[strcspn(line, "\t\n\r")] = '\0'; 54 55 /* Skip over (patch_level) number of leading directories */ 56 while (patch_level--) { 57 temp = strchr(filename_start_ptr, '/'); 58 if (!temp) 59 break; 60 filename_start_ptr = temp + 1; 61 } 62 temp = xstrdup(filename_start_ptr); 63 } 64 free(line); 65 return temp; 66} 67 68int patch_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; 69int patch_main(int argc UNUSED_PARAM, char **argv) 70{ 71 struct stat saved_stat; 72 char *patch_line; 73 FILE *patch_file; 74 int patch_level; 75 int ret = 0; 76 char plus = '+'; 77 unsigned opt; 78 enum { 79 OPT_R = (1 << 2), 80 OPT_N = (1 << 3), 81 /*OPT_f = (1 << 4), ignored */ 82 /*OPT_E = (1 << 5), ignored, this is the default */ 83 /*OPT_g = (1 << 6), ignored */ 84 OPT_dry_run = (1 << 7) * ENABLE_LONG_OPTS, 85 }; 86 87 xfunc_error_retval = 2; 88 { 89 const char *p = "-1"; 90 const char *i = "-"; /* compat */ 91#if ENABLE_LONG_OPTS 92 static const char patch_longopts[] ALIGN1 = 93 "strip\0" Required_argument "p" 94 "input\0" Required_argument "i" 95 "reverse\0" No_argument "R" 96 "forward\0" No_argument "N" 97 /* "Assume user knows what [s]he is doing, do not ask any questions": */ 98 "force\0" No_argument "f" /*ignored*/ 99# if ENABLE_DESKTOP 100 "remove-empty-files\0" No_argument "E" /*ignored*/ 101 /* "Controls actions when a file is under RCS or SCCS control, 102 * and does not exist or is read-only and matches the default version, 103 * or when a file is under ClearCase control and does not exist..." 104 * IOW: rather obscure option. 105 * But Gentoo's portage does use -g0 */ 106 "get\0" Required_argument "g" /*ignored*/ 107# endif 108 "dry-run\0" No_argument "\xfd" 109# if ENABLE_DESKTOP 110 "backup-if-mismatch\0" No_argument "\xfe" /*ignored*/ 111 "no-backup-if-mismatch\0" No_argument "\xff" /*ignored*/ 112# endif 113 ; 114 applet_long_options = patch_longopts; 115#endif 116 /* -f,-E,-g are ignored */ 117 opt = getopt32(argv, "p:i:RN""fEg:", &p, &i, NULL); 118 if (opt & OPT_R) 119 plus = '-'; 120 patch_level = xatoi(p); /* can be negative! */ 121 patch_file = xfopen_stdin(i); 122 } 123 124 patch_line = xmalloc_fgetline(patch_file); 125 while (patch_line) { 126 FILE *src_stream; 127 FILE *dst_stream; 128 //char *old_filename; 129 char *new_filename; 130 char *backup_filename = NULL; 131 unsigned src_cur_line = 1; 132 unsigned dst_cur_line = 0; 133 unsigned dst_beg_line; 134 unsigned bad_hunk_count = 0; 135 unsigned hunk_count = 0; 136 smallint copy_trailing_lines_flag = 0; 137 138 /* Skip everything upto the "---" marker 139 * No need to parse the lines "Only in <dir>", and "diff <args>" 140 */ 141 do { 142 /* Extract the filename used before the patch was generated */ 143 new_filename = extract_filename(patch_line, patch_level, "--- "); 144 // was old_filename above 145 patch_line = xmalloc_fgetline(patch_file); 146 if (!patch_line) goto quit; 147 } while (!new_filename); 148 free(new_filename); // "source" filename is irrelevant 149 150 new_filename = extract_filename(patch_line, patch_level, "+++ "); 151 if (!new_filename) { 152 bb_error_msg_and_die("invalid patch"); 153 } 154 155 /* Get access rights from the file to be patched */ 156 if (stat(new_filename, &saved_stat) != 0) { 157 char *slash = strrchr(new_filename, '/'); 158 if (slash) { 159 /* Create leading directories */ 160 *slash = '\0'; 161 bb_make_directory(new_filename, -1, FILEUTILS_RECUR); 162 *slash = '/'; 163 } 164 src_stream = NULL; 165 saved_stat.st_mode = 0644; 166 } else if (!(opt & OPT_dry_run)) { 167 backup_filename = xasprintf("%s.orig", new_filename); 168 xrename(new_filename, backup_filename); 169 src_stream = xfopen_for_read(backup_filename); 170 } else 171 src_stream = xfopen_for_read(new_filename); 172 173 if (opt & OPT_dry_run) { 174 dst_stream = xfopen_for_write("/dev/null"); 175 } else { 176 dst_stream = xfopen_for_write(new_filename); 177 fchmod(fileno(dst_stream), saved_stat.st_mode); 178 } 179 180 printf("patching file %s\n", new_filename); 181 182 /* Handle all hunks for this file */ 183 patch_line = xmalloc_fgets(patch_file); 184 while (patch_line) { 185 unsigned count; 186 unsigned src_beg_line; 187 unsigned hunk_offset_start; 188 unsigned src_last_line = 1; 189 unsigned dst_last_line = 1; 190 191 if ((sscanf(patch_line, "@@ -%d,%d +%d,%d", &src_beg_line, &src_last_line, &dst_beg_line, &dst_last_line) < 3) 192 && (sscanf(patch_line, "@@ -%d +%d,%d", &src_beg_line, &dst_beg_line, &dst_last_line) < 2) 193 ) { 194 /* No more hunks for this file */ 195 break; 196 } 197 if (plus != '+') { 198 /* reverse patch */ 199 unsigned tmp = src_last_line; 200 src_last_line = dst_last_line; 201 dst_last_line = tmp; 202 tmp = src_beg_line; 203 src_beg_line = dst_beg_line; 204 dst_beg_line = tmp; 205 } 206 hunk_count++; 207 208 if (src_beg_line && dst_beg_line) { 209 /* Copy unmodified lines upto start of hunk */ 210 /* src_beg_line will be 0 if it's a new file */ 211 count = src_beg_line - src_cur_line; 212 if (copy_lines(src_stream, dst_stream, count)) { 213 bb_error_msg_and_die("bad src file"); 214 } 215 src_cur_line += count; 216 dst_cur_line += count; 217 copy_trailing_lines_flag = 1; 218 } 219 src_last_line += hunk_offset_start = src_cur_line; 220 dst_last_line += dst_cur_line; 221 222 while (1) { 223 free(patch_line); 224 patch_line = xmalloc_fgets(patch_file); 225 if (patch_line == NULL) 226 break; /* EOF */ 227 if (!*patch_line) { 228 /* whitespace-damaged patch with "" lines */ 229 free(patch_line); 230 patch_line = xstrdup(" "); 231 } 232 if ((*patch_line != '-') && (*patch_line != '+') 233 && (*patch_line != ' ') 234 ) { 235 break; /* End of hunk */ 236 } 237 if (*patch_line != plus) { /* '-' or ' ' */ 238 char *src_line = NULL; 239 if (src_cur_line == src_last_line) 240 break; 241 if (src_stream) { 242 src_line = xmalloc_fgets(src_stream); 243 if (src_line) { 244 int diff = strcmp(src_line, patch_line + 1); 245 src_cur_line++; 246 free(src_line); 247 if (diff) 248 src_line = NULL; 249 } 250 } 251 /* Do not patch an already patched hunk with -N */ 252 if (src_line == 0 && (opt & OPT_N)) { 253 continue; 254 } 255 if (!src_line) { 256 bb_error_msg("hunk #%u FAILED at %u", hunk_count, hunk_offset_start); 257 bad_hunk_count++; 258 break; 259 } 260 if (*patch_line != ' ') { /* '-' */ 261 continue; 262 } 263 } 264 if (dst_cur_line == dst_last_line) 265 break; 266 fputs(patch_line + 1, dst_stream); 267 dst_cur_line++; 268 } /* end of while loop handling one hunk */ 269 } /* end of while loop handling one file */ 270 271 /* Cleanup last patched file */ 272 if (copy_trailing_lines_flag) { 273 copy_lines(src_stream, dst_stream, (unsigned)(-1)); 274 } 275 if (src_stream) { 276 fclose(src_stream); 277 } 278 fclose(dst_stream); 279 if (bad_hunk_count) { 280 ret = 1; 281 bb_error_msg("%u out of %u hunk FAILED", bad_hunk_count, hunk_count); 282 } else { 283 /* It worked, we can remove the backup */ 284 if (backup_filename) { 285 unlink(backup_filename); 286 } 287 if (!(opt & OPT_dry_run) 288 && ((dst_cur_line == 0) || (dst_beg_line == 0)) 289 ) { 290 /* The new patched file is empty, remove it */ 291 xunlink(new_filename); 292 // /* old_filename and new_filename may be the same file */ 293 // unlink(old_filename); 294 } 295 } 296 free(backup_filename); 297 //free(old_filename); 298 free(new_filename); 299 } /* end of "while there are patch lines" */ 300 quit: 301 /* 0 = SUCCESS 302 * 1 = Some hunks failed 303 * 2 = More serious problems (exited earlier) 304 */ 305 return ret; 306} 307