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 
copy_lines(FILE * src_stream,FILE * dst_stream,unsigned lines_count)24 static 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_simple_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  */
extract_filename(char * line,int patch_level,const char * pat)47 static 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 
68 int patch_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
patch_main(int argc UNUSED_PARAM,char ** argv)69 int 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 #endif
115 		/* -f,-E,-g are ignored */
116 		opt = getopt32long(argv, "p:i:RN""fEg:", patch_longopts, &p, &i, NULL);
117 		if (opt & OPT_R)
118 			plus = '-';
119 		patch_level = xatoi(p); /* can be negative! */
120 		patch_file = xfopen_stdin(i);
121 	}
122 
123 	patch_line = xmalloc_fgetline(patch_file);
124 	while (patch_line) {
125 		FILE *src_stream;
126 		FILE *dst_stream;
127 		//char *old_filename;
128 		char *new_filename;
129 		char *backup_filename = NULL;
130 		unsigned src_cur_line = 1;
131 		unsigned dst_cur_line = 0;
132 		unsigned dst_beg_line;
133 		unsigned bad_hunk_count = 0;
134 		unsigned hunk_count = 0;
135 		smallint copy_trailing_lines_flag = 0;
136 
137 		/* Skip everything upto the "---" marker
138 		 * No need to parse the lines "Only in <dir>", and "diff <args>"
139 		 */
140 		do {
141 			/* Extract the filename used before the patch was generated */
142 			new_filename = extract_filename(patch_line, patch_level, "--- ");
143 			// was old_filename above
144 			patch_line = xmalloc_fgetline(patch_file);
145 			if (!patch_line) goto quit;
146 		} while (!new_filename);
147 		free(new_filename); // "source" filename is irrelevant
148 
149 		new_filename = extract_filename(patch_line, patch_level, "+++ ");
150 		if (!new_filename) {
151 			bb_simple_error_msg_and_die("invalid patch");
152 		}
153 
154 		/* Get access rights from the file to be patched */
155 		if (stat(new_filename, &saved_stat) != 0) {
156 			char *slash = strrchr(new_filename, '/');
157 			if (slash) {
158 				/* Create leading directories */
159 				*slash = '\0';
160 				bb_make_directory(new_filename, -1, FILEUTILS_RECUR);
161 				*slash = '/';
162 			}
163 			src_stream = NULL;
164 			saved_stat.st_mode = 0644;
165 		} else if (!(opt & OPT_dry_run)) {
166 			backup_filename = xasprintf("%s.orig", new_filename);
167 			xrename(new_filename, backup_filename);
168 			src_stream = xfopen_for_read(backup_filename);
169 		} else
170 			src_stream = xfopen_for_read(new_filename);
171 
172 		if (opt & OPT_dry_run) {
173 			dst_stream = xfopen_for_write("/dev/null");
174 		} else {
175 			dst_stream = xfopen_for_write(new_filename);
176 			fchmod(fileno(dst_stream), saved_stat.st_mode);
177 		}
178 
179 		printf("patching file %s\n", new_filename);
180 
181 		/* Handle all hunks for this file */
182 		patch_line = xmalloc_fgets(patch_file);
183 		while (patch_line) {
184 			unsigned count;
185 			unsigned src_beg_line;
186 			unsigned hunk_offset_start;
187 			unsigned src_last_line = 1;
188 			unsigned dst_last_line = 1;
189 
190 			if ((sscanf(patch_line, "@@ -%u,%u +%u,%u", &src_beg_line, &src_last_line, &dst_beg_line, &dst_last_line) < 3)
191 			 && (sscanf(patch_line, "@@ -%u +%u,%u", &src_beg_line, &dst_beg_line, &dst_last_line) < 2)
192 			) {
193 				/* No more hunks for this file */
194 				break;
195 			}
196 			if (plus != '+') {
197 				/* reverse patch */
198 				unsigned tmp = src_last_line;
199 				src_last_line = dst_last_line;
200 				dst_last_line = tmp;
201 				tmp = src_beg_line;
202 				src_beg_line = dst_beg_line;
203 				dst_beg_line = tmp;
204 			}
205 			hunk_count++;
206 
207 			if (src_beg_line && dst_beg_line) {
208 				/* Copy unmodified lines upto start of hunk */
209 				/* src_beg_line will be 0 if it's a new file */
210 				count = src_beg_line - src_cur_line;
211 				if (copy_lines(src_stream, dst_stream, count)) {
212 					bb_simple_error_msg_and_die("bad src file");
213 				}
214 				src_cur_line += count;
215 				dst_cur_line += count;
216 				copy_trailing_lines_flag = 1;
217 			}
218 			src_last_line += hunk_offset_start = src_cur_line;
219 			dst_last_line += dst_cur_line;
220 
221 			while (1) {
222 				free(patch_line);
223 				patch_line = xmalloc_fgets(patch_file);
224 				if (patch_line == NULL)
225 					break; /* EOF */
226 				if (!*patch_line) {
227 					/* whitespace-damaged patch with "" lines */
228 					free(patch_line);
229 					patch_line = xstrdup(" ");
230 				}
231 				if ((*patch_line != '-') && (*patch_line != '+')
232 				 && (*patch_line != ' ')
233 				) {
234 					break; /* End of hunk */
235 				}
236 				if (*patch_line != plus) { /* '-' or ' ' */
237 					char *src_line = NULL;
238 					if (src_cur_line == src_last_line)
239 						break;
240 					if (src_stream) {
241 						src_line = xmalloc_fgets(src_stream);
242 						if (src_line) {
243 							int diff = strcmp(src_line, patch_line + 1);
244 							src_cur_line++;
245 							free(src_line);
246 							if (diff)
247 								src_line = NULL;
248 						}
249 					}
250 					/* Do not patch an already patched hunk with -N */
251 					if (src_line == 0 && (opt & OPT_N)) {
252 						continue;
253 					}
254 					if (!src_line) {
255 						bb_error_msg("hunk #%u FAILED at %u", hunk_count, hunk_offset_start);
256 						bad_hunk_count++;
257 						break;
258 					}
259 					if (*patch_line != ' ') { /* '-' */
260 						continue;
261 					}
262 				}
263 				if (dst_cur_line == dst_last_line)
264 					break;
265 				fputs(patch_line + 1, dst_stream);
266 				dst_cur_line++;
267 			} /* end of while loop handling one hunk */
268 		} /* end of while loop handling one file */
269 
270 		/* Cleanup last patched file */
271 		if (copy_trailing_lines_flag) {
272 			copy_lines(src_stream, dst_stream, (unsigned)(-1));
273 		}
274 		if (src_stream) {
275 			fclose(src_stream);
276 		}
277 		fclose(dst_stream);
278 		if (bad_hunk_count) {
279 			ret = 1;
280 			bb_error_msg("%u out of %u hunk FAILED", bad_hunk_count, hunk_count);
281 		} else {
282 			/* It worked, we can remove the backup */
283 			if (backup_filename) {
284 				unlink(backup_filename);
285 			}
286 			if (!(opt & OPT_dry_run)
287 			 && ((dst_cur_line == 0) || (dst_beg_line == 0))
288 			) {
289 				/* The new patched file is empty, remove it */
290 				xunlink(new_filename);
291 				// /* old_filename and new_filename may be the same file */
292 				// unlink(old_filename);
293 			}
294 		}
295 		free(backup_filename);
296 		//free(old_filename);
297 		free(new_filename);
298 	} /* end of "while there are patch lines" */
299  quit:
300 	/* 0 = SUCCESS
301 	 * 1 = Some hunks failed
302 	 * 2 = More serious problems (exited earlier)
303 	 */
304 	return ret;
305 }
306