busybox/editors/patch_bbox.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 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, "@@ -%u,%u +%u,%u", &src_beg_line, &src_last_line, &dst_beg_line, &dst_last_line) < 3)
 192                         && (sscanf(patch_line, "@@ -%u +%u,%u", &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