busybox/editors/patch.c
<<
>>
Prefs
   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 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 "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
  78        xfunc_error_retval = 2;
  79        {
  80                const char *p = "-1";
  81                const char *i = "-"; /* compat */
  82                if (getopt32(argv, "p:i:R", &p, &i) & 4)
  83                        plus = '-';
  84                patch_level = xatoi(p); /* can be negative! */
  85                patch_file = xfopen_stdin(i);
  86        }
  87
  88        patch_line = xmalloc_fgetline(patch_file);
  89        while (patch_line) {
  90                FILE *src_stream;
  91                FILE *dst_stream;
  92                //char *old_filename;
  93                char *new_filename;
  94                char *backup_filename;
  95                unsigned src_cur_line = 1;
  96                unsigned dst_cur_line = 0;
  97                unsigned dst_beg_line;
  98                unsigned bad_hunk_count = 0;
  99                unsigned hunk_count = 0;
 100                smallint copy_trailing_lines_flag = 0;
 101
 102                /* Skip everything upto the "---" marker
 103                 * No need to parse the lines "Only in <dir>", and "diff <args>"
 104                 */
 105                do {
 106                        /* Extract the filename used before the patch was generated */
 107                        new_filename = extract_filename(patch_line, patch_level, "--- ");
 108                        // was old_filename above
 109                        patch_line = xmalloc_fgetline(patch_file);
 110                        if (!patch_line) goto quit;
 111                } while (!new_filename);
 112                free(new_filename); // "source" filename is irrelevant
 113
 114                new_filename = extract_filename(patch_line, patch_level, "+++ ");
 115                if (!new_filename) {
 116                        bb_error_msg_and_die("invalid patch");
 117                }
 118
 119                /* Get access rights from the file to be patched */
 120                if (stat(new_filename, &saved_stat) != 0) {
 121                        char *slash = strrchr(new_filename, '/');
 122                        if (slash) {
 123                                /* Create leading directories */
 124                                *slash = '\0';
 125                                bb_make_directory(new_filename, -1, FILEUTILS_RECUR);
 126                                *slash = '/';
 127                        }
 128                        backup_filename = NULL;
 129                        src_stream = NULL;
 130                        saved_stat.st_mode = 0644;
 131                } else {
 132                        backup_filename = xasprintf("%s.orig", new_filename);
 133                        xrename(new_filename, backup_filename);
 134                        src_stream = xfopen_for_read(backup_filename);
 135                }
 136                dst_stream = xfopen_for_write(new_filename);
 137                fchmod(fileno(dst_stream), saved_stat.st_mode);
 138
 139                printf("patching file %s\n", new_filename);
 140
 141                /* Handle all hunks for this file */
 142                patch_line = xmalloc_fgets(patch_file);
 143                while (patch_line) {
 144                        unsigned count;
 145                        unsigned src_beg_line;
 146                        unsigned hunk_offset_start;
 147                        unsigned src_last_line = 1;
 148                        unsigned dst_last_line = 1;
 149
 150                        if ((sscanf(patch_line, "@@ -%d,%d +%d,%d", &src_beg_line, &src_last_line, &dst_beg_line, &dst_last_line) < 3)
 151                         && (sscanf(patch_line, "@@ -%d +%d,%d", &src_beg_line, &dst_beg_line, &dst_last_line) < 2)
 152                        ) {
 153                                /* No more hunks for this file */
 154                                break;
 155                        }
 156                        if (plus != '+') {
 157                                /* reverse patch */
 158                                unsigned tmp = src_last_line;
 159                                src_last_line = dst_last_line;
 160                                dst_last_line = tmp;
 161                                tmp = src_beg_line;
 162                                src_beg_line = dst_beg_line;
 163                                dst_beg_line = tmp;
 164                        }
 165                        hunk_count++;
 166
 167                        if (src_beg_line && dst_beg_line) {
 168                                /* Copy unmodified lines upto start of hunk */
 169                                /* src_beg_line will be 0 if it's a new file */
 170                                count = src_beg_line - src_cur_line;
 171                                if (copy_lines(src_stream, dst_stream, count)) {
 172                                        bb_error_msg_and_die("bad src file");
 173                                }
 174                                src_cur_line += count;
 175                                dst_cur_line += count;
 176                                copy_trailing_lines_flag = 1;
 177                        }
 178                        src_last_line += hunk_offset_start = src_cur_line;
 179                        dst_last_line += dst_cur_line;
 180
 181                        while (1) {
 182                                free(patch_line);
 183                                patch_line = xmalloc_fgets(patch_file);
 184                                if (patch_line == NULL)
 185                                        break; /* EOF */
 186                                if ((*patch_line != '-') && (*patch_line != '+')
 187                                 && (*patch_line != ' ')
 188                                ) {
 189                                        break; /* End of hunk */
 190                                }
 191                                if (*patch_line != plus) { /* '-' or ' ' */
 192                                        char *src_line = NULL;
 193                                        if (src_cur_line == src_last_line)
 194                                                break;
 195                                        if (src_stream) {
 196                                                src_line = xmalloc_fgets(src_stream);
 197                                                if (src_line) {
 198                                                        int diff = strcmp(src_line, patch_line + 1);
 199                                                        src_cur_line++;
 200                                                        free(src_line);
 201                                                        if (diff)
 202                                                                src_line = NULL;
 203                                                }
 204                                        }
 205                                        if (!src_line) {
 206                                                bb_error_msg("hunk #%u FAILED at %u", hunk_count, hunk_offset_start);
 207                                                bad_hunk_count++;
 208                                                break;
 209                                        }
 210                                        if (*patch_line != ' ') { /* '-' */
 211                                                continue;
 212                                        }
 213                                }
 214                                if (dst_cur_line == dst_last_line)
 215                                        break;
 216                                fputs(patch_line + 1, dst_stream);
 217                                dst_cur_line++;
 218                        } /* end of while loop handling one hunk */
 219                } /* end of while loop handling one file */
 220
 221                /* Cleanup last patched file */
 222                if (copy_trailing_lines_flag) {
 223                        copy_lines(src_stream, dst_stream, (unsigned)(-1));
 224                }
 225                if (src_stream) {
 226                        fclose(src_stream);
 227                }
 228                fclose(dst_stream);
 229                if (bad_hunk_count) {
 230                        ret = 1;
 231                        bb_error_msg("%u out of %u hunk FAILED", bad_hunk_count, hunk_count);
 232                } else {
 233                        /* It worked, we can remove the backup */
 234                        if (backup_filename) {
 235                                unlink(backup_filename);
 236                        }
 237                        if ((dst_cur_line == 0) || (dst_beg_line == 0)) {
 238                                /* The new patched file is empty, remove it */
 239                                xunlink(new_filename);
 240                                // /* old_filename and new_filename may be the same file */
 241                                // unlink(old_filename);
 242                        }
 243                }
 244                free(backup_filename);
 245                //free(old_filename);
 246                free(new_filename);
 247        } /* end of "while there are patch lines" */
 248 quit:
 249        /* 0 = SUCCESS
 250         * 1 = Some hunks failed
 251         * 2 = More serious problems (exited earlier)
 252         */
 253        return ret;
 254}
 255