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#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_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_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