hush/editors/patch.c
Rob Landley d921b2ecc0 Remove bb_ prefixes from xfuncs.c (and a few other places), consolidate
things like xasprintf() into xfuncs.c, remove xprint_file_by_name() (it only
had one user), clean up lots of #includes...  General cleanup pass.  What I've
been doing for the last couple days.

And it conflicts!  I've removed httpd.c from this checkin due to somebody else
touching that file.  It builds for me.  I have to catch a bus.  (Now you know
why I'm looking forward to Mercurial.)
2006-08-03 15:41:12 +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(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 = 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 = 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 = 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 = xfopen(backup_filename, "r");
} else {
src_stream = 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);
}