hush/editors/patch.c
Rob Landley baa89b398d Patch to fix bug 868, and some related cleanup while I was in the area.
A tab is now taken as the end of filename if it's there, but if it isn't
(because the timestamp isn't there) we continue with the existing untruncated
line as the filename.
2006-05-07 21:10:06 +00:00

274 lines
7.1 KiB
C

/* vi: set sw=4 ts=4: */
/*
* busybox patch applet to handle the unified diff format.
* Copyright (C) 2003 Glenn McGrath <bug1@iinet.net.au>
*
* Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
*
* This applet is written to work with patches generated by GNU diff,
* where there is equivalent functionality busybox patch shall behave
* as per GNU patch.
*
* There is a SUSv3 specification for patch, however it looks to be
* incomplete, it doesnt even mention unified diff format.
* http://www.opengroup.org/onlinepubs/007904975/utilities/patch.html
*
* Issues
* - Non-interactive
* - Patches must apply cleanly or the hunk will fail.
* - Reject file isnt saved
* -
*/
#include <getopt.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include "busybox.h"
static unsigned int copy_lines(FILE *src_stream, FILE *dest_stream, const unsigned int lines_count)
{
unsigned int i = 0;
while (src_stream && (i < lines_count)) {
char *line;
line = bb_get_line_from_file(src_stream);
if (line == NULL) {
break;
}
if (fputs(line, dest_stream) == EOF) {
bb_perror_msg_and_die("Error writing to new file");
}
free(line);
i++;
}
return(i);
}
/* If patch_level is -1 it will remove all directory names
* char *line must be greater than 4 chars
* returns NULL if the file doesnt exist or error
* returns malloc'ed filename
*/
static char *extract_filename(char *line, int patch_level)
{
char *temp, *filename_start_ptr = line + 4;
int i;
/* Terminate string at end of source filename */
temp = strchr(filename_start_ptr, '\t');
if (temp) *temp = 0;
/* skip over (patch_level) number of leading directories */
for (i = 0; i < patch_level; i++) {
if(!(temp = strchr(filename_start_ptr, '/'))) break;
filename_start_ptr = temp + 1;
}
return(bb_xstrdup(filename_start_ptr));
}
static int file_doesnt_exist(const char *filename)
{
struct stat statbuf;
return(stat(filename, &statbuf));
}
int patch_main(int argc, char **argv)
{
int patch_level = -1;
char *patch_line;
int ret;
FILE *patch_file = NULL;
{
char *p, *i;
ret = bb_getopt_ulflags(argc, argv, "p:i:", &p, &i);
if (ret & 1)
patch_level = bb_xgetlarg(p, 10, -1, USHRT_MAX);
if (ret & 2) {
patch_file = bb_xfopen(i, "r");
} else {
patch_file = stdin;
}
ret = 0;
}
patch_line = bb_get_line_from_file(patch_file);
while (patch_line) {
FILE *src_stream;
FILE *dst_stream;
char *original_filename;
char *new_filename;
char *backup_filename;
unsigned int src_cur_line = 1;
unsigned int dest_cur_line = 0;
unsigned int dest_beg_line;
unsigned int bad_hunk_count = 0;
unsigned int hunk_count = 0;
char copy_trailing_lines_flag = 0;
/* Skip everything upto the "---" marker
* No need to parse the lines "Only in <dir>", and "diff <args>"
*/
while (patch_line && strncmp(patch_line, "--- ", 4) != 0) {
free(patch_line);
patch_line = bb_get_line_from_file(patch_file);
}
/* Extract the filename used before the patch was generated */
original_filename = extract_filename(patch_line, patch_level);
free(patch_line);
patch_line = bb_get_line_from_file(patch_file);
if (strncmp(patch_line, "+++ ", 4) != 0) {
ret = 2;
bb_error_msg("Invalid patch");
continue;
}
new_filename = extract_filename(patch_line, patch_level);
free(patch_line);
if (file_doesnt_exist(new_filename)) {
char *line_ptr;
/* Create leading directories */
line_ptr = strrchr(new_filename, '/');
if (line_ptr) {
*line_ptr = '\0';
bb_make_directory(new_filename, -1, FILEUTILS_RECUR);
*line_ptr = '/';
}
dst_stream = bb_xfopen(new_filename, "w+");
backup_filename = NULL;
} else {
backup_filename = xmalloc(strlen(new_filename) + 6);
strcpy(backup_filename, new_filename);
strcat(backup_filename, ".orig");
if (rename(new_filename, backup_filename) == -1) {
bb_perror_msg_and_die("Couldnt create file %s",
backup_filename);
}
dst_stream = bb_xfopen(new_filename, "w");
}
if ((backup_filename == NULL) || file_doesnt_exist(original_filename)) {
src_stream = NULL;
} else {
if (strcmp(original_filename, new_filename) == 0) {
src_stream = bb_xfopen(backup_filename, "r");
} else {
src_stream = bb_xfopen(original_filename, "r");
}
}
printf("patching file %s\n", new_filename);
/* Handle each hunk */
patch_line = bb_get_line_from_file(patch_file);
while (patch_line) {
unsigned int count;
unsigned int src_beg_line;
unsigned int unused;
unsigned int hunk_offset_start = 0;
int hunk_error = 0;
/* This bit should be improved */
if ((sscanf(patch_line, "@@ -%d,%d +%d,%d @@", &src_beg_line, &unused, &dest_beg_line, &unused) != 4) &&
(sscanf(patch_line, "@@ -%d,%d +%d @@", &src_beg_line, &unused, &dest_beg_line) != 3) &&
(sscanf(patch_line, "@@ -%d +%d,%d @@", &src_beg_line, &dest_beg_line, &unused) != 3)) {
/* No more hunks for this file */
break;
}
free(patch_line);
hunk_count++;
if (src_beg_line && dest_beg_line) {
/* Copy unmodified lines upto start of hunk */
/* src_beg_line will be 0 if its a new file */
count = src_beg_line - src_cur_line;
if (copy_lines(src_stream, dst_stream, count) != count) {
bb_error_msg_and_die("Bad src file");
}
src_cur_line += count;
dest_cur_line += count;
copy_trailing_lines_flag = 1;
}
hunk_offset_start = src_cur_line;
while ((patch_line = bb_get_line_from_file(patch_file)) != NULL) {
if ((*patch_line == '-') || (*patch_line == ' ')) {
char *src_line = NULL;
if (src_stream) {
src_line = bb_get_line_from_file(src_stream);
if (!src_line) {
hunk_error++;
break;
} else {
src_cur_line++;
}
if (strcmp(src_line, patch_line + 1) != 0) {
bb_error_msg("Hunk #%d FAILED at %d.", hunk_count, hunk_offset_start);
hunk_error++;
free(patch_line);
break;
}
free(src_line);
}
if (*patch_line == ' ') {
fputs(patch_line + 1, dst_stream);
dest_cur_line++;
}
} else if (*patch_line == '+') {
fputs(patch_line + 1, dst_stream);
dest_cur_line++;
} else {
break;
}
free(patch_line);
}
if (hunk_error) {
bad_hunk_count++;
}
}
/* Cleanup last patched file */
if (copy_trailing_lines_flag) {
copy_lines(src_stream, dst_stream, -1);
}
if (src_stream) {
fclose(src_stream);
}
if (dst_stream) {
fclose(dst_stream);
}
if (bad_hunk_count) {
if (!ret) {
ret = 1;
}
bb_error_msg("%d out of %d hunk FAILED", bad_hunk_count, hunk_count);
} else {
/* It worked, we can remove the backup */
if (backup_filename) {
unlink(backup_filename);
}
if ((dest_cur_line == 0) || (dest_beg_line == 0)) {
/* The new patched file is empty, remove it */
if (unlink(new_filename) == -1) {
bb_perror_msg_and_die("Couldnt remove file %s", new_filename);
}
if (unlink(original_filename) == -1) {
bb_perror_msg_and_die("Couldnt remove original file %s", new_filename);
}
}
}
}
/* 0 = SUCCESS
* 1 = Some hunks failed
* 2 = More serious problems
*/
return(ret);
}