From ce948adbe6c19c7df4b908ec12c02fd187d086fc Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Fri, 17 Oct 2025 14:46:14 -0700 Subject: [PATCH 01/18] Make patch arguments more extensible in apply_patch() It's difficult to conditionally add additional arguments to the patch execution in apply_patch() because they are placed within a compound literal array. Make the arguments more extensible by creating a local array and an index variable to place the next argument into the array. This way, it's much easier to change the number of arguments provided at runtime. --- src/interdiff.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/interdiff.c b/src/interdiff.c index d1cc9e25..513a390e 100644 --- a/src/interdiff.c +++ b/src/interdiff.c @@ -936,6 +936,9 @@ output_patch1_only (FILE *p1, FILE *out, int not_reverted) static int apply_patch (FILE *patch, const char *file, int reverted) { +#define MAX_PATCH_ARGS 4 + const char *argv[MAX_PATCH_ARGS]; + int argc = 0; const char *basename; unsigned long orig_lines, new_lines; size_t linelen; @@ -959,10 +962,14 @@ apply_patch (FILE *patch, const char *file, int reverted) } } - w = xpipe(PATCH, &child, "w", (char **) (const char *[]) { PATCH, - reverted ? (has_ignore_all_space ? "-Rlsp0" : "-Rsp0") - : (has_ignore_all_space ? "-lsp0" : "-sp0"), - file, NULL }); + /* Add up to MAX_PATCH_ARGS arguments for the patch execution */ + argv[argc++] = PATCH; + argv[argc++] = reverted ? (has_ignore_all_space ? "-Rlsp0" : "-Rsp0") + : (has_ignore_all_space ? "-lsp0" : "-sp0"); + argv[argc++] = file; + argv[argc++] = NULL; + + w = xpipe(PATCH, &child, "w", (char **) argv); fprintf (w, "--- %s\n+++ %s\n", basename, basename); line = NULL; From dcd431d0fba1c597365d506edb1f5f873ebeea7d Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Thu, 30 Oct 2025 17:25:23 -0700 Subject: [PATCH 02/18] Simplify original file creation in output_delta() Remove the superfluous fseeks and simplify the original file creation process by moving relevant fseeks to come right after the file cursor was last modified. --- src/interdiff.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/interdiff.c b/src/interdiff.c index 513a390e..3f9e837c 100644 --- a/src/interdiff.c +++ b/src/interdiff.c @@ -1241,19 +1241,16 @@ output_delta (FILE *p1, FILE *p2, FILE *out) fseek (p1, pos1, SEEK_SET); fseek (p2, pos2, SEEK_SET); create_orig (p2, &file, 0, NULL); - fseek (p1, pos1, SEEK_SET); - fseek (p2, pos2, SEEK_SET); create_orig (p1, &file2, mode == mode_combine, NULL); - merge_lines(&file, &file2); pos1 = ftell (p1); + fseek (p1, start1, SEEK_SET); + fseek (p2, start2, SEEK_SET); + merge_lines(&file, &file2); /* Write it out. */ write_file (&file, tmpp1fd); write_file (&file, tmpp2fd); - fseek (p1, start1, SEEK_SET); - fseek (p2, start2, SEEK_SET); - if (apply_patch (p1, tmpp1, mode == mode_combine)) error (EXIT_FAILURE, 0, "Error applying patch1 to reconstructed file"); From 430bbfce143e89725fb23d8f9d229a0873aa91a2 Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Thu, 13 Nov 2025 17:36:12 -0800 Subject: [PATCH 03/18] Exclude newline character from colorized output Coloring the newline character results in the terminal cursor becoming colored when the final line in the interdiff is colored. Fix this by not coloring the newline character. --- src/interdiff.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/interdiff.c b/src/interdiff.c index 3f9e837c..460c1a90 100644 --- a/src/interdiff.c +++ b/src/interdiff.c @@ -1143,7 +1143,8 @@ trim_context (FILE *f /* positioned at start of @@ line */, fwrite (line, (size_t) got, 1, out); continue; } - print_color (out, type, "%s", line); + print_color (out, type, "%.*s", (int) got - 1, line); + fputc ('\n', out); } } From 29ff82547fbb48c3efcdbb2c71126a72d39c97bb Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Fri, 14 Nov 2025 09:19:49 -0800 Subject: [PATCH 04/18] Fix content skipping for patch2 in index_patch_generic() When an @@ line isn't immediately after the +++ line in patch2, the next line is checked from the top of the loop which tries to search for a +++ line again, even though the +++ was already found. This results in the +++ not being found again and thus a spurious error that patch2 is empty. Fix this by making the patch2 case loop over the next line until either an @@ is found or the patch is exhausted. --- src/interdiff.c | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/src/interdiff.c b/src/interdiff.c index 460c1a90..2a15eef0 100644 --- a/src/interdiff.c +++ b/src/interdiff.c @@ -1447,29 +1447,35 @@ index_patch_generic (FILE *patch_file, struct file_list **file_list, int need_sk /* For patch2, we need to handle the @@ line and skip content */ if (need_skip_content) { - if (getline (&line, &linelen, patch_file) == -1) { + int found = 0; + + while (!found && + getline (&line, &linelen, patch_file) > 0) { + if (strncmp (line, "@@ ", 3)) + continue; + + p = strchr (line + 3, '+'); + if (!p) + continue; + p = strchr (p, ','); + if (p) { + /* Like '@@ -1,3 +1,3 @@' */ + p++; + skip = strtoul (p, &end, 10); + if (p == end) + continue; + } else + /* Like '@@ -1 +1 @@' */ + skip = 1; + found = 1; + } + + if (!found) { free (names[0]); free (names[1]); break; } - if (strncmp (line, "@@ ", 3)) - goto try_next; - - p = strchr (line + 3, '+'); - if (!p) - goto try_next; - p = strchr (p, ','); - if (p) { - /* Like '@@ -1,3 +1,3 @@' */ - p++; - skip = strtoul (p, &end, 10); - if (p == end) - goto try_next; - } else - /* Like '@@ -1 +1 @@' */ - skip = 1; - add_to_list (file_list, best_name (2, names), pos); while (skip--) { @@ -1485,7 +1491,6 @@ index_patch_generic (FILE *patch_file, struct file_list **file_list, int need_sk add_to_list (file_list, best_name (2, names), pos); } - try_next: free (names[0]); free (names[1]); } From 365f24f3c8c4dcad94f6a4c8857386b5dff0098f Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Thu, 20 Nov 2025 00:04:35 -0800 Subject: [PATCH 05/18] Implement an advanced fuzzy diffing feature for interdiff This implements a --fuzzy option to make interdiff perform a fuzzy comparison between two diffs. This is very helpful, for example, for comparing a backport patch to its upstream source patch to assist a human reviewer in verifying the correctness of the backport. The fuzzy diffing process is complex and works by: - Generating a new patch file with hunks split up into smaller hunks to separate out multiple deltas (+/- lines) in a single hunk that are spaced apart by context lines, increasing the amount of deltas that can be applied successfully with fuzz - Applying the rewritten p1 patch to p2's original file, and the rewritten p2 patch to p1's original file; the original files aren't ever merged - Relocating patched hunks in only p1's original file to align with their respective locations in the other file, based on the reported line offset printed out by `patch` for each hunk it successfully applied - Squashing unline gaps fewer than max_context*2 lines between hunks in the patched files, to hide unknown contextual information that is irrelevant for comparing the two diffs while also improving hunk alignment between the two patched files - Diffing the two patched files as usual - Rewriting the hunks in the diff output to exclude unlines from the unified diff, even splitting up hunks to remove unlines present in the middle of a hunk, while also adjusting the @@ line to compensate for the change in line offsets - Emitting the rewritten diff output while interleaving rejected hunks from both p1 and p2 in the output in order by line number, with a comment on the @@ line indicating when an emitted hunk is a rejected hunk This also involves working around some bugs in `patch` itself encountered along the way, such as occasionally inaccurate line offsets printed out and spurious fuzzing in certain cases that involve hunks with an unequal number of pre-context and post-context lines. The end result of all of this is a minimal set of real differences in the context lines of each hunk between the user's provided diffs. Even when fuzzing results in a faulty patch, the context differences are shown so there is never a risk of any real deltas getting hidden due to fuzzing. By default, the fuzz factor used is just the default used in `patch`. The fuzz factor can be adjusted by the user via appending =N to `--fuzzy` to specify the maximum number of context lines for `patch` to fuzz. --- src/interdiff.c | 1101 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 1071 insertions(+), 30 deletions(-) diff --git a/src/interdiff.c b/src/interdiff.c index 2a15eef0..b3535017 100644 --- a/src/interdiff.c +++ b/src/interdiff.c @@ -37,6 +37,7 @@ #include #include #include +#include #ifdef HAVE_UNISTD_H # include #endif /* HAVE_UNISTD_H */ @@ -47,6 +48,8 @@ #ifdef HAVE_SYS_WAIT_H # include #endif /* HAVE_SYS_WAIT_H */ +#include +#include #include "util.h" #include "diff.h" @@ -110,6 +113,38 @@ struct lines_info { struct lines *tail; }; +struct hunk_info { + char *s; /* Start of hunk */ + size_t len; /* Length of hunk in bytes */ + unsigned long nstart; /* Starting line number */ + unsigned long nend; /* Ending line number (inclusive) */ + int relocated:1, /* Whether or not this hunk was relocated */ + discard:1; /* Whether or not to discard this hunk */ +}; + +struct hunk_reloc { + unsigned long new; /* New starting line number */ + long off; /* Offset from the old starting line number */ + unsigned long fuzz; /* Fuzz amount reported by patch */ + int ignored:1; /* Whether or not this relocation was ignored */ +}; + +struct line_info { + const char *s; /* Start of line */ + size_t len; /* Length of line in bytes */ +}; + +struct xtra_context { + unsigned long num; /* Number of extra context lines */ + char *s; /* String of extra context lines */ + size_t len; /* Length of extra context string in bytes */ +}; + +struct rej_file { + FILE *fp; + unsigned long off; +}; + static int human_readable = 1; static char *diff_opts[100]; static int num_diff_opts = 0; @@ -122,6 +157,8 @@ static int no_revert_omitted = 0; static int use_colors = 0; static int color_option_specified = 0; static int debug = 0; +static int fuzzy = 0; +static int max_fuzz_user = -1; static struct patlist *pat_drop_context = NULL; @@ -934,18 +971,19 @@ output_patch1_only (FILE *p1, FILE *out, int not_reverted) } static int -apply_patch (FILE *patch, const char *file, int reverted) +apply_patch (FILE *patch, const char *file, int reverted, FILE **out) { -#define MAX_PATCH_ARGS 4 +#define MAX_PATCH_ARGS 9 const char *argv[MAX_PATCH_ARGS]; int argc = 0; const char *basename; unsigned long orig_lines, new_lines; + char *line, *fuzz_arg = NULL; size_t linelen; - char *line; + int fildes[4]; + FILE *r, *w; pid_t child; int status; - FILE *w; basename = strrchr (file, '/'); if (basename) @@ -964,12 +1002,68 @@ apply_patch (FILE *patch, const char *file, int reverted) /* Add up to MAX_PATCH_ARGS arguments for the patch execution */ argv[argc++] = PATCH; - argv[argc++] = reverted ? (has_ignore_all_space ? "-Rlsp0" : "-Rsp0") - : (has_ignore_all_space ? "-lsp0" : "-sp0"); + argv[argc++] = reverted ? (has_ignore_all_space ? "-Rlp0" : "-Rp0") + : (has_ignore_all_space ? "-lp0" : "-p0"); + if (fuzzy) { + int fuzz = 0; + + /* Don't generate .orig files when we expect rejected hunks */ + argv[argc++] = "--no-backup-if-mismatch"; + + /* When reverting a rejected hunk, use the maximum possible + * fuzz, don't generate .rej files, and don't let patch ask to + * unreverse our hunk. Otherwise, either pass in the user- + * supplied max fuzz, or fuzz all but one pre-context and one + * post-context line by default. */ + if (reverted) { + fuzz = INT_MAX; + argv[argc++] = "--reject-file=-"; + argv[argc++] = "-N"; + } else if (max_fuzz_user >= 0) { + fuzz = max_fuzz_user; + } else if (max_context) { + fuzz = max_context - 1; + } + if (asprintf (&fuzz_arg, "--fuzz=%d", fuzz) < 0) + error (EXIT_FAILURE, errno, "asprintf failed"); + argv[argc++] = fuzz_arg; + } + /* Fuzzy mode needs hunk offset messages. Only silence output when + * piping stdout wasn't requested. */ + if (!out) + argv[argc++] = "--silent"; argv[argc++] = file; argv[argc++] = NULL; - w = xpipe(PATCH, &child, "w", (char **) argv); + /* Flush any pending writes, set up two pipes, and then fork */ + fflush (NULL); + if (pipe (fildes) == -1 || pipe (&fildes[2]) == -1) + error (EXIT_FAILURE, errno, "pipe failed"); + child = fork (); + if (child == -1) { + perror ("fork"); + exit (1); + } + + if (child == 0) { + /* Keep two pipes: one open to stdin, one to stdout */ + close (0); + close (1); + if (dup (fildes[0]) == -1 || dup (fildes[3]) == -1) + error (EXIT_FAILURE, errno, "dup failed"); + close (fildes[0]); + close (fildes[1]); + close (fildes[2]); + close (fildes[3]); + execvp (argv[0], (char **)argv); + } + free (fuzz_arg); + + /* Open the read and write ends of the two pipes */ + if (!(r = fdopen (fildes[2], "r")) || !(w = fdopen (fildes[1], "w"))) + error (EXIT_FAILURE, errno, "fdopen"); + close (fildes[0]); + close (fildes[3]); fprintf (w, "--- %s\n+++ %s\n", basename, basename); line = NULL; @@ -1006,6 +1100,12 @@ apply_patch (FILE *patch, const char *file, int reverted) fclose (w); waitpid (child, &status, 0); + /* Provide the output from patch if requested */ + if (out) + *out = r; + else + fclose (r); + if (line) free (line); @@ -1031,9 +1131,12 @@ trim_context (FILE *f /* positioned at start of @@ line */, unsigned long orig_count, orig_orig_count, new_orig_count; unsigned long new_count, orig_new_count, new_new_count; unsigned long total_count = 0; + char *atat_comment; + ssize_t got; /* Read @@ line. */ - if (getline (&line, &linelen, f) < 0) + got = getline (&line, &linelen, f); + if (got < 0) break; if (line[0] == '\\') { @@ -1043,10 +1146,16 @@ trim_context (FILE *f /* positioned at start of @@ line */, } if (read_atatline (line, &orig_offset, &orig_count, - &new_offset, &new_count)) + &new_offset, &new_count) || + !(atat_comment = strstr (line + 1, "@@"))) error (EXIT_FAILURE, 0, "Line not understood: %s", line); + /* Check if there's a comment after the @@ line to retain */ + if (atat_comment + 3 - line < got) + atat_comment = xstrdup (atat_comment + 2); + else + atat_comment = NULL; orig_orig_count = new_orig_count = orig_count; orig_new_count = new_new_count = new_count; fgetpos (f, &pos); @@ -1107,18 +1216,25 @@ trim_context (FILE *f /* positioned at start of @@ line */, fsetpos (f, &pos); if (new_orig_count != 1 && new_new_count != 1) - print_color (out, LINE_HUNK, "@@ -%lu,%lu +%lu,%lu @@\n", + print_color (out, LINE_HUNK, "@@ -%lu,%lu +%lu,%lu @@", orig_offset, new_orig_count, new_offset, new_new_count); else if (new_orig_count != 1) - print_color (out, LINE_HUNK, "@@ -%lu,%lu +%lu @@\n", + print_color (out, LINE_HUNK, "@@ -%lu,%lu +%lu @@", orig_offset, new_orig_count, new_offset); else if (new_new_count != 1) - print_color (out, LINE_HUNK, "@@ -%lu +%lu,%lu @@\n", + print_color (out, LINE_HUNK, "@@ -%lu +%lu,%lu @@", orig_offset, new_offset, new_new_count); else - print_color (out, LINE_HUNK, "@@ -%lu +%lu @@\n", + print_color (out, LINE_HUNK, "@@ -%lu +%lu @@", orig_offset, new_offset); + if (atat_comment) { + fputs (atat_comment, out); + free (atat_comment); + } else { + fputc ('\n', out); + } + while (total_count--) { enum line_type type; ssize_t got = getline (&line, &linelen, f); @@ -1157,17 +1273,859 @@ trim_context (FILE *f /* positioned at start of @@ line */, return 0; } +static void +output_rej_hunks (const char *diff, struct rej_file **rej1, + struct rej_file **rej2, FILE *out) +{ + char *line = NULL; + + while (*rej1 || *rej2) { + struct rej_file **rej_ptr = rej1, *rej; + int first_line_done = 0, patch_id = 1; + unsigned long diff_off; + long next_atat_pos; + size_t linelen; + ssize_t got; + + /* Pick the reject hunk that comes first */ + if (!*rej1 || (*rej2 && (*rej2)->off < (*rej1)->off)) { + rej_ptr = rej2; + patch_id = 2; + } + rej = *rej_ptr; + + if (diff) { + /* Wait until the current diff line is an @@ line */ + if (strncmp (diff, "@@ ", 3)) + return; + + if (read_atatline (diff, &diff_off, NULL, NULL, NULL)) + error (EXIT_FAILURE, 0, "line not understood: %s", + diff); + + /* Stop if the diff hunk comes next */ + if (rej->off > diff_off) + return; + } + + /* Write the rej hunk until EOF or the next @@ line (i.e., next + * hunk). Note that rej starts at the current @@ line that we + * must write, so don't look for the next @@ until after the + * first line is written. */ + for (;;) { + got = getline (&line, &linelen, rej->fp); + if (got <= 0) { + if (feof (rej->fp)) + goto rej_file_eof; + error (EXIT_FAILURE, errno, + "Failed to read line from .rej"); + } + if (first_line_done) { + if (!strncmp (line, "@@ ", 3)) + break; + + fwrite (line, (size_t) got, 1, out); + next_atat_pos = ftell (rej->fp); + } else { + /* Append a comment after the @@ line indicating + * this is a rejected hunk. */ + first_line_done = 1; + fwrite (line, (size_t) got - 1, 1, out); + fprintf (out, " INTERDIFF: rejected hunk from patch%d, cannot diff context\n", + patch_id); + } + } + + /* Record the line offset of the next rej hunk, if any */ + if (read_atatline (line, &rej->off, NULL, NULL, NULL)) + error (EXIT_FAILURE, 0, "line not understood: %s", line); + fseek (rej->fp, next_atat_pos, SEEK_SET); + + if (!feof (rej->fp)) + continue; + +rej_file_eof: + /* Clear out this reject file pointer when it's finished */ + *rej_ptr = NULL; + } + + free (line); +} + +/* `xctx` must come with `num` initialized and `s` and `len` zeroed */ +static void +ctx_lookbehind (const struct line_info *lines, unsigned long start_line_idx, + struct xtra_context *xctx) +{ + unsigned long i, num = 0; + + for (i = start_line_idx - 1; i < start_line_idx; i--) { + const struct line_info *line = &lines[i]; + + if (*line->s == '+') + continue; + + /* Copy out the line and ensure the first character is a space, + * since it may be a minus. */ + xctx->s = xrealloc (xctx->s, xctx->len + line->len); + memmove (xctx->s + line->len, xctx->s, xctx->len); + memcpy (xctx->s, line->s, line->len); + *xctx->s = ' '; + xctx->len += line->len; + + /* Quit when we've got the desired number of context lines */ + if (++num == xctx->num) + return; + } + + /* Record the actual number of extra content lines found, since it is + * less than the number of lines requested. */ + xctx->num = num; +} + +/* `xctx` must come with `num` initialized and `s` and `len` zeroed */ +static void +ctx_lookahead (const char *hunk, size_t hlen, struct xtra_context *xctx) +{ + const char *line, *next_line; + unsigned long num = 0; + size_t linelen; + + /* `hunk` is positioned at the first character of the current line + * parsed by split_patch_hunks(). Reduce it by one first to go to the + * newline character of the previous line, to make our loop simpler. */ + for (line = hunk - 1;; line = next_line) { + /* Go to the character _after_ the newline character */ + line++; + + /* Get the next line now to find the length of the line */ + next_line = memchr (line, '\n', hunk + hlen - line); + if (*line == '+') + continue; + + linelen = next_line + 1 - line; + + /* Copy out the line and ensure the first character is a space, + * since it may be a minus. */ + xctx->s = xrealloc (xctx->s, xctx->len + linelen); + memcpy (xctx->s + xctx->len, line, linelen); + xctx->s[xctx->len] = ' '; + xctx->len += linelen; + + /* Quit when we've got the desired number of context lines */ + if (++num == xctx->num) + break; + + /* Stop when this is the end of the hunk, recording the actual + * number of extra context lines found. */ + if (!next_line || next_line + 1 == hunk + hlen) { + xctx->num = num; + break; + } + } +} + +/* Squash up to max_context*2 unlines between two hunks */ +static int +squash_unline_gap (const char **line_ptr, size_t hlen, const char *unline, + size_t unline_len) +{ + const char *hunk = *line_ptr, *line = hunk, *prev = line; + unsigned int num_unlines = 1; + int squash = 0; + + for (; (line = memchr (line, '\n', hunk + hlen - line)); prev = line) { + /* Go to the character _after_ the newline character */ + line++; + + /* Stop when there's nothing left */ + if (line == hunk + hlen) + break; + + /* Move the line pointer to the last unline in the chunk of up + * to max_context*2 unlines so the loop in split_patch_hunks() + * skips over it and thus skips over the entire unline chunk. */ + if (strncmp (line + 1, unline, unline_len)) { + squash = 1; + break; + } + + if (++num_unlines > max_context * 2) + break; + } + + /* Always advance the line pointer even without squashing */ + *line_ptr = prev; + return squash; +} + +static void +write_xctx (struct xtra_context *xctx, FILE *out) +{ + if (xctx->s) { + fwrite (xctx->s, xctx->len, 1, out); + free (xctx->s); + } +} + +/* Regenerate a patch with the hunks split up to ensure more of the patch gets + * applied successfully. Outputs a `hunk_offs` array (if requested) to map each + * hunk's post-split offset from the original hunk's new line number. + * + * When the unline is provided, that is a hint to strip unlines from context and + * perform splits at unlines in the middle of a hunk. */ +static FILE * +split_patch_hunks (FILE *patch, size_t len, char *file, + unsigned long **hunk_offs, const char *unline) +{ + char *fbuf, *hunk, *next_hunk; + unsigned long hnum = 0; + int has_output = 0; + size_t unline_len; + FILE *out; + + /* Read the patch into a NUL-terminated buffer */ + if (len) { + fbuf = xmalloc (len + 1); + if (fread (fbuf, 1, len, patch) != len) + error (EXIT_FAILURE, errno, "fread() of patch failed"); + } else { + /* The patch is a pipe; we can't seek it, so read until EOF */ + fbuf = NULL; + for (int ch; (ch = fgetc (patch)) != EOF;) { + fbuf = xrealloc (fbuf, ++len + 1); + fbuf[len - 1] = ch; + } + fclose (patch); + } + fbuf[len] = '\0'; + + /* Find the first hunk. `fbuf` is positioned at the start of a line. */ + if (!strncmp (fbuf, "@@ ", 3)) { + hunk = fbuf; + } else { + hunk = strstr (fbuf, "\n@@ "); + if (!hunk) + error (EXIT_FAILURE, 0, "patch file malformed: %s", fbuf); + } + + if (unline) { + /* Create a temporary file for the unline-cleansed output */ + out = xtmpfile (); + + /* Find the length of the unline now to use it in the loop */ + unline_len = strlen (unline); + } else { + /* Create the output file by temporarily modifying `file` */ + strcat (file, ".patch"); + out = xopen (file, "w+"); + file[strlen (file) - strlen (".patch")] = '\0'; + } + + do { + /* nctx[0] = pre-context lines, nctx[1] = post-context lines + * ndelta[0] = deleted lines, ndelta[1] = added lines */ + unsigned long nctx[2] = {}, ndelta[2] = {}, nctx_target; + unsigned long ostart, nstart, orig_nstart, start_line_idx = 0; + struct xtra_context xctx_pre = {}; + struct line_info *lines = NULL; + unsigned long num_lines = 0; + int skipped_lines = 0; + const char *line; + size_t hlen; + + if (read_atatline (hunk, &ostart, NULL, &nstart, NULL)) + error (EXIT_FAILURE, 0, "line not understood: %s", + strsep (&hunk, "\n")); + + /* Save the original hunk's new line number */ + orig_nstart = nstart; + + /* Find the next hunk now to tell where the current hunk ends */ + next_hunk = strstr (hunk, "\n@@ "); + if (next_hunk) + hlen = ++next_hunk - hunk; + else + hlen = strlen (hunk); + + /* Count the number of pre-context and post-context lines in + * this hunk. The greater of the two will be the number of pre- + * context and post-context lines targeted per split hunk. */ + if (!unline) { + unsigned long orig_hunk_nctx[2] = {}; + + for (line = hunk; + (line = memchr (line, '\n', hunk + hlen - line)) && + line[1] == ' '; line++, orig_hunk_nctx[0]++); + for (line = hunk + hlen - 1; + (line = memrchr (hunk, '\n', line - hunk)) && + line[1] == ' '; line--, orig_hunk_nctx[1]++); + nctx_target = MAX (orig_hunk_nctx[0], orig_hunk_nctx[1]); + } + + /* Split this hunk into multiple smaller hunks, if possible. + * This is done by looking for deltas (+/- lines) that aren't + * contiguous and thus have context lines in between them. Note + * that the first line is intentionally skipped because the + * first line is the @@ line. When no splitting occurs, this + * still has the effect of trimming context lines for the hunk + * to ensure the number of pre-context lines and post-context + * lines are equal. */ + for (line = hunk; (line = memchr (line, '\n', hunk + hlen - line));) { + unsigned long start_off = 0, onum, nnum; + struct line_info *start_line, *end_line; + struct xtra_context xctx_post = {}; + size_t hlen_rem; + + /* Go to the character _after_ the newline character */ + line++; + + /* Set the length of the previous line (if any). Only do + * this once because when doing unline splitting, the + * unlines aren't recorded into the lines array. */ + if (lines && !lines[num_lines - 1].len) + lines[num_lines - 1].len = + line - lines[num_lines - 1].s; + + /* Check if this is the end. If so, terminate the hunk + * now because there isn't any new line to parse. */ + hlen_rem = hunk + hlen - line; + if (!hlen_rem) + goto split_hunk_incl_latest; + + /* Check if this is an unline that we need to remove */ + if (unline && !strncmp (line + 1, unline, unline_len)) { + /* Split the hunk now if there's a delta, unless + * this is a bogus hunk from a rejected patch + * hunk. Bogus hunks stem from one side of the + * diff operation consisting only of unlines. + * Such diffs have only unlines in their context + * and only one delta type: either additions or + * subtractions, _not_ both. Discard bogus hunks + * by skipping over them here, which is fine + * since the corresponding rejected patch hunk + * is emitted later. + * + * Sometimes a hunk may appear bogus when it is + * not; this can be identified by checking if + * there are no more than max_context*2 unlines + * until the next hunk. Squash the unlines away + * in that case, which alters the line numbers + * of the hunk as a side effect. The assumption + * is that these two hunks are related to each + * other but are just slightly offset in the two + * diffed files due to small bits of missing + * context that were filled in with unlines. */ + if (ndelta[0] || ndelta[1]) { + if (nctx[0] || nctx[1] || + (ndelta[0] && ndelta[1])) + goto split_hunk_incl_latest; + + if (squash_unline_gap (&line, hlen_rem, + unline, + unline_len)) { + skipped_lines = 1; + continue; + } + } + + /* Move forward the starting line offset, + * discarding any pre-context lines seen. The + * starting line index is set to the _next_ + * (non-unline) line, which may not exist. */ + start_line_idx = num_lines; + start_off += nctx[0] + 1; + nctx[0] = 0; + continue; + } + + /* Record the current line, setting `len` to zero */ + lines = xrealloc (lines, ++num_lines * sizeof (*lines)); + lines[num_lines - 1] = (typeof(*lines)){ line }; + + /* Track +/- lines as well as pre-context and post- + * context lines. Split the hunk upon encountering a +/- + * line after post-context lines, unless we're splitting + * at unlines instead. */ + if (*line == '+' || *line == '-') { + if (!unline && nctx[1]) { + /* The current line belongs to the + * _next_ split hunk. Exclude it. */ + end_line = &lines[num_lines - 2]; + goto split_hunk; + } + + ndelta[*line == '+']++; + } else { + nctx[ndelta[0] || ndelta[1]]++; + } + + /* Keep parsing until there's a need to do a split */ + continue; + +split_hunk_incl_latest: + /* Split the hunk including the latest recorded line */ + end_line = &lines[num_lines - 1]; +split_hunk: + /* Stop now if there are no lines left to make a hunk */ + if (start_line_idx == num_lines) + break; + + /* Check that there's an actual delta recorded */ + if (!ndelta[0] && !ndelta[1]) + error (EXIT_FAILURE, 0, "hunk without +/- lines?"); + + /* Split the current hunk by terminating it and starting + * a new hunk. When generating a patch to apply, there + * must be the same number of pre-context lines as post- + * context lines, otherwise patch will need to fuzz the + * extra context lines. An exception is when the context + * is at either the beginning or end of the file. Target + * having the same number of pre-context and post- + * context lines as the original hunk itself, so the + * user-provided fuzz factor behaves as expected. Note + * that this adjustment impacts ostart and nstart either + * for the current split hunk or the next split hunk. */ + start_line = &lines[start_line_idx]; + if (unline) { + /* Add the start offset to the old/new lines */ + ostart += start_off; + nstart += start_off; + } else if (nctx[1] < nctx_target && hlen_rem) { + /* If the number of post-context lines is still + * below the target number afterwards, then it + * means we hit the end of the original hunk + * itself. It's technically fine because it + * means the original hunk came with an unequal + * number of pre- and post-context lines. */ + xctx_post.num = nctx_target - nctx[1]; + ctx_lookahead (line, hlen_rem, &xctx_post); + } + + /* Calculate the old and new line counts */ + onum = nnum = xctx_pre.num + /* Extra pre-context */ + end_line + 1 - start_line + /* Hunk */ + xctx_post.num; /* Extra post-context */ + onum -= ndelta[1]; + nnum -= ndelta[0]; + + /* Emit the hunk to the output file */ + fprintf (out, "@@ -%lu,%lu +%lu,%lu @@\n", + ostart, onum, nstart, nnum); + write_xctx (&xctx_pre, out); + /* If lines were skipped, then the output needs to be + * written one line at a time. */ + if (skipped_lines) { + skipped_lines = 0; + for (unsigned long i = start_line_idx; + &lines[i] <= end_line; i++) + fwrite (lines[i].s, lines[i].len, 1, out); + } else { + fwrite (start_line->s, + end_line->s + end_line->len - start_line->s, + 1, out); + } + write_xctx (&xctx_post, out); + has_output = 1; + + /* Save the offset from this hunk's original new line */ + if (hunk_offs) { + *hunk_offs = xrealloc (*hunk_offs, ++hnum * + sizeof (*hunk_offs)); + (*hunk_offs)[hnum - 1] = nstart - orig_nstart; + } + + /* Stop when there's nothing left */ + if (!hlen_rem) + break; + + /* Start the next hunk */ + start_line_idx = num_lines; + ostart += onum; + nstart += nnum; + if (unline) { + /* The current line is not included in the next + * hunk when splitting at unlines. */ + nctx[0] = nctx[1] = ndelta[0] = ndelta[1] = 0; + } else { + /* Find extra pre-context if extra post-context + * was used for this split hunk, since it means + * that there isn't enough normal post-context + * to be the next split hunk's pre-context. */ + start_line_idx -= 1 + nctx[1]; + xctx_pre = (typeof(xctx_pre)){ xctx_post.num }; + if (xctx_pre.num) + ctx_lookbehind (lines, start_line_idx, + &xctx_pre); + + /* Subtract the extra post-context lines of this + * hunk, the normal post-context lines of this + * hunk, and the extra pre-context lines for the + * _next_ hunk to get the _next_ hunk's starting + * line numbers. */ + ostart -= xctx_pre.num + xctx_post.num + nctx[1]; + nstart -= xctx_pre.num + xctx_post.num + nctx[1]; + nctx[0] = nctx[1]; + nctx[1] = 0; + ndelta[1] = *line == '+'; + ndelta[0] = !ndelta[1]; + } + } + free (lines); + } while ((hunk = next_hunk)); + free (fbuf); + + /* No output, no party. Can happen if the hunks were only unlines. */ + if (!has_output) { + fclose (out); + return NULL; + } + + /* Reposition the output file back to the beginning */ + rewind (out); + return out; +} + +static int +hunk_info_cmp (const void *lhs_ptr, const void *rhs_ptr) +{ + const struct hunk_info *lhs = lhs_ptr, *rhs = rhs_ptr; + + return lhs->nstart - rhs->nstart; +} + +static int +hunk_reloc_cmp (const void *lhs_ptr, const void *rhs_ptr) +{ + const struct hunk_reloc *lhs = lhs_ptr, *rhs = rhs_ptr; + + return lhs->new - rhs->new; +} + +static void +parse_fuzzed_hunks (FILE *patch_out, const unsigned long *hunk_offs, + struct hunk_reloc **relocs, unsigned long *num_relocs) +{ + char *line = NULL; + size_t linelen; + + /* Parse out each fuzzed hunk's line offset */ + while (getline (&line, &linelen, patch_out) > 0) { + struct hunk_reloc *prev = &(*relocs)[*num_relocs - 1]; + unsigned long fuzz = 0, hnum, lnum; + long off; + + if (sscanf (line, "Hunk #%lu succeeded at %lu (offset %ld", + &hnum, &lnum, &off) != 3 && + sscanf (line, "Hunk #%lu succeeded at %lu with fuzz %lu (offset %ld", + &hnum, &lnum, &fuzz, &off) != 4) + continue; + + /* Recover the correct new line number of the possibly-split + * hunk, and skip it if it matches the relocated new line number + * of the previous hunk (if any). Split hunks are contiguous. */ + lnum -= hunk_offs[hnum - 1]; + if (*relocs && lnum - off == prev->new - prev->off) + continue; + + *relocs = xrealloc (*relocs, ++*num_relocs * sizeof (**relocs)); + (*relocs)[*num_relocs - 1] = + (typeof (**relocs)){ lnum, off, fuzz }; + } + free (line); +} + +static void +fuzzy_relocate_hunks (const char *file, const char *unline, FILE *patch_out, + const unsigned long *hunk_offs) +{ + struct hunk_info *hunks = NULL; + struct hunk_reloc *relocs = NULL; + unsigned long num_hunks = 0, num_relocs = 0; + unsigned long i, j, num_unlines = 0; + char *end, *endl, *fbuf, *start; + int new_hunk = 1; + size_t unlinelen; + struct stat st; + FILE *fp; + + /* Parse the fuzzed hunks when relocating for line offset differences */ + if (patch_out) + parse_fuzzed_hunks (patch_out, hunk_offs, &relocs, &num_relocs); + + /* Open the patched file and copy it into a buffer */ + if (stat (file, &st) < 0) + error (EXIT_FAILURE, errno, "stat() fail"); + fbuf = xmalloc (st.st_size); + fp = xopen (file, "r"); + if (fread (fbuf, 1, st.st_size, fp) != st.st_size) + error (EXIT_FAILURE, errno, "fread() fail"); + fclose (fp); + + /* Sort the relocations array by ascending order of new line number. A + * relocation may indicate that a contiguous block of code should + * actually be split into two or more hunks to better align with the + * other file, since they are split up in the other file. Sorting the + * relocations is needed for tracking this during hunk enumeration. */ + if (relocs) + qsort (relocs, num_relocs, sizeof (*relocs), hunk_reloc_cmp); + + /* Enumerate every hunk in the file */ + start = fbuf; /* Start of the line */ + end = fbuf + st.st_size; /* End of the file */ + unlinelen = strlen(unline); /* Unline length (includes newline char) */ + for (endl = fbuf, i = 1, j = 0; + (endl = memchr (endl, '\n', end - endl)); + start = ++endl, i++) { + size_t len = endl - start + 1; + + /* Cut a new hunk if a relocated hunk starts at this line. This + * is important because a relocated hunk may start in the middle + * of a larger hunk, which is a hint to split the hunk. Note + * that a relocation may occur on an unline, which is corrected + * later on in a different loop. When that is the case, we still + * need to iterate past the relocation at that line in order to + * continue through the relocations array. */ + if (j < num_relocs && i == relocs[j].new) { + j++; + new_hunk = 1; + } + + /* Skip over unlines */ + if (len == unlinelen && !memcmp (start, unline, len)) { + num_unlines++; + new_hunk = 1; + continue; + } + + /* Keep expanding the current detected hunk */ + if (!new_hunk) { + hunks[num_hunks - 1].len += len; + hunks[num_hunks - 1].nend++; + num_unlines = 0; + continue; + } + new_hunk = 0; + + /* Start a new hunk */ + hunks = xrealloc (hunks, ++num_hunks * sizeof (*hunks)); + hunks[num_hunks - 1] = (typeof (*hunks)){ start, len, i, i }; + + /* Check the number of unlines between the end of the previous + * hunk (if any) and the start of the current hunk. If there are + * no more than max_context*2 unlines between the two, then eat + * the unlines and combine the hunks together. Note that we must + * also ignore the relocation for this hunk, if any, while + * accounting for the relocation new line possibly being up to + * `fuzz` lines _before_ the actual line (see more below). */ + if (num_hunks > 1 && num_unlines <= max_context * 2) { + struct hunk_info *hcurr = &hunks[num_hunks - 1]; + struct hunk_info *hprev = hcurr - 1; + + for (int k = num_relocs - 1; k >= 0; k--) { + struct hunk_reloc *rcurr = &relocs[k]; + unsigned long delta; + + if (rcurr->new <= hcurr->nstart) { + delta = hcurr->nstart - rcurr->new; + if (delta <= rcurr->fuzz) + rcurr->ignored = 1; + break; + } + } + + hcurr->nstart = hcurr->nend = hprev->nend + 1; + } + num_unlines = 0; + } + + /* Check and possibly correct the new line number in the case of fuzzed + * hunks. Patch can screw this up and emit a line number up to `fuzz` + * lines _before_ the actual line. */ + for (i = 0; i < num_relocs; i++) { + struct hunk_reloc *rcurr = &relocs[i]; + + /* Skip ignored relocations and relocations without fuzz */ + if (rcurr->ignored || !rcurr->fuzz) + continue; + + for (j = 0; j < num_hunks; j++) { + struct hunk_info *hcurr = &hunks[j]; + unsigned long delta; + + /* Find a hunk that starts within `fuzz` lines after + * this relocation. If it does, correct the new line + * number and the offset to use this hunk. */ + if (hcurr->nstart >= rcurr->new) { + delta = hcurr->nstart - rcurr->new; + if (delta <= rcurr->fuzz) { + rcurr->new += delta; + rcurr->off += delta; + } + break; + } + } + } + + /* Apply relocations */ + for (i = 0; i < num_relocs; i++) { + struct hunk_reloc *rcurr = &relocs[i]; + int found = 0; + + if (rcurr->ignored) + continue; + + for (j = 0; j < num_hunks; j++) { + struct hunk_info *hcurr = &hunks[j], *hprev = hcurr - 1; + + /* Make sure we don't relocate a hunk more than once */ + if (hcurr->relocated) + continue; + + /* Look for the hunk that starts at the new line number, + * subtracting the offset to get the hunk's _original_ + * new line number. And relocate succeeding hunks that + * had their unlines squelched between this hunk. */ + if (hcurr->nstart == rcurr->new || + (found && hcurr->nstart == + hprev->nend + rcurr->off + 1)) { + hcurr->nstart -= rcurr->off; + hcurr->nend -= rcurr->off; + hcurr->relocated = 1; + found = 1; + } else if (found) { + break; + } + } + + /* Fail if we couldn't find the hunk in question */ + if (!found) + error (EXIT_FAILURE, 0, "failed to relocate hunk"); + } + + /* Now that all hunks' final positions are determined, discard hunks + * that overlap with a relocated hunk's new position. Such hunks will + * have generated rejects on the other orig file, which will be emitted + * separately and thus removing the conflicting hunk here won't result + * in any loss of information from the diff. */ + for (i = 0; i < num_hunks; i++) { + /* Find the next relocated hunk */ + if (!hunks[i].relocated) + continue; + + /* Check all non-relocated hunks for conflicts to discard. It is + * possible for there to be more than one conflicting hunk. */ + for (j = 0; j < num_hunks; j++) { + if (hunks[j].relocated || hunks[j].discard) + continue; + + /* Check if hunks[j] starts or ends in hunks[i] */ + if ((hunks[j].nstart >= hunks[i].nstart && + hunks[j].nstart <= hunks[i].nend) || + (hunks[j].nend >= hunks[i].nstart && + hunks[j].nend <= hunks[i].nend)) + hunks[j].discard = 1; + } + } + + /* Sort the hunks by ascending order of starting line number */ + qsort (hunks, num_hunks, sizeof (*hunks), hunk_info_cmp); + + /* Write the final result to the patched file, maintaining the same + * unline. The result (in bytes, not lines) may be smaller than before + * due to some hunks getting discarded and thus replaced by unlines, so + * truncate the entire file before writing. */ + fp = xopen (file, "w+"); + for (i = 0, j = 1; i < num_hunks; i++) { + if (hunks[i].discard) + continue; + + /* Write out unlines between the previous and current hunks */ + for (; j < hunks[i].nstart; j++) + fwrite (unline, unlinelen, 1, fp); + j = hunks[i].nend + 1; + + /* Write out the hunk itself */ + fwrite (hunks[i].s, hunks[i].len, 1, fp); + } + + /* All done, clean everything up */ + fclose (fp); + free (fbuf); + free (hunks); + free (relocs); +} + +static void +fuzzy_do_rej (char *file, struct rej_file *rej, const char *other_file) +{ + char *line = NULL; + size_t linelen; + long atat_pos; + + /* Briefly modify `file` in-place to open the .rej file */ + strcat (file, ".rej"); + rej->fp = xopen (file, "r"); + file[strlen (file) - strlen (".rej")] = '\0'; + + /* Skip (the first two) lines to get to the start of the @@ line */ + do { + atat_pos = ftell (rej->fp); + if (getline (&line, &linelen, rej->fp) <= 0) + error (EXIT_FAILURE, errno, + "Failed to read line from .rej"); + } while (strncmp (line, "@@ ", 3)); + fseek (rej->fp, atat_pos, SEEK_SET); + + /* Export the line offset of the first rej hunk */ + if (read_atatline (line, &rej->off, NULL, NULL, NULL)) + error (EXIT_FAILURE, 0, "line not understood: %s", line); + free (line); + + /* Revert the rejected hunks on the _other_ file, so they're excluded + * from the 'diff' output. Otherwise, 'diff' will output the _reverse_ + * of the rejected hunks, which will muddy the final output as we will + * print out the rejected hunks themselves later anyway. */ + apply_patch (rej->fp, other_file, 1, NULL); + + /* Go back to the @@ after apply_patch() moved the file cursor */ + fseek (rej->fp, atat_pos, SEEK_SET); +} + +static void +fuzzy_cleanup (char *file, int rej) +{ + /* Modify the `file` string in-place */ + char *end = strchr (file, '\0'); + + /* Remove the .rej file if one was generated */ + if (rej) { + strcpy (end, ".rej"); + unlink (file); + } + + /* Remove the .patch file generated from splitting up the hunks */ + strcpy (end, ".patch"); + unlink (file); + + /* Terminate `file` back at where it was terminated originally */ + *end = '\0'; +} + static int output_delta (FILE *p1, FILE *p2, FILE *out) { const char *tmpdir = getenv ("TMPDIR"); unsigned int tmplen; - const char tail1[] = "/interdiff-1.XXXXXX"; - const char tail2[] = "/interdiff-2.XXXXXX"; + /* Reserve space for appending .rej and .patch at the end of tmpp1/2 */ + const char tail1[] = "/interdiff-1.XXXXXX\0patch"; + const char tail2[] = "/interdiff-2.XXXXXX\0patch"; char *tmpp1, *tmpp2; int tmpp1fd, tmpp2fd; struct lines_info file = { NULL, 0, 0, NULL, NULL }; struct lines_info file2 = { NULL, 0, 0, NULL, NULL }; + struct rej_file rej1, rej2; + int ret1 = 0, ret2 = 0; char *oldname = NULL, *newname = NULL; pid_t child; FILE *in; @@ -1244,21 +2202,63 @@ output_delta (FILE *p1, FILE *p2, FILE *out) create_orig (p2, &file, 0, NULL); create_orig (p1, &file2, mode == mode_combine, NULL); pos1 = ftell (p1); + pos2 = ftell (p2); fseek (p1, start1, SEEK_SET); fseek (p2, start2, SEEK_SET); - merge_lines(&file, &file2); /* Write it out. */ - write_file (&file, tmpp1fd); - write_file (&file, tmpp2fd); + if (fuzzy) { + /* Ensure the same unline is used for both files */ + write_file (&file, tmpp1fd); + file2.unline = xstrdup (file.unline); + write_file (&file2, tmpp2fd); + } else { + merge_lines (&file, &file2); + write_file (&file, tmpp1fd); + write_file (&file, tmpp2fd); + } - if (apply_patch (p1, tmpp1, mode == mode_combine)) - error (EXIT_FAILURE, 0, - "Error applying patch1 to reconstructed file"); + if (fuzzy) { + unsigned long *hunk_offs = NULL; + FILE *patch_out, *sp; + + /* Split the patch hunks into smaller hunks, then apply that */ + sp = split_patch_hunks (p1, pos1 - start1, tmpp1, &hunk_offs, NULL); + ret1 = apply_patch (sp, tmpp1, false, &patch_out); + fclose (sp); + + /* Relocate hunks in tmpp1 in order to make them align with the + * positions of the hunks in tmpp2. */ + fuzzy_relocate_hunks (tmpp1, file.unline, patch_out, hunk_offs); + fclose (patch_out); + free (hunk_offs); + + /* Split the patch hunks into smaller hunks, then apply that */ + sp = split_patch_hunks (p2, pos2 - start2, tmpp2, NULL, NULL); + ret2 = apply_patch (sp, tmpp2, false, NULL); + fclose (sp); + + /* For tmpp2 relocations, only eat unline gaps between hunks + * that amount to no more than max_context*2 lines. This was + * also done to tmpp1 during its relocation pass. */ + fuzzy_relocate_hunks (tmpp2, file.unline, NULL, NULL); + + /* Handle the rejected hunks. This needs to be done after both + * files are patched because it may revert a rejected hunk from + * the other file. */ + if (ret1) + fuzzy_do_rej (tmpp1, &rej1, tmpp2); + if (ret2) + fuzzy_do_rej (tmpp2, &rej2, tmpp1); + } else { + if (apply_patch (p1, tmpp1, mode == mode_combine, NULL)) + error (EXIT_FAILURE, 0, + "Error applying patch1 to reconstructed file"); - if (apply_patch (p2, tmpp2, 0)) - error (EXIT_FAILURE, 0, - "Error applying patch2 to reconstructed file"); + if (apply_patch (p2, tmpp2, 0, NULL)) + error (EXIT_FAILURE, 0, + "Error applying patch2 to reconstructed file"); + } fseek (p1, pos1, SEEK_SET); @@ -1285,17 +2285,30 @@ output_delta (FILE *p1, FILE *p2, FILE *out) break; } - if (!diff_is_empty) { + /* Rebuild the diff hunks without unlines, since fuzzy diffing shows + * context line differences that therefore may cause unlines to appear + * in the diff output. We don't want any unlines in the final output. */ + if (fuzzy && !diff_is_empty) { + in = split_patch_hunks (in, 0, NULL, NULL, file.unline); + diff_is_empty = !in; + } + + if (!diff_is_empty || ret1 || ret2) { + /* Initialize the rej pointers for output_rej_hunks() */ + struct rej_file *rej1_ptr = ret1 ? &rej1 : NULL; + struct rej_file *rej2_ptr = ret2 ? &rej2 : NULL; /* ANOTHER temporary file! This is to catch the case * where we just don't have enough context to generate * a proper interdiff. */ FILE *tmpdiff = xtmpfile (); char *line = NULL; size_t linelen; - for (;;) { + for (; !diff_is_empty;) { ssize_t got = getline (&line, &linelen, in); if (got < 0) break; + /* Output fuzzy diff reject hunks in order */ + output_rej_hunks (line, &rej1_ptr, &rej2_ptr, tmpdiff); fwrite (line, (size_t) got, 1, tmpdiff); if (*line != ' ' && !strcmp (line + 1, file.unline)) { /* Uh-oh. We're trying to output a @@ -1321,6 +2334,9 @@ output_delta (FILE *p1, FILE *p2, FILE *out) } free (line); + /* Output any remaining reject hunks */ + output_rej_hunks (NULL, &rej1_ptr, &rej2_ptr, tmpdiff); + /* First character */ if (human_readable) { char *p, *q, c, d; @@ -1347,13 +2363,18 @@ output_delta (FILE *p1, FILE *p2, FILE *out) fclose (tmpdiff); } - fclose (in); + if (in) + fclose (in); waitpid (child, NULL, 0); if (debug) printf ("reconstructed orig1=%s orig2=%s\n", tmpp1, tmpp2); else { unlink (tmpp1); unlink (tmpp2); + if (fuzzy) { + fuzzy_cleanup (tmpp1, ret1); + fuzzy_cleanup (tmpp2, ret2); + } } free (oldname); free (newname); @@ -1366,6 +2387,10 @@ output_delta (FILE *p1, FILE *p2, FILE *out) else { unlink (tmpp1); unlink (tmpp2); + if (fuzzy) { + fuzzy_cleanup (tmpp1, ret1); + fuzzy_cleanup (tmpp2, ret2); + } } if (human_readable) fprintf (out, "%s impossible; taking evasive action\n", @@ -1819,7 +2844,7 @@ flipdiff (FILE *p1, FILE *p2, FILE *flip1, FILE *flip2) tmpfd = xmkstemp (tmpp1); write_file (&intermediate, tmpfd); fsetpos (p1, &at1); - if (apply_patch (p1, tmpp1, 1)) + if (apply_patch (p1, tmpp1, 1, NULL)) error (EXIT_FAILURE, 0, "Error reconstructing original file"); @@ -1828,7 +2853,7 @@ flipdiff (FILE *p1, FILE *p2, FILE *flip1, FILE *flip2) tmpfd = xmkstemp (tmpp3); write_file (&intermediate, tmpfd); fsetpos (p2, &at2); - if (apply_patch (p2, tmpp3, 0)) + if (apply_patch (p2, tmpp3, 0, NULL)) error (EXIT_FAILURE, 0, "Error reconstructing final file"); @@ -2230,7 +3255,12 @@ syntax (int err) " (interdiff) When a patch from patch1 is not in patch2,\n" " don't revert it\n" " --in-place (flipdiff) Write the output to the original input\n" -" files\n"; +" files\n" +" --fuzzy[=N]\n" +" (interdiff) Perform a fuzzy comparison, showing the minimal\n" +" set of differences including those in context lines.\n" +" Optionally set N to the maximum number of context lines\n" +" to fuzz (which passes '--fuzz=N' to the patch utility).\n"; fprintf (err ? stderr : stdout, syntax_str, progname, progname); exit (err); @@ -2292,6 +3322,7 @@ main (int argc, char *argv[]) {"flip", 0, 0, 1000 + 'F' }, {"no-revert-omitted", 0, 0, 1000 + 'R' }, {"in-place", 0, 0, 1000 + 'i' }, + {"fuzzy", 2, 0, 1000 + 'f' }, {"debug", 0, 0, 1000 + 'D' }, {"strip-match", 1, 0, 'p'}, {"unified", 1, 0, 'U'}, @@ -2379,6 +3410,16 @@ main (int argc, char *argv[]) syntax (1); flipdiff_inplace = 1; break; + case 1000 + 'f': + if (mode != mode_inter) + syntax (1); + if (optarg) { + max_fuzz_user = strtoul (optarg, &end, 0); + if (optarg == end) + syntax (1); + } + fuzzy = 1; + break; case 1000 + 'D': debug = 1; break; From 0da66618b146244e852dd650d9ccdec24239aa9f Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Thu, 20 Nov 2025 00:04:41 -0800 Subject: [PATCH 06/18] Add tests for interdiff fuzzy diffing --- Makefile.am | 10 +- tests/fuzzy1/run-test | 74 ++ tests/fuzzy2/run-test | 42 + tests/fuzzy3/run-test | 57 ++ tests/fuzzy4/run-test | 82 ++ tests/fuzzy5/run-test | 122 +++ tests/fuzzy6/run-test | 58 ++ tests/fuzzy7/run-test | 945 ++++++++++++++++++++++ tests/fuzzy8/run-test | 1760 +++++++++++++++++++++++++++++++++++++++++ 9 files changed, 3149 insertions(+), 1 deletion(-) create mode 100755 tests/fuzzy1/run-test create mode 100755 tests/fuzzy2/run-test create mode 100755 tests/fuzzy3/run-test create mode 100755 tests/fuzzy4/run-test create mode 100755 tests/fuzzy5/run-test create mode 100755 tests/fuzzy6/run-test create mode 100755 tests/fuzzy7/run-test create mode 100755 tests/fuzzy8/run-test diff --git a/Makefile.am b/Makefile.am index 2214bf97..46352b00 100644 --- a/Makefile.am +++ b/Makefile.am @@ -432,7 +432,15 @@ TESTS = tests/newline1/run-test \ tests/git-deleted-file/run-test \ tests/git-pure-rename/run-test \ tests/git-diff-edge-cases/run-test \ - tests/malformed-diff-headers/run-test + tests/malformed-diff-headers/run-test \ + tests/fuzzy1/run-test \ + tests/fuzzy2/run-test \ + tests/fuzzy3/run-test \ + tests/fuzzy4/run-test \ + tests/fuzzy5/run-test \ + tests/fuzzy6/run-test \ + tests/fuzzy7/run-test \ + tests/fuzzy8/run-test # Scanner tests (only when scanner-patchfilter is enabled) if USE_SCANNER_PATCHFILTER diff --git a/tests/fuzzy1/run-test b/tests/fuzzy1/run-test new file mode 100755 index 00000000..f206c938 --- /dev/null +++ b/tests/fuzzy1/run-test @@ -0,0 +1,74 @@ +#!/bin/sh + +# This is an interdiff(1) testcase. +# Test: Fuzzy diffing with one rejected hunk per patched file. + + +. ${top_srcdir-.}/tests/common.sh + +cat << 'EOF' > patch1 +--- file ++++ file +@@ -1,4 +1,4 @@ +-line 1 ++LINE 1 + line 2 + line 3 + line 4 +EOF + +cat << 'EOF' > patch2 +--- file ++++ file +@@ -5,9 +5,6 @@ + line 5 + if + 1 +-fi +-if +-2 + fi + A + B +EOF + +cat << 'EOF' > expected +diff -u file file +--- file ++++ file +@@ -1,4 +1,4 @@ INTERDIFF: rejected hunk from patch1, cannot diff context +-line 1 ++LINE 1 + line 2 + line 3 + line 4 +@@ -1,9 +1,4 @@ +-line 5 +-if +-1 +-fi +-if +-2 +-fi +-A +-B ++line 1 ++line 2 ++line 3 ++line 4 +@@ -5,9 +5,6 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + line 5 + if + 1 +-fi +-if +-2 + fi + A + B +EOF + +${INTERDIFF} --fuzzy patch1 patch2 2>errors >output +[ -s errors ] && exit 1 + +cmp output expected || exit 1 diff --git a/tests/fuzzy2/run-test b/tests/fuzzy2/run-test new file mode 100755 index 00000000..8d41cd89 --- /dev/null +++ b/tests/fuzzy2/run-test @@ -0,0 +1,42 @@ +#!/bin/sh + +# This is an interdiff(1) testcase. +# Test: Fuzzy diffing with line offsets successfully fuzzed. + + +. ${top_srcdir-.}/tests/common.sh + +cat << 'EOF' > patch1 +--- file ++++ file +@@ -5,9 +5,6 @@ + line 5 + if + 1 +-fi +-if +-2 + fi + A + B +EOF + +cat << 'EOF' > patch2 +--- file ++++ file +@@ -50,9 +50,6 @@ + line 5 + if + 1 +-fi +-if +-2 + fi + A + B +EOF + +${INTERDIFF} --fuzzy patch1 patch2 2>errors >output +[ -s errors ] && exit 1 +[ -s output ] && exit 1 +exit 0 diff --git a/tests/fuzzy3/run-test b/tests/fuzzy3/run-test new file mode 100755 index 00000000..d763a1e9 --- /dev/null +++ b/tests/fuzzy3/run-test @@ -0,0 +1,57 @@ +#!/bin/sh + +# This is an interdiff(1) testcase. +# Test: Fuzzy diffing with differing context lines and line offsets fuzzed. + + +. ${top_srcdir-.}/tests/common.sh + +cat << 'EOF' > patch1 +--- file ++++ file +@@ -5,9 +5,6 @@ + line 5 + if + 1 +-fi +-if +-2 + fi + A + B +EOF + +cat << 'EOF' > patch2 +--- file ++++ file +@@ -50,9 +50,6 @@ + line 6 + if + 1 +-fi +-if +-2 + fi + B + C +EOF + +cat << 'EOF' > expected +diff -u file file +--- file ++++ file +@@ -2,6 +2,6 @@ +-line 6 ++line 5 + if + 1 + fi ++A + B +-C +EOF + +${INTERDIFF} --fuzzy patch1 patch2 2>errors >output +[ -s errors ] && exit 1 + +cmp output expected || exit 1 diff --git a/tests/fuzzy4/run-test b/tests/fuzzy4/run-test new file mode 100755 index 00000000..3f33783e --- /dev/null +++ b/tests/fuzzy4/run-test @@ -0,0 +1,82 @@ +#!/bin/sh + +# This is an interdiff(1) testcase. +# Test: Fuzzy diffing test of the optional N argument to --fuzzy. Triggers +# rejects by setting the fuzz value to 1, when it could've been fuzzed with the +# default fuzz value of 2 in `patch`. + + +. ${top_srcdir-.}/tests/common.sh + +cat << 'EOF' > patch1 +--- file ++++ file +@@ -5,9 +5,6 @@ + line 5 + if + 1 +-fi +-if +-2 + fi + A + B +EOF + +cat << 'EOF' > patch2 +--- file ++++ file +@@ -50,9 +50,6 @@ + line 6 + if + 1 +-fi +-if +-2 + fi + B + C +EOF + +cat << 'EOF' > expected +diff -u file file +--- file ++++ file +@@ -2,9 +2,9 @@ +-line 6 ++line 5 + if + 1 + fi + if + 2 + fi ++A + B +-C +@@ -5,9 +5,6 @@ INTERDIFF: rejected hunk from patch1, cannot diff context + line 5 + if + 1 +-fi +-if +-2 + fi + A + B +@@ -50,9 +50,6 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + line 6 + if + 1 +-fi +-if +-2 + fi + B + C +EOF + +${INTERDIFF} --fuzzy=1 patch1 patch2 2>errors >output +[ -s errors ] && exit 1 + +cmp output expected || exit 1 diff --git a/tests/fuzzy5/run-test b/tests/fuzzy5/run-test new file mode 100755 index 00000000..f27b3b9c --- /dev/null +++ b/tests/fuzzy5/run-test @@ -0,0 +1,122 @@ +#!/bin/sh + +# This is an interdiff(1) testcase. +# Test: Fuzzy diffing with more than one rejected hunk per patched file. This +# also tests the hunk parser by inserting whitespace between the +++ line and +# the first hunk, which should be gracefully ignored. + + +. ${top_srcdir-.}/tests/common.sh + +cat << 'EOF' > patch1 +--- file ++++ file + + + +@@ -5,9 +5,6 @@ + line 5 + if + 1 +-fi +-if +-2 + fi + A + B +@@ -7,7 +7,7 @@ + C + 9 + 8 +-1 ++7 + D + E + if +EOF + +cat << 'EOF' > patch2 +--- file ++++ file + +@@ -50,9 +50,6 @@ + line 6 + if + 1 +-fi +-if +-2 + fi + B + C +@@ -7,7 +7,7 @@ + D + Z + 1 +-1 ++7 + F + E + if +EOF + +cat << 'EOF' > expected +diff -u file file +--- file ++++ file +@@ -2,9 +2,2 @@ +-line 6 ++line 5 + if + 1 + fi + if + 2 + fi ++A + B +-C +@@ -5,9 +5,6 @@ INTERDIFF: rejected hunk from patch1, cannot diff context + line 5 + if + 1 +-fi +-if +-2 + fi + A + B +@@ -7,7 +7,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context + C + 9 + 8 +-1 ++7 + D + E + if +@@ -50,9 +50,6 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + line 6 + if + 1 +-fi +-if +-2 + fi + B + C +@@ -7,7 +7,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + D + Z + 1 +-1 ++7 + F + E + if +EOF + +${INTERDIFF} --fuzzy=1 patch1 patch2 2>errors >output +[ -s errors ] && exit 1 + +cmp output expected || exit 1 diff --git a/tests/fuzzy6/run-test b/tests/fuzzy6/run-test new file mode 100755 index 00000000..f8091c83 --- /dev/null +++ b/tests/fuzzy6/run-test @@ -0,0 +1,58 @@ +#!/bin/sh + +# This is an interdiff(1) testcase. +# Test: Fuzzy diffing with the hunk splitter stressed by having a +/- line as +# either the first or last line in a hunk, while triggering a split by +# separating two deltas by some context lines. This also tests patches with an +# unequal number of pre-context and post-context lines. + + +. ${top_srcdir-.}/tests/common.sh + +cat << 'EOF' > patch1 +--- file ++++ file +@@ -5,2 +5,2 @@ +-line 5 + if ++1 +EOF + +cat << 'EOF' > patch2 +--- file ++++ file +@@ -50,5 +50,5 @@ + hi + line 4 +-line 5 + if ++1 + 2 +EOF + +cat << 'EOF' > expected +diff -u file file +--- file ++++ file +@@ -2,5 +2,2 @@ +-hi +-line 4 + line 5 + if +-2 +@@ -50,5 +50,4 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + hi + line 4 +-line 5 + if + 2 +@@ -53,2 +52,3 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + if ++1 + 2 +EOF + +${INTERDIFF} --fuzzy=1 patch1 patch2 2>errors >output +[ -s errors ] && exit 1 + +cmp output expected || exit 1 diff --git a/tests/fuzzy7/run-test b/tests/fuzzy7/run-test new file mode 100755 index 00000000..06b9d17b --- /dev/null +++ b/tests/fuzzy7/run-test @@ -0,0 +1,945 @@ +#!/bin/sh + +# This is an interdiff(1) testcase. +# Test: Fuzzy diffing with a real Linux kernel backport compared against its +# upstream version. Stresses having multiple relocations and most of the fuzzy +# diffing machinery as a whole. + + +. ${top_srcdir-.}/tests/common.sh + +cat << 'EOF' > patch1 +From ed0708f971a26619964eea4e9eccb73847caa0c7 Mon Sep 17 00:00:00 2001 +From: Shreeya Patel +Date: Wed, 3 Sep 2025 12:36:41 +0000 +Subject: [PATCH] net: mana: Handle Reset Request from MANA NIC + +jira LE-3923 +commit-author Haiyang Zhang +commit fbe346ce9d626680a4dd0f079e17c7b5dd32ffad +upstream-diff There were conflicts seen when applying this +patch due to the following missing commits :- +ca8ac489ca33 ("net: mana: Handle unsupported HWC commands") +505cc26bcae0 ("net: mana: Add support for auxiliary device servicing +events") + +Upon receiving the Reset Request, pause the connection and clean up +queues, wait for the specified period, then resume the NIC. +In the cleanup phase, the HWC is no longer responding, so set hwc_timeout +to zero to skip waiting on the response. + + Signed-off-by: Haiyang Zhang +Link: https://patch.msgid.link/1751055983-29760-1-git-send-email-haiyangz@linux.microsoft.com + Signed-off-by: Jakub Kicinski +(cherry picked from commit fbe346ce9d626680a4dd0f079e17c7b5dd32ffad) + Signed-off-by: Shreeya Patel +--- + .../net/ethernet/microsoft/mana/gdma_main.c | 127 ++++++++++++++---- + .../net/ethernet/microsoft/mana/hw_channel.c | 4 +- + drivers/net/ethernet/microsoft/mana/mana_en.c | 37 +++-- + include/net/mana/gdma.h | 10 ++ + 4 files changed, 143 insertions(+), 35 deletions(-) + +diff --git a/drivers/net/ethernet/microsoft/mana/gdma_main.c b/drivers/net/ethernet/microsoft/mana/gdma_main.c +index 9d19df7cd82b3..cf3b920476cf6 100644 +--- a/drivers/net/ethernet/microsoft/mana/gdma_main.c ++++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c +@@ -8,6 +8,7 @@ + #include + + #include ++#include + + #include + struct dentry *mana_debugfs_root; +@@ -64,6 +65,24 @@ static void mana_gd_init_registers(struct pci_dev *pdev) + mana_gd_init_vf_regs(pdev); + } + ++/* Suppress logging when we set timeout to zeo */ ++bool mana_need_log(struct gdma_context *gc, int err) ++{ ++ struct hw_channel_context *hwc; ++ ++ if (err != -ETIMEDOUT) ++ return true; ++ ++ if (!gc) ++ return true; ++ ++ hwc = gc->hwc.driver_data; ++ if (hwc && hwc->hwc_timeout == 0) ++ return false; ++ ++ return true; ++} ++ + static int mana_gd_query_max_resources(struct pci_dev *pdev) + { + struct gdma_context *gc = pci_get_drvdata(pdev); +@@ -267,8 +286,9 @@ static int mana_gd_disable_queue(struct gdma_queue *queue) + + err = mana_gd_send_request(gc, sizeof(req), &req, sizeof(resp), &resp); + if (err || resp.hdr.status) { +- dev_err(gc->dev, "Failed to disable queue: %d, 0x%x\n", err, +- resp.hdr.status); ++ if (mana_need_log(gc, err)) ++ dev_err(gc->dev, "Failed to disable queue: %d, 0x%x\n", err, ++ resp.hdr.status); + return err ? err : -EPROTO; + } + +@@ -353,25 +373,12 @@ void mana_gd_ring_cq(struct gdma_queue *cq, u8 arm_bit) + + #define MANA_SERVICE_PERIOD 10 + +-struct mana_serv_work { +- struct work_struct serv_work; +- struct pci_dev *pdev; +-}; +- +-static void mana_serv_func(struct work_struct *w) ++static void mana_serv_fpga(struct pci_dev *pdev) + { +- struct mana_serv_work *mns_wk; + struct pci_bus *bus, *parent; +- struct pci_dev *pdev; +- +- mns_wk = container_of(w, struct mana_serv_work, serv_work); +- pdev = mns_wk->pdev; + + pci_lock_rescan_remove(); + +- if (!pdev) +- goto out; +- + bus = pdev->bus; + if (!bus) { + dev_err(&pdev->dev, "MANA service: no bus\n"); +@@ -392,7 +399,74 @@ static void mana_serv_func(struct work_struct *w) + + out: + pci_unlock_rescan_remove(); ++} ++ ++static void mana_serv_reset(struct pci_dev *pdev) ++{ ++ struct gdma_context *gc = pci_get_drvdata(pdev); ++ struct hw_channel_context *hwc; ++ ++ if (!gc) { ++ dev_err(&pdev->dev, "MANA service: no GC\n"); ++ return; ++ } ++ ++ hwc = gc->hwc.driver_data; ++ if (!hwc) { ++ dev_err(&pdev->dev, "MANA service: no HWC\n"); ++ goto out; ++ } ++ ++ /* HWC is not responding in this case, so don't wait */ ++ hwc->hwc_timeout = 0; ++ ++ dev_info(&pdev->dev, "MANA reset cycle start\n"); + ++ mana_gd_suspend(pdev, PMSG_SUSPEND); ++ ++ msleep(MANA_SERVICE_PERIOD * 1000); ++ ++ mana_gd_resume(pdev); ++ ++ dev_info(&pdev->dev, "MANA reset cycle completed\n"); ++ ++out: ++ gc->in_service = false; ++} ++ ++struct mana_serv_work { ++ struct work_struct serv_work; ++ struct pci_dev *pdev; ++ enum gdma_eqe_type type; ++}; ++ ++static void mana_serv_func(struct work_struct *w) ++{ ++ struct mana_serv_work *mns_wk; ++ struct pci_dev *pdev; ++ ++ mns_wk = container_of(w, struct mana_serv_work, serv_work); ++ pdev = mns_wk->pdev; ++ ++ if (!pdev) ++ goto out; ++ ++ switch (mns_wk->type) { ++ case GDMA_EQE_HWC_FPGA_RECONFIG: ++ mana_serv_fpga(pdev); ++ break; ++ ++ case GDMA_EQE_HWC_RESET_REQUEST: ++ mana_serv_reset(pdev); ++ break; ++ ++ default: ++ dev_err(&pdev->dev, "MANA service: unknown type %d\n", ++ mns_wk->type); ++ break; ++ } ++ ++out: + pci_dev_put(pdev); + kfree(mns_wk); + module_put(THIS_MODULE); +@@ -448,6 +522,7 @@ static void mana_gd_process_eqe(struct gdma_queue *eq) + break; + + case GDMA_EQE_HWC_FPGA_RECONFIG: ++ case GDMA_EQE_HWC_RESET_REQUEST: + dev_info(gc->dev, "Recv MANA service type:%d\n", type); + + if (gc->in_service) { +@@ -469,6 +544,7 @@ static void mana_gd_process_eqe(struct gdma_queue *eq) + dev_info(gc->dev, "Start MANA service type:%d\n", type); + gc->in_service = true; + mns_wk->pdev = to_pci_dev(gc->dev); ++ mns_wk->type = type; + pci_dev_get(mns_wk->pdev); + INIT_WORK(&mns_wk->serv_work, mana_serv_func); + schedule_work(&mns_wk->serv_work); +@@ -615,7 +691,8 @@ int mana_gd_test_eq(struct gdma_context *gc, struct gdma_queue *eq) + + err = mana_gd_send_request(gc, sizeof(req), &req, sizeof(resp), &resp); + if (err) { +- dev_err(dev, "test_eq failed: %d\n", err); ++ if (mana_need_log(gc, err)) ++ dev_err(dev, "test_eq failed: %d\n", err); + goto out; + } + +@@ -650,7 +727,7 @@ static void mana_gd_destroy_eq(struct gdma_context *gc, bool flush_evenets, + + if (flush_evenets) { + err = mana_gd_test_eq(gc, queue); +- if (err) ++ if (err && mana_need_log(gc, err)) + dev_warn(gc->dev, "Failed to flush EQ: %d\n", err); + } + +@@ -796,8 +873,9 @@ int mana_gd_destroy_dma_region(struct gdma_context *gc, u64 dma_region_handle) + + err = mana_gd_send_request(gc, sizeof(req), &req, sizeof(resp), &resp); + if (err || resp.hdr.status) { +- dev_err(gc->dev, "Failed to destroy DMA region: %d, 0x%x\n", +- err, resp.hdr.status); ++ if (mana_need_log(gc, err)) ++ dev_err(gc->dev, "Failed to destroy DMA region: %d, 0x%x\n", ++ err, resp.hdr.status); + return -EPROTO; + } + +@@ -1096,8 +1174,9 @@ int mana_gd_deregister_device(struct gdma_dev *gd) + + err = mana_gd_send_request(gc, sizeof(req), &req, sizeof(resp), &resp); + if (err || resp.hdr.status) { +- dev_err(gc->dev, "Failed to deregister device: %d, 0x%x\n", +- err, resp.hdr.status); ++ if (mana_need_log(gc, err)) ++ dev_err(gc->dev, "Failed to deregister device: %d, 0x%x\n", ++ err, resp.hdr.status); + if (!err) + err = -EPROTO; + } +@@ -1697,7 +1776,7 @@ static void mana_gd_remove(struct pci_dev *pdev) + } + + /* The 'state' parameter is not used. */ +-static int mana_gd_suspend(struct pci_dev *pdev, pm_message_t state) ++int mana_gd_suspend(struct pci_dev *pdev, pm_message_t state) + { + struct gdma_context *gc = pci_get_drvdata(pdev); + +@@ -1712,7 +1791,7 @@ static int mana_gd_suspend(struct pci_dev *pdev, pm_message_t state) + * fail -- if this happens, it's safer to just report an error than try to undo + * what has been done. + */ +-static int mana_gd_resume(struct pci_dev *pdev) ++int mana_gd_resume(struct pci_dev *pdev) + { + struct gdma_context *gc = pci_get_drvdata(pdev); + int err; +diff --git a/drivers/net/ethernet/microsoft/mana/hw_channel.c b/drivers/net/ethernet/microsoft/mana/hw_channel.c +index 4291a2fc2710e..aed60be5ee389 100644 +--- a/drivers/net/ethernet/microsoft/mana/hw_channel.c ++++ b/drivers/net/ethernet/microsoft/mana/hw_channel.c +@@ -854,7 +854,9 @@ int mana_hwc_send_request(struct hw_channel_context *hwc, u32 req_len, + + if (!wait_for_completion_timeout(&ctx->comp_event, + (msecs_to_jiffies(hwc->hwc_timeout)))) { +- dev_err(hwc->dev, "HWC: Request timed out!\n"); ++ if (hwc->hwc_timeout != 0) ++ dev_err(hwc->dev, "HWC: Request timed out!\n"); ++ + err = -ETIMEDOUT; + goto out; + } +diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c +index cbecacf503422..acf1342536463 100644 +--- a/drivers/net/ethernet/microsoft/mana/mana_en.c ++++ b/drivers/net/ethernet/microsoft/mana/mana_en.c +@@ -45,6 +45,15 @@ static const struct file_operations mana_dbg_q_fops = { + .read = mana_dbg_q_read, + }; + ++static bool mana_en_need_log(struct mana_port_context *apc, int err) ++{ ++ if (apc && apc->ac && apc->ac->gdma_dev && ++ apc->ac->gdma_dev->gdma_context) ++ return mana_need_log(apc->ac->gdma_dev->gdma_context, err); ++ else ++ return true; ++} ++ + /* Microsoft Azure Network Adapter (MANA) functions */ + + static int mana_open(struct net_device *ndev) +@@ -768,7 +777,8 @@ static int mana_send_request(struct mana_context *ac, void *in_buf, + err = mana_gd_send_request(gc, in_len, in_buf, out_len, + out_buf); + if (err || resp->status) { +- if (req->req.msg_type != MANA_QUERY_PHY_STAT) ++ if (req->req.msg_type != MANA_QUERY_PHY_STAT && ++ mana_need_log(gc, err)) + dev_err(dev, "Failed to send mana message: %d, 0x%x\n", + err, resp->status); + return err ? err : -EPROTO; +@@ -845,8 +855,10 @@ static void mana_pf_deregister_hw_vport(struct mana_port_context *apc) + err = mana_send_request(apc->ac, &req, sizeof(req), &resp, + sizeof(resp)); + if (err) { +- netdev_err(apc->ndev, "Failed to unregister hw vPort: %d\n", +- err); ++ if (mana_en_need_log(apc, err)) ++ netdev_err(apc->ndev, "Failed to unregister hw vPort: %d\n", ++ err); ++ + return; + } + +@@ -901,8 +913,10 @@ static void mana_pf_deregister_filter(struct mana_port_context *apc) + err = mana_send_request(apc->ac, &req, sizeof(req), &resp, + sizeof(resp)); + if (err) { +- netdev_err(apc->ndev, "Failed to unregister filter: %d\n", +- err); ++ if (mana_en_need_log(apc, err)) ++ netdev_err(apc->ndev, "Failed to unregister filter: %d\n", ++ err); ++ + return; + } + +@@ -1132,7 +1146,9 @@ static int mana_cfg_vport_steering(struct mana_port_context *apc, + err = mana_send_request(apc->ac, req, req_buf_size, &resp, + sizeof(resp)); + if (err) { +- netdev_err(ndev, "Failed to configure vPort RX: %d\n", err); ++ if (mana_en_need_log(apc, err)) ++ netdev_err(ndev, "Failed to configure vPort RX: %d\n", err); ++ + goto out; + } + +@@ -1227,7 +1243,9 @@ void mana_destroy_wq_obj(struct mana_port_context *apc, u32 wq_type, + err = mana_send_request(apc->ac, &req, sizeof(req), &resp, + sizeof(resp)); + if (err) { +- netdev_err(ndev, "Failed to destroy WQ object: %d\n", err); ++ if (mana_en_need_log(apc, err)) ++ netdev_err(ndev, "Failed to destroy WQ object: %d\n", err); ++ + return; + } + +@@ -2872,11 +2890,10 @@ static int mana_dealloc_queues(struct net_device *ndev) + + apc->rss_state = TRI_STATE_FALSE; + err = mana_config_rss(apc, TRI_STATE_FALSE, false, false); +- if (err) { ++ if (err && mana_en_need_log(apc, err)) + netdev_err(ndev, "Failed to disable vPort: %d\n", err); +- return err; +- } + ++ /* Even in err case, still need to cleanup the vPort */ + mana_destroy_vport(apc); + + return 0; +diff --git a/include/net/mana/gdma.h b/include/net/mana/gdma.h +index b602b2e55939c..af5596bf46878 100644 +--- a/include/net/mana/gdma.h ++++ b/include/net/mana/gdma.h +@@ -60,6 +60,7 @@ enum gdma_eqe_type { + GDMA_EQE_HWC_INIT_DONE = 131, + GDMA_EQE_HWC_FPGA_RECONFIG = 132, + GDMA_EQE_HWC_SOC_RECONFIG_DATA = 133, ++ GDMA_EQE_HWC_RESET_REQUEST = 135, + GDMA_EQE_RNIC_QP_FATAL = 176, + }; + +@@ -559,6 +560,9 @@ enum { + /* Driver can handle holes (zeros) in the device list */ + #define GDMA_DRV_CAP_FLAG_1_DEV_LIST_HOLES_SUP BIT(11) + ++/* Driver can self reset on EQE notification */ ++#define GDMA_DRV_CAP_FLAG_1_SELF_RESET_ON_EQE BIT(14) ++ + /* Driver can self reset on FPGA Reconfig EQE notification */ + #define GDMA_DRV_CAP_FLAG_1_HANDLE_RECONFIG_EQE BIT(17) + +@@ -568,6 +572,7 @@ enum { + GDMA_DRV_CAP_FLAG_1_HWC_TIMEOUT_RECONFIG | \ + GDMA_DRV_CAP_FLAG_1_VARIABLE_INDIRECTION_TABLE_SUPPORT | \ + GDMA_DRV_CAP_FLAG_1_DEV_LIST_HOLES_SUP | \ ++ GDMA_DRV_CAP_FLAG_1_SELF_RESET_ON_EQE | \ + GDMA_DRV_CAP_FLAG_1_HANDLE_RECONFIG_EQE) + + #define GDMA_DRV_CAP_FLAGS2 0 +@@ -892,4 +897,9 @@ int mana_gd_destroy_dma_region(struct gdma_context *gc, u64 dma_region_handle); + void mana_register_debugfs(void); + void mana_unregister_debugfs(void); + ++int mana_gd_suspend(struct pci_dev *pdev, pm_message_t state); ++int mana_gd_resume(struct pci_dev *pdev); ++ ++bool mana_need_log(struct gdma_context *gc, int err); ++ + #endif /* _GDMA_H */ +-- +2.51.2 + +EOF + +cat << 'EOF' > patch2 +From fbe346ce9d626680a4dd0f079e17c7b5dd32ffad Mon Sep 17 00:00:00 2001 +From: Haiyang Zhang +Date: Fri, 27 Jun 2025 13:26:23 -0700 +Subject: [PATCH] net: mana: Handle Reset Request from MANA NIC + +Upon receiving the Reset Request, pause the connection and clean up +queues, wait for the specified period, then resume the NIC. +In the cleanup phase, the HWC is no longer responding, so set hwc_timeout +to zero to skip waiting on the response. + +Signed-off-by: Haiyang Zhang +Link: https://patch.msgid.link/1751055983-29760-1-git-send-email-haiyangz@linux.microsoft.com +Signed-off-by: Jakub Kicinski +--- + .../net/ethernet/microsoft/mana/gdma_main.c | 127 ++++++++++++++---- + .../net/ethernet/microsoft/mana/hw_channel.c | 4 +- + drivers/net/ethernet/microsoft/mana/mana_en.c | 37 +++-- + include/net/mana/gdma.h | 10 ++ + 4 files changed, 143 insertions(+), 35 deletions(-) + +diff --git a/drivers/net/ethernet/microsoft/mana/gdma_main.c b/drivers/net/ethernet/microsoft/mana/gdma_main.c +index 55dd7dee718cc..a468cd8e5f361 100644 +--- a/drivers/net/ethernet/microsoft/mana/gdma_main.c ++++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c +@@ -10,6 +10,7 @@ + #include + + #include ++#include + + struct dentry *mana_debugfs_root; + +@@ -68,6 +69,24 @@ static void mana_gd_init_registers(struct pci_dev *pdev) + mana_gd_init_vf_regs(pdev); + } + ++/* Suppress logging when we set timeout to zero */ ++bool mana_need_log(struct gdma_context *gc, int err) ++{ ++ struct hw_channel_context *hwc; ++ ++ if (err != -ETIMEDOUT) ++ return true; ++ ++ if (!gc) ++ return true; ++ ++ hwc = gc->hwc.driver_data; ++ if (hwc && hwc->hwc_timeout == 0) ++ return false; ++ ++ return true; ++} ++ + static int mana_gd_query_max_resources(struct pci_dev *pdev) + { + struct gdma_context *gc = pci_get_drvdata(pdev); +@@ -278,8 +297,9 @@ static int mana_gd_disable_queue(struct gdma_queue *queue) + + err = mana_gd_send_request(gc, sizeof(req), &req, sizeof(resp), &resp); + if (err || resp.hdr.status) { +- dev_err(gc->dev, "Failed to disable queue: %d, 0x%x\n", err, +- resp.hdr.status); ++ if (mana_need_log(gc, err)) ++ dev_err(gc->dev, "Failed to disable queue: %d, 0x%x\n", err, ++ resp.hdr.status); + return err ? err : -EPROTO; + } + +@@ -366,25 +386,12 @@ EXPORT_SYMBOL_NS(mana_gd_ring_cq, "NET_MANA"); + + #define MANA_SERVICE_PERIOD 10 + +-struct mana_serv_work { +- struct work_struct serv_work; +- struct pci_dev *pdev; +-}; +- +-static void mana_serv_func(struct work_struct *w) ++static void mana_serv_fpga(struct pci_dev *pdev) + { +- struct mana_serv_work *mns_wk; + struct pci_bus *bus, *parent; +- struct pci_dev *pdev; +- +- mns_wk = container_of(w, struct mana_serv_work, serv_work); +- pdev = mns_wk->pdev; + + pci_lock_rescan_remove(); + +- if (!pdev) +- goto out; +- + bus = pdev->bus; + if (!bus) { + dev_err(&pdev->dev, "MANA service: no bus\n"); +@@ -405,7 +412,74 @@ static void mana_serv_func(struct work_struct *w) + + out: + pci_unlock_rescan_remove(); ++} ++ ++static void mana_serv_reset(struct pci_dev *pdev) ++{ ++ struct gdma_context *gc = pci_get_drvdata(pdev); ++ struct hw_channel_context *hwc; ++ ++ if (!gc) { ++ dev_err(&pdev->dev, "MANA service: no GC\n"); ++ return; ++ } ++ ++ hwc = gc->hwc.driver_data; ++ if (!hwc) { ++ dev_err(&pdev->dev, "MANA service: no HWC\n"); ++ goto out; ++ } ++ ++ /* HWC is not responding in this case, so don't wait */ ++ hwc->hwc_timeout = 0; ++ ++ dev_info(&pdev->dev, "MANA reset cycle start\n"); + ++ mana_gd_suspend(pdev, PMSG_SUSPEND); ++ ++ msleep(MANA_SERVICE_PERIOD * 1000); ++ ++ mana_gd_resume(pdev); ++ ++ dev_info(&pdev->dev, "MANA reset cycle completed\n"); ++ ++out: ++ gc->in_service = false; ++} ++ ++struct mana_serv_work { ++ struct work_struct serv_work; ++ struct pci_dev *pdev; ++ enum gdma_eqe_type type; ++}; ++ ++static void mana_serv_func(struct work_struct *w) ++{ ++ struct mana_serv_work *mns_wk; ++ struct pci_dev *pdev; ++ ++ mns_wk = container_of(w, struct mana_serv_work, serv_work); ++ pdev = mns_wk->pdev; ++ ++ if (!pdev) ++ goto out; ++ ++ switch (mns_wk->type) { ++ case GDMA_EQE_HWC_FPGA_RECONFIG: ++ mana_serv_fpga(pdev); ++ break; ++ ++ case GDMA_EQE_HWC_RESET_REQUEST: ++ mana_serv_reset(pdev); ++ break; ++ ++ default: ++ dev_err(&pdev->dev, "MANA service: unknown type %d\n", ++ mns_wk->type); ++ break; ++ } ++ ++out: + pci_dev_put(pdev); + kfree(mns_wk); + module_put(THIS_MODULE); +@@ -462,6 +536,7 @@ static void mana_gd_process_eqe(struct gdma_queue *eq) + break; + + case GDMA_EQE_HWC_FPGA_RECONFIG: ++ case GDMA_EQE_HWC_RESET_REQUEST: + dev_info(gc->dev, "Recv MANA service type:%d\n", type); + + if (gc->in_service) { +@@ -483,6 +558,7 @@ static void mana_gd_process_eqe(struct gdma_queue *eq) + dev_info(gc->dev, "Start MANA service type:%d\n", type); + gc->in_service = true; + mns_wk->pdev = to_pci_dev(gc->dev); ++ mns_wk->type = type; + pci_dev_get(mns_wk->pdev); + INIT_WORK(&mns_wk->serv_work, mana_serv_func); + schedule_work(&mns_wk->serv_work); +@@ -634,7 +710,8 @@ int mana_gd_test_eq(struct gdma_context *gc, struct gdma_queue *eq) + + err = mana_gd_send_request(gc, sizeof(req), &req, sizeof(resp), &resp); + if (err) { +- dev_err(dev, "test_eq failed: %d\n", err); ++ if (mana_need_log(gc, err)) ++ dev_err(dev, "test_eq failed: %d\n", err); + goto out; + } + +@@ -669,7 +746,7 @@ static void mana_gd_destroy_eq(struct gdma_context *gc, bool flush_evenets, + + if (flush_evenets) { + err = mana_gd_test_eq(gc, queue); +- if (err) ++ if (err && mana_need_log(gc, err)) + dev_warn(gc->dev, "Failed to flush EQ: %d\n", err); + } + +@@ -815,8 +892,9 @@ int mana_gd_destroy_dma_region(struct gdma_context *gc, u64 dma_region_handle) + + err = mana_gd_send_request(gc, sizeof(req), &req, sizeof(resp), &resp); + if (err || resp.hdr.status) { +- dev_err(gc->dev, "Failed to destroy DMA region: %d, 0x%x\n", +- err, resp.hdr.status); ++ if (mana_need_log(gc, err)) ++ dev_err(gc->dev, "Failed to destroy DMA region: %d, 0x%x\n", ++ err, resp.hdr.status); + return -EPROTO; + } + +@@ -1116,8 +1194,9 @@ int mana_gd_deregister_device(struct gdma_dev *gd) + + err = mana_gd_send_request(gc, sizeof(req), &req, sizeof(resp), &resp); + if (err || resp.hdr.status) { +- dev_err(gc->dev, "Failed to deregister device: %d, 0x%x\n", +- err, resp.hdr.status); ++ if (mana_need_log(gc, err)) ++ dev_err(gc->dev, "Failed to deregister device: %d, 0x%x\n", ++ err, resp.hdr.status); + if (!err) + err = -EPROTO; + } +@@ -1915,7 +1994,7 @@ static void mana_gd_remove(struct pci_dev *pdev) + } + + /* The 'state' parameter is not used. */ +-static int mana_gd_suspend(struct pci_dev *pdev, pm_message_t state) ++int mana_gd_suspend(struct pci_dev *pdev, pm_message_t state) + { + struct gdma_context *gc = pci_get_drvdata(pdev); + +@@ -1931,7 +2010,7 @@ static int mana_gd_suspend(struct pci_dev *pdev, pm_message_t state) + * fail -- if this happens, it's safer to just report an error than try to undo + * what has been done. + */ +-static int mana_gd_resume(struct pci_dev *pdev) ++int mana_gd_resume(struct pci_dev *pdev) + { + struct gdma_context *gc = pci_get_drvdata(pdev); + int err; +diff --git a/drivers/net/ethernet/microsoft/mana/hw_channel.c b/drivers/net/ethernet/microsoft/mana/hw_channel.c +index 650d22654d499..ef072e24c46d0 100644 +--- a/drivers/net/ethernet/microsoft/mana/hw_channel.c ++++ b/drivers/net/ethernet/microsoft/mana/hw_channel.c +@@ -880,7 +880,9 @@ int mana_hwc_send_request(struct hw_channel_context *hwc, u32 req_len, + + if (!wait_for_completion_timeout(&ctx->comp_event, + (msecs_to_jiffies(hwc->hwc_timeout)))) { +- dev_err(hwc->dev, "HWC: Request timed out!\n"); ++ if (hwc->hwc_timeout != 0) ++ dev_err(hwc->dev, "HWC: Request timed out!\n"); ++ + err = -ETIMEDOUT; + goto out; + } +diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c +index 016fd808ccad4..a7973651ae51b 100644 +--- a/drivers/net/ethernet/microsoft/mana/mana_en.c ++++ b/drivers/net/ethernet/microsoft/mana/mana_en.c +@@ -47,6 +47,15 @@ static const struct file_operations mana_dbg_q_fops = { + .read = mana_dbg_q_read, + }; + ++static bool mana_en_need_log(struct mana_port_context *apc, int err) ++{ ++ if (apc && apc->ac && apc->ac->gdma_dev && ++ apc->ac->gdma_dev->gdma_context) ++ return mana_need_log(apc->ac->gdma_dev->gdma_context, err); ++ else ++ return true; ++} ++ + /* Microsoft Azure Network Adapter (MANA) functions */ + + static int mana_open(struct net_device *ndev) +@@ -854,7 +863,8 @@ static int mana_send_request(struct mana_context *ac, void *in_buf, + if (err == -EOPNOTSUPP) + return err; + +- if (req->req.msg_type != MANA_QUERY_PHY_STAT) ++ if (req->req.msg_type != MANA_QUERY_PHY_STAT && ++ mana_need_log(gc, err)) + dev_err(dev, "Failed to send mana message: %d, 0x%x\n", + err, resp->status); + return err ? err : -EPROTO; +@@ -931,8 +941,10 @@ static void mana_pf_deregister_hw_vport(struct mana_port_context *apc) + err = mana_send_request(apc->ac, &req, sizeof(req), &resp, + sizeof(resp)); + if (err) { +- netdev_err(apc->ndev, "Failed to unregister hw vPort: %d\n", +- err); ++ if (mana_en_need_log(apc, err)) ++ netdev_err(apc->ndev, "Failed to unregister hw vPort: %d\n", ++ err); ++ + return; + } + +@@ -987,8 +999,10 @@ static void mana_pf_deregister_filter(struct mana_port_context *apc) + err = mana_send_request(apc->ac, &req, sizeof(req), &resp, + sizeof(resp)); + if (err) { +- netdev_err(apc->ndev, "Failed to unregister filter: %d\n", +- err); ++ if (mana_en_need_log(apc, err)) ++ netdev_err(apc->ndev, "Failed to unregister filter: %d\n", ++ err); ++ + return; + } + +@@ -1218,7 +1232,9 @@ static int mana_cfg_vport_steering(struct mana_port_context *apc, + err = mana_send_request(apc->ac, req, req_buf_size, &resp, + sizeof(resp)); + if (err) { +- netdev_err(ndev, "Failed to configure vPort RX: %d\n", err); ++ if (mana_en_need_log(apc, err)) ++ netdev_err(ndev, "Failed to configure vPort RX: %d\n", err); ++ + goto out; + } + +@@ -1402,7 +1418,9 @@ void mana_destroy_wq_obj(struct mana_port_context *apc, u32 wq_type, + err = mana_send_request(apc->ac, &req, sizeof(req), &resp, + sizeof(resp)); + if (err) { +- netdev_err(ndev, "Failed to destroy WQ object: %d\n", err); ++ if (mana_en_need_log(apc, err)) ++ netdev_err(ndev, "Failed to destroy WQ object: %d\n", err); ++ + return; + } + +@@ -3067,11 +3085,10 @@ static int mana_dealloc_queues(struct net_device *ndev) + + apc->rss_state = TRI_STATE_FALSE; + err = mana_config_rss(apc, TRI_STATE_FALSE, false, false); +- if (err) { ++ if (err && mana_en_need_log(apc, err)) + netdev_err(ndev, "Failed to disable vPort: %d\n", err); +- return err; +- } + ++ /* Even in err case, still need to cleanup the vPort */ + mana_destroy_vport(apc); + + return 0; +diff --git a/include/net/mana/gdma.h b/include/net/mana/gdma.h +index 92ab85061df00..57df78cfbf82c 100644 +--- a/include/net/mana/gdma.h ++++ b/include/net/mana/gdma.h +@@ -62,6 +62,7 @@ enum gdma_eqe_type { + GDMA_EQE_HWC_FPGA_RECONFIG = 132, + GDMA_EQE_HWC_SOC_RECONFIG_DATA = 133, + GDMA_EQE_HWC_SOC_SERVICE = 134, ++ GDMA_EQE_HWC_RESET_REQUEST = 135, + GDMA_EQE_RNIC_QP_FATAL = 176, + }; + +@@ -584,6 +585,9 @@ enum { + /* Driver supports dynamic MSI-X vector allocation */ + #define GDMA_DRV_CAP_FLAG_1_DYNAMIC_IRQ_ALLOC_SUPPORT BIT(13) + ++/* Driver can self reset on EQE notification */ ++#define GDMA_DRV_CAP_FLAG_1_SELF_RESET_ON_EQE BIT(14) ++ + /* Driver can self reset on FPGA Reconfig EQE notification */ + #define GDMA_DRV_CAP_FLAG_1_HANDLE_RECONFIG_EQE BIT(17) + +@@ -594,6 +598,7 @@ enum { + GDMA_DRV_CAP_FLAG_1_VARIABLE_INDIRECTION_TABLE_SUPPORT | \ + GDMA_DRV_CAP_FLAG_1_DEV_LIST_HOLES_SUP | \ + GDMA_DRV_CAP_FLAG_1_DYNAMIC_IRQ_ALLOC_SUPPORT | \ ++ GDMA_DRV_CAP_FLAG_1_SELF_RESET_ON_EQE | \ + GDMA_DRV_CAP_FLAG_1_HANDLE_RECONFIG_EQE) + + #define GDMA_DRV_CAP_FLAGS2 0 +@@ -921,4 +926,9 @@ void mana_unregister_debugfs(void); + + int mana_rdma_service_event(struct gdma_context *gc, enum gdma_service_type event); + ++int mana_gd_suspend(struct pci_dev *pdev, pm_message_t state); ++int mana_gd_resume(struct pci_dev *pdev); ++ ++bool mana_need_log(struct gdma_context *gc, int err); ++ + #endif /* _GDMA_H */ +-- +2.51.2 + +EOF + +cat << 'EOF' > expected +diff -u b/drivers/net/ethernet/microsoft/mana/gdma_main.c b/drivers/net/ethernet/microsoft/mana/gdma_main.c +--- b/drivers/net/ethernet/microsoft/mana/gdma_main.c ++++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c +@@ -5,7 +5,7 @@ +-#include ++#include + + #include + #include + ++#include + struct dentry *mana_debugfs_root; +- +@@ -65,7 +65,7 @@ + mana_gd_init_vf_regs(pdev); + } + +-/* Suppress logging when we set timeout to zeo */ ++/* Suppress logging when we set timeout to zero */ + bool mana_need_log(struct gdma_context *gc, int err) + { + struct hw_channel_context *hwc; +diff -u b/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c +--- b/drivers/net/ethernet/microsoft/mana/mana_en.c ++++ b/drivers/net/ethernet/microsoft/mana/mana_en.c +@@ -777,7 +786,8 @@ INTERDIFF: rejected hunk from patch1, cannot diff context + err = mana_gd_send_request(gc, in_len, in_buf, out_len, + out_buf); + if (err || resp->status) { +- if (req->req.msg_type != MANA_QUERY_PHY_STAT) ++ if (req->req.msg_type != MANA_QUERY_PHY_STAT && ++ mana_need_log(gc, err)) + dev_err(dev, "Failed to send mana message: %d, 0x%x\n", + err, resp->status); + return err ? err : -EPROTO; +@@ -863,7 +872,8 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + if (err == -EOPNOTSUPP) + return err; + +- if (req->req.msg_type != MANA_QUERY_PHY_STAT) ++ if (req->req.msg_type != MANA_QUERY_PHY_STAT && ++ mana_need_log(gc, err)) + dev_err(dev, "Failed to send mana message: %d, 0x%x\n", + err, resp->status); + return err ? err : -EPROTO; +diff -u b/include/net/mana/gdma.h b/include/net/mana/gdma.h +--- b/include/net/mana/gdma.h ++++ b/include/net/mana/gdma.h +@@ -57,6 +57,6 @@ ++ GDMA_EQE_HWC_INIT_DONE = 131, + GDMA_EQE_HWC_FPGA_RECONFIG = 132, + GDMA_EQE_HWC_SOC_RECONFIG_DATA = 133, +- GDMA_EQE_HWC_SOC_SERVICE = 134, + GDMA_EQE_RNIC_QP_FATAL = 176, + }; + +@@ -60,6 +60,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context + GDMA_EQE_HWC_INIT_DONE = 131, + GDMA_EQE_HWC_FPGA_RECONFIG = 132, + GDMA_EQE_HWC_SOC_RECONFIG_DATA = 133, ++ GDMA_EQE_HWC_RESET_REQUEST = 135, + GDMA_EQE_RNIC_QP_FATAL = 176, + }; + +@@ -62,6 +62,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + GDMA_EQE_HWC_FPGA_RECONFIG = 132, + GDMA_EQE_HWC_SOC_RECONFIG_DATA = 133, + GDMA_EQE_HWC_SOC_SERVICE = 134, ++ GDMA_EQE_HWC_RESET_REQUEST = 135, + GDMA_EQE_RNIC_QP_FATAL = 176, + }; + +@@ -556,5 +554,5 @@ +-/* Driver supports dynamic MSI-X vector allocation */ +-#define GDMA_DRV_CAP_FLAG_1_DYNAMIC_IRQ_ALLOC_SUPPORT BIT(13) ++/* Driver can handle holes (zeros) in the device list */ ++#define GDMA_DRV_CAP_FLAG_1_DEV_LIST_HOLES_SUP BIT(11) + + /* Driver can self reset on EQE notification */ + #define GDMA_DRV_CAP_FLAG_1_SELF_RESET_ON_EQE BIT(14) +@@ -565,9 +565,9 @@ + /* Driver can self reset on FPGA Reconfig EQE notification */ + #define GDMA_DRV_CAP_FLAG_1_HANDLE_RECONFIG_EQE BIT(17) + ++ GDMA_DRV_CAP_FLAG_1_HWC_TIMEOUT_RECONFIG | \ + GDMA_DRV_CAP_FLAG_1_VARIABLE_INDIRECTION_TABLE_SUPPORT | \ + GDMA_DRV_CAP_FLAG_1_DEV_LIST_HOLES_SUP | \ +- GDMA_DRV_CAP_FLAG_1_DYNAMIC_IRQ_ALLOC_SUPPORT | \ + GDMA_DRV_CAP_FLAG_1_HANDLE_RECONFIG_EQE) + + #define GDMA_DRV_CAP_FLAGS2 0 +@@ -571,6 +575,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context + GDMA_DRV_CAP_FLAG_1_HWC_TIMEOUT_RECONFIG | \ + GDMA_DRV_CAP_FLAG_1_VARIABLE_INDIRECTION_TABLE_SUPPORT | \ + GDMA_DRV_CAP_FLAG_1_DEV_LIST_HOLES_SUP | \ ++ GDMA_DRV_CAP_FLAG_1_SELF_RESET_ON_EQE | \ + GDMA_DRV_CAP_FLAG_1_HANDLE_RECONFIG_EQE) + + #define GDMA_DRV_CAP_FLAGS2 0 +@@ -597,6 +601,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + GDMA_DRV_CAP_FLAG_1_VARIABLE_INDIRECTION_TABLE_SUPPORT | \ + GDMA_DRV_CAP_FLAG_1_DEV_LIST_HOLES_SUP | \ + GDMA_DRV_CAP_FLAG_1_DYNAMIC_IRQ_ALLOC_SUPPORT | \ ++ GDMA_DRV_CAP_FLAG_1_SELF_RESET_ON_EQE | \ + GDMA_DRV_CAP_FLAG_1_HANDLE_RECONFIG_EQE) + + #define GDMA_DRV_CAP_FLAGS2 0 +@@ -892,5 +892,5 @@ +- +-int mana_rdma_service_event(struct gdma_context *gc, enum gdma_service_type event); ++void mana_register_debugfs(void); ++void mana_unregister_debugfs(void); + + int mana_gd_suspend(struct pci_dev *pdev, pm_message_t state); + int mana_gd_resume(struct pci_dev *pdev); +EOF + +${INTERDIFF} --fuzzy patch1 patch2 2>errors >output +[ -s errors ] && exit 1 + +cmp output expected || exit 1 diff --git a/tests/fuzzy8/run-test b/tests/fuzzy8/run-test new file mode 100755 index 00000000..680f3fa8 --- /dev/null +++ b/tests/fuzzy8/run-test @@ -0,0 +1,1760 @@ +#!/bin/sh + +# This is an interdiff(1) testcase. +# Test: Fuzzy diffing (using --fuzzy=3) with a real Linux kernel backport +# compared against its upstream version. Stresses having multiple relocations +# and most of the fuzzy diffing machinery as a whole. + + +. ${top_srcdir-.}/tests/common.sh + +cat << 'EOF' > patch1 +From b0c8e943e409740752a9a34c96743088094223e9 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Marcin=20Wcis=C5=82o?= +Date: Tue, 4 Nov 2025 20:51:12 +0100 +Subject: [PATCH] netfilter: nf_tables: report use refcount overflow +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +jira VULN-430 +cve-pre CVE-2023-4244 +commit-author Pablo Neira Ayuso +commit 1689f25924ada8fe14a4a82c38925d04994c7142 +upstream-diff Used the cleanly applying 9.4 backport + 854ec8345abb60f1fb65446a6aef2627f71196ca + +Overflow use refcount checks are not complete. + +Add helper function to deal with object reference counter tracking. +Report -EMFILE in case UINT_MAX is reached. + +nft_use_dec() splats in case that reference counter underflows, +which should not ever happen. + +Add nft_use_inc_restore() and nft_use_dec_restore() which are used +to restore reference counter from error and abort paths. + +Use u32 in nft_flowtable and nft_object since helper functions cannot +work on bitfields. + +Remove the few early incomplete checks now that the helper functions +are in place and used to check for refcount overflow. + +Fixes: 96518518cc41 ("netfilter: add nftables") + Signed-off-by: Pablo Neira Ayuso +(cherry picked from commit 1689f25924ada8fe14a4a82c38925d04994c7142) + Signed-off-by: Marcin WcisÅ‚o +--- + include/net/netfilter/nf_tables.h | 31 +++++- + net/netfilter/nf_tables_api.c | 163 ++++++++++++++++++------------ + net/netfilter/nft_flow_offload.c | 6 +- + net/netfilter/nft_immediate.c | 8 +- + net/netfilter/nft_objref.c | 8 +- + 5 files changed, 141 insertions(+), 75 deletions(-) + +diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h +index ccb3b3e4ce88e..3554c8ea03d3e 100644 +--- a/include/net/netfilter/nf_tables.h ++++ b/include/net/netfilter/nf_tables.h +@@ -1145,6 +1145,29 @@ int __nft_release_basechain(struct nft_ctx *ctx); + + unsigned int nft_do_chain(struct nft_pktinfo *pkt, void *priv); + ++static inline bool nft_use_inc(u32 *use) ++{ ++ if (*use == UINT_MAX) ++ return false; ++ ++ (*use)++; ++ ++ return true; ++} ++ ++static inline void nft_use_dec(u32 *use) ++{ ++ WARN_ON_ONCE((*use)-- == 0); ++} ++ ++/* For error and abort path: restore use counter to previous state. */ ++static inline void nft_use_inc_restore(u32 *use) ++{ ++ WARN_ON_ONCE(!nft_use_inc(use)); ++} ++ ++#define nft_use_dec_restore nft_use_dec ++ + /** + * struct nft_table - nf_tables table + * +@@ -1228,8 +1251,8 @@ struct nft_object { + struct list_head list; + struct rhlist_head rhlhead; + struct nft_object_hash_key key; +- u32 genmask:2, +- use:30; ++ u32 genmask:2; ++ u32 use; + u64 handle; + u16 udlen; + u8 *udata; +@@ -1331,8 +1354,8 @@ struct nft_flowtable { + char *name; + int hooknum; + int ops_len; +- u32 genmask:2, +- use:30; ++ u32 genmask:2; ++ u32 use; + u64 handle; + /* runtime data below here */ + struct list_head hook_list ____cacheline_aligned; +diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c +index 943bcc9342ea6..56291ca0d6518 100644 +--- a/net/netfilter/nf_tables_api.c ++++ b/net/netfilter/nf_tables_api.c +@@ -255,8 +255,10 @@ int nf_tables_bind_chain(const struct nft_ctx *ctx, struct nft_chain *chain) + if (chain->bound) + return -EBUSY; + ++ if (!nft_use_inc(&chain->use)) ++ return -EMFILE; ++ + chain->bound = true; +- chain->use++; + nft_chain_trans_bind(ctx, chain); + + return 0; +@@ -439,7 +441,7 @@ static int nft_delchain(struct nft_ctx *ctx) + if (IS_ERR(trans)) + return PTR_ERR(trans); + +- ctx->table->use--; ++ nft_use_dec(&ctx->table->use); + nft_deactivate_next(ctx->net, ctx->chain); + + return 0; +@@ -478,7 +480,7 @@ nf_tables_delrule_deactivate(struct nft_ctx *ctx, struct nft_rule *rule) + /* You cannot delete the same rule twice */ + if (nft_is_active_next(ctx->net, rule)) { + nft_deactivate_next(ctx->net, rule); +- ctx->chain->use--; ++ nft_use_dec(&ctx->chain->use); + return 0; + } + return -ENOENT; +@@ -645,7 +647,7 @@ static int nft_delset(const struct nft_ctx *ctx, struct nft_set *set) + nft_map_deactivate(ctx, set); + + nft_deactivate_next(ctx->net, set); +- ctx->table->use--; ++ nft_use_dec(&ctx->table->use); + + return err; + } +@@ -677,7 +679,7 @@ static int nft_delobj(struct nft_ctx *ctx, struct nft_object *obj) + return err; + + nft_deactivate_next(ctx->net, obj); +- ctx->table->use--; ++ nft_use_dec(&ctx->table->use); + + return err; + } +@@ -712,7 +714,7 @@ static int nft_delflowtable(struct nft_ctx *ctx, + return err; + + nft_deactivate_next(ctx->net, flowtable); +- ctx->table->use--; ++ nft_use_dec(&ctx->table->use); + + return err; + } +@@ -2298,9 +2300,6 @@ static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask, + struct nft_rule **rules; + int err; + +- if (table->use == UINT_MAX) +- return -EOVERFLOW; +- + if (nla[NFTA_CHAIN_HOOK]) { + struct nft_stats __percpu *stats = NULL; + struct nft_chain_hook hook; +@@ -2397,6 +2396,11 @@ static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask, + if (err < 0) + goto err_destroy_chain; + ++ if (!nft_use_inc(&table->use)) { ++ err = -EMFILE; ++ goto err_use; ++ } ++ + trans = nft_trans_chain_add(ctx, NFT_MSG_NEWCHAIN); + if (IS_ERR(trans)) { + err = PTR_ERR(trans); +@@ -2413,10 +2417,11 @@ static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask, + goto err_unregister_hook; + } + +- table->use++; +- + return 0; ++ + err_unregister_hook: ++ nft_use_dec_restore(&table->use); ++err_use: + nf_tables_unregister_hook(net, table, chain); + err_destroy_chain: + nf_tables_chain_destroy(ctx); +@@ -3616,9 +3621,6 @@ static int nf_tables_newrule(struct sk_buff *skb, const struct nfnl_info *info, + return -EINVAL; + handle = nf_tables_alloc_handle(table); + +- if (chain->use == UINT_MAX) +- return -EOVERFLOW; +- + if (nla[NFTA_RULE_POSITION]) { + pos_handle = be64_to_cpu(nla_get_be64(nla[NFTA_RULE_POSITION])); + old_rule = __nft_rule_lookup(chain, pos_handle); +@@ -3712,6 +3714,11 @@ static int nf_tables_newrule(struct sk_buff *skb, const struct nfnl_info *info, + } + } + ++ if (!nft_use_inc(&chain->use)) { ++ err = -EMFILE; ++ goto err_release_rule; ++ } ++ + if (info->nlh->nlmsg_flags & NLM_F_REPLACE) { + err = nft_delrule(&ctx, old_rule); + if (err < 0) +@@ -3743,7 +3750,6 @@ static int nf_tables_newrule(struct sk_buff *skb, const struct nfnl_info *info, + } + } + kvfree(expr_info); +- chain->use++; + + if (flow) + nft_trans_flow_rule(trans) = flow; +@@ -3754,6 +3760,7 @@ static int nf_tables_newrule(struct sk_buff *skb, const struct nfnl_info *info, + return 0; + + err_destroy_flow_rule: ++ nft_use_dec_restore(&chain->use); + if (flow) + nft_flow_rule_destroy(flow); + err_release_rule: +@@ -4786,9 +4793,15 @@ static int nf_tables_newset(struct sk_buff *skb, const struct nfnl_info *info, + alloc_size = sizeof(*set) + size + udlen; + if (alloc_size < size || alloc_size > INT_MAX) + return -ENOMEM; ++ ++ if (!nft_use_inc(&table->use)) ++ return -EMFILE; ++ + set = kvzalloc(alloc_size, GFP_KERNEL); +- if (!set) +- return -ENOMEM; ++ if (!set) { ++ err = -ENOMEM; ++ goto err_alloc; ++ } + + name = nla_strdup(nla[NFTA_SET_NAME], GFP_KERNEL); + if (!name) { +@@ -4846,7 +4859,7 @@ static int nf_tables_newset(struct sk_buff *skb, const struct nfnl_info *info, + goto err_set_expr_alloc; + + list_add_tail_rcu(&set->list, &table->sets); +- table->use++; ++ + return 0; + + err_set_expr_alloc: +@@ -4858,6 +4871,9 @@ err_set_init: + kfree(set->name); + err_set_name: + kvfree(set); ++err_alloc: ++ nft_use_dec_restore(&table->use); ++ + return err; + } + +@@ -4996,9 +5012,6 @@ int nf_tables_bind_set(const struct nft_ctx *ctx, struct nft_set *set, + struct nft_set_binding *i; + struct nft_set_iter iter; + +- if (set->use == UINT_MAX) +- return -EOVERFLOW; +- + if (!list_empty(&set->bindings) && nft_set_is_anonymous(set)) + return -EBUSY; + +@@ -5026,10 +5039,12 @@ int nf_tables_bind_set(const struct nft_ctx *ctx, struct nft_set *set, + return iter.err; + } + bind: ++ if (!nft_use_inc(&set->use)) ++ return -EMFILE; ++ + binding->chain = ctx->chain; + list_add_tail_rcu(&binding->list, &set->bindings); + nft_set_trans_bind(ctx, set); +- set->use++; + + return 0; + } +@@ -5103,7 +5118,7 @@ void nf_tables_activate_set(const struct nft_ctx *ctx, struct nft_set *set) + nft_clear(ctx->net, set); + } + +- set->use++; ++ nft_use_inc_restore(&set->use); + } + EXPORT_SYMBOL_GPL(nf_tables_activate_set); + +@@ -5119,7 +5134,7 @@ void nf_tables_deactivate_set(const struct nft_ctx *ctx, struct nft_set *set, + else + list_del_rcu(&binding->list); + +- set->use--; ++ nft_use_dec(&set->use); + break; + case NFT_TRANS_PREPARE: + if (nft_set_is_anonymous(set)) { +@@ -5128,7 +5143,7 @@ void nf_tables_deactivate_set(const struct nft_ctx *ctx, struct nft_set *set, + + nft_deactivate_next(ctx->net, set); + } +- set->use--; ++ nft_use_dec(&set->use); + return; + case NFT_TRANS_ABORT: + case NFT_TRANS_RELEASE: +@@ -5136,7 +5151,7 @@ void nf_tables_deactivate_set(const struct nft_ctx *ctx, struct nft_set *set, + set->flags & (NFT_SET_MAP | NFT_SET_OBJECT)) + nft_map_deactivate(ctx, set); + +- set->use--; ++ nft_use_dec(&set->use); + fallthrough; + default: + nf_tables_unbind_set(ctx, set, binding, +@@ -5927,7 +5942,7 @@ void nft_set_elem_destroy(const struct nft_set *set, void *elem, + nft_set_elem_expr_destroy(&ctx, nft_set_ext_expr(ext)); + + if (nft_set_ext_exists(ext, NFT_SET_EXT_OBJREF)) +- (*nft_set_ext_obj(ext))->use--; ++ nft_use_dec(&(*nft_set_ext_obj(ext))->use); + kfree(elem); + } + EXPORT_SYMBOL_GPL(nft_set_elem_destroy); +@@ -6429,8 +6444,16 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, + set->objtype, genmask); + if (IS_ERR(obj)) { + err = PTR_ERR(obj); ++ obj = NULL; + goto err_parse_key_end; + } ++ ++ if (!nft_use_inc(&obj->use)) { ++ err = -EMFILE; ++ obj = NULL; ++ goto err_parse_key_end; ++ } ++ + err = nft_set_ext_add(&tmpl, NFT_SET_EXT_OBJREF); + if (err < 0) + goto err_parse_key_end; +@@ -6499,10 +6522,9 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, + if (flags) + *nft_set_ext_flags(ext) = flags; + +- if (obj) { ++ if (obj) + *nft_set_ext_obj(ext) = obj; +- obj->use++; +- } ++ + if (ulen > 0) { + if (nft_set_ext_check(&tmpl, NFT_SET_EXT_USERDATA, ulen) < 0) { + err = -EINVAL; +@@ -6567,12 +6589,13 @@ err_element_clash: + kfree(trans); + err_elem_free: + nf_tables_set_elem_destroy(ctx, set, elem.priv); +- if (obj) +- obj->use--; + err_parse_data: + if (nla[NFTA_SET_ELEM_DATA] != NULL) + nft_data_release(&elem.data.val, desc.type); + err_parse_key_end: ++ if (obj) ++ nft_use_dec_restore(&obj->use); ++ + nft_data_release(&elem.key_end.val, NFT_DATA_VALUE); + err_parse_key: + nft_data_release(&elem.key.val, NFT_DATA_VALUE); +@@ -6653,7 +6676,7 @@ void nft_data_hold(const struct nft_data *data, enum nft_data_types type) + case NFT_JUMP: + case NFT_GOTO: + chain = data->verdict.chain; +- chain->use++; ++ nft_use_inc_restore(&chain->use); + break; + } + } +@@ -6668,7 +6691,7 @@ static void nft_setelem_data_activate(const struct net *net, + if (nft_set_ext_exists(ext, NFT_SET_EXT_DATA)) + nft_data_hold(nft_set_ext_data(ext), set->dtype); + if (nft_set_ext_exists(ext, NFT_SET_EXT_OBJREF)) +- (*nft_set_ext_obj(ext))->use++; ++ nft_use_inc_restore(&(*nft_set_ext_obj(ext))->use); + } + + static void nft_setelem_data_deactivate(const struct net *net, +@@ -6680,7 +6703,7 @@ static void nft_setelem_data_deactivate(const struct net *net, + if (nft_set_ext_exists(ext, NFT_SET_EXT_DATA)) + nft_data_release(nft_set_ext_data(ext), set->dtype); + if (nft_set_ext_exists(ext, NFT_SET_EXT_OBJREF)) +- (*nft_set_ext_obj(ext))->use--; ++ nft_use_dec(&(*nft_set_ext_obj(ext))->use); + } + + static int nft_del_setelem(struct nft_ctx *ctx, struct nft_set *set, +@@ -7220,9 +7243,14 @@ static int nf_tables_newobj(struct sk_buff *skb, const struct nfnl_info *info, + + nft_ctx_init(&ctx, net, skb, info->nlh, family, table, NULL, nla); + ++ if (!nft_use_inc(&table->use)) ++ return -EMFILE; ++ + type = nft_obj_type_get(net, objtype); +- if (IS_ERR(type)) +- return PTR_ERR(type); ++ if (IS_ERR(type)) { ++ err = PTR_ERR(type); ++ goto err_type; ++ } + + obj = nft_obj_init(&ctx, type, nla[NFTA_OBJ_DATA]); + if (IS_ERR(obj)) { +@@ -7256,7 +7284,7 @@ static int nf_tables_newobj(struct sk_buff *skb, const struct nfnl_info *info, + goto err_obj_ht; + + list_add_tail_rcu(&obj->list, &table->objects); +- table->use++; ++ + return 0; + err_obj_ht: + /* queued in transaction log */ +@@ -7272,6 +7300,9 @@ err_strdup: + kfree(obj); + err_init: + module_put(type->owner); ++err_type: ++ nft_use_dec_restore(&table->use); ++ + return err; + } + +@@ -7666,7 +7697,7 @@ void nf_tables_deactivate_flowtable(const struct nft_ctx *ctx, + case NFT_TRANS_PREPARE: + case NFT_TRANS_ABORT: + case NFT_TRANS_RELEASE: +- flowtable->use--; ++ nft_use_dec(&flowtable->use); + fallthrough; + default: + return; +@@ -8014,9 +8045,14 @@ static int nf_tables_newflowtable(struct sk_buff *skb, + + nft_ctx_init(&ctx, net, skb, info->nlh, family, table, NULL, nla); + ++ if (!nft_use_inc(&table->use)) ++ return -EMFILE; ++ + flowtable = kzalloc(sizeof(*flowtable), GFP_KERNEL); +- if (!flowtable) +- return -ENOMEM; ++ if (!flowtable) { ++ err = -ENOMEM; ++ goto flowtable_alloc; ++ } + + flowtable->table = table; + flowtable->handle = nf_tables_alloc_handle(table); +@@ -8071,7 +8107,6 @@ static int nf_tables_newflowtable(struct sk_buff *skb, + goto err5; + + list_add_tail_rcu(&flowtable->list, &table->flowtables); +- table->use++; + + return 0; + err5: +@@ -8088,6 +8123,9 @@ err2: + kfree(flowtable->name); + err1: + kfree(flowtable); ++flowtable_alloc: ++ nft_use_dec_restore(&table->use); ++ + return err; + } + +@@ -9392,7 +9430,7 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb) + */ + if (nft_set_is_anonymous(nft_trans_set(trans)) && + !list_empty(&nft_trans_set(trans)->bindings)) +- trans->ctx.table->use--; ++ nft_use_dec(&trans->ctx.table->use); + } + nf_tables_set_notify(&trans->ctx, nft_trans_set(trans), + NFT_MSG_NEWSET, GFP_KERNEL); +@@ -9616,7 +9654,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + nft_trans_destroy(trans); + break; + } +- trans->ctx.table->use--; ++ nft_use_dec_restore(&trans->ctx.table->use); + nft_chain_del(trans->ctx.chain); + nf_tables_unregister_hook(trans->ctx.net, + trans->ctx.table, +@@ -9625,7 +9663,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + break; + case NFT_MSG_DELCHAIN: + case NFT_MSG_DESTROYCHAIN: +- trans->ctx.table->use++; ++ nft_use_inc_restore(&trans->ctx.table->use); + nft_clear(trans->ctx.net, trans->ctx.chain); + nft_trans_destroy(trans); + break; +@@ -9634,7 +9672,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + nft_trans_destroy(trans); + break; + } +- trans->ctx.chain->use--; ++ nft_use_dec_restore(&trans->ctx.chain->use); + list_del_rcu(&nft_trans_rule(trans)->list); + nft_rule_expr_deactivate(&trans->ctx, + nft_trans_rule(trans), +@@ -9644,7 +9682,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + break; + case NFT_MSG_DELRULE: + case NFT_MSG_DESTROYRULE: +- trans->ctx.chain->use++; ++ nft_use_inc_restore(&trans->ctx.chain->use); + nft_clear(trans->ctx.net, nft_trans_rule(trans)); + nft_rule_expr_activate(&trans->ctx, nft_trans_rule(trans)); + if (trans->ctx.chain->flags & NFT_CHAIN_HW_OFFLOAD) +@@ -9657,7 +9695,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + nft_trans_destroy(trans); + break; + } +- trans->ctx.table->use--; ++ nft_use_dec_restore(&trans->ctx.table->use); + if (nft_trans_set_bound(trans)) { + nft_trans_destroy(trans); + break; +@@ -9666,7 +9704,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + break; + case NFT_MSG_DELSET: + case NFT_MSG_DESTROYSET: +- trans->ctx.table->use++; ++ nft_use_inc_restore(&trans->ctx.table->use); + nft_clear(trans->ctx.net, nft_trans_set(trans)); + if (nft_trans_set(trans)->flags & (NFT_SET_MAP | NFT_SET_OBJECT)) + nft_map_activate(&trans->ctx, nft_trans_set(trans)); +@@ -9710,13 +9748,13 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + nft_obj_destroy(&trans->ctx, nft_trans_obj_newobj(trans)); + nft_trans_destroy(trans); + } else { +- trans->ctx.table->use--; ++ nft_use_dec_restore(&trans->ctx.table->use); + nft_obj_del(nft_trans_obj(trans)); + } + break; + case NFT_MSG_DELOBJ: + case NFT_MSG_DESTROYOBJ: +- trans->ctx.table->use++; ++ nft_use_inc_restore(&trans->ctx.table->use); + nft_clear(trans->ctx.net, nft_trans_obj(trans)); + nft_trans_destroy(trans); + break; +@@ -9725,7 +9763,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + nft_unregister_flowtable_net_hooks(net, + &nft_trans_flowtable_hooks(trans)); + } else { +- trans->ctx.table->use--; ++ nft_use_dec_restore(&trans->ctx.table->use); + list_del_rcu(&nft_trans_flowtable(trans)->list); + nft_unregister_flowtable_net_hooks(net, + &nft_trans_flowtable(trans)->hook_list); +@@ -9737,7 +9775,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + list_splice(&nft_trans_flowtable_hooks(trans), + &nft_trans_flowtable(trans)->hook_list); + } else { +- trans->ctx.table->use++; ++ nft_use_inc_restore(&trans->ctx.table->use); + nft_clear(trans->ctx.net, nft_trans_flowtable(trans)); + } + nft_trans_destroy(trans); +@@ -10181,8 +10219,9 @@ static int nft_verdict_init(const struct nft_ctx *ctx, struct nft_data *data, + if (desc->flags & NFT_DATA_DESC_SETELEM && + chain->flags & NFT_CHAIN_BINDING) + return -EINVAL; ++ if (!nft_use_inc(&chain->use)) ++ return -EMFILE; + +- chain->use++; + data->verdict.chain = chain; + break; + default: +@@ -10202,7 +10241,7 @@ static void nft_verdict_uninit(const struct nft_data *data) + case NFT_JUMP: + case NFT_GOTO: + chain = data->verdict.chain; +- chain->use--; ++ nft_use_dec(&chain->use); + break; + } + } +@@ -10371,11 +10410,11 @@ int __nft_release_basechain(struct nft_ctx *ctx) + nf_tables_unregister_hook(ctx->net, ctx->chain->table, ctx->chain); + list_for_each_entry_safe(rule, nr, &ctx->chain->rules, list) { + list_del(&rule->list); +- ctx->chain->use--; ++ nft_use_dec(&ctx->chain->use); + nf_tables_rule_release(ctx, rule); + } + nft_chain_del(ctx->chain); +- ctx->table->use--; ++ nft_use_dec(&ctx->table->use); + nf_tables_chain_destroy(ctx); + + return 0; +@@ -10425,18 +10464,18 @@ static void __nft_release_table(struct net *net, struct nft_table *table) + ctx.chain = chain; + list_for_each_entry_safe(rule, nr, &chain->rules, list) { + list_del(&rule->list); +- chain->use--; ++ nft_use_dec(&chain->use); + nf_tables_rule_release(&ctx, rule); + } + } + list_for_each_entry_safe(flowtable, nf, &table->flowtables, list) { + list_del(&flowtable->list); +- table->use--; ++ nft_use_dec(&table->use); + nf_tables_flowtable_destroy(flowtable); + } + list_for_each_entry_safe(set, ns, &table->sets, list) { + list_del(&set->list); +- table->use--; ++ nft_use_dec(&table->use); + if (set->flags & (NFT_SET_MAP | NFT_SET_OBJECT)) + nft_map_deactivate(&ctx, set); + +@@ -10444,13 +10483,13 @@ static void __nft_release_table(struct net *net, struct nft_table *table) + } + list_for_each_entry_safe(obj, ne, &table->objects, list) { + nft_obj_del(obj); +- table->use--; ++ nft_use_dec(&table->use); + nft_obj_destroy(&ctx, obj); + } + list_for_each_entry_safe(chain, nc, &table->chains, list) { + ctx.chain = chain; + nft_chain_del(chain); +- table->use--; ++ nft_use_dec(&table->use); + nf_tables_chain_destroy(&ctx); + } + nf_tables_table_destroy(&ctx); +diff --git a/net/netfilter/nft_flow_offload.c b/net/netfilter/nft_flow_offload.c +index 6db8c802d5e76..9a05fca9c48b7 100644 +--- a/net/netfilter/nft_flow_offload.c ++++ b/net/netfilter/nft_flow_offload.c +@@ -381,8 +381,10 @@ static int nft_flow_offload_init(const struct nft_ctx *ctx, + if (IS_ERR(flowtable)) + return PTR_ERR(flowtable); + ++ if (!nft_use_inc(&flowtable->use)) ++ return -EMFILE; ++ + priv->flowtable = flowtable; +- flowtable->use++; + + return nf_ct_netns_get(ctx->net, ctx->family); + } +@@ -401,7 +403,7 @@ static void nft_flow_offload_activate(const struct nft_ctx *ctx, + { + struct nft_flow_offload *priv = nft_expr_priv(expr); + +- priv->flowtable->use++; ++ nft_use_inc_restore(&priv->flowtable->use); + } + + static void nft_flow_offload_destroy(const struct nft_ctx *ctx, +diff --git a/net/netfilter/nft_immediate.c b/net/netfilter/nft_immediate.c +index 7c810005a1f9f..11a39289fe49b 100644 +--- a/net/netfilter/nft_immediate.c ++++ b/net/netfilter/nft_immediate.c +@@ -159,7 +159,7 @@ static void nft_immediate_deactivate(const struct nft_ctx *ctx, + default: + nft_chain_del(chain); + chain->bound = false; +- chain->table->use--; ++ nft_use_dec(&chain->table->use); + break; + } + break; +@@ -198,7 +198,7 @@ static void nft_immediate_destroy(const struct nft_ctx *ctx, + * let the transaction records release this chain and its rules. + */ + if (chain->bound) { +- chain->use--; ++ nft_use_dec(&chain->use); + break; + } + +@@ -206,9 +206,9 @@ static void nft_immediate_destroy(const struct nft_ctx *ctx, + chain_ctx = *ctx; + chain_ctx.chain = chain; + +- chain->use--; ++ nft_use_dec(&chain->use); + list_for_each_entry_safe(rule, n, &chain->rules, list) { +- chain->use--; ++ nft_use_dec(&chain->use); + list_del(&rule->list); + nf_tables_rule_destroy(&chain_ctx, rule); + } +diff --git a/net/netfilter/nft_objref.c b/net/netfilter/nft_objref.c +index e873401182899..10850266221a6 100644 +--- a/net/netfilter/nft_objref.c ++++ b/net/netfilter/nft_objref.c +@@ -41,8 +41,10 @@ static int nft_objref_init(const struct nft_ctx *ctx, + if (IS_ERR(obj)) + return -ENOENT; + ++ if (!nft_use_inc(&obj->use)) ++ return -EMFILE; ++ + nft_objref_priv(expr) = obj; +- obj->use++; + + return 0; + } +@@ -72,7 +74,7 @@ static void nft_objref_deactivate(const struct nft_ctx *ctx, + if (phase == NFT_TRANS_COMMIT) + return; + +- obj->use--; ++ nft_use_dec(&obj->use); + } + + static void nft_objref_activate(const struct nft_ctx *ctx, +@@ -80,7 +82,7 @@ static void nft_objref_activate(const struct nft_ctx *ctx, + { + struct nft_object *obj = nft_objref_priv(expr); + +- obj->use++; ++ nft_use_inc_restore(&obj->use); + } + + static struct nft_expr_type nft_objref_type; +-- +2.52.0 + +EOF + +cat << 'EOF' > patch2 +From 1689f25924ada8fe14a4a82c38925d04994c7142 Mon Sep 17 00:00:00 2001 +From: Pablo Neira Ayuso +Date: Wed, 28 Jun 2023 16:24:27 +0200 +Subject: [PATCH] netfilter: nf_tables: report use refcount overflow + +Overflow use refcount checks are not complete. + +Add helper function to deal with object reference counter tracking. +Report -EMFILE in case UINT_MAX is reached. + +nft_use_dec() splats in case that reference counter underflows, +which should not ever happen. + +Add nft_use_inc_restore() and nft_use_dec_restore() which are used +to restore reference counter from error and abort paths. + +Use u32 in nft_flowtable and nft_object since helper functions cannot +work on bitfields. + +Remove the few early incomplete checks now that the helper functions +are in place and used to check for refcount overflow. + +Fixes: 96518518cc41 ("netfilter: add nftables") +Signed-off-by: Pablo Neira Ayuso +--- + include/net/netfilter/nf_tables.h | 31 +++++- + net/netfilter/nf_tables_api.c | 163 ++++++++++++++++++------------ + net/netfilter/nft_flow_offload.c | 6 +- + net/netfilter/nft_immediate.c | 8 +- + net/netfilter/nft_objref.c | 8 +- + 5 files changed, 141 insertions(+), 75 deletions(-) + +diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h +index 84f2fd85fd5ae..640441a2f9266 100644 +--- a/include/net/netfilter/nf_tables.h ++++ b/include/net/netfilter/nf_tables.h +@@ -1211,6 +1211,29 @@ int __nft_release_basechain(struct nft_ctx *ctx); + + unsigned int nft_do_chain(struct nft_pktinfo *pkt, void *priv); + ++static inline bool nft_use_inc(u32 *use) ++{ ++ if (*use == UINT_MAX) ++ return false; ++ ++ (*use)++; ++ ++ return true; ++} ++ ++static inline void nft_use_dec(u32 *use) ++{ ++ WARN_ON_ONCE((*use)-- == 0); ++} ++ ++/* For error and abort path: restore use counter to previous state. */ ++static inline void nft_use_inc_restore(u32 *use) ++{ ++ WARN_ON_ONCE(!nft_use_inc(use)); ++} ++ ++#define nft_use_dec_restore nft_use_dec ++ + /** + * struct nft_table - nf_tables table + * +@@ -1296,8 +1319,8 @@ struct nft_object { + struct list_head list; + struct rhlist_head rhlhead; + struct nft_object_hash_key key; +- u32 genmask:2, +- use:30; ++ u32 genmask:2; ++ u32 use; + u64 handle; + u16 udlen; + u8 *udata; +@@ -1399,8 +1422,8 @@ struct nft_flowtable { + char *name; + int hooknum; + int ops_len; +- u32 genmask:2, +- use:30; ++ u32 genmask:2; ++ u32 use; + u64 handle; + /* runtime data below here */ + struct list_head hook_list ____cacheline_aligned; +diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c +index 9573a8fcad796..86b3c4de7f40d 100644 +--- a/net/netfilter/nf_tables_api.c ++++ b/net/netfilter/nf_tables_api.c +@@ -253,8 +253,10 @@ int nf_tables_bind_chain(const struct nft_ctx *ctx, struct nft_chain *chain) + if (chain->bound) + return -EBUSY; + ++ if (!nft_use_inc(&chain->use)) ++ return -EMFILE; ++ + chain->bound = true; +- chain->use++; + nft_chain_trans_bind(ctx, chain); + + return 0; +@@ -437,7 +439,7 @@ static int nft_delchain(struct nft_ctx *ctx) + if (IS_ERR(trans)) + return PTR_ERR(trans); + +- ctx->table->use--; ++ nft_use_dec(&ctx->table->use); + nft_deactivate_next(ctx->net, ctx->chain); + + return 0; +@@ -476,7 +478,7 @@ nf_tables_delrule_deactivate(struct nft_ctx *ctx, struct nft_rule *rule) + /* You cannot delete the same rule twice */ + if (nft_is_active_next(ctx->net, rule)) { + nft_deactivate_next(ctx->net, rule); +- ctx->chain->use--; ++ nft_use_dec(&ctx->chain->use); + return 0; + } + return -ENOENT; +@@ -644,7 +646,7 @@ static int nft_delset(const struct nft_ctx *ctx, struct nft_set *set) + nft_map_deactivate(ctx, set); + + nft_deactivate_next(ctx->net, set); +- ctx->table->use--; ++ nft_use_dec(&ctx->table->use); + + return err; + } +@@ -676,7 +678,7 @@ static int nft_delobj(struct nft_ctx *ctx, struct nft_object *obj) + return err; + + nft_deactivate_next(ctx->net, obj); +- ctx->table->use--; ++ nft_use_dec(&ctx->table->use); + + return err; + } +@@ -711,7 +713,7 @@ static int nft_delflowtable(struct nft_ctx *ctx, + return err; + + nft_deactivate_next(ctx->net, flowtable); +- ctx->table->use--; ++ nft_use_dec(&ctx->table->use); + + return err; + } +@@ -2396,9 +2398,6 @@ static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask, + struct nft_chain *chain; + int err; + +- if (table->use == UINT_MAX) +- return -EOVERFLOW; +- + if (nla[NFTA_CHAIN_HOOK]) { + struct nft_stats __percpu *stats = NULL; + struct nft_chain_hook hook = {}; +@@ -2494,6 +2493,11 @@ static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask, + if (err < 0) + goto err_destroy_chain; + ++ if (!nft_use_inc(&table->use)) { ++ err = -EMFILE; ++ goto err_use; ++ } ++ + trans = nft_trans_chain_add(ctx, NFT_MSG_NEWCHAIN); + if (IS_ERR(trans)) { + err = PTR_ERR(trans); +@@ -2510,10 +2514,11 @@ static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask, + goto err_unregister_hook; + } + +- table->use++; +- + return 0; ++ + err_unregister_hook: ++ nft_use_dec_restore(&table->use); ++err_use: + nf_tables_unregister_hook(net, table, chain); + err_destroy_chain: + nf_tables_chain_destroy(ctx); +@@ -3840,9 +3845,6 @@ static int nf_tables_newrule(struct sk_buff *skb, const struct nfnl_info *info, + return -EINVAL; + handle = nf_tables_alloc_handle(table); + +- if (chain->use == UINT_MAX) +- return -EOVERFLOW; +- + if (nla[NFTA_RULE_POSITION]) { + pos_handle = be64_to_cpu(nla_get_be64(nla[NFTA_RULE_POSITION])); + old_rule = __nft_rule_lookup(chain, pos_handle); +@@ -3936,6 +3938,11 @@ static int nf_tables_newrule(struct sk_buff *skb, const struct nfnl_info *info, + } + } + ++ if (!nft_use_inc(&chain->use)) { ++ err = -EMFILE; ++ goto err_release_rule; ++ } ++ + if (info->nlh->nlmsg_flags & NLM_F_REPLACE) { + err = nft_delrule(&ctx, old_rule); + if (err < 0) +@@ -3967,7 +3974,6 @@ static int nf_tables_newrule(struct sk_buff *skb, const struct nfnl_info *info, + } + } + kvfree(expr_info); +- chain->use++; + + if (flow) + nft_trans_flow_rule(trans) = flow; +@@ -3978,6 +3984,7 @@ static int nf_tables_newrule(struct sk_buff *skb, const struct nfnl_info *info, + return 0; + + err_destroy_flow_rule: ++ nft_use_dec_restore(&chain->use); + if (flow) + nft_flow_rule_destroy(flow); + err_release_rule: +@@ -5014,9 +5021,15 @@ static int nf_tables_newset(struct sk_buff *skb, const struct nfnl_info *info, + alloc_size = sizeof(*set) + size + udlen; + if (alloc_size < size || alloc_size > INT_MAX) + return -ENOMEM; ++ ++ if (!nft_use_inc(&table->use)) ++ return -EMFILE; ++ + set = kvzalloc(alloc_size, GFP_KERNEL_ACCOUNT); +- if (!set) +- return -ENOMEM; ++ if (!set) { ++ err = -ENOMEM; ++ goto err_alloc; ++ } + + name = nla_strdup(nla[NFTA_SET_NAME], GFP_KERNEL_ACCOUNT); + if (!name) { +@@ -5074,7 +5087,7 @@ static int nf_tables_newset(struct sk_buff *skb, const struct nfnl_info *info, + goto err_set_expr_alloc; + + list_add_tail_rcu(&set->list, &table->sets); +- table->use++; ++ + return 0; + + err_set_expr_alloc: +@@ -5086,6 +5099,9 @@ err_set_init: + kfree(set->name); + err_set_name: + kvfree(set); ++err_alloc: ++ nft_use_dec_restore(&table->use); ++ + return err; + } + +@@ -5224,9 +5240,6 @@ int nf_tables_bind_set(const struct nft_ctx *ctx, struct nft_set *set, + struct nft_set_binding *i; + struct nft_set_iter iter; + +- if (set->use == UINT_MAX) +- return -EOVERFLOW; +- + if (!list_empty(&set->bindings) && nft_set_is_anonymous(set)) + return -EBUSY; + +@@ -5254,10 +5267,12 @@ int nf_tables_bind_set(const struct nft_ctx *ctx, struct nft_set *set, + return iter.err; + } + bind: ++ if (!nft_use_inc(&set->use)) ++ return -EMFILE; ++ + binding->chain = ctx->chain; + list_add_tail_rcu(&binding->list, &set->bindings); + nft_set_trans_bind(ctx, set); +- set->use++; + + return 0; + } +@@ -5331,7 +5346,7 @@ void nf_tables_activate_set(const struct nft_ctx *ctx, struct nft_set *set) + nft_clear(ctx->net, set); + } + +- set->use++; ++ nft_use_inc_restore(&set->use); + } + EXPORT_SYMBOL_GPL(nf_tables_activate_set); + +@@ -5347,7 +5362,7 @@ void nf_tables_deactivate_set(const struct nft_ctx *ctx, struct nft_set *set, + else + list_del_rcu(&binding->list); + +- set->use--; ++ nft_use_dec(&set->use); + break; + case NFT_TRANS_PREPARE: + if (nft_set_is_anonymous(set)) { +@@ -5356,7 +5371,7 @@ void nf_tables_deactivate_set(const struct nft_ctx *ctx, struct nft_set *set, + + nft_deactivate_next(ctx->net, set); + } +- set->use--; ++ nft_use_dec(&set->use); + return; + case NFT_TRANS_ABORT: + case NFT_TRANS_RELEASE: +@@ -5364,7 +5379,7 @@ void nf_tables_deactivate_set(const struct nft_ctx *ctx, struct nft_set *set, + set->flags & (NFT_SET_MAP | NFT_SET_OBJECT)) + nft_map_deactivate(ctx, set); + +- set->use--; ++ nft_use_dec(&set->use); + fallthrough; + default: + nf_tables_unbind_set(ctx, set, binding, +@@ -6155,7 +6170,7 @@ void nft_set_elem_destroy(const struct nft_set *set, void *elem, + nft_set_elem_expr_destroy(&ctx, nft_set_ext_expr(ext)); + + if (nft_set_ext_exists(ext, NFT_SET_EXT_OBJREF)) +- (*nft_set_ext_obj(ext))->use--; ++ nft_use_dec(&(*nft_set_ext_obj(ext))->use); + kfree(elem); + } + EXPORT_SYMBOL_GPL(nft_set_elem_destroy); +@@ -6657,8 +6672,16 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, + set->objtype, genmask); + if (IS_ERR(obj)) { + err = PTR_ERR(obj); ++ obj = NULL; + goto err_parse_key_end; + } ++ ++ if (!nft_use_inc(&obj->use)) { ++ err = -EMFILE; ++ obj = NULL; ++ goto err_parse_key_end; ++ } ++ + err = nft_set_ext_add(&tmpl, NFT_SET_EXT_OBJREF); + if (err < 0) + goto err_parse_key_end; +@@ -6727,10 +6750,9 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set, + if (flags) + *nft_set_ext_flags(ext) = flags; + +- if (obj) { ++ if (obj) + *nft_set_ext_obj(ext) = obj; +- obj->use++; +- } ++ + if (ulen > 0) { + if (nft_set_ext_check(&tmpl, NFT_SET_EXT_USERDATA, ulen) < 0) { + err = -EINVAL; +@@ -6798,12 +6820,13 @@ err_element_clash: + kfree(trans); + err_elem_free: + nf_tables_set_elem_destroy(ctx, set, elem.priv); +- if (obj) +- obj->use--; + err_parse_data: + if (nla[NFTA_SET_ELEM_DATA] != NULL) + nft_data_release(&elem.data.val, desc.type); + err_parse_key_end: ++ if (obj) ++ nft_use_dec_restore(&obj->use); ++ + nft_data_release(&elem.key_end.val, NFT_DATA_VALUE); + err_parse_key: + nft_data_release(&elem.key.val, NFT_DATA_VALUE); +@@ -6883,7 +6906,7 @@ void nft_data_hold(const struct nft_data *data, enum nft_data_types type) + case NFT_JUMP: + case NFT_GOTO: + chain = data->verdict.chain; +- chain->use++; ++ nft_use_inc_restore(&chain->use); + break; + } + } +@@ -6898,7 +6921,7 @@ static void nft_setelem_data_activate(const struct net *net, + if (nft_set_ext_exists(ext, NFT_SET_EXT_DATA)) + nft_data_hold(nft_set_ext_data(ext), set->dtype); + if (nft_set_ext_exists(ext, NFT_SET_EXT_OBJREF)) +- (*nft_set_ext_obj(ext))->use++; ++ nft_use_inc_restore(&(*nft_set_ext_obj(ext))->use); + } + + static void nft_setelem_data_deactivate(const struct net *net, +@@ -6910,7 +6933,7 @@ static void nft_setelem_data_deactivate(const struct net *net, + if (nft_set_ext_exists(ext, NFT_SET_EXT_DATA)) + nft_data_release(nft_set_ext_data(ext), set->dtype); + if (nft_set_ext_exists(ext, NFT_SET_EXT_OBJREF)) +- (*nft_set_ext_obj(ext))->use--; ++ nft_use_dec(&(*nft_set_ext_obj(ext))->use); + } + + static int nft_del_setelem(struct nft_ctx *ctx, struct nft_set *set, +@@ -7453,9 +7476,14 @@ static int nf_tables_newobj(struct sk_buff *skb, const struct nfnl_info *info, + + nft_ctx_init(&ctx, net, skb, info->nlh, family, table, NULL, nla); + ++ if (!nft_use_inc(&table->use)) ++ return -EMFILE; ++ + type = nft_obj_type_get(net, objtype); +- if (IS_ERR(type)) +- return PTR_ERR(type); ++ if (IS_ERR(type)) { ++ err = PTR_ERR(type); ++ goto err_type; ++ } + + obj = nft_obj_init(&ctx, type, nla[NFTA_OBJ_DATA]); + if (IS_ERR(obj)) { +@@ -7489,7 +7517,7 @@ static int nf_tables_newobj(struct sk_buff *skb, const struct nfnl_info *info, + goto err_obj_ht; + + list_add_tail_rcu(&obj->list, &table->objects); +- table->use++; ++ + return 0; + err_obj_ht: + /* queued in transaction log */ +@@ -7505,6 +7533,9 @@ err_strdup: + kfree(obj); + err_init: + module_put(type->owner); ++err_type: ++ nft_use_dec_restore(&table->use); ++ + return err; + } + +@@ -7906,7 +7937,7 @@ void nf_tables_deactivate_flowtable(const struct nft_ctx *ctx, + case NFT_TRANS_PREPARE: + case NFT_TRANS_ABORT: + case NFT_TRANS_RELEASE: +- flowtable->use--; ++ nft_use_dec(&flowtable->use); + fallthrough; + default: + return; +@@ -8260,9 +8291,14 @@ static int nf_tables_newflowtable(struct sk_buff *skb, + + nft_ctx_init(&ctx, net, skb, info->nlh, family, table, NULL, nla); + ++ if (!nft_use_inc(&table->use)) ++ return -EMFILE; ++ + flowtable = kzalloc(sizeof(*flowtable), GFP_KERNEL_ACCOUNT); +- if (!flowtable) +- return -ENOMEM; ++ if (!flowtable) { ++ err = -ENOMEM; ++ goto flowtable_alloc; ++ } + + flowtable->table = table; + flowtable->handle = nf_tables_alloc_handle(table); +@@ -8317,7 +8353,6 @@ static int nf_tables_newflowtable(struct sk_buff *skb, + goto err5; + + list_add_tail_rcu(&flowtable->list, &table->flowtables); +- table->use++; + + return 0; + err5: +@@ -8334,6 +8369,9 @@ err2: + kfree(flowtable->name); + err1: + kfree(flowtable); ++flowtable_alloc: ++ nft_use_dec_restore(&table->use); ++ + return err; + } + +@@ -9713,7 +9751,7 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb) + */ + if (nft_set_is_anonymous(nft_trans_set(trans)) && + !list_empty(&nft_trans_set(trans)->bindings)) +- trans->ctx.table->use--; ++ nft_use_dec(&trans->ctx.table->use); + } + nf_tables_set_notify(&trans->ctx, nft_trans_set(trans), + NFT_MSG_NEWSET, GFP_KERNEL); +@@ -9943,7 +9981,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + nft_trans_destroy(trans); + break; + } +- trans->ctx.table->use--; ++ nft_use_dec_restore(&trans->ctx.table->use); + nft_chain_del(trans->ctx.chain); + nf_tables_unregister_hook(trans->ctx.net, + trans->ctx.table, +@@ -9956,7 +9994,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + list_splice(&nft_trans_chain_hooks(trans), + &nft_trans_basechain(trans)->hook_list); + } else { +- trans->ctx.table->use++; ++ nft_use_inc_restore(&trans->ctx.table->use); + nft_clear(trans->ctx.net, trans->ctx.chain); + } + nft_trans_destroy(trans); +@@ -9966,7 +10004,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + nft_trans_destroy(trans); + break; + } +- trans->ctx.chain->use--; ++ nft_use_dec_restore(&trans->ctx.chain->use); + list_del_rcu(&nft_trans_rule(trans)->list); + nft_rule_expr_deactivate(&trans->ctx, + nft_trans_rule(trans), +@@ -9976,7 +10014,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + break; + case NFT_MSG_DELRULE: + case NFT_MSG_DESTROYRULE: +- trans->ctx.chain->use++; ++ nft_use_inc_restore(&trans->ctx.chain->use); + nft_clear(trans->ctx.net, nft_trans_rule(trans)); + nft_rule_expr_activate(&trans->ctx, nft_trans_rule(trans)); + if (trans->ctx.chain->flags & NFT_CHAIN_HW_OFFLOAD) +@@ -9989,7 +10027,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + nft_trans_destroy(trans); + break; + } +- trans->ctx.table->use--; ++ nft_use_dec_restore(&trans->ctx.table->use); + if (nft_trans_set_bound(trans)) { + nft_trans_destroy(trans); + break; +@@ -9998,7 +10036,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + break; + case NFT_MSG_DELSET: + case NFT_MSG_DESTROYSET: +- trans->ctx.table->use++; ++ nft_use_inc_restore(&trans->ctx.table->use); + nft_clear(trans->ctx.net, nft_trans_set(trans)); + if (nft_trans_set(trans)->flags & (NFT_SET_MAP | NFT_SET_OBJECT)) + nft_map_activate(&trans->ctx, nft_trans_set(trans)); +@@ -10042,13 +10080,13 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + nft_obj_destroy(&trans->ctx, nft_trans_obj_newobj(trans)); + nft_trans_destroy(trans); + } else { +- trans->ctx.table->use--; ++ nft_use_dec_restore(&trans->ctx.table->use); + nft_obj_del(nft_trans_obj(trans)); + } + break; + case NFT_MSG_DELOBJ: + case NFT_MSG_DESTROYOBJ: +- trans->ctx.table->use++; ++ nft_use_inc_restore(&trans->ctx.table->use); + nft_clear(trans->ctx.net, nft_trans_obj(trans)); + nft_trans_destroy(trans); + break; +@@ -10057,7 +10095,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + nft_unregister_flowtable_net_hooks(net, + &nft_trans_flowtable_hooks(trans)); + } else { +- trans->ctx.table->use--; ++ nft_use_dec_restore(&trans->ctx.table->use); + list_del_rcu(&nft_trans_flowtable(trans)->list); + nft_unregister_flowtable_net_hooks(net, + &nft_trans_flowtable(trans)->hook_list); +@@ -10069,7 +10107,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action) + list_splice(&nft_trans_flowtable_hooks(trans), + &nft_trans_flowtable(trans)->hook_list); + } else { +- trans->ctx.table->use++; ++ nft_use_inc_restore(&trans->ctx.table->use); + nft_clear(trans->ctx.net, nft_trans_flowtable(trans)); + } + nft_trans_destroy(trans); +@@ -10518,8 +10556,9 @@ static int nft_verdict_init(const struct nft_ctx *ctx, struct nft_data *data, + if (desc->flags & NFT_DATA_DESC_SETELEM && + chain->flags & NFT_CHAIN_BINDING) + return -EINVAL; ++ if (!nft_use_inc(&chain->use)) ++ return -EMFILE; + +- chain->use++; + data->verdict.chain = chain; + break; + } +@@ -10537,7 +10576,7 @@ static void nft_verdict_uninit(const struct nft_data *data) + case NFT_JUMP: + case NFT_GOTO: + chain = data->verdict.chain; +- chain->use--; ++ nft_use_dec(&chain->use); + break; + } + } +@@ -10706,11 +10745,11 @@ int __nft_release_basechain(struct nft_ctx *ctx) + nf_tables_unregister_hook(ctx->net, ctx->chain->table, ctx->chain); + list_for_each_entry_safe(rule, nr, &ctx->chain->rules, list) { + list_del(&rule->list); +- ctx->chain->use--; ++ nft_use_dec(&ctx->chain->use); + nf_tables_rule_release(ctx, rule); + } + nft_chain_del(ctx->chain); +- ctx->table->use--; ++ nft_use_dec(&ctx->table->use); + nf_tables_chain_destroy(ctx); + + return 0; +@@ -10760,18 +10799,18 @@ static void __nft_release_table(struct net *net, struct nft_table *table) + ctx.chain = chain; + list_for_each_entry_safe(rule, nr, &chain->rules, list) { + list_del(&rule->list); +- chain->use--; ++ nft_use_dec(&chain->use); + nf_tables_rule_release(&ctx, rule); + } + } + list_for_each_entry_safe(flowtable, nf, &table->flowtables, list) { + list_del(&flowtable->list); +- table->use--; ++ nft_use_dec(&table->use); + nf_tables_flowtable_destroy(flowtable); + } + list_for_each_entry_safe(set, ns, &table->sets, list) { + list_del(&set->list); +- table->use--; ++ nft_use_dec(&table->use); + if (set->flags & (NFT_SET_MAP | NFT_SET_OBJECT)) + nft_map_deactivate(&ctx, set); + +@@ -10779,13 +10818,13 @@ static void __nft_release_table(struct net *net, struct nft_table *table) + } + list_for_each_entry_safe(obj, ne, &table->objects, list) { + nft_obj_del(obj); +- table->use--; ++ nft_use_dec(&table->use); + nft_obj_destroy(&ctx, obj); + } + list_for_each_entry_safe(chain, nc, &table->chains, list) { + ctx.chain = chain; + nft_chain_del(chain); +- table->use--; ++ nft_use_dec(&table->use); + nf_tables_chain_destroy(&ctx); + } + nf_tables_table_destroy(&ctx); +diff --git a/net/netfilter/nft_flow_offload.c b/net/netfilter/nft_flow_offload.c +index 5ef9146e74ad9..ab3362c483b4a 100644 +--- a/net/netfilter/nft_flow_offload.c ++++ b/net/netfilter/nft_flow_offload.c +@@ -408,8 +408,10 @@ static int nft_flow_offload_init(const struct nft_ctx *ctx, + if (IS_ERR(flowtable)) + return PTR_ERR(flowtable); + ++ if (!nft_use_inc(&flowtable->use)) ++ return -EMFILE; ++ + priv->flowtable = flowtable; +- flowtable->use++; + + return nf_ct_netns_get(ctx->net, ctx->family); + } +@@ -428,7 +430,7 @@ static void nft_flow_offload_activate(const struct nft_ctx *ctx, + { + struct nft_flow_offload *priv = nft_expr_priv(expr); + +- priv->flowtable->use++; ++ nft_use_inc_restore(&priv->flowtable->use); + } + + static void nft_flow_offload_destroy(const struct nft_ctx *ctx, +diff --git a/net/netfilter/nft_immediate.c b/net/netfilter/nft_immediate.c +index 3d76ebfe8939b..407d7197f75bb 100644 +--- a/net/netfilter/nft_immediate.c ++++ b/net/netfilter/nft_immediate.c +@@ -159,7 +159,7 @@ static void nft_immediate_deactivate(const struct nft_ctx *ctx, + default: + nft_chain_del(chain); + chain->bound = false; +- chain->table->use--; ++ nft_use_dec(&chain->table->use); + break; + } + break; +@@ -198,7 +198,7 @@ static void nft_immediate_destroy(const struct nft_ctx *ctx, + * let the transaction records release this chain and its rules. + */ + if (chain->bound) { +- chain->use--; ++ nft_use_dec(&chain->use); + break; + } + +@@ -206,9 +206,9 @@ static void nft_immediate_destroy(const struct nft_ctx *ctx, + chain_ctx = *ctx; + chain_ctx.chain = chain; + +- chain->use--; ++ nft_use_dec(&chain->use); + list_for_each_entry_safe(rule, n, &chain->rules, list) { +- chain->use--; ++ nft_use_dec(&chain->use); + list_del(&rule->list); + nf_tables_rule_destroy(&chain_ctx, rule); + } +diff --git a/net/netfilter/nft_objref.c b/net/netfilter/nft_objref.c +index a48dd5b5d45b1..509011b1ef597 100644 +--- a/net/netfilter/nft_objref.c ++++ b/net/netfilter/nft_objref.c +@@ -41,8 +41,10 @@ static int nft_objref_init(const struct nft_ctx *ctx, + if (IS_ERR(obj)) + return -ENOENT; + ++ if (!nft_use_inc(&obj->use)) ++ return -EMFILE; ++ + nft_objref_priv(expr) = obj; +- obj->use++; + + return 0; + } +@@ -72,7 +74,7 @@ static void nft_objref_deactivate(const struct nft_ctx *ctx, + if (phase == NFT_TRANS_COMMIT) + return; + +- obj->use--; ++ nft_use_dec(&obj->use); + } + + static void nft_objref_activate(const struct nft_ctx *ctx, +@@ -80,7 +82,7 @@ static void nft_objref_activate(const struct nft_ctx *ctx, + { + struct nft_object *obj = nft_objref_priv(expr); + +- obj->use++; ++ nft_use_inc_restore(&obj->use); + } + + static const struct nft_expr_ops nft_objref_ops = { +-- +2.52.0 + +EOF + +cat << 'EOF' > expected +diff -u b/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c +--- b/net/netfilter/nf_tables_api.c ++++ b/net/netfilter/nf_tables_api.c +@@ -2297,6 +2297,6 @@ +- struct nft_chain *chain; ++ struct nft_rule **rules; + int err; + + if (nla[NFTA_CHAIN_HOOK]) { + struct nft_stats __percpu *stats = NULL; +- struct nft_chain_hook hook = {}; ++ struct nft_chain_hook hook; +@@ -4790,13 +4790,13 @@ +- +- if (!nft_use_inc(&table->use)) +- return -EMFILE; +- + alloc_size = sizeof(*set) + size + udlen; + if (alloc_size < size || alloc_size > INT_MAX) + return -ENOMEM; +- set = kvzalloc(alloc_size, GFP_KERNEL_ACCOUNT); ++ set = kvzalloc(alloc_size, GFP_KERNEL); + if (!set) + return -ENOMEM; + +- name = nla_strdup(nla[NFTA_SET_NAME], GFP_KERNEL_ACCOUNT); ++ ++ if (!nft_use_inc(&table->use)) ++ return -EMFILE; ++ ++ name = nla_strdup(nla[NFTA_SET_NAME], GFP_KERNEL); + if (!name) { +@@ -5026,8 +5037,10 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + if (alloc_size < size || alloc_size > INT_MAX) + return -ENOMEM; + set = kvzalloc(alloc_size, GFP_KERNEL_ACCOUNT); +- if (!set) +- return -ENOMEM; ++ if (!set) { ++ err = -ENOMEM; ++ goto err_alloc; ++ } + + name = nla_strdup(nla[NFTA_SET_NAME], GFP_KERNEL_ACCOUNT); + if (!name) { +@@ -8040,12 +8040,12 @@ +- if (!nft_use_inc(&table->use)) +- return -EMFILE; +- + + nft_ctx_init(&ctx, net, skb, info->nlh, family, table, NULL, nla); + +- flowtable = kzalloc(sizeof(*flowtable), GFP_KERNEL_ACCOUNT); ++ flowtable = kzalloc(sizeof(*flowtable), GFP_KERNEL); + if (!flowtable) + return -ENOMEM; + + flowtable->table = table; + flowtable->handle = nf_tables_alloc_handle(table); ++ if (!nft_use_inc(&table->use)) ++ return -EMFILE; ++ +@@ -8293,8 +8327,10 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + nft_ctx_init(&ctx, net, skb, info->nlh, family, table, NULL, nla); + + flowtable = kzalloc(sizeof(*flowtable), GFP_KERNEL_ACCOUNT); +- if (!flowtable) +- return -ENOMEM; ++ if (!flowtable) { ++ err = -ENOMEM; ++ goto flowtable_alloc; ++ } + + flowtable->table = table; + flowtable->handle = nf_tables_alloc_handle(table); +@@ -9654,13 +9654,13 @@ + nft_chain_del(trans->ctx.chain); + nf_tables_unregister_hook(trans->ctx.net, + trans->ctx.table, +- list_splice(&nft_trans_chain_hooks(trans), +- &nft_trans_basechain(trans)->hook_list); +- } else { +- trans->ctx.table->use++; +- nft_clear(trans->ctx.net, trans->ctx.chain); +- } ++ break; ++ case NFT_MSG_DELCHAIN: ++ case NFT_MSG_DESTROYCHAIN: ++ trans->ctx.table->use++; ++ nft_clear(trans->ctx.net, trans->ctx.chain); + nft_trans_destroy(trans); ++ break; + nft_trans_destroy(trans); + break; + } +@@ -9672,7 +9710,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context + nft_trans_destroy(trans); + break; + } +- trans->ctx.chain->use--; ++ nft_use_dec_restore(&trans->ctx.chain->use); + list_del_rcu(&nft_trans_rule(trans)->list); + nft_rule_expr_deactivate(&trans->ctx, + nft_trans_rule(trans), +@@ -9682,7 +9720,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context + break; + case NFT_MSG_DELRULE: + case NFT_MSG_DESTROYRULE: +- trans->ctx.chain->use++; ++ nft_use_inc_restore(&trans->ctx.chain->use); + nft_clear(trans->ctx.net, nft_trans_rule(trans)); + nft_rule_expr_activate(&trans->ctx, nft_trans_rule(trans)); + if (trans->ctx.chain->flags & NFT_CHAIN_HW_OFFLOAD) +@@ -9695,7 +9733,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context + nft_trans_destroy(trans); + break; + } +- trans->ctx.table->use--; ++ nft_use_dec_restore(&trans->ctx.table->use); + if (nft_trans_set_bound(trans)) { + nft_trans_destroy(trans); + break; +@@ -9748,9 +9786,9 @@ INTERDIFF: rejected hunk from patch1, cannot diff context + nft_obj_destroy(&trans->ctx, nft_trans_obj_newobj(trans)); + nft_trans_destroy(trans); + } else { +- trans->ctx.table->use--; ++ nft_use_dec_restore(&trans->ctx.table->use); + nft_obj_del(nft_trans_obj(trans)); + } + break; + case NFT_MSG_DELOBJ: + case NFT_MSG_DESTROYOBJ: +@@ -9754,7 +9792,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context + break; + case NFT_MSG_DELOBJ: + case NFT_MSG_DESTROYOBJ: +- trans->ctx.table->use++; ++ nft_use_inc_restore(&trans->ctx.table->use); + nft_clear(trans->ctx.net, nft_trans_obj(trans)); + nft_trans_destroy(trans); + break; +@@ -10000,7 +10038,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + nft_trans_destroy(trans); + break; + } +- trans->ctx.chain->use--; ++ nft_use_dec_restore(&trans->ctx.chain->use); + list_del_rcu(&nft_trans_rule(trans)->list); + nft_rule_expr_deactivate(&trans->ctx, + nft_trans_rule(trans), +@@ -10010,7 +10048,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + break; + case NFT_MSG_DELRULE: + case NFT_MSG_DESTROYRULE: +- trans->ctx.chain->use++; ++ nft_use_inc_restore(&trans->ctx.chain->use); + nft_clear(trans->ctx.net, nft_trans_rule(trans)); + nft_rule_expr_activate(&trans->ctx, nft_trans_rule(trans)); + if (trans->ctx.chain->flags & NFT_CHAIN_HW_OFFLOAD) +@@ -10023,7 +10061,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + nft_trans_destroy(trans); + break; + } +- trans->ctx.table->use--; ++ nft_use_dec_restore(&trans->ctx.table->use); + if (nft_trans_set_bound(trans)) { + nft_trans_destroy(trans); + break; +@@ -10032,7 +10070,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + break; + case NFT_MSG_DELSET: + case NFT_MSG_DESTROYSET: +- trans->ctx.table->use++; ++ nft_use_inc_restore(&trans->ctx.table->use); + nft_clear(trans->ctx.net, nft_trans_set(trans)); + if (nft_trans_set(trans)->flags & (NFT_SET_MAP | NFT_SET_OBJECT)) + nft_map_activate(&trans->ctx, nft_trans_set(trans)); +@@ -10076,9 +10114,9 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + nft_obj_destroy(&trans->ctx, nft_trans_obj_newobj(trans)); + nft_trans_destroy(trans); + } else { +- trans->ctx.table->use--; ++ nft_use_dec_restore(&trans->ctx.table->use); + nft_obj_del(nft_trans_obj(trans)); + } + break; + case NFT_MSG_DELOBJ: + case NFT_MSG_DESTROYOBJ: +@@ -10082,7 +10120,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + break; + case NFT_MSG_DELOBJ: + case NFT_MSG_DESTROYOBJ: +- trans->ctx.table->use++; ++ nft_use_inc_restore(&trans->ctx.table->use); + nft_clear(trans->ctx.net, nft_trans_obj(trans)); + nft_trans_destroy(trans); + break; +@@ -10091,7 +10129,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + nft_unregister_flowtable_net_hooks(net, + &nft_trans_flowtable_hooks(trans)); + } else { +- trans->ctx.table->use--; ++ nft_use_dec_restore(&trans->ctx.table->use); + list_del_rcu(&nft_trans_flowtable(trans)->list); + nft_unregister_flowtable_net_hooks(net, + &nft_trans_flowtable(trans)->hook_list); +@@ -10103,7 +10141,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + list_splice(&nft_trans_flowtable_hooks(trans), + &nft_trans_flowtable(trans)->hook_list); + } else { +- trans->ctx.table->use++; ++ nft_use_inc_restore(&trans->ctx.table->use); + nft_clear(trans->ctx.net, nft_trans_flowtable(trans)); + } + nft_trans_destroy(trans); +@@ -10220,4 +10220,4 @@ + + data->verdict.chain = chain; + break; +- } ++ default: +diff -u b/net/netfilter/nft_objref.c b/net/netfilter/nft_objref.c +--- b/net/netfilter/nft_objref.c ++++ b/net/netfilter/nft_objref.c +@@ -84,4 +84,4 @@ + nft_use_inc_restore(&obj->use); + } + +-static const struct nft_expr_ops nft_objref_ops = { ++static struct nft_expr_type nft_objref_type; +EOF + +${INTERDIFF} --fuzzy=3 patch1 patch2 2>errors >output +[ -s errors ] && exit 1 + +cmp output expected || exit 1 From d84f55db0485fdc8379cd111241698c65fbe2168 Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Tue, 2 Dec 2025 09:21:21 -0800 Subject: [PATCH 07/18] interdiff: Fix incorrect base file direction in fuzzy mode Fuzzy interdiffs would show how to go from patch2 to patch1, the opposite of how it should be. Fix it so that the output shows how to go from patch1 to patch2. --- src/interdiff.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/interdiff.c b/src/interdiff.c index b3535017..497c4ceb 100644 --- a/src/interdiff.c +++ b/src/interdiff.c @@ -2209,9 +2209,9 @@ output_delta (FILE *p1, FILE *p2, FILE *out) /* Write it out. */ if (fuzzy) { /* Ensure the same unline is used for both files */ - write_file (&file, tmpp1fd); + write_file (&file, tmpp2fd); file2.unline = xstrdup (file.unline); - write_file (&file2, tmpp2fd); + write_file (&file2, tmpp1fd); } else { merge_lines (&file, &file2); write_file (&file, tmpp1fd); @@ -2223,7 +2223,7 @@ output_delta (FILE *p1, FILE *p2, FILE *out) FILE *patch_out, *sp; /* Split the patch hunks into smaller hunks, then apply that */ - sp = split_patch_hunks (p1, pos1 - start1, tmpp1, &hunk_offs, NULL); + sp = split_patch_hunks (p2, pos2 - start2, tmpp1, &hunk_offs, NULL); ret1 = apply_patch (sp, tmpp1, false, &patch_out); fclose (sp); @@ -2234,7 +2234,7 @@ output_delta (FILE *p1, FILE *p2, FILE *out) free (hunk_offs); /* Split the patch hunks into smaller hunks, then apply that */ - sp = split_patch_hunks (p2, pos2 - start2, tmpp2, NULL, NULL); + sp = split_patch_hunks (p1, pos1 - start1, tmpp2, NULL, NULL); ret2 = apply_patch (sp, tmpp2, false, NULL); fclose (sp); From a034d6a6b2120a3f2f5a37676780e17b8335c655 Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Tue, 2 Dec 2025 10:39:45 -0800 Subject: [PATCH 08/18] interdiff: Fix bogus hunk removal in split_patch_hunks() split_patch_hunks() fails to filter bogus hunks that contain real context lines mixed with unlines. Such hunks are still bogus because they don't have any delta lines. Additionally, the numbers in the @@ line become nonsense when more than one bogus hunk is filtered. Fix both of these issues affecting the bogus hunk filter logic. --- src/interdiff.c | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/interdiff.c b/src/interdiff.c index 497c4ceb..36f98f7a 100644 --- a/src/interdiff.c +++ b/src/interdiff.c @@ -1525,8 +1525,9 @@ split_patch_hunks (FILE *patch, size_t len, char *file, do { /* nctx[0] = pre-context lines, nctx[1] = post-context lines * ndelta[0] = deleted lines, ndelta[1] = added lines */ - unsigned long nctx[2] = {}, ndelta[2] = {}, nctx_target; + unsigned long nctx[2] = {}, ndelta[2] = {}; unsigned long ostart, nstart, orig_nstart, start_line_idx = 0; + unsigned long nctx_target = 0; /* Init for spurious GCC warn */ struct xtra_context xctx_pre = {}; struct line_info *lines = NULL; unsigned long num_lines = 0; @@ -1587,14 +1588,15 @@ split_patch_hunks (FILE *patch, size_t len, char *file, lines[num_lines - 1].len = line - lines[num_lines - 1].s; - /* Check if this is the end. If so, terminate the hunk - * now because there isn't any new line to parse. */ + /* Count the number of characters left to parse */ hlen_rem = hunk + hlen - line; - if (!hlen_rem) - goto split_hunk_incl_latest; - /* Check if this is an unline that we need to remove */ - if (unline && !strncmp (line + 1, unline, unline_len)) { + /* Check if this is an unline that we need to remove, or + * if this is a bogus hunk. A bogus hunk may not have an + * unline as its final line, hence we need to consider + * this when there are no more lines left to parse. */ + if (unline && (!hlen_rem || !strncmp (line + 1, unline, + unline_len))) { /* Split the hunk now if there's a delta, unless * this is a bogus hunk from a rejected patch * hunk. Bogus hunks stem from one side of the @@ -1627,8 +1629,16 @@ split_patch_hunks (FILE *patch, size_t len, char *file, skipped_lines = 1; continue; } + + /* Bogus hunk, reset the delta counts */ + ndelta[0] = ndelta[1] = 0; } + /* Stop now when nothing remains, since all that + * we've got here is a bogus hunk to discard. */ + if (!hlen_rem) + break; + /* Move forward the starting line offset, * discarding any pre-context lines seen. The * starting line index is set to the _next_ @@ -1639,6 +1649,11 @@ split_patch_hunks (FILE *patch, size_t len, char *file, continue; } + /* Check if this is the end. If so, terminate the hunk + * now because there isn't any new line to parse. */ + if (!hlen_rem) + goto split_hunk_incl_latest; + /* Record the current line, setting `len` to zero */ lines = xrealloc (lines, ++num_lines * sizeof (*lines)); lines[num_lines - 1] = (typeof(*lines)){ line }; From 090cf8369ba8f1b97f6ef14bd90d9cda1a025712 Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Tue, 2 Dec 2025 11:12:10 -0800 Subject: [PATCH 09/18] Update interdiff fuzzy mode tests --- Makefile.am | 3 +- tests/fuzzy1/run-test | 32 ++-- tests/fuzzy3/run-test | 10 +- tests/fuzzy4/run-test | 13 +- tests/fuzzy5/run-test | 26 ++-- tests/fuzzy6/run-test | 12 +- tests/fuzzy7/run-test | 72 ++++----- tests/fuzzy8/run-test | 144 ++++++++---------- tests/fuzzy9/run-test | 329 ++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 479 insertions(+), 162 deletions(-) create mode 100755 tests/fuzzy9/run-test diff --git a/Makefile.am b/Makefile.am index 46352b00..5fc67d37 100644 --- a/Makefile.am +++ b/Makefile.am @@ -440,7 +440,8 @@ TESTS = tests/newline1/run-test \ tests/fuzzy5/run-test \ tests/fuzzy6/run-test \ tests/fuzzy7/run-test \ - tests/fuzzy8/run-test + tests/fuzzy8/run-test \ + tests/fuzzy9/run-test # Scanner tests (only when scanner-patchfilter is enabled) if USE_SCANNER_PATCHFILTER diff --git a/tests/fuzzy1/run-test b/tests/fuzzy1/run-test index f206c938..9ec01b10 100755 --- a/tests/fuzzy1/run-test +++ b/tests/fuzzy1/run-test @@ -36,27 +36,27 @@ cat << 'EOF' > expected diff -u file file --- file +++ file -@@ -1,4 +1,4 @@ INTERDIFF: rejected hunk from patch1, cannot diff context +@@ -1,4 +1,4 @@ INTERDIFF: rejected hunk from patch2, cannot diff context -line 1 +LINE 1 line 2 line 3 line 4 -@@ -1,9 +1,4 @@ --line 5 --if --1 --fi --if --2 --fi --A --B -+line 1 -+line 2 -+line 3 -+line 4 -@@ -5,9 +5,6 @@ INTERDIFF: rejected hunk from patch2, cannot diff context +@@ -1,4 +1,9 @@ +-line 1 +-line 2 +-line 3 +-line 4 ++line 5 ++if ++1 ++fi ++if ++2 ++fi ++A ++B +@@ -5,9 +5,6 @@ INTERDIFF: rejected hunk from patch1, cannot diff context line 5 if 1 diff --git a/tests/fuzzy3/run-test b/tests/fuzzy3/run-test index d763a1e9..585ad259 100755 --- a/tests/fuzzy3/run-test +++ b/tests/fuzzy3/run-test @@ -40,15 +40,15 @@ cat << 'EOF' > expected diff -u file file --- file +++ file -@@ -2,6 +2,6 @@ --line 6 -+line 5 +@@ -47,6 +47,6 @@ +-line 5 ++line 6 if 1 fi -+A +-A B --C ++C EOF ${INTERDIFF} --fuzzy patch1 patch2 2>errors >output diff --git a/tests/fuzzy4/run-test b/tests/fuzzy4/run-test index 3f33783e..e1578fcb 100755 --- a/tests/fuzzy4/run-test +++ b/tests/fuzzy4/run-test @@ -42,19 +42,18 @@ cat << 'EOF' > expected diff -u file file --- file +++ file -@@ -2,9 +2,9 @@ --line 6 -+line 5 +@@ -2,8 +2,9 @@ ++line 6 if 1 fi if 2 fi -+A +-A B --C -@@ -5,9 +5,6 @@ INTERDIFF: rejected hunk from patch1, cannot diff context ++C +@@ -5,9 +5,6 @@ INTERDIFF: rejected hunk from patch2, cannot diff context line 5 if 1 @@ -64,7 +63,7 @@ diff -u file file fi A B -@@ -50,9 +50,6 @@ INTERDIFF: rejected hunk from patch2, cannot diff context +@@ -50,9 +50,6 @@ INTERDIFF: rejected hunk from patch1, cannot diff context line 6 if 1 diff --git a/tests/fuzzy5/run-test b/tests/fuzzy5/run-test index f27b3b9c..11dad5bd 100755 --- a/tests/fuzzy5/run-test +++ b/tests/fuzzy5/run-test @@ -64,19 +64,27 @@ cat << 'EOF' > expected diff -u file file --- file +++ file -@@ -2,9 +2,2 @@ --line 6 -+line 5 +@@ -2 +2,7 @@ +-line 5 ++D ++Z ++1 ++1 ++F ++E ++if +@@ -3,8 +9,9 @@ ++line 6 if 1 fi if 2 fi -+A +-A B --C -@@ -5,9 +5,6 @@ INTERDIFF: rejected hunk from patch1, cannot diff context ++C +@@ -5,9 +5,6 @@ INTERDIFF: rejected hunk from patch2, cannot diff context line 5 if 1 @@ -86,7 +94,7 @@ diff -u file file fi A B -@@ -7,7 +7,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context +@@ -7,7 +7,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context C 9 8 @@ -95,7 +103,7 @@ diff -u file file D E if -@@ -50,9 +50,6 @@ INTERDIFF: rejected hunk from patch2, cannot diff context +@@ -50,9 +50,6 @@ INTERDIFF: rejected hunk from patch1, cannot diff context line 6 if 1 @@ -105,7 +113,7 @@ diff -u file file fi B C -@@ -7,7 +7,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context +@@ -7,7 +7,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context D Z 1 diff --git a/tests/fuzzy6/run-test b/tests/fuzzy6/run-test index f8091c83..8dcdc853 100755 --- a/tests/fuzzy6/run-test +++ b/tests/fuzzy6/run-test @@ -34,19 +34,19 @@ cat << 'EOF' > expected diff -u file file --- file +++ file -@@ -2,5 +2,2 @@ --hi --line 4 +@@ -2,2 +2,5 @@ ++hi ++line 4 line 5 if --2 -@@ -50,5 +50,4 @@ INTERDIFF: rejected hunk from patch2, cannot diff context ++2 +@@ -50,5 +50,4 @@ INTERDIFF: rejected hunk from patch1, cannot diff context hi line 4 -line 5 if 2 -@@ -53,2 +52,3 @@ INTERDIFF: rejected hunk from patch2, cannot diff context +@@ -53,2 +52,3 @@ INTERDIFF: rejected hunk from patch1, cannot diff context if +1 2 diff --git a/tests/fuzzy7/run-test b/tests/fuzzy7/run-test index 06b9d17b..c49b50ae 100755 --- a/tests/fuzzy7/run-test +++ b/tests/fuzzy7/run-test @@ -825,29 +825,29 @@ cat << 'EOF' > expected diff -u b/drivers/net/ethernet/microsoft/mana/gdma_main.c b/drivers/net/ethernet/microsoft/mana/gdma_main.c --- b/drivers/net/ethernet/microsoft/mana/gdma_main.c +++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c -@@ -5,7 +5,7 @@ --#include -+#include +@@ -7,7 +7,7 @@ +-#include ++#include #include #include -+#include +-#include struct dentry *mana_debugfs_root; -- -@@ -65,7 +65,7 @@ ++ +@@ -69,7 +69,7 @@ mana_gd_init_vf_regs(pdev); } --/* Suppress logging when we set timeout to zeo */ -+/* Suppress logging when we set timeout to zero */ +-/* Suppress logging when we set timeout to zero */ ++/* Suppress logging when we set timeout to zeo */ bool mana_need_log(struct gdma_context *gc, int err) { struct hw_channel_context *hwc; diff -u b/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c --- b/drivers/net/ethernet/microsoft/mana/mana_en.c +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c -@@ -777,7 +786,8 @@ INTERDIFF: rejected hunk from patch1, cannot diff context +@@ -777,7 +786,8 @@ INTERDIFF: rejected hunk from patch2, cannot diff context err = mana_gd_send_request(gc, in_len, in_buf, out_len, out_buf); if (err || resp->status) { @@ -857,7 +857,7 @@ diff -u b/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/m dev_err(dev, "Failed to send mana message: %d, 0x%x\n", err, resp->status); return err ? err : -EPROTO; -@@ -863,7 +872,8 @@ INTERDIFF: rejected hunk from patch2, cannot diff context +@@ -863,7 +872,8 @@ INTERDIFF: rejected hunk from patch1, cannot diff context if (err == -EOPNOTSUPP) return err; @@ -871,14 +871,14 @@ diff -u b/include/net/mana/gdma.h b/include/net/mana/gdma.h --- b/include/net/mana/gdma.h +++ b/include/net/mana/gdma.h @@ -57,6 +57,6 @@ -+ GDMA_EQE_HWC_INIT_DONE = 131, +- GDMA_EQE_HWC_INIT_DONE = 131, GDMA_EQE_HWC_FPGA_RECONFIG = 132, GDMA_EQE_HWC_SOC_RECONFIG_DATA = 133, -- GDMA_EQE_HWC_SOC_SERVICE = 134, ++ GDMA_EQE_HWC_SOC_SERVICE = 134, GDMA_EQE_RNIC_QP_FATAL = 176, }; -@@ -60,6 +60,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context +@@ -60,6 +60,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context GDMA_EQE_HWC_INIT_DONE = 131, GDMA_EQE_HWC_FPGA_RECONFIG = 132, GDMA_EQE_HWC_SOC_RECONFIG_DATA = 133, @@ -886,7 +886,7 @@ diff -u b/include/net/mana/gdma.h b/include/net/mana/gdma.h GDMA_EQE_RNIC_QP_FATAL = 176, }; -@@ -62,6 +62,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context +@@ -62,6 +62,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context GDMA_EQE_HWC_FPGA_RECONFIG = 132, GDMA_EQE_HWC_SOC_RECONFIG_DATA = 133, GDMA_EQE_HWC_SOC_SERVICE = 134, @@ -894,34 +894,34 @@ diff -u b/include/net/mana/gdma.h b/include/net/mana/gdma.h GDMA_EQE_RNIC_QP_FATAL = 176, }; -@@ -556,5 +554,5 @@ --/* Driver supports dynamic MSI-X vector allocation */ --#define GDMA_DRV_CAP_FLAG_1_DYNAMIC_IRQ_ALLOC_SUPPORT BIT(13) -+/* Driver can handle holes (zeros) in the device list */ -+#define GDMA_DRV_CAP_FLAG_1_DEV_LIST_HOLES_SUP BIT(11) +@@ -571,6 +575,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + GDMA_DRV_CAP_FLAG_1_HWC_TIMEOUT_RECONFIG | \ + GDMA_DRV_CAP_FLAG_1_VARIABLE_INDIRECTION_TABLE_SUPPORT | \ + GDMA_DRV_CAP_FLAG_1_DEV_LIST_HOLES_SUP | \ ++ GDMA_DRV_CAP_FLAG_1_SELF_RESET_ON_EQE | \ + GDMA_DRV_CAP_FLAG_1_HANDLE_RECONFIG_EQE) + + #define GDMA_DRV_CAP_FLAGS2 0 +@@ -579,5 +581,5 @@ +-/* Driver can handle holes (zeros) in the device list */ +-#define GDMA_DRV_CAP_FLAG_1_DEV_LIST_HOLES_SUP BIT(11) ++/* Driver supports dynamic MSI-X vector allocation */ ++#define GDMA_DRV_CAP_FLAG_1_DYNAMIC_IRQ_ALLOC_SUPPORT BIT(13) /* Driver can self reset on EQE notification */ #define GDMA_DRV_CAP_FLAG_1_SELF_RESET_ON_EQE BIT(14) -@@ -565,9 +565,9 @@ +@@ -590,9 +590,9 @@ /* Driver can self reset on FPGA Reconfig EQE notification */ #define GDMA_DRV_CAP_FLAG_1_HANDLE_RECONFIG_EQE BIT(17) -+ GDMA_DRV_CAP_FLAG_1_HWC_TIMEOUT_RECONFIG | \ - GDMA_DRV_CAP_FLAG_1_VARIABLE_INDIRECTION_TABLE_SUPPORT | \ - GDMA_DRV_CAP_FLAG_1_DEV_LIST_HOLES_SUP | \ -- GDMA_DRV_CAP_FLAG_1_DYNAMIC_IRQ_ALLOC_SUPPORT | \ - GDMA_DRV_CAP_FLAG_1_HANDLE_RECONFIG_EQE) - - #define GDMA_DRV_CAP_FLAGS2 0 -@@ -571,6 +575,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context - GDMA_DRV_CAP_FLAG_1_HWC_TIMEOUT_RECONFIG | \ +- GDMA_DRV_CAP_FLAG_1_HWC_TIMEOUT_RECONFIG | \ GDMA_DRV_CAP_FLAG_1_VARIABLE_INDIRECTION_TABLE_SUPPORT | \ GDMA_DRV_CAP_FLAG_1_DEV_LIST_HOLES_SUP | \ -+ GDMA_DRV_CAP_FLAG_1_SELF_RESET_ON_EQE | \ ++ GDMA_DRV_CAP_FLAG_1_DYNAMIC_IRQ_ALLOC_SUPPORT | \ GDMA_DRV_CAP_FLAG_1_HANDLE_RECONFIG_EQE) #define GDMA_DRV_CAP_FLAGS2 0 -@@ -597,6 +601,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context +@@ -597,6 +601,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context GDMA_DRV_CAP_FLAG_1_VARIABLE_INDIRECTION_TABLE_SUPPORT | \ GDMA_DRV_CAP_FLAG_1_DEV_LIST_HOLES_SUP | \ GDMA_DRV_CAP_FLAG_1_DYNAMIC_IRQ_ALLOC_SUPPORT | \ @@ -929,11 +929,11 @@ diff -u b/include/net/mana/gdma.h b/include/net/mana/gdma.h GDMA_DRV_CAP_FLAG_1_HANDLE_RECONFIG_EQE) #define GDMA_DRV_CAP_FLAGS2 0 -@@ -892,5 +892,5 @@ -- --int mana_rdma_service_event(struct gdma_context *gc, enum gdma_service_type event); -+void mana_register_debugfs(void); -+void mana_unregister_debugfs(void); +@@ -921,5 +921,5 @@ +-void mana_register_debugfs(void); +-void mana_unregister_debugfs(void); ++ ++int mana_rdma_service_event(struct gdma_context *gc, enum gdma_service_type event); int mana_gd_suspend(struct pci_dev *pdev, pm_message_t state); int mana_gd_resume(struct pci_dev *pdev); diff --git a/tests/fuzzy8/run-test b/tests/fuzzy8/run-test index 680f3fa8..d6e18e6a 100755 --- a/tests/fuzzy8/run-test +++ b/tests/fuzzy8/run-test @@ -1524,36 +1524,16 @@ cat << 'EOF' > expected diff -u b/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c --- b/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c -@@ -2297,6 +2297,6 @@ -- struct nft_chain *chain; -+ struct nft_rule **rules; +@@ -2395,6 +2395,6 @@ +- struct nft_rule **rules; ++ struct nft_chain *chain; int err; if (nla[NFTA_CHAIN_HOOK]) { struct nft_stats __percpu *stats = NULL; -- struct nft_chain_hook hook = {}; -+ struct nft_chain_hook hook; -@@ -4790,13 +4790,13 @@ -- -- if (!nft_use_inc(&table->use)) -- return -EMFILE; -- - alloc_size = sizeof(*set) + size + udlen; - if (alloc_size < size || alloc_size > INT_MAX) - return -ENOMEM; -- set = kvzalloc(alloc_size, GFP_KERNEL_ACCOUNT); -+ set = kvzalloc(alloc_size, GFP_KERNEL); - if (!set) - return -ENOMEM; - -- name = nla_strdup(nla[NFTA_SET_NAME], GFP_KERNEL_ACCOUNT); -+ -+ if (!nft_use_inc(&table->use)) -+ return -EMFILE; -+ -+ name = nla_strdup(nla[NFTA_SET_NAME], GFP_KERNEL); - if (!name) { -@@ -5026,8 +5037,10 @@ INTERDIFF: rejected hunk from patch2, cannot diff context +- struct nft_chain_hook hook; ++ struct nft_chain_hook hook = {}; +@@ -5026,8 +5037,10 @@ INTERDIFF: rejected hunk from patch1, cannot diff context if (alloc_size < size || alloc_size > INT_MAX) return -ENOMEM; set = kvzalloc(alloc_size, GFP_KERNEL_ACCOUNT); @@ -1566,24 +1546,7 @@ diff -u b/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c name = nla_strdup(nla[NFTA_SET_NAME], GFP_KERNEL_ACCOUNT); if (!name) { -@@ -8040,12 +8040,12 @@ -- if (!nft_use_inc(&table->use)) -- return -EMFILE; -- - - nft_ctx_init(&ctx, net, skb, info->nlh, family, table, NULL, nla); - -- flowtable = kzalloc(sizeof(*flowtable), GFP_KERNEL_ACCOUNT); -+ flowtable = kzalloc(sizeof(*flowtable), GFP_KERNEL); - if (!flowtable) - return -ENOMEM; - - flowtable->table = table; - flowtable->handle = nf_tables_alloc_handle(table); -+ if (!nft_use_inc(&table->use)) -+ return -EMFILE; -+ -@@ -8293,8 +8327,10 @@ INTERDIFF: rejected hunk from patch2, cannot diff context +@@ -8293,8 +8327,10 @@ INTERDIFF: rejected hunk from patch1, cannot diff context nft_ctx_init(&ctx, net, skb, info->nlh, family, table, NULL, nla); flowtable = kzalloc(sizeof(*flowtable), GFP_KERNEL_ACCOUNT); @@ -1596,27 +1559,7 @@ diff -u b/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c flowtable->table = table; flowtable->handle = nf_tables_alloc_handle(table); -@@ -9654,13 +9654,13 @@ - nft_chain_del(trans->ctx.chain); - nf_tables_unregister_hook(trans->ctx.net, - trans->ctx.table, -- list_splice(&nft_trans_chain_hooks(trans), -- &nft_trans_basechain(trans)->hook_list); -- } else { -- trans->ctx.table->use++; -- nft_clear(trans->ctx.net, trans->ctx.chain); -- } -+ break; -+ case NFT_MSG_DELCHAIN: -+ case NFT_MSG_DESTROYCHAIN: -+ trans->ctx.table->use++; -+ nft_clear(trans->ctx.net, trans->ctx.chain); - nft_trans_destroy(trans); -+ break; - nft_trans_destroy(trans); - break; - } -@@ -9672,7 +9710,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context +@@ -9672,7 +9710,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context nft_trans_destroy(trans); break; } @@ -1625,7 +1568,7 @@ diff -u b/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c list_del_rcu(&nft_trans_rule(trans)->list); nft_rule_expr_deactivate(&trans->ctx, nft_trans_rule(trans), -@@ -9682,7 +9720,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context +@@ -9682,7 +9720,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context break; case NFT_MSG_DELRULE: case NFT_MSG_DESTROYRULE: @@ -1634,7 +1577,7 @@ diff -u b/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c nft_clear(trans->ctx.net, nft_trans_rule(trans)); nft_rule_expr_activate(&trans->ctx, nft_trans_rule(trans)); if (trans->ctx.chain->flags & NFT_CHAIN_HW_OFFLOAD) -@@ -9695,7 +9733,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context +@@ -9695,7 +9733,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context nft_trans_destroy(trans); break; } @@ -1643,7 +1586,24 @@ diff -u b/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c if (nft_trans_set_bound(trans)) { nft_trans_destroy(trans); break; -@@ -9748,9 +9786,9 @@ INTERDIFF: rejected hunk from patch1, cannot diff context +@@ -9741,16 +9741,3 @@ +- nft_obj_destroy(&trans->ctx, nft_trans_obj_newobj(trans)); +- nft_trans_destroy(trans); +- } else { +- trans->ctx.table->use--; +- nft_obj_del(nft_trans_obj(trans)); +- } +- break; +- case NFT_MSG_DELOBJ: +- case NFT_MSG_DESTROYOBJ: +- trans->ctx.table->use++; +- nft_clear(trans->ctx.net, nft_trans_obj(trans)); +- nft_trans_destroy(trans); +- break; + */ + if (nft_set_is_anonymous(nft_trans_set(trans)) && + !list_empty(&nft_trans_set(trans)->bindings)) +@@ -9748,9 +9786,9 @@ INTERDIFF: rejected hunk from patch2, cannot diff context nft_obj_destroy(&trans->ctx, nft_trans_obj_newobj(trans)); nft_trans_destroy(trans); } else { @@ -1654,7 +1614,7 @@ diff -u b/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c break; case NFT_MSG_DELOBJ: case NFT_MSG_DESTROYOBJ: -@@ -9754,7 +9792,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context +@@ -9754,7 +9792,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context break; case NFT_MSG_DELOBJ: case NFT_MSG_DESTROYOBJ: @@ -1663,7 +1623,27 @@ diff -u b/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c nft_clear(trans->ctx.net, nft_trans_obj(trans)); nft_trans_destroy(trans); break; -@@ -10000,7 +10038,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context +@@ -9991,13 +9981,13 @@ + nft_chain_del(trans->ctx.chain); + nf_tables_unregister_hook(trans->ctx.net, + trans->ctx.table, +- break; +- case NFT_MSG_DELCHAIN: +- case NFT_MSG_DESTROYCHAIN: +- trans->ctx.table->use++; +- nft_clear(trans->ctx.net, trans->ctx.chain); ++ list_splice(&nft_trans_chain_hooks(trans), ++ &nft_trans_basechain(trans)->hook_list); ++ } else { ++ trans->ctx.table->use++; ++ nft_clear(trans->ctx.net, trans->ctx.chain); ++ } + nft_trans_destroy(trans); +- break; + nft_trans_destroy(trans); + break; + } +@@ -10000,7 +10038,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context nft_trans_destroy(trans); break; } @@ -1672,7 +1652,7 @@ diff -u b/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c list_del_rcu(&nft_trans_rule(trans)->list); nft_rule_expr_deactivate(&trans->ctx, nft_trans_rule(trans), -@@ -10010,7 +10048,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context +@@ -10010,7 +10048,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context break; case NFT_MSG_DELRULE: case NFT_MSG_DESTROYRULE: @@ -1681,7 +1661,7 @@ diff -u b/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c nft_clear(trans->ctx.net, nft_trans_rule(trans)); nft_rule_expr_activate(&trans->ctx, nft_trans_rule(trans)); if (trans->ctx.chain->flags & NFT_CHAIN_HW_OFFLOAD) -@@ -10023,7 +10061,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context +@@ -10023,7 +10061,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context nft_trans_destroy(trans); break; } @@ -1690,7 +1670,7 @@ diff -u b/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c if (nft_trans_set_bound(trans)) { nft_trans_destroy(trans); break; -@@ -10032,7 +10070,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context +@@ -10032,7 +10070,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context break; case NFT_MSG_DELSET: case NFT_MSG_DESTROYSET: @@ -1699,7 +1679,7 @@ diff -u b/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c nft_clear(trans->ctx.net, nft_trans_set(trans)); if (nft_trans_set(trans)->flags & (NFT_SET_MAP | NFT_SET_OBJECT)) nft_map_activate(&trans->ctx, nft_trans_set(trans)); -@@ -10076,9 +10114,9 @@ INTERDIFF: rejected hunk from patch2, cannot diff context +@@ -10076,9 +10114,9 @@ INTERDIFF: rejected hunk from patch1, cannot diff context nft_obj_destroy(&trans->ctx, nft_trans_obj_newobj(trans)); nft_trans_destroy(trans); } else { @@ -1710,7 +1690,7 @@ diff -u b/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c break; case NFT_MSG_DELOBJ: case NFT_MSG_DESTROYOBJ: -@@ -10082,7 +10120,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context +@@ -10082,7 +10120,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context break; case NFT_MSG_DELOBJ: case NFT_MSG_DESTROYOBJ: @@ -1719,7 +1699,7 @@ diff -u b/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c nft_clear(trans->ctx.net, nft_trans_obj(trans)); nft_trans_destroy(trans); break; -@@ -10091,7 +10129,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context +@@ -10091,7 +10129,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context nft_unregister_flowtable_net_hooks(net, &nft_trans_flowtable_hooks(trans)); } else { @@ -1728,7 +1708,7 @@ diff -u b/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c list_del_rcu(&nft_trans_flowtable(trans)->list); nft_unregister_flowtable_net_hooks(net, &nft_trans_flowtable(trans)->hook_list); -@@ -10103,7 +10141,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context +@@ -10103,7 +10141,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context list_splice(&nft_trans_flowtable_hooks(trans), &nft_trans_flowtable(trans)->hook_list); } else { @@ -1737,12 +1717,12 @@ diff -u b/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c nft_clear(trans->ctx.net, nft_trans_flowtable(trans)); } nft_trans_destroy(trans); -@@ -10220,4 +10220,4 @@ +@@ -10567,4 +10557,4 @@ data->verdict.chain = chain; break; -- } -+ default: +- default: ++ } diff -u b/net/netfilter/nft_objref.c b/net/netfilter/nft_objref.c --- b/net/netfilter/nft_objref.c +++ b/net/netfilter/nft_objref.c @@ -1750,8 +1730,8 @@ diff -u b/net/netfilter/nft_objref.c b/net/netfilter/nft_objref.c nft_use_inc_restore(&obj->use); } --static const struct nft_expr_ops nft_objref_ops = { -+static struct nft_expr_type nft_objref_type; +-static struct nft_expr_type nft_objref_type; ++static const struct nft_expr_ops nft_objref_ops = { EOF ${INTERDIFF} --fuzzy=3 patch1 patch2 2>errors >output diff --git a/tests/fuzzy9/run-test b/tests/fuzzy9/run-test new file mode 100755 index 00000000..b2689f73 --- /dev/null +++ b/tests/fuzzy9/run-test @@ -0,0 +1,329 @@ +#!/bin/sh + +# This is an interdiff(1) testcase. +# Test: Fuzzy diffing (using --fuzzy) with a real Linux kernel backport compared +# against its upstream version. Stresses having multiple relocations and most of +# the fuzzy diffing machinery as a whole. + + +. ${top_srcdir-.}/tests/common.sh + +cat << 'EOF' > patch1 +From 132eaaa4a021d14a93fcec81921723d4d034b693 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Marcin=20Wcis=C5=82o?= +Date: Sat Nov 15 01:52:19 2025 +0100 +Subject: [PATCH] wifi: cfg80211: check A-MSDU format more carefully +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +jira VULN-5183 +cve CVE-2024-35937 +commit-author Johannes Berg +commit 9ad7974856926129f190ffbe3beea78460b3b7cc +upstream-diff | + 1. All changes to the `ieee80211_is_valid_amsdu' function were discarded + because it's missing from `ciqlts9_2'. + 2. Changes to `ieee80211_amsdu_to_8023s' were adapted to account for the + missing 986e43b19ae9176093da35e0a844e65c8bf9ede7 from `ciqlts9_2' + history: the `copy_len > remaining' condition was changed to + `sizeof(eth) > remaining', as `sizeof(eth)' is the only possible + value `copy_len' could have assumed in `ciqlts9_2' if it was + introduced without backporting 986e43b (pointless). + +If it looks like there's another subframe in the A-MSDU +but the header isn't fully there, we can end up reading +data out of bounds, only to discard later. Make this a +bit more careful and check if the subframe header can +even be present. + + Reported-by: syzbot+d050d437fe47d479d210@syzkaller.appspotmail.com +Link: https://msgid.link/20240226203405.a731e2c95e38.I82ce7d8c0cc8970ce29d0a39fdc07f1ffc425be4@changeid + Signed-off-by: Johannes Berg +(cherry picked from commit 9ad7974856926129f190ffbe3beea78460b3b7cc) + Signed-off-by: Marcin WcisÅ‚o +--- + net/wireless/util.c | 7 +++++-- + 1 file changed, 5 insertions(+), 2 deletions(-) + +diff --git a/net/wireless/util.c b/net/wireless/util.c +index 39680e7bad45a..582b9dbde01fe 100644 +--- a/net/wireless/util.c ++++ b/net/wireless/util.c +@@ -757,24 +757,27 @@ void ieee80211_amsdu_to_8023s(struct sk_buff *skb, struct sk_buff_head *list, + struct sk_buff *frame = NULL; + u16 ethertype; + u8 *payload; +- int offset = 0, remaining; ++ int offset = 0; + struct ethhdr eth; + bool reuse_frag = skb->head_frag && !skb_has_frag_list(skb); + bool reuse_skb = false; + bool last = false; + + while (!last) { ++ int remaining = skb->len - offset; + unsigned int subframe_len; + int len; + u8 padding; + ++ if (sizeof(eth) > remaining) ++ goto purge; ++ + skb_copy_bits(skb, offset, ð, sizeof(eth)); + len = ntohs(eth.h_proto); + subframe_len = sizeof(struct ethhdr) + len; + padding = (4 - subframe_len) & 0x3; + + /* the last MSDU has no padding */ +- remaining = skb->len - offset; + if (subframe_len > remaining) + goto purge; + /* mitigate A-MSDU aggregation injection attacks */ +EOF + +cat << 'EOF' > patch2 +From 9ad7974856926129f190ffbe3beea78460b3b7cc Mon Sep 17 00:00:00 2001 +From: Johannes Berg +Date: Mon, 26 Feb 2024 20:34:06 +0100 +Subject: [PATCH] wifi: cfg80211: check A-MSDU format more carefully + +If it looks like there's another subframe in the A-MSDU +but the header isn't fully there, we can end up reading +data out of bounds, only to discard later. Make this a +bit more careful and check if the subframe header can +even be present. + +Reported-by: syzbot+d050d437fe47d479d210@syzkaller.appspotmail.com +Link: https://msgid.link/20240226203405.a731e2c95e38.I82ce7d8c0cc8970ce29d0a39fdc07f1ffc425be4@changeid +Signed-off-by: Johannes Berg +--- + net/wireless/util.c | 14 ++++++++++---- + 1 file changed, 10 insertions(+), 4 deletions(-) + +diff --git a/net/wireless/util.c b/net/wireless/util.c +index 379f742fd7415..2bde8a3546313 100644 +--- a/net/wireless/util.c ++++ b/net/wireless/util.c +@@ -791,15 +791,19 @@ ieee80211_amsdu_subframe_length(void *field, u8 mesh_flags, u8 hdr_type) + + bool ieee80211_is_valid_amsdu(struct sk_buff *skb, u8 mesh_hdr) + { +- int offset = 0, remaining, subframe_len, padding; ++ int offset = 0, subframe_len, padding; + + for (offset = 0; offset < skb->len; offset += subframe_len + padding) { ++ int remaining = skb->len - offset; + struct { + __be16 len; + u8 mesh_flags; + } hdr; + u16 len; + ++ if (sizeof(hdr) > remaining) ++ return false; ++ + if (skb_copy_bits(skb, offset + 2 * ETH_ALEN, &hdr, sizeof(hdr)) < 0) + return false; + +@@ -807,7 +811,6 @@ bool ieee80211_is_valid_amsdu(struct sk_buff *skb, u8 mesh_hdr) + mesh_hdr); + subframe_len = sizeof(struct ethhdr) + len; + padding = (4 - subframe_len) & 0x3; +- remaining = skb->len - offset; + + if (subframe_len > remaining) + return false; +@@ -825,7 +828,7 @@ void ieee80211_amsdu_to_8023s(struct sk_buff *skb, struct sk_buff_head *list, + { + unsigned int hlen = ALIGN(extra_headroom, 4); + struct sk_buff *frame = NULL; +- int offset = 0, remaining; ++ int offset = 0; + struct { + struct ethhdr eth; + uint8_t flags; +@@ -839,10 +842,14 @@ void ieee80211_amsdu_to_8023s(struct sk_buff *skb, struct sk_buff_head *list, + copy_len = sizeof(hdr); + + while (!last) { ++ int remaining = skb->len - offset; + unsigned int subframe_len; + int len, mesh_len = 0; + u8 padding; + ++ if (copy_len > remaining) ++ goto purge; ++ + skb_copy_bits(skb, offset, &hdr, copy_len); + if (iftype == NL80211_IFTYPE_MESH_POINT) + mesh_len = __ieee80211_get_mesh_hdrlen(hdr.flags); +@@ -852,7 +859,6 @@ void ieee80211_amsdu_to_8023s(struct sk_buff *skb, struct sk_buff_head *list, + padding = (4 - subframe_len) & 0x3; + + /* the last MSDU has no padding */ +- remaining = skb->len - offset; + if (subframe_len > remaining) + goto purge; + /* mitigate A-MSDU aggregation injection attacks */ +-- +2.52.0 + +EOF + +cat << 'EOF' > expected +diff -u b/net/wireless/util.c b/net/wireless/util.c +--- b/net/wireless/util.c ++++ b/net/wireless/util.c +@@ -754,8 +754,7 @@ ++{ ++ unsigned int hlen = ALIGN(extra_headroom, 4); + struct sk_buff *frame = NULL; +- u16 ethertype; +- u8 *payload; + int offset = 0, remaining; +- struct ethhdr eth; +- bool reuse_frag = skb->head_frag && !skb_has_frag_list(skb); +- bool reuse_skb = false; +- bool last = false; ++ struct { ++ struct ethhdr eth; ++ uint8_t flags; +@@ -757,10 +757,10 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + struct sk_buff *frame = NULL; + u16 ethertype; + u8 *payload; +- int offset = 0, remaining; ++ int offset = 0; + struct ethhdr eth; + bool reuse_frag = skb->head_frag && !skb_has_frag_list(skb); + bool reuse_skb = false; + bool last = false; + + while (!last) { +@@ -763,8 +763,9 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + bool reuse_skb = false; + bool last = false; + + while (!last) { ++ int remaining = skb->len - offset; + unsigned int subframe_len; + int len; + u8 padding; + +@@ -762,15 +761,17 @@ ++ copy_len = sizeof(hdr); + + while (!last) { + unsigned int subframe_len; +- int len; ++ int len, mesh_len = 0; + u8 padding; + +- skb_copy_bits(skb, offset, ð, sizeof(eth)); +- len = ntohs(eth.h_proto); +- subframe_len = sizeof(struct ethhdr) + len; ++ skb_copy_bits(skb, offset, &hdr, copy_len); ++ if (iftype == NL80211_IFTYPE_MESH_POINT) ++ mesh_len = __ieee80211_get_mesh_hdrlen(hdr.flags); + padding = (4 - subframe_len) & 0x3; + + /* the last MSDU has no padding */ ++ remaining = skb->len - offset; + if (subframe_len > remaining) + goto purge; + /* mitigate A-MSDU aggregation injection attacks */ +@@ -767,10 +768,13 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + unsigned int subframe_len; + int len; + u8 padding; + ++ if (sizeof(eth) > remaining) ++ goto purge; ++ + skb_copy_bits(skb, offset, ð, sizeof(eth)); + len = ntohs(eth.h_proto); + subframe_len = sizeof(struct ethhdr) + len; + padding = (4 - subframe_len) & 0x3; + + /* the last MSDU has no padding */ +@@ -774,7 +778,6 @@ INTERDIFF: rejected hunk from patch2, cannot diff context + padding = (4 - subframe_len) & 0x3; + + /* the last MSDU has no padding */ +- remaining = skb->len - offset; + if (subframe_len > remaining) + goto purge; + /* mitigate A-MSDU aggregation injection attacks */ +@@ -791,7 +791,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context + + bool ieee80211_is_valid_amsdu(struct sk_buff *skb, u8 mesh_hdr) + { +- int offset = 0, remaining, subframe_len, padding; ++ int offset = 0, subframe_len, padding; + + for (offset = 0; offset < skb->len; offset += subframe_len + padding) { + struct { +@@ -794,9 +794,10 @@ INTERDIFF: rejected hunk from patch1, cannot diff context + int offset = 0, remaining, subframe_len, padding; + + for (offset = 0; offset < skb->len; offset += subframe_len + padding) { ++ int remaining = skb->len - offset; + struct { + __be16 len; + u8 mesh_flags; + } hdr; + u16 len; + +@@ -800,6 +801,9 @@ INTERDIFF: rejected hunk from patch1, cannot diff context + } hdr; + u16 len; + ++ if (sizeof(hdr) > remaining) ++ return false; ++ + if (skb_copy_bits(skb, offset + 2 * ETH_ALEN, &hdr, sizeof(hdr)) < 0) + return false; + +@@ -807,7 +811,6 @@ INTERDIFF: rejected hunk from patch1, cannot diff context + mesh_hdr); + subframe_len = sizeof(struct ethhdr) + len; + padding = (4 - subframe_len) & 0x3; +- remaining = skb->len - offset; + + if (subframe_len > remaining) + return false; +@@ -825,7 +828,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context + { + unsigned int hlen = ALIGN(extra_headroom, 4); + struct sk_buff *frame = NULL; +- int offset = 0, remaining; ++ int offset = 0; + struct { + struct ethhdr eth; + uint8_t flags; +@@ -839,7 +842,8 @@ INTERDIFF: rejected hunk from patch1, cannot diff context + copy_len = sizeof(hdr); + + while (!last) { ++ int remaining = skb->len - offset; + unsigned int subframe_len; + int len, mesh_len = 0; + u8 padding; + +@@ -843,6 +847,9 @@ INTERDIFF: rejected hunk from patch1, cannot diff context + int len, mesh_len = 0; + u8 padding; + ++ if (copy_len > remaining) ++ goto purge; ++ + skb_copy_bits(skb, offset, &hdr, copy_len); + if (iftype == NL80211_IFTYPE_MESH_POINT) + mesh_len = __ieee80211_get_mesh_hdrlen(hdr.flags); +EOF + +${INTERDIFF} --fuzzy patch1 patch2 2>errors >output +[ -s errors ] && exit 1 + +cmp output expected || exit 1 From 8368b2ebd7f0414b1b14a7036606cb8b96e31c22 Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Fri, 23 Jan 2026 16:27:34 -0800 Subject: [PATCH 10/18] interdiff: Use -N on patch all the time So that patch never tries to prompt and ask for something when running interdiff on an interactive terminal. --- src/interdiff.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/interdiff.c b/src/interdiff.c index 36f98f7a..5e038252 100644 --- a/src/interdiff.c +++ b/src/interdiff.c @@ -973,7 +973,7 @@ output_patch1_only (FILE *p1, FILE *out, int not_reverted) static int apply_patch (FILE *patch, const char *file, int reverted, FILE **out) { -#define MAX_PATCH_ARGS 9 +#define MAX_PATCH_ARGS 8 const char *argv[MAX_PATCH_ARGS]; int argc = 0; const char *basename; @@ -1002,8 +1002,8 @@ apply_patch (FILE *patch, const char *file, int reverted, FILE **out) /* Add up to MAX_PATCH_ARGS arguments for the patch execution */ argv[argc++] = PATCH; - argv[argc++] = reverted ? (has_ignore_all_space ? "-Rlp0" : "-Rp0") - : (has_ignore_all_space ? "-lp0" : "-p0"); + argv[argc++] = reverted ? (has_ignore_all_space ? "-NRlp0" : "-NRp0") + : (has_ignore_all_space ? "-Nlp0" : "-Np0"); if (fuzzy) { int fuzz = 0; @@ -1011,14 +1011,12 @@ apply_patch (FILE *patch, const char *file, int reverted, FILE **out) argv[argc++] = "--no-backup-if-mismatch"; /* When reverting a rejected hunk, use the maximum possible - * fuzz, don't generate .rej files, and don't let patch ask to - * unreverse our hunk. Otherwise, either pass in the user- - * supplied max fuzz, or fuzz all but one pre-context and one - * post-context line by default. */ + * fuzz and don't generate .rej files. Otherwise, either pass in + * the user-supplied max fuzz, or fuzz all but one pre-context + * and one post-context line by default. */ if (reverted) { fuzz = INT_MAX; argv[argc++] = "--reject-file=-"; - argv[argc++] = "-N"; } else if (max_fuzz_user >= 0) { fuzz = max_fuzz_user; } else if (max_context) { From 4cacf07198661626c785af9fab4a9633c2cde1ad Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Mon, 22 Dec 2025 17:43:05 -0800 Subject: [PATCH 11/18] interdiff: Begin fixing the direction of delta differences in fuzzy mode Fuzzy mode output is incoherent because context differences show up as going from patch1 -> patch2, while delta differences show up in the opposite direction: patch2 -> patch1. This makes it rather impossible to tell which patch file a +/- line originates from, not to mention that the diff itself is totally nonsense in terms of the actual code changed by the patches. Bring interdiff part of the way there to fixing this issue, by eliminating delta differences from the compared files. Note that this makes fuzzy mode only do context diffing, which is fixed in the subsequent commits. --- src/interdiff.c | 138 +++++++++++++++++++++++------------------------- 1 file changed, 65 insertions(+), 73 deletions(-) diff --git a/src/interdiff.c b/src/interdiff.c index 5e038252..6d783811 100644 --- a/src/interdiff.c +++ b/src/interdiff.c @@ -970,8 +970,39 @@ output_patch1_only (FILE *p1, FILE *out, int not_reverted) return 0; } +static void +open_rej_file (char *file, struct rej_file *rej) +{ + char *line = NULL; + size_t linelen; + long atat_pos; + + /* Briefly modify `file` in-place to open the .rej file */ + strcat (file, ".rej"); + rej->fp = xopen (file, "r"); + file[strlen (file) - strlen (".rej")] = '\0'; + + /* Skip (the first two) lines to get to the start of the @@ line */ + do { + atat_pos = ftell (rej->fp); + if (getline (&line, &linelen, rej->fp) <= 0) + error (EXIT_FAILURE, errno, + "Failed to read line from .rej"); + } while (strncmp (line, "@@ ", 3)); + fseek (rej->fp, atat_pos, SEEK_SET); + + /* Export the line offset of the first rej hunk */ + if (read_atatline (line, &rej->off, NULL, NULL, NULL)) + error (EXIT_FAILURE, 0, "line not understood: %s", line); + free (line); + + /* Go back to the @@ after apply_patch() moved the file cursor */ + fseek (rej->fp, atat_pos, SEEK_SET); +} + static int -apply_patch (FILE *patch, const char *file, int reverted, FILE **out) +apply_patch (FILE *patch, const char *file, int reverted, struct rej_file *rej, + FILE **out) { #define MAX_PATCH_ARGS 8 const char *argv[MAX_PATCH_ARGS]; @@ -1010,18 +1041,12 @@ apply_patch (FILE *patch, const char *file, int reverted, FILE **out) /* Don't generate .orig files when we expect rejected hunks */ argv[argc++] = "--no-backup-if-mismatch"; - /* When reverting a rejected hunk, use the maximum possible - * fuzz and don't generate .rej files. Otherwise, either pass in - * the user-supplied max fuzz, or fuzz all but one pre-context - * and one post-context line by default. */ - if (reverted) { - fuzz = INT_MAX; - argv[argc++] = "--reject-file=-"; - } else if (max_fuzz_user >= 0) { + /* Either pass in the user-supplied max fuzz, or fuzz all but + * one pre-context and one post-context line by default. */ + if (max_fuzz_user >= 0) fuzz = max_fuzz_user; - } else if (max_context) { + else if (max_context) fuzz = max_context - 1; - } if (asprintf (&fuzz_arg, "--fuzz=%d", fuzz) < 0) error (EXIT_FAILURE, errno, "asprintf failed"); argv[argc++] = fuzz_arg; @@ -1096,7 +1121,9 @@ apply_patch (FILE *patch, const char *file, int reverted, FILE **out) new_lines--; } fclose (w); + free (line); waitpid (child, &status, 0); + status = WEXITSTATUS (status); /* Provide the output from patch if requested */ if (out) @@ -1104,10 +1131,11 @@ apply_patch (FILE *patch, const char *file, int reverted, FILE **out) else fclose (r); - if (line) - free (line); + /* Open the reject file if requested and there are rejects */ + if (status && rej) + open_rej_file ((char *) file, rej); - return WEXITSTATUS (status); + return status; } static int @@ -2069,42 +2097,6 @@ fuzzy_relocate_hunks (const char *file, const char *unline, FILE *patch_out, free (relocs); } -static void -fuzzy_do_rej (char *file, struct rej_file *rej, const char *other_file) -{ - char *line = NULL; - size_t linelen; - long atat_pos; - - /* Briefly modify `file` in-place to open the .rej file */ - strcat (file, ".rej"); - rej->fp = xopen (file, "r"); - file[strlen (file) - strlen (".rej")] = '\0'; - - /* Skip (the first two) lines to get to the start of the @@ line */ - do { - atat_pos = ftell (rej->fp); - if (getline (&line, &linelen, rej->fp) <= 0) - error (EXIT_FAILURE, errno, - "Failed to read line from .rej"); - } while (strncmp (line, "@@ ", 3)); - fseek (rej->fp, atat_pos, SEEK_SET); - - /* Export the line offset of the first rej hunk */ - if (read_atatline (line, &rej->off, NULL, NULL, NULL)) - error (EXIT_FAILURE, 0, "line not understood: %s", line); - free (line); - - /* Revert the rejected hunks on the _other_ file, so they're excluded - * from the 'diff' output. Otherwise, 'diff' will output the _reverse_ - * of the rejected hunks, which will muddy the final output as we will - * print out the rejected hunks themselves later anyway. */ - apply_patch (rej->fp, other_file, 1, NULL); - - /* Go back to the @@ after apply_patch() moved the file cursor */ - fseek (rej->fp, atat_pos, SEEK_SET); -} - static void fuzzy_cleanup (char *file, int rej) { @@ -2219,25 +2211,18 @@ output_delta (FILE *p1, FILE *p2, FILE *out) fseek (p1, start1, SEEK_SET); fseek (p2, start2, SEEK_SET); - /* Write it out. */ if (fuzzy) { + FILE *patch_out, *sp; + unsigned long *hunk_offs = NULL; + /* Ensure the same unline is used for both files */ write_file (&file, tmpp2fd); file2.unline = xstrdup (file.unline); write_file (&file2, tmpp1fd); - } else { - merge_lines (&file, &file2); - write_file (&file, tmpp1fd); - write_file (&file, tmpp2fd); - } - - if (fuzzy) { - unsigned long *hunk_offs = NULL; - FILE *patch_out, *sp; /* Split the patch hunks into smaller hunks, then apply that */ sp = split_patch_hunks (p2, pos2 - start2, tmpp1, &hunk_offs, NULL); - ret1 = apply_patch (sp, tmpp1, false, &patch_out); + ret1 = apply_patch (sp, tmpp1, false, &rej1, &patch_out); fclose (sp); /* Relocate hunks in tmpp1 in order to make them align with the @@ -2246,9 +2231,14 @@ output_delta (FILE *p1, FILE *p2, FILE *out) fclose (patch_out); free (hunk_offs); + /* Revert the successful p2 deltas from tmpp1 so they don't + * appear as minus lines in the final diff. */ + fseek (p2, start2, SEEK_SET); + apply_patch (p2, tmpp1, true, NULL, NULL); + /* Split the patch hunks into smaller hunks, then apply that */ sp = split_patch_hunks (p1, pos1 - start1, tmpp2, NULL, NULL); - ret2 = apply_patch (sp, tmpp2, false, NULL); + ret2 = apply_patch (sp, tmpp2, false, &rej2, NULL); fclose (sp); /* For tmpp2 relocations, only eat unline gaps between hunks @@ -2256,19 +2246,21 @@ output_delta (FILE *p1, FILE *p2, FILE *out) * also done to tmpp1 during its relocation pass. */ fuzzy_relocate_hunks (tmpp2, file.unline, NULL, NULL); - /* Handle the rejected hunks. This needs to be done after both - * files are patched because it may revert a rejected hunk from - * the other file. */ - if (ret1) - fuzzy_do_rej (tmpp1, &rej1, tmpp2); - if (ret2) - fuzzy_do_rej (tmpp2, &rej2, tmpp1); + /* Revert the successful p1 deltas from tmpp2 so they don't + * appear as plus lines in the final diff. */ + fseek (p1, start1, SEEK_SET); + apply_patch (p1, tmpp2, true, NULL, NULL); } else { - if (apply_patch (p1, tmpp1, mode == mode_combine, NULL)) + /* Write it out. */ + merge_lines (&file, &file2); + write_file (&file, tmpp1fd); + write_file (&file, tmpp2fd); + + if (apply_patch (p1, tmpp1, mode == mode_combine, NULL, NULL)) error (EXIT_FAILURE, 0, "Error applying patch1 to reconstructed file"); - if (apply_patch (p2, tmpp2, 0, NULL)) + if (apply_patch (p2, tmpp2, 0, NULL, NULL)) error (EXIT_FAILURE, 0, "Error applying patch2 to reconstructed file"); } @@ -2857,7 +2849,7 @@ flipdiff (FILE *p1, FILE *p2, FILE *flip1, FILE *flip2) tmpfd = xmkstemp (tmpp1); write_file (&intermediate, tmpfd); fsetpos (p1, &at1); - if (apply_patch (p1, tmpp1, 1, NULL)) + if (apply_patch (p1, tmpp1, 1, NULL, NULL)) error (EXIT_FAILURE, 0, "Error reconstructing original file"); @@ -2866,7 +2858,7 @@ flipdiff (FILE *p1, FILE *p2, FILE *flip1, FILE *flip2) tmpfd = xmkstemp (tmpp3); write_file (&intermediate, tmpfd); fsetpos (p2, &at2); - if (apply_patch (p2, tmpp3, 0, NULL)) + if (apply_patch (p2, tmpp3, 0, NULL, NULL)) error (EXIT_FAILURE, 0, "Error reconstructing final file"); From e1966fb72a03ab36fb312c2ac378269983eed54e Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Thu, 15 Jan 2026 17:29:54 -0800 Subject: [PATCH 12/18] interdiff: Fix hunk lookup to use actual applied position, not adjusted one When using --fuzzy mode, interdiff splits patch hunks and applies them with fuzz to maximize successful application. The parse_fuzzed_hunks() function parses patch's output to create relocation records that track where hunks were applied and how much they need to be relocated back to their original intended positions. The bug was in how the relocation's `new` field was calculated. The code was storing: new = lnum - hunk_offs[hnum - 1] where `lnum` is the line number where patch applied the hunk, and `hunk_offs` is the offset introduced by splitting the original hunk into smaller pieces. This calculation gives the "original hunk's intended line number" before any splitting occurred. However, fuzzy_relocate_hunks() later searches for hunks in the patched file by looking for hunks where `hcurr->nstart == rcurr->new`. The hunks in the patched file are located at their *actual* applied positions (i.e., `lnum`), not at the adjusted position (`lnum - hunk_offs`). This mismatch caused the relocation logic to fail to find the target hunk, triggering the fatal "failed to relocate hunk" error. The fix changes the relocation record to store: new = lnum (actual position in patched file) off = off + split_off (total offset: patch offset + split offset) This ensures that: 1. Hunks can be found at their actual positions in the patched file (matching `hcurr->nstart == rcurr->new` succeeds) 2. When relocating, subtracting `off` correctly moves the hunk back to its original intended position, accounting for both the patch offset (where patch chose to apply the hunk) and the split offset (the difference introduced by hunk splitting) 3. Duplicate detection still works correctly by comparing original intended positions: `lnum - off - split_off == prev->new - prev->off` simplifies to comparing the same values as before since prev->off now also includes the split offset Co-authored-by: Claude Opus 4.5 --- src/interdiff.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/interdiff.c b/src/interdiff.c index 6d783811..bf2f41f4 100644 --- a/src/interdiff.c +++ b/src/interdiff.c @@ -1853,7 +1853,7 @@ parse_fuzzed_hunks (FILE *patch_out, const unsigned long *hunk_offs, /* Parse out each fuzzed hunk's line offset */ while (getline (&line, &linelen, patch_out) > 0) { struct hunk_reloc *prev = &(*relocs)[*num_relocs - 1]; - unsigned long fuzz = 0, hnum, lnum; + unsigned long fuzz = 0, hnum, lnum, split_off; long off; if (sscanf (line, "Hunk #%lu succeeded at %lu (offset %ld", @@ -1862,16 +1862,22 @@ parse_fuzzed_hunks (FILE *patch_out, const unsigned long *hunk_offs, &hnum, &lnum, &fuzz, &off) != 4) continue; - /* Recover the correct new line number of the possibly-split - * hunk, and skip it if it matches the relocated new line number - * of the previous hunk (if any). Split hunks are contiguous. */ - lnum -= hunk_offs[hnum - 1]; - if (*relocs && lnum - off == prev->new - prev->off) + /* Get the split offset for this hunk - the difference between + * the split hunk's new line number and the original hunk's. */ + split_off = hunk_offs[hnum - 1]; + + /* Skip if this hunk belongs to the same original hunk as the + * previous relocation. Split hunks are contiguous. Compare the + * original intended line numbers (applied position - total offset). */ + if (*relocs && lnum - off - split_off == prev->new - prev->off) continue; + /* Store the actual applied position in the patched file as `new`, + * and the total offset (patch offset + split offset) needed to + * relocate the hunk back to its original intended position. */ *relocs = xrealloc (*relocs, ++*num_relocs * sizeof (**relocs)); (*relocs)[*num_relocs - 1] = - (typeof (**relocs)){ lnum, off, fuzz }; + (typeof (**relocs)){ lnum, off + split_off, fuzz }; } free (line); } From 72bef60826c4892efb7d970f1e9bb5a7449aa8a7 Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Fri, 23 Jan 2026 17:03:55 -0800 Subject: [PATCH 13/18] interdiff: Stop modifying file strings in-place This is a slippery slope and tricky to keep track of to ensure that the file string buffer is sufficiently large. Just use dynamic allocations. --- src/interdiff.c | 47 ++++++++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/src/interdiff.c b/src/interdiff.c index bf2f41f4..9b752bbc 100644 --- a/src/interdiff.c +++ b/src/interdiff.c @@ -971,16 +971,17 @@ output_patch1_only (FILE *p1, FILE *out, int not_reverted) } static void -open_rej_file (char *file, struct rej_file *rej) +open_rej_file (const char *file, struct rej_file *rej) { - char *line = NULL; + char *rej_file, *line = NULL; size_t linelen; long atat_pos; - /* Briefly modify `file` in-place to open the .rej file */ - strcat (file, ".rej"); - rej->fp = xopen (file, "r"); - file[strlen (file) - strlen (".rej")] = '\0'; + /* Open the .rej file */ + if (asprintf (&rej_file, "%s.rej", file) < 0) + error (EXIT_FAILURE, errno, "asprintf failed"); + rej->fp = xopen (rej_file, "r"); + free (rej_file); /* Skip (the first two) lines to get to the start of the @@ line */ do { @@ -1133,7 +1134,7 @@ apply_patch (FILE *patch, const char *file, int reverted, struct rej_file *rej, /* Open the reject file if requested and there are rejects */ if (status && rej) - open_rej_file ((char *) file, rej); + open_rej_file (file, rej); return status; } @@ -1542,10 +1543,13 @@ split_patch_hunks (FILE *patch, size_t len, char *file, /* Find the length of the unline now to use it in the loop */ unline_len = strlen (unline); } else { - /* Create the output file by temporarily modifying `file` */ - strcat (file, ".patch"); - out = xopen (file, "w+"); - file[strlen (file) - strlen (".patch")] = '\0'; + char *out_file; + + /* Create the output file */ + if (asprintf (&out_file, "%s.patch", file) < 0) + error (EXIT_FAILURE, errno, "asprintf failed"); + out = xopen (out_file, "w+"); + free (out_file); } do { @@ -2104,23 +2108,25 @@ fuzzy_relocate_hunks (const char *file, const char *unline, FILE *patch_out, } static void -fuzzy_cleanup (char *file, int rej) +fuzzy_cleanup (const char *file, int rej) { - /* Modify the `file` string in-place */ - char *end = strchr (file, '\0'); + size_t len = strlen (file); + char *tmp = xmalloc (len + sizeof (".patch")); + char *end = &tmp[len]; + + memcpy (tmp, file, len); /* Remove the .rej file if one was generated */ if (rej) { strcpy (end, ".rej"); - unlink (file); + unlink (tmp); } /* Remove the .patch file generated from splitting up the hunks */ strcpy (end, ".patch"); - unlink (file); + unlink (tmp); - /* Terminate `file` back at where it was terminated originally */ - *end = '\0'; + free (tmp); } static int @@ -2128,9 +2134,8 @@ output_delta (FILE *p1, FILE *p2, FILE *out) { const char *tmpdir = getenv ("TMPDIR"); unsigned int tmplen; - /* Reserve space for appending .rej and .patch at the end of tmpp1/2 */ - const char tail1[] = "/interdiff-1.XXXXXX\0patch"; - const char tail2[] = "/interdiff-2.XXXXXX\0patch"; + const char tail1[] = "/interdiff-1.XXXXXX"; + const char tail2[] = "/interdiff-2.XXXXXX"; char *tmpp1, *tmpp2; int tmpp1fd, tmpp2fd; struct lines_info file = { NULL, 0, 0, NULL, NULL }; From 6db1f3509044ccece0f77cb312cf962632399d9d Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Wed, 4 Feb 2026 14:50:05 -0800 Subject: [PATCH 14/18] interdiff: Add run_diff() helper function Extract the common diff invocation pattern into a reusable run_diff() helper function. This function: - Takes options string as a parameter (caller builds it) - Builds the diff command with the options and diff_opts - Executes diff via xpipe() - Consumes the --- and +++ header lines - Returns a FILE* positioned at the first @@ line, or NULL if empty - Optionally returns the child pid for the caller to waitpid() Refactor output_delta() to use run_diff() instead of inline diff code. This simplifies the function and prepares for additional diff call sites. Co-authored-by: Claude Opus 4.5 --- src/interdiff.c | 76 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 52 insertions(+), 24 deletions(-) diff --git a/src/interdiff.c b/src/interdiff.c index 9b752bbc..965d01f3 100644 --- a/src/interdiff.c +++ b/src/interdiff.c @@ -2129,6 +2129,53 @@ fuzzy_cleanup (const char *file, int rej) free (tmp); } +/* Run diff on two files and return a FILE* positioned at the first @@ line. + * The --- and +++ header lines are consumed. Returns NULL if diff is empty. + * The caller must waitpid() on the returned pid when the return is non-NULL. */ +static FILE * +run_diff (const char *options, const char *file1, const char *file2, + pid_t *child_out) +{ + pid_t child; + FILE *in; + int diff_is_empty = 1; + + fflush (NULL); + + char *argv[2 + num_diff_opts + 2 + 1]; + memcpy (argv, ((const char *[]) { DIFF, options }), 2 * sizeof (char *)); + memcpy (argv + 2, diff_opts, num_diff_opts * sizeof (char *)); + memcpy (argv + 2 + num_diff_opts, + ((char *[]) { (char *)file1, (char *)file2, NULL }), + (2 + 1) * sizeof (char *)); + in = xpipe (DIFF, &child, "r", argv); + + /* Eat the first line (--- ...) */ + for (;;) { + int ch = fgetc (in); + if (ch == EOF || ch == '\n') + break; + diff_is_empty = 0; + } + + /* Eat the second line (+++ ...) */ + for (;;) { + int ch = fgetc (in); + if (ch == EOF || ch == '\n') + break; + } + + *child_out = child; + + if (diff_is_empty) { + fclose (in); + waitpid (child, NULL, 0); + return NULL; + } + + return in; +} + static int output_delta (FILE *p1, FILE *p2, FILE *out) { @@ -2278,28 +2325,8 @@ output_delta (FILE *p1, FILE *p2, FILE *out) fseek (p1, pos1, SEEK_SET); - fflush (NULL); - - char *argv[2 + num_diff_opts + 2 + 1]; - memcpy (argv, ((const char *[]) { DIFF, options }), 2 * sizeof (char *)); - memcpy (argv + 2, diff_opts, num_diff_opts * sizeof (char *)); - memcpy (argv + 2 + num_diff_opts, ((char *[]) { tmpp1, tmpp2, NULL }), (2 + 1) * sizeof (char *)); - in = xpipe (DIFF, &child, "r", argv); - - /* Eat the first line */ - for (;;) { - int ch = fgetc (in); - if (ch == EOF || ch == '\n') - break; - diff_is_empty = 0; - } - - /* Eat the second line */ - for (;;) { - int ch = fgetc (in); - if (ch == EOF || ch == '\n') - break; - } + in = run_diff (options, tmpp1, tmpp2, &child); + diff_is_empty = !in; /* Rebuild the diff hunks without unlines, since fuzzy diffing shows * context line differences that therefore may cause unlines to appear @@ -2379,9 +2406,10 @@ output_delta (FILE *p1, FILE *p2, FILE *out) fclose (tmpdiff); } - if (in) + if (in) { fclose (in); - waitpid (child, NULL, 0); + waitpid (child, NULL, 0); + } if (debug) printf ("reconstructed orig1=%s orig2=%s\n", tmpp1, tmpp2); else { From 22c4c0c58504bab92cc6ed56bf4e88bc7846b879 Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Mon, 9 Feb 2026 18:30:32 -0800 Subject: [PATCH 15/18] interdiff: Separate delta and context differences in fuzzy mode Refactor fuzzy mode to produce clearly separated output sections that distinguish between actual code changes (delta differences) and surrounding context changes (context differences). This makes it much easier for reviewers to understand backport patches by showing: 1. What behavioral changes differ between the patches 2. What contextual adaptations were made for the target codebase 3. Which hunks couldn't be applied and need manual review DELTA DIFFING: The goal is to show code changes present in one patch but not the other, even when patch2 has hunks that fail to apply due to context mismatch. 1. Construct patch1_orig and patch1_new from patch1 2. Split patch2 and apply to patch1_orig with normal fuzz 3. Generate delta_diff = diff(patch1_new, patch1_orig + patch2) 4. Filter delta hunks that are just the inverse of rejected patch2 hunks (see REJECTION FILTERING below) 5. Filter bogus hunks containing unlines via split_patch_hunks() CONTEXT DIFFING: The goal is to show only context differences with delta changes removed. 1. Create a fresh copy of patch1_orig (delta diffing modified tmpp1) 2. Apply delta_diff in reverse to patch2_orig to remove delta changes 3. Parse fuzz offsets and relocate hunks in patch2_orig 4. Generate ctx_diff = diff(patch1_orig, patch2_orig) 5. Filter bogus hunks containing unlines via split_patch_hunks() REJECTION FILTERING: When patch2 has a hunk that was rejected on patch1_orig, and patch1 makes the same change, the delta diff will show a bogus difference (patch1_new has the change but tmpp1 doesn't). Rather than reverse-applying rejected hunks with GNU patch (which is unreliable when multiple similar code patterns exist), delta_diff is post-processed to filter these out: 1. Parse all delta hunks and rejected hunks, extracting +/- lines and context lines with their distances from the nearest change line. 2. For each rejected hunk, find the best matching delta hunk: the rejected hunk's +lines must appear as a contiguous segment in the delta's -lines (and vice versa). Among matches, pick the delta hunk with the highest context score. The score is the sum of matching context line lengths divided by distance from the nearest change, so lines adjacent to changes are weighted most heavily (similar to how GNU patch prioritizes inner context for fuzzy matching). 3. Each rejected hunk is assigned to at most one delta hunk. A single delta hunk may have multiple rejected hunks assigned (when diff merged adjacent rejected changes into one hunk). 4. A delta hunk is filtered only if ALL its change lines are fully covered by assigned rejected hunks, verified by greedy sequential matching. OUTPUT STRUCTURE: Output is organized into distinct sections with 80-column ASCII banners. Delta differences use = borders with * sides, rejected hunks use # borders with ! sides, and context differences use = borders with * sides. Assisted-by: Claude Opus 4.6 (1M context) --- src/interdiff.c | 742 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 607 insertions(+), 135 deletions(-) diff --git a/src/interdiff.c b/src/interdiff.c index 965d01f3..be12be9a 100644 --- a/src/interdiff.c +++ b/src/interdiff.c @@ -62,6 +62,22 @@ #define PATCH "patch" #endif +/* Fuzzy mode section headers */ +#define DELTA_DIFF_HEADER \ + "================================================================================\n" \ + "* DELTA DIFFERENCES - code changes that differ between the patches *\n" \ + "================================================================================\n\n" + +#define DELTA_REJ_HEADER \ + "################################################################################\n" \ + "! REJECTED PATCH2 HUNKS - could not be compared; manual review needed !\n" \ + "################################################################################\n\n" + +#define CONTEXT_DIFF_HEADER \ + "================================================================================\n" \ + "* CONTEXT DIFFERENCES - surrounding code differences between the patches *\n" \ + "================================================================================\n\n" + /* Line type for coloring */ enum line_type { LINE_FILE, @@ -160,6 +176,19 @@ static int debug = 0; static int fuzzy = 0; static int max_fuzz_user = -1; +/* Per-file record for fuzzy mode output accumulation */ +struct fuzzy_file_record { + char *oldname; + char *newname; + FILE *hunks; /* Raw hunk data (before trim_context) */ + struct fuzzy_file_record *next; +}; + +/* Accumulators for fuzzy mode output sections */ +static struct fuzzy_file_record *fuzzy_delta_files = NULL; +static struct fuzzy_file_record *fuzzy_ctx_files = NULL; +static struct fuzzy_file_record *fuzzy_delta_rej_files = NULL; + static struct patlist *pat_drop_context = NULL; static struct file_list *files_done = NULL; @@ -1199,12 +1228,12 @@ trim_context (FILE *f /* positioned at start of @@ line */, if (new_count) new_count--; if (!pre_seen) { pre++; - if (!strcmp (line + 1, unline)) + if (unline && !strcmp (line + 1, unline)) strip_pre = pre; } else { post++; if (strip_post || - !strcmp (line + 1, unline)) + (unline && !strcmp (line + 1, unline))) strip_post++; } break; @@ -1300,82 +1329,68 @@ trim_context (FILE *f /* positioned at start of @@ line */, return 0; } +/* Add a file record to a fuzzy output list */ static void -output_rej_hunks (const char *diff, struct rej_file **rej1, - struct rej_file **rej2, FILE *out) +fuzzy_add_file (struct fuzzy_file_record **list, const char *oldname, + const char *newname, FILE *hunks) { - char *line = NULL; - - while (*rej1 || *rej2) { - struct rej_file **rej_ptr = rej1, *rej; - int first_line_done = 0, patch_id = 1; - unsigned long diff_off; - long next_atat_pos; - size_t linelen; - ssize_t got; - - /* Pick the reject hunk that comes first */ - if (!*rej1 || (*rej2 && (*rej2)->off < (*rej1)->off)) { - rej_ptr = rej2; - patch_id = 2; - } - rej = *rej_ptr; - - if (diff) { - /* Wait until the current diff line is an @@ line */ - if (strncmp (diff, "@@ ", 3)) - return; + struct fuzzy_file_record *rec = xmalloc (sizeof (*rec)); + rec->oldname = xstrdup (oldname); + rec->newname = xstrdup (newname); + rec->hunks = hunks; + rec->next = NULL; + + /* Append to end of list to preserve file order */ + if (!*list) { + *list = rec; + } else { + struct fuzzy_file_record *last = *list; + while (last->next) + last = last->next; + last->next = rec; + } +} - if (read_atatline (diff, &diff_off, NULL, NULL, NULL)) - error (EXIT_FAILURE, 0, "line not understood: %s", - diff); +/* Free a fuzzy file record list */ +static void +fuzzy_free_list (struct fuzzy_file_record *list) +{ + while (list) { + struct fuzzy_file_record *next = list->next; + free (list->oldname); + free (list->newname); + if (list->hunks) + fclose (list->hunks); + free (list); + list = next; + } +} - /* Stop if the diff hunk comes next */ - if (rej->off > diff_off) - return; - } +/* Output a fuzzy file list with colorization through trim_context */ +static void +fuzzy_output_list (struct fuzzy_file_record *list, FILE *out) +{ + char *line = NULL; + size_t linelen; - /* Write the rej hunk until EOF or the next @@ line (i.e., next - * hunk). Note that rej starts at the current @@ line that we - * must write, so don't look for the next @@ until after the - * first line is written. */ - for (;;) { - got = getline (&line, &linelen, rej->fp); - if (got <= 0) { - if (feof (rej->fp)) - goto rej_file_eof; - error (EXIT_FAILURE, errno, - "Failed to read line from .rej"); - } - if (first_line_done) { - if (!strncmp (line, "@@ ", 3)) - break; + for (; list; list = list->next) { + if (!list->hunks) + continue; + rewind (list->hunks); - fwrite (line, (size_t) got, 1, out); - next_atat_pos = ftell (rej->fp); - } else { - /* Append a comment after the @@ line indicating - * this is a rejected hunk. */ - first_line_done = 1; - fwrite (line, (size_t) got - 1, 1, out); - fprintf (out, " INTERDIFF: rejected hunk from patch%d, cannot diff context\n", - patch_id); + /* Skip past any --- / +++ headers (e.g., from .rej files) */ + while (getline (&line, &linelen, list->hunks) > 0) { + if (!strncmp (line, "@@ ", 3)) { + fseek (list->hunks, + -(long) strlen (line), SEEK_CUR); + break; } } - /* Record the line offset of the next rej hunk, if any */ - if (read_atatline (line, &rej->off, NULL, NULL, NULL)) - error (EXIT_FAILURE, 0, "line not understood: %s", line); - fseek (rej->fp, next_atat_pos, SEEK_SET); - - if (!feof (rej->fp)) - continue; - -rej_file_eof: - /* Clear out this reject file pointer when it's finished */ - *rej_ptr = NULL; + print_color (out, LINE_FILE, "--- %s\n", list->oldname); + print_color (out, LINE_FILE, "+++ %s\n", list->newname); + trim_context (list->hunks, NULL, out); } - free (line); } @@ -1523,7 +1538,6 @@ split_patch_hunks (FILE *patch, size_t len, char *file, fbuf = xrealloc (fbuf, ++len + 1); fbuf[len - 1] = ch; } - fclose (patch); } fbuf[len] = '\0'; @@ -2176,6 +2190,364 @@ run_diff (const char *options, const char *file1, const char *file2, return in; } +static void +strarray_push (char ***arr, size_t *n, const char *s) +{ + *arr = xrealloc (*arr, (*n + 1) * sizeof (char *)); + (*arr)[(*n)++] = xstrdup (s); +} + +static void +strarray_free (char **arr, size_t n) +{ + for (size_t i = 0; i < n; i++) + free (arr[i]); + free (arr); +} + +/* Per-hunk change/context line arrays for rejection filtering */ +struct hunk_lines { + char **add, **del, **ctx; + unsigned *ctx_dist; /* distance of each ctx line from nearest +/- line */ + size_t nadd, ndel, nctx; +}; + +/* Parse hunks from a FILE* into an array of hunk_lines. For delta hunks, + * also captures raw content into bufs[]/buf_lens[] for later output. + * Pass NULL for those if not needed. */ +static size_t +parse_hunks (FILE *f, struct hunk_lines **out, char ***bufs, size_t **buf_lens) +{ + struct hunk_lines *hunks = NULL; + size_t nhunks = 0, linelen; + char *line = NULL; + ssize_t got; + + rewind (f); + while ((got = getline (&line, &linelen, f)) > 0) { + unsigned char *ltypes = NULL; + size_t nlines = 0, ctx_idx; + struct hunk_lines *h; + unsigned dist; + + if (strncmp (line, "@@ ", 3)) + continue; + + nhunks++; + hunks = xrealloc (hunks, nhunks * sizeof (*hunks)); + h = &hunks[nhunks - 1]; + *h = (typeof (*h)){}; + + if (bufs) { + *bufs = xrealloc (*bufs, nhunks * sizeof (char *)); + *buf_lens = xrealloc (*buf_lens, + nhunks * sizeof (size_t)); + (*bufs)[nhunks - 1] = xstrdup (line); + (*buf_lens)[nhunks - 1] = (size_t) got; + } + + while ((got = getline (&line, &linelen, f)) > 0) { + if (!strncmp (line, "@@ ", 3)) { + fseek (f, -(long) got, SEEK_CUR); + break; + } + + if (bufs) { + size_t old = (*buf_lens)[nhunks - 1]; + (*bufs)[nhunks - 1] = xrealloc ( + (*bufs)[nhunks - 1], + old + (size_t) got + 1); + memcpy ((*bufs)[nhunks - 1] + old, + line, (size_t) got); + (*buf_lens)[nhunks - 1] = old + (size_t) got; + (*bufs)[nhunks - 1][old + (size_t) got] = '\0'; + } + + ltypes = xrealloc (ltypes, + (nlines + 1) * sizeof (*ltypes)); + + if (line[0] == '+') { + strarray_push (&h->add, &h->nadd, line + 1); + ltypes[nlines] = 1; + } else if (line[0] == '-') { + strarray_push (&h->del, &h->ndel, line + 1); + ltypes[nlines] = 1; + } else if (line[0] == ' ') { + strarray_push (&h->ctx, &h->nctx, line + 1); + ltypes[nlines] = 0; + } else { + ltypes[nlines] = 1; + } + nlines++; + } + + /* Compute distance of each context line from nearest + * change line. Lines closest to changes are most + * valuable for location disambiguation. */ + h->ctx_dist = xmalloc (h->nctx * sizeof (*h->ctx_dist)); + dist = UINT_MAX; + ctx_idx = 0; + for (size_t k = 0; k < nlines; k++) { + if (ltypes[k]) { + dist = 0; + } else { + if (dist < UINT_MAX) + dist++; + h->ctx_dist[ctx_idx++] = dist; + } + } + + /* Backward pass: take min with distance from next change */ + dist = UINT_MAX; + ctx_idx = h->nctx; + for (size_t k = nlines; k > 0; k--) { + if (ltypes[k - 1]) { + dist = 0; + } else { + ctx_idx--; + if (dist < UINT_MAX) + dist++; + if (dist < h->ctx_dist[ctx_idx]) + h->ctx_dist[ctx_idx] = dist; + } + } + + free (ltypes); + } + + free (line); + *out = hunks; + return nhunks; +} + +static void +free_hunk_lines (struct hunk_lines *hunks, size_t nhunks) +{ + for (size_t i = 0; i < nhunks; i++) { + strarray_free (hunks[i].add, hunks[i].nadd); + strarray_free (hunks[i].del, hunks[i].ndel); + strarray_free (hunks[i].ctx, hunks[i].nctx); + free (hunks[i].ctx_dist); + } + free (hunks); +} + +/* Score how well the context lines from a rejected hunk match a delta hunk. + * Context lines closer to +/- changes are weighted more heavily, mirroring + * how GNU patch prioritizes inner context for fuzzy matching. The score is + * line_length / distance_from_nearest_change for each matching line. */ +static int +context_score (const struct hunk_lines *rej, const struct hunk_lines *delta) +{ + int score = 0; + + for (size_t i = 0; i < rej->nctx; i++) { + for (size_t j = 0; j < delta->nctx; j++) { + if (!strcmp (rej->ctx[i], delta->ctx[j])) { + unsigned dist = rej->ctx_dist[i]; + if (dist < delta->ctx_dist[j]) + dist = delta->ctx_dist[j]; + score += strlen (rej->ctx[i]) / (dist ? dist : 1); + break; + } + } + } + + return score; +} + +/* Check if a rejected hunk matches a delta hunk at the given positions. + * Since the delta is the inverse of the rejection, the rejected hunk's + * +lines are compared against the delta's -lines and vice versa. + * On match, *pos_del and *pos_add are advanced past the matched lines. */ +static int +rej_matches_delta_at (const struct hunk_lines *rej, + const struct hunk_lines *delta, + size_t *pos_del, size_t *pos_add) +{ + size_t i; + + if (!rej->nadd && !rej->ndel) + return 0; + + if (rej->nadd) { + if (*pos_del + rej->nadd > delta->ndel) + return 0; + for (i = 0; i < rej->nadd; i++) { + if (strcmp (delta->del[*pos_del + i], rej->add[i])) + return 0; + } + } + + if (rej->ndel) { + if (*pos_add + rej->ndel > delta->nadd) + return 0; + for (i = 0; i < rej->ndel; i++) { + if (strcmp (delta->add[*pos_add + i], rej->del[i])) + return 0; + } + } + + *pos_del += rej->nadd; + *pos_add += rej->ndel; + return 1; +} + +/* Check if a rejected hunk's change lines appear anywhere in a delta hunk's + * change lines. Unlike rej_matches_delta_at(), this searches all positions. */ +static int +rej_matches_delta (const struct hunk_lines *rej, const struct hunk_lines *delta) +{ + size_t pd, pa; + + if (rej->nadd > delta->ndel || rej->ndel > delta->nadd) + return 0; + + for (pd = 0; pd <= delta->ndel - rej->nadd; pd++) { + for (pa = 0; pa <= delta->nadd - rej->ndel; pa++) { + size_t try_pd = pd, try_pa = pa; + if (rej_matches_delta_at (rej, delta, &try_pd, &try_pa)) + return 1; + } + } + + return 0; +} + +/* Filter delta diff hunks that are just the inverse of rejected hunks. + * When patch2 has a hunk that was rejected on patch1_orig, and patch1 makes + * the same change, the delta diff will show a bogus difference. This function + * removes those bogus hunks by comparing each delta hunk's change lines + * against the rejected hunks' change lines in reverse. + * + * Each rejected hunk is used at most once. When a rejected hunk matches + * multiple delta hunks, the one with the most matching context lines wins. + * A single delta hunk may span multiple rejected hunks that diff merged. + * + * Returns a new FILE* with the filtered output, or NULL if all hunks were + * filtered out. */ +static FILE * +filter_inverted_rejects (FILE *delta, FILE *rej) +{ + struct hunk_lines *rej_hunks, *delta_hunks; + size_t nrej, ndelta, *buf_lens = NULL; + long *rej_assigned = NULL; + int *d_filtered = NULL; + char **bufs = NULL; + int has_output = 0; + FILE *out; + + nrej = parse_hunks (rej, &rej_hunks, NULL, NULL); + ndelta = parse_hunks (delta, &delta_hunks, &bufs, &buf_lens); + + if (!nrej || !ndelta) + goto output; + + rej_assigned = xmalloc (nrej * sizeof (*rej_assigned)); + d_filtered = xmalloc (ndelta * sizeof (*d_filtered)); + memset (d_filtered, 0, ndelta * sizeof (*d_filtered)); + + /* For each rejected hunk, find the delta hunk whose change lines + * match (inverted) and which has the best context overlap */ + for (size_t r = 0; r < nrej; r++) { + int best_score = -1; + + rej_assigned[r] = -1; + for (size_t d = 0; d < ndelta; d++) { + int score; + + if (!rej_matches_delta (&rej_hunks[r], &delta_hunks[d])) + continue; + + score = context_score (&rej_hunks[r], &delta_hunks[d]); + if (score > best_score) { + best_score = score; + rej_assigned[r] = (long) d; + } + } + } + + /* For each delta hunk, check if all its change lines are fully + * covered by the rejected hunks assigned to it (in order) */ + for (size_t d = 0; d < ndelta; d++) { + size_t pos_del = 0, pos_add = 0; + + for (size_t r = 0; r < nrej; r++) { + if (rej_assigned[r] != (long) d) + continue; + if (!rej_matches_delta_at (&rej_hunks[r], + &delta_hunks[d], + &pos_del, &pos_add)) + break; + } + + if (pos_del == delta_hunks[d].ndel && + pos_add == delta_hunks[d].nadd) + d_filtered[d] = 1; + } + + free (rej_assigned); + +output: + out = xtmpfile (); + for (size_t d = 0; d < ndelta; d++) { + if (!d_filtered || !d_filtered[d]) { + fwrite (bufs[d], buf_lens[d], 1, out); + has_output = 1; + } + } + + free (d_filtered); + for (size_t d = 0; d < ndelta; d++) + free (bufs[d]); + free (bufs); + free (buf_lens); + free_hunk_lines (rej_hunks, nrej); + free_hunk_lines (delta_hunks, ndelta); + + if (!has_output) { + fclose (out); + return NULL; + } + + return out; +} + +/* Run diff and filter out bogus hunks containing unlines. Returns NULL if + * the resulting diff is empty. */ +static FILE * +run_and_clean_diff (const char *options, const char *file1, const char *file2, + const char *unline) +{ + pid_t child; + FILE *orig, *diff = run_diff (options, file1, file2, &child); + if (diff) { + orig = diff; + diff = split_patch_hunks (diff, 0, NULL, NULL, unline); + fclose (orig); + } + waitpid (child, NULL, 0); + return diff; +} + +/* Write a lines_info struct to a new temp file derived from the given + * template path (replaces the last 6 chars with XXXXXX for mkstemp). + * Returns the allocated filename which must be freed by the caller. */ +static char * +write_to_tmpfile (const char *tmpl, struct lines_info *info) +{ + char *file = xstrdup (tmpl); + int fd; + + strcpy (file + strlen (file) - 6, "XXXXXX"); + fd = mkstemp (file); + if (fd < 0) + error (EXIT_FAILURE, errno, "mkstemp failed"); + write_file (info, fd); + close (fd); + return file; +} + static int output_delta (FILE *p1, FILE *p2, FILE *out) { @@ -2184,20 +2556,20 @@ output_delta (FILE *p1, FILE *p2, FILE *out) const char tail1[] = "/interdiff-1.XXXXXX"; const char tail2[] = "/interdiff-2.XXXXXX"; char *tmpp1, *tmpp2; + char *unline = NULL; int tmpp1fd, tmpp2fd; struct lines_info file = { NULL, 0, 0, NULL, NULL }; struct lines_info file2 = { NULL, 0, 0, NULL, NULL }; - struct rej_file rej1, rej2; - int ret1 = 0, ret2 = 0; + struct rej_file rej; + int has_rejects = 0, ctx_ret = 0; char *oldname = NULL, *newname = NULL; pid_t child; - FILE *in; + FILE *in = NULL; size_t namelen; long pos1 = ftell (p1), pos2 = ftell (p2); long pristine1, pristine2; long start1, start2; char options[100]; - int diff_is_empty = 1; pristine1 = ftell (p1); pristine2 = ftell (p2); @@ -2270,44 +2642,116 @@ output_delta (FILE *p1, FILE *p2, FILE *out) fseek (p2, start2, SEEK_SET); if (fuzzy) { - FILE *patch_out, *sp; - unsigned long *hunk_offs = NULL; - - /* Ensure the same unline is used for both files */ + unsigned long *hunk_offs = NULL, *ctx_hunk_offs = NULL; + FILE *sp, *delta_diff, *ctx_diff; + char *patch1_new_file, *ctx_patch1_orig_file; + struct lines_info patch1_new_info = {}; + FILE *ctx_patch_out = NULL; + int delta_empty, ctx_empty; + + /* Ensure the same unline is used for both files. + * file = patch2_orig, file2 = patch1_orig */ write_file (&file, tmpp2fd); - file2.unline = xstrdup (file.unline); + unline = file.unline; + file2.unline = unline; write_file (&file2, tmpp1fd); - /* Split the patch hunks into smaller hunks, then apply that */ + /* + * DELTA DIFFING: + * 1. Construct patch1_new from patch1 (reverted=1 -> new side) + * 2. Split patch2 and apply to tmpp1 (patch1_orig) + * 3. delta_diff = diff(patch1_new, tmpp1) + * 4. Filter delta hunks that match rejected patch2 hunks + * + * CONTEXT DIFFING: + * 1. Make a fresh copy of patch1_orig (tmpp1 is modified above) + * 2. Apply delta_diff in reverse to tmpp2 (patch2_orig) to + * remove delta differences + * 3. Relocate hunks using fuzz offsets + * 4. ctx_diff = diff(patch1_orig, patch2_orig) + */ + + /* Create patch1_new (reverted=1 gives the new side of patch1) */ + fseek (p1, start1, SEEK_SET); + create_orig (p1, &patch1_new_info, 1, NULL); + patch1_new_info.unline = unline; + patch1_new_file = write_to_tmpfile (tmpp1, &patch1_new_info); + free_lines (patch1_new_info.head); + + /* Split patch2 and apply to tmpp1 (patch1_orig) */ sp = split_patch_hunks (p2, pos2 - start2, tmpp1, &hunk_offs, NULL); - ret1 = apply_patch (sp, tmpp1, false, &rej1, &patch_out); + has_rejects = apply_patch (sp, tmpp1, 0, &rej, NULL); fclose (sp); - - /* Relocate hunks in tmpp1 in order to make them align with the - * positions of the hunks in tmpp2. */ - fuzzy_relocate_hunks (tmpp1, file.unline, patch_out, hunk_offs); - fclose (patch_out); free (hunk_offs); - /* Revert the successful p2 deltas from tmpp1 so they don't - * appear as minus lines in the final diff. */ - fseek (p2, start2, SEEK_SET); - apply_patch (p2, tmpp1, true, NULL, NULL); + /* Delta diff: diff(patch1_new, patch1_orig + patch2) */ + delta_diff = run_and_clean_diff (options, patch1_new_file, + tmpp1, unline); + delta_empty = !delta_diff; + + /* Filter bogus delta hunks that are just the inverse of rejected + * hunks (both patches make the same change but patch2's was + * rejected due to context mismatch) */ + if (has_rejects && rej.fp) { + /* rej.fp ownership transfers to the list; + * fuzzy_free_list() will fclose it. */ + fuzzy_add_file (&fuzzy_delta_rej_files, oldname + 4, + newname + 4, rej.fp); + + if (!delta_empty) { + FILE *filtered; + rewind (rej.fp); + filtered = filter_inverted_rejects (delta_diff, + rej.fp); + fclose (delta_diff); + delta_diff = filtered; + delta_empty = !delta_diff; + } + } - /* Split the patch hunks into smaller hunks, then apply that */ - sp = split_patch_hunks (p1, pos1 - start1, tmpp2, NULL, NULL); - ret2 = apply_patch (sp, tmpp2, false, &rej2, NULL); - fclose (sp); + /* Apply delta_diff in reverse to tmpp2 (patch2_orig) to + * remove delta differences and isolate context diffs */ + if (!delta_empty) { + FILE *sp2; - /* For tmpp2 relocations, only eat unline gaps between hunks - * that amount to no more than max_context*2 lines. This was - * also done to tmpp1 during its relocation pass. */ - fuzzy_relocate_hunks (tmpp2, file.unline, NULL, NULL); + rewind (delta_diff); + sp2 = split_patch_hunks (delta_diff, 0, tmpp2, + &ctx_hunk_offs, NULL); + if (sp2) { + ctx_ret = apply_patch (sp2, tmpp2, 1, + NULL, &ctx_patch_out); + fclose (sp2); + } - /* Revert the successful p1 deltas from tmpp2 so they don't - * appear as plus lines in the final diff. */ - fseek (p1, start1, SEEK_SET); - apply_patch (p1, tmpp2, true, NULL, NULL); + if (ctx_patch_out && ctx_hunk_offs) + fuzzy_relocate_hunks (tmpp2, unline, + ctx_patch_out, + ctx_hunk_offs); + if (ctx_patch_out) + fclose (ctx_patch_out); + free (ctx_hunk_offs); + } + + /* Fresh copy of patch1_orig for context comparison + * since tmpp1 was modified by delta diffing above */ + ctx_patch1_orig_file = write_to_tmpfile (tmpp1, &file2); + + ctx_diff = run_and_clean_diff (options, ctx_patch1_orig_file, + tmpp2, unline); + ctx_empty = !ctx_diff; + + unlink (ctx_patch1_orig_file); + free (ctx_patch1_orig_file); + unlink (patch1_new_file); + free (patch1_new_file); + + if (!delta_empty) + fuzzy_add_file (&fuzzy_delta_files, oldname + 4, + newname + 4, delta_diff); + + if (!ctx_empty) + fuzzy_add_file (&fuzzy_ctx_files, oldname + 4, + newname + 4, ctx_diff); } else { /* Write it out. */ merge_lines (&file, &file2); @@ -2323,35 +2767,18 @@ output_delta (FILE *p1, FILE *p2, FILE *out) "Error applying patch2 to reconstructed file"); } - fseek (p1, pos1, SEEK_SET); - - in = run_diff (options, tmpp1, tmpp2, &child); - diff_is_empty = !in; - - /* Rebuild the diff hunks without unlines, since fuzzy diffing shows - * context line differences that therefore may cause unlines to appear - * in the diff output. We don't want any unlines in the final output. */ - if (fuzzy && !diff_is_empty) { - in = split_patch_hunks (in, 0, NULL, NULL, file.unline); - diff_is_empty = !in; - } - - if (!diff_is_empty || ret1 || ret2) { - /* Initialize the rej pointers for output_rej_hunks() */ - struct rej_file *rej1_ptr = ret1 ? &rej1 : NULL; - struct rej_file *rej2_ptr = ret2 ? &rej2 : NULL; + if (!fuzzy && (in = run_diff (options, tmpp1, tmpp2, &child))) { /* ANOTHER temporary file! This is to catch the case * where we just don't have enough context to generate * a proper interdiff. */ FILE *tmpdiff = xtmpfile (); + int exit_err = 0; char *line = NULL; size_t linelen; - for (; !diff_is_empty;) { + for (;;) { ssize_t got = getline (&line, &linelen, in); if (got < 0) break; - /* Output fuzzy diff reject hunks in order */ - output_rej_hunks (line, &rej1_ptr, &rej2_ptr, tmpdiff); fwrite (line, (size_t) got, 1, tmpdiff); if (*line != ' ' && !strcmp (line + 1, file.unline)) { /* Uh-oh. We're trying to output a @@ -2369,16 +2796,17 @@ output_delta (FILE *p1, FILE *p2, FILE *out) * original and copy the new * version. */ fclose (tmpdiff); - free (line); - goto evasive_action; + exit_err = 1; + break; } fwrite (line, (size_t) got, 1, tmpdiff); } } free (line); - - /* Output any remaining reject hunks */ - output_rej_hunks (NULL, &rej1_ptr, &rej2_ptr, tmpdiff); + fclose (in); + waitpid (child, NULL, 0); + if (exit_err) + goto evasive_action; /* First character */ if (human_readable) { @@ -2406,23 +2834,31 @@ output_delta (FILE *p1, FILE *p2, FILE *out) fclose (tmpdiff); } - if (in) { - fclose (in); - waitpid (child, NULL, 0); - } + /* Restore file positions for the caller's iteration loop */ + fseek (p1, pos1, SEEK_SET); + fseek (p2, pos2, SEEK_SET); + if (debug) printf ("reconstructed orig1=%s orig2=%s\n", tmpp1, tmpp2); else { unlink (tmpp1); unlink (tmpp2); if (fuzzy) { - fuzzy_cleanup (tmpp1, ret1); - fuzzy_cleanup (tmpp2, ret2); + fuzzy_cleanup (tmpp1, has_rejects); + fuzzy_cleanup (tmpp2, ctx_ret); } } free (oldname); free (newname); - clear_lines_info (&file); + if (fuzzy) { + free_lines (file.head); + free_lines (file2.head); + free (unline); + } else { + clear_lines_info (&file); + /* In non-fuzzy mode, merge_lines() transfers file2's nodes + * into file, so they're already freed above. */ + } return 0; evasive_action: @@ -2432,8 +2868,8 @@ output_delta (FILE *p1, FILE *p2, FILE *out) unlink (tmpp1); unlink (tmpp2); if (fuzzy) { - fuzzy_cleanup (tmpp1, ret1); - fuzzy_cleanup (tmpp2, ret2); + fuzzy_cleanup (tmpp1, has_rejects); + fuzzy_cleanup (tmpp2, 0); } } if (human_readable) @@ -3234,6 +3670,42 @@ interdiff (FILE *p1, FILE *p2, const char *patch1, const char *patch2) copy_residue (p2, mode == mode_flip ? flip1 : stdout); + /* Output the fuzzy sections after all files have been processed */ + if (fuzzy && (fuzzy_delta_files || fuzzy_ctx_files || + fuzzy_delta_rej_files)) { + FILE *out = (mode == mode_flip) ? flip1 : stdout; + int printed = 0; + + if (fuzzy_delta_files) { + fprintf (out, DELTA_DIFF_HEADER); + fuzzy_output_list (fuzzy_delta_files, out); + printed = 1; + } + + if (fuzzy_delta_rej_files) { + if (printed) + fputc ('\n', out); + fprintf (out, DELTA_REJ_HEADER); + fuzzy_output_list (fuzzy_delta_rej_files, out); + printed = 1; + } + + if (fuzzy_ctx_files) { + if (printed) + fputc ('\n', out); + fprintf (out, CONTEXT_DIFF_HEADER); + fuzzy_output_list (fuzzy_ctx_files, out); + printed = 1; + } + + if (printed) + fputc ('\n', out); + + fuzzy_free_list (fuzzy_delta_files); + fuzzy_free_list (fuzzy_ctx_files); + fuzzy_free_list (fuzzy_delta_rej_files); + } + if (mode == mode_flip) { /* Now we flipped the two patches, show them. */ rewind (flip1); From d3e9407606ef2deed2c4226f5f75ed10f0ebc697 Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Mon, 9 Feb 2026 17:23:58 -0800 Subject: [PATCH 16/18] interdiff: Show files only in one patch under fuzzy mode headers In fuzzy mode, files modified by only one patch were output raw without colorization or section headers. Redirect their output into per-section tmpfile accumulators and display them under new ONLY IN PATCH1 / ONLY IN PATCH2 banners alongside the existing delta/context/rejected sections. Suppress the "reverted:" and "unchanged:" labels in fuzzy mode since the section headers already convey the meaning. Co-authored-by: Claude Opus 4.6 (1M context) --- src/interdiff.c | 95 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 86 insertions(+), 9 deletions(-) diff --git a/src/interdiff.c b/src/interdiff.c index be12be9a..8014a1b5 100644 --- a/src/interdiff.c +++ b/src/interdiff.c @@ -78,6 +78,16 @@ "* CONTEXT DIFFERENCES - surrounding code differences between the patches *\n" \ "================================================================================\n\n" +#define ONLY_IN_PATCH1_HEADER \ + "================================================================================\n" \ + "* ONLY IN PATCH1 - files not modified by patch2 *\n" \ + "================================================================================\n\n" + +#define ONLY_IN_PATCH2_HEADER \ + "================================================================================\n" \ + "* ONLY IN PATCH2 - files not modified by patch1 *\n" \ + "================================================================================\n\n" + /* Line type for coloring */ enum line_type { LINE_FILE, @@ -188,6 +198,8 @@ struct fuzzy_file_record { static struct fuzzy_file_record *fuzzy_delta_files = NULL; static struct fuzzy_file_record *fuzzy_ctx_files = NULL; static struct fuzzy_file_record *fuzzy_delta_rej_files = NULL; +static FILE *fuzzy_only_in_patch1 = NULL; +static FILE *fuzzy_only_in_patch2 = NULL; static struct patlist *pat_drop_context = NULL; @@ -727,12 +739,12 @@ do_output_patch1_only (FILE *p1, FILE *out, int not_reverted) if (not_reverted) { /* Combinediff: copy patch */ - if (human_readable && mode != mode_flip) + if (human_readable && !fuzzy && mode != mode_flip) fprintf (out, "unchanged:\n"); fputs (oldname, out); fputs (line, out); } else if (!no_revert_omitted) { - if (human_readable) + if (human_readable && !fuzzy) fprintf (out, "reverted:\n"); fprintf (out, "--- %s", line + 4); fprintf (out, "+++ %s", oldname + 4); @@ -1394,6 +1406,36 @@ fuzzy_output_list (struct fuzzy_file_record *list, FILE *out) free (line); } +/* Colorize and output a raw diff (with --- / +++ / @@ headers) */ +static void +colorize_diff (FILE *in, FILE *out) +{ + char *line = NULL; + size_t linelen; + ssize_t got; + + rewind (in); + while ((got = getline (&line, &linelen, in)) > 0) { + enum line_type type; + + if (!strncmp (line, "--- ", 4) || !strncmp (line, "+++ ", 4)) { + type = LINE_FILE; + } else if (!strncmp (line, "@@ ", 3)) { + type = LINE_HUNK; + } else if (line[0] == '-') { + type = LINE_REMOVED; + } else if (line[0] == '+') { + type = LINE_ADDED; + } else { + fwrite (line, (size_t) got, 1, out); + continue; + } + print_color (out, type, "%.*s", (int) got - 1, line); + fputc ('\n', out); + } + free (line); +} + /* `xctx` must come with `num` initialized and `s` and `len` zeroed */ static void ctx_lookbehind (const struct line_info *lines, unsigned long start_line_idx, @@ -2888,6 +2930,7 @@ copy_residue (FILE *p2, FILE *out) struct file_list *at; for (at = files_in_patch2; at; at = at->next) { + FILE *p2out; if (file_in_list (files_done, at->file) != -1) continue; @@ -2897,10 +2940,18 @@ copy_residue (FILE *p2, FILE *out) continue; fseek (p2, at->pos, SEEK_SET); - if (human_readable && mode != mode_flip) - fprintf (out, "only in patch2:\n"); - output_patch1_only (p2, out, 1); + if (fuzzy) { + if (!fuzzy_only_in_patch2) + fuzzy_only_in_patch2 = xtmpfile (); + p2out = fuzzy_only_in_patch2; + } else { + if (human_readable && mode != mode_flip) + fprintf (out, "only in patch2:\n"); + p2out = out; + } + + output_patch1_only (p2, p2out, 1); } return 0; @@ -3650,9 +3701,16 @@ interdiff (FILE *p1, FILE *p2, const char *patch1, const char *patch2) fseek (p1, start_pos, SEEK_SET); pos = file_in_list (files_in_patch2, p); if (pos == -1) { - output_patch1_only (p1, - mode == mode_flip ? flip2 : stdout, - mode != mode_inter); + FILE *p1out; + + if (fuzzy && mode == mode_inter) { + if (!fuzzy_only_in_patch1) + fuzzy_only_in_patch1 = xtmpfile (); + p1out = fuzzy_only_in_patch1; + } else { + p1out = mode == mode_flip ? flip2 : stdout; + } + output_patch1_only (p1, p1out, mode != mode_inter); } else { fseek (p2, pos, SEEK_SET); if (mode == mode_flip) @@ -3672,7 +3730,8 @@ interdiff (FILE *p1, FILE *p2, const char *patch1, const char *patch2) /* Output the fuzzy sections after all files have been processed */ if (fuzzy && (fuzzy_delta_files || fuzzy_ctx_files || - fuzzy_delta_rej_files)) { + fuzzy_delta_rej_files || fuzzy_only_in_patch1 || + fuzzy_only_in_patch2)) { FILE *out = (mode == mode_flip) ? flip1 : stdout; int printed = 0; @@ -3698,6 +3757,24 @@ interdiff (FILE *p1, FILE *p2, const char *patch1, const char *patch2) printed = 1; } + if (fuzzy_only_in_patch1) { + if (printed) + fputc ('\n', out); + fprintf (out, ONLY_IN_PATCH1_HEADER); + colorize_diff (fuzzy_only_in_patch1, out); + fclose (fuzzy_only_in_patch1); + printed = 1; + } + + if (fuzzy_only_in_patch2) { + if (printed) + fputc ('\n', out); + fprintf (out, ONLY_IN_PATCH2_HEADER); + colorize_diff (fuzzy_only_in_patch2, out); + fclose (fuzzy_only_in_patch2); + printed = 1; + } + if (printed) fputc ('\n', out); From 114454a348f527c3e88c5d7d0f2a5c57a3111c46 Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Tue, 10 Feb 2026 18:14:55 -0800 Subject: [PATCH 17/18] interdiff: Filter spurious edge lines from context differences When comparing context differences between two patches, diff hunks can contain changes at the top or bottom edge that are exclusively additions or exclusively deletions. These are not real differences -- they are artifacts of one patch having captured more or fewer context lines than the other around the same code change. For example, if patch2 includes 5 lines of context above a change but patch1 only includes 3, the context diff will show those 2 extra lines as additions at the top of a hunk. This is misleading because the patches make the same change; they just differ in how much surrounding code was captured. Add filter_edge_hunks() to detect and handle these spurious edge lines. For each hunk, the first and last context lines partition the body into three regions: top edge, middle, and bottom edge. Each edge is then classified: - Two-sided (has both additions and deletions): a real change, kept as-is - One-sided (exclusively additions or exclusively deletions): spurious, trimmed from the hunk and the @@ header line counts adjusted If no changes remain after trimming (the entire hunk was spurious edges), the hunk is dropped. If all hunks are dropped, the CONTEXT DIFFERENCES section is suppressed entirely. Assisted-by: Claude Opus 4.6 (1M context) --- src/interdiff.c | 123 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/src/interdiff.c b/src/interdiff.c index 8014a1b5..ebba7f78 100644 --- a/src/interdiff.c +++ b/src/interdiff.c @@ -2555,6 +2555,127 @@ filter_inverted_rejects (FILE *delta, FILE *rej) return out; } +/* Filter out spurious edge lines from context diff hunks. Changes that + * are exclusively additions or exclusively deletions at the top or + * bottom edge of a hunk are artifacts of one patch capturing more + * context lines than the other. Hunks composed entirely of such edges + * are dropped. Hunks with real changes in the middle have their + * additions-only or deletions-only edges trimmed and the + * @@ header line counts adjusted accordingly. + * Closes the input file. Returns NULL if nothing remains. */ +static FILE * +filter_edge_hunks (FILE *in) +{ + struct line_info atat, *lines = NULL; + char *fbuf, *end, *line; + FILE *out = xtmpfile (); + size_t nlines = 0, fsz; + + /* Read entire input into a buffer */ + fseek (in, 0, SEEK_END); + fsz = ftell (in); + fbuf = xmalloc (fsz); + rewind (in); + if (fread (fbuf, 1, fsz, in) != fsz) + error (EXIT_FAILURE, errno, "fread() fail"); + fclose (in); + + end = fbuf + fsz; + atat = (typeof(atat)){ fbuf }; /* The first line is the @@ line */ + for (line = fbuf; (line = memchr (line, '\n', end - line));) { + /* ntop/nbot[0] = deleted, [1] = added edge lines */ + int ntop[2] = {}, nbot[2] = {}; + size_t first_ctx, last_ctx = 0, from = 0, to; + + /* Set the previous line length, advancing `line` past '\n' */ + if (atat.len) + lines[nlines - 1].len = ++line - lines[nlines - 1].s; + else + atat.len = ++line - atat.s; + + /* Accumulate non-@@ lines into the current hunk. + * At EOF, line == end so we fall through to process + * the last hunk. */ + if (line < end && strncmp (line, "@@ ", 3)) { + lines = xrealloc (lines, (nlines + 1) * sizeof (*lines)); + lines[nlines++].s = line; + continue; + } + + first_ctx = to = nlines; + + /* Process accumulated hunk on new @@ or final line (no-op + * when nlines == 0 since all loop ranges are empty). Find + * first and last context lines. */ + for (size_t i = 0; i < nlines; i++) { + if (lines[i].s[0] == ' ') { + if (first_ctx == nlines) + first_ctx = i; + last_ctx = i; + } + } + + /* Count top edge +/- lines (before first context) */ + for (size_t i = 0; i < first_ctx; i++) + ntop[lines[i].s[0] == '+']++; + + /* Count bottom edge +/- lines (after last context) */ + for (size_t i = last_ctx + 1; i < nlines; i++) + nbot[lines[i].s[0] == '+']++; + + /* Trim one-sided edges; reset counts for two-sided edges */ + if (ntop[0] && ntop[1]) + ntop[0] = ntop[1] = 0; + else if (ntop[0] || ntop[1]) + from = first_ctx; + if (nbot[0] && nbot[1]) + nbot[0] = nbot[1] = 0; + else if (nbot[0] || nbot[1]) + to = last_ctx + 1; + + /* Write hunk if remaining lines have changes */ + for (size_t i = from; i < to; i++) { + if (lines[i].s[0] == ' ') + continue; + + if (from || to < nlines) { + /* Edges were trimmed; regenerate the @@ + * header with adjusted line counts. + * sscanf is fine instead of read_atatline + * because the input comes directly from + * diff and is always uniformly formatted */ + int ostart, ocount, nstart, ncount; + sscanf (atat.s, "@@ -%d,%d +%d,%d @@", + &ostart, &ocount, &nstart, &ncount); + fprintf (out, "@@ -%d,%d +%d,%d @@\n", + ostart + ntop[0], + ocount - ntop[0] - nbot[0], + nstart + ntop[1], + ncount - ntop[1] - nbot[1]); + } else { + fwrite (atat.s, atat.len, 1, out); + } + for (size_t j = from; j < to; j++) + fwrite (lines[j].s, lines[j].len, 1, out); + break; + } + + nlines = 0; + atat = (typeof(atat)){ line }; + } + + free (lines); + free (fbuf); + + if (!ftell (out)) { + fclose (out); + return NULL; + } + + rewind (out); + return out; +} + /* Run diff and filter out bogus hunks containing unlines. Returns NULL if * the resulting diff is empty. */ static FILE * @@ -2780,6 +2901,8 @@ output_delta (FILE *p1, FILE *p2, FILE *out) ctx_diff = run_and_clean_diff (options, ctx_patch1_orig_file, tmpp2, unline); + if (ctx_diff) + ctx_diff = filter_edge_hunks (ctx_diff); ctx_empty = !ctx_diff; unlink (ctx_patch1_orig_file); From d90325ceaa8835b85db56ded37cf3407377078db Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Tue, 10 Feb 2026 19:11:39 -0800 Subject: [PATCH 18/18] tests: Update fuzzy mode expected output Update expected output for fuzzy1-9 tests to reflect the new section-based output format and improved hunk matching. Co-authored-by: Claude Opus 4.6 (1M context) --- tests/fuzzy1/run-test | 46 ++++++--- tests/fuzzy3/run-test | 13 ++- tests/fuzzy4/run-test | 47 +++++---- tests/fuzzy5/run-test | 71 +++++++------- tests/fuzzy6/run-test | 16 ++- tests/fuzzy7/run-test | 112 +++++++++------------ tests/fuzzy8/run-test | 220 +++++++++++++++++++++--------------------- tests/fuzzy9/run-test | 134 ++++++++++++------------- 8 files changed, 321 insertions(+), 338 deletions(-) diff --git a/tests/fuzzy1/run-test b/tests/fuzzy1/run-test index 9ec01b10..7059869d 100755 --- a/tests/fuzzy1/run-test +++ b/tests/fuzzy1/run-test @@ -33,15 +33,42 @@ cat << 'EOF' > patch2 EOF cat << 'EOF' > expected -diff -u file file +================================================================================ +* DELTA DIFFERENCES - code changes that differ between the patches * +================================================================================ + --- file +++ file -@@ -1,4 +1,4 @@ INTERDIFF: rejected hunk from patch2, cannot diff context --line 1 -+LINE 1 +@@ -1,4 +1,4 @@ +-LINE 1 ++line 1 line 2 line 3 line 4 + +################################################################################ +! REJECTED PATCH2 HUNKS - could not be compared; manual review needed ! +################################################################################ + +--- file ++++ file +@@ -5,9 +5,6 @@ + line 5 + if + 1 +-fi +-if +-2 + fi + A + B + +================================================================================ +* CONTEXT DIFFERENCES - surrounding code differences between the patches * +================================================================================ + +--- file ++++ file @@ -1,4 +1,9 @@ -line 1 -line 2 @@ -56,16 +83,7 @@ diff -u file file +fi +A +B -@@ -5,9 +5,6 @@ INTERDIFF: rejected hunk from patch1, cannot diff context - line 5 - if - 1 --fi --if --2 - fi - A - B + EOF ${INTERDIFF} --fuzzy patch1 patch2 2>errors >output diff --git a/tests/fuzzy3/run-test b/tests/fuzzy3/run-test index 585ad259..b874984e 100755 --- a/tests/fuzzy3/run-test +++ b/tests/fuzzy3/run-test @@ -37,18 +37,17 @@ cat << 'EOF' > patch2 EOF cat << 'EOF' > expected -diff -u file file +================================================================================ +* CONTEXT DIFFERENCES - surrounding code differences between the patches * +================================================================================ + --- file +++ file -@@ -47,6 +47,6 @@ --line 5 -+line 6 - if - 1 +@@ -7,3 +8,2 @@ fi -A B -+C + EOF ${INTERDIFF} --fuzzy patch1 patch2 2>errors >output diff --git a/tests/fuzzy4/run-test b/tests/fuzzy4/run-test index e1578fcb..aafdfa53 100755 --- a/tests/fuzzy4/run-test +++ b/tests/fuzzy4/run-test @@ -39,31 +39,28 @@ cat << 'EOF' > patch2 EOF cat << 'EOF' > expected -diff -u file file +================================================================================ +* DELTA DIFFERENCES - code changes that differ between the patches * +================================================================================ + --- file +++ file -@@ -2,8 +2,9 @@ -+line 6 - if +@@ -7,4 +7,7 @@ 1 fi - if - 2 - fi --A - B -+C -@@ -5,9 +5,6 @@ INTERDIFF: rejected hunk from patch2, cannot diff context - line 5 - if - 1 --fi --if --2 - fi ++if ++2 ++fi A B -@@ -50,9 +50,6 @@ INTERDIFF: rejected hunk from patch1, cannot diff context + +################################################################################ +! REJECTED PATCH2 HUNKS - could not be compared; manual review needed ! +################################################################################ + +--- file ++++ file +@@ -50,9 +50,6 @@ line 6 if 1 @@ -73,6 +70,18 @@ diff -u file file fi B C + +================================================================================ +* CONTEXT DIFFERENCES - surrounding code differences between the patches * +================================================================================ + +--- file ++++ file +@@ -7,3 +8,2 @@ + fi +-A + B + EOF ${INTERDIFF} --fuzzy=1 patch1 patch2 2>errors >output diff --git a/tests/fuzzy5/run-test b/tests/fuzzy5/run-test index 11dad5bd..09c9bdcc 100755 --- a/tests/fuzzy5/run-test +++ b/tests/fuzzy5/run-test @@ -61,49 +61,32 @@ cat << 'EOF' > patch2 EOF cat << 'EOF' > expected -diff -u file file +================================================================================ +* DELTA DIFFERENCES - code changes that differ between the patches * +================================================================================ + --- file +++ file -@@ -2 +2,7 @@ --line 5 -+D -+Z -+1 -+1 -+F -+E -+if -@@ -3,8 +9,9 @@ -+line 6 - if - 1 - fi - if - 2 - fi --A - B -+C -@@ -5,9 +5,6 @@ INTERDIFF: rejected hunk from patch2, cannot diff context - line 5 +@@ -6,8 +6,8 @@ if 1 --fi --if --2 fi ++if ++2 ++fi A B -@@ -7,7 +7,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context - C - 9 - 8 --1 -+7 - D - E - if -@@ -50,9 +50,6 @@ INTERDIFF: rejected hunk from patch1, cannot diff context +-D +-E +-if + +################################################################################ +! REJECTED PATCH2 HUNKS - could not be compared; manual review needed ! +################################################################################ + +--- file ++++ file +@@ -50,9 +50,6 @@ line 6 if 1 @@ -113,7 +96,7 @@ diff -u file file fi B C -@@ -7,7 +7,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context +@@ -7,7 +7,7 @@ D Z 1 @@ -122,6 +105,20 @@ diff -u file file F E if + +================================================================================ +* CONTEXT DIFFERENCES - surrounding code differences between the patches * +================================================================================ + +--- file ++++ file +@@ -2 +2,0 @@ +-line 5 +@@ -8,3 +15,2 @@ + fi +-A + B + EOF ${INTERDIFF} --fuzzy=1 patch1 patch2 2>errors >output diff --git a/tests/fuzzy6/run-test b/tests/fuzzy6/run-test index 8dcdc853..dfddce57 100755 --- a/tests/fuzzy6/run-test +++ b/tests/fuzzy6/run-test @@ -31,25 +31,23 @@ cat << 'EOF' > patch2 EOF cat << 'EOF' > expected -diff -u file file +################################################################################ +! REJECTED PATCH2 HUNKS - could not be compared; manual review needed ! +################################################################################ + --- file +++ file -@@ -2,2 +2,5 @@ -+hi -+line 4 - line 5 - if -+2 -@@ -50,5 +50,4 @@ INTERDIFF: rejected hunk from patch1, cannot diff context +@@ -50,5 +50,4 @@ hi line 4 -line 5 if 2 -@@ -53,2 +52,3 @@ INTERDIFF: rejected hunk from patch1, cannot diff context +@@ -53,2 +52,3 @@ if +1 2 + EOF ${INTERDIFF} --fuzzy=1 patch1 patch2 2>errors >output diff --git a/tests/fuzzy7/run-test b/tests/fuzzy7/run-test index c49b50ae..82768ff8 100755 --- a/tests/fuzzy7/run-test +++ b/tests/fuzzy7/run-test @@ -822,42 +822,29 @@ index 92ab85061df00..57df78cfbf82c 100644 EOF cat << 'EOF' > expected -diff -u b/drivers/net/ethernet/microsoft/mana/gdma_main.c b/drivers/net/ethernet/microsoft/mana/gdma_main.c +================================================================================ +* DELTA DIFFERENCES - code changes that differ between the patches * +================================================================================ + --- b/drivers/net/ethernet/microsoft/mana/gdma_main.c +++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c -@@ -7,7 +7,7 @@ --#include -+#include - - #include - #include - --#include - struct dentry *mana_debugfs_root; -+ -@@ -69,7 +69,7 @@ +@@ -65,7 +65,7 @@ mana_gd_init_vf_regs(pdev); } --/* Suppress logging when we set timeout to zero */ -+/* Suppress logging when we set timeout to zeo */ +-/* Suppress logging when we set timeout to zeo */ ++/* Suppress logging when we set timeout to zero */ bool mana_need_log(struct gdma_context *gc, int err) { struct hw_channel_context *hwc; -diff -u b/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c + +################################################################################ +! REJECTED PATCH2 HUNKS - could not be compared; manual review needed ! +################################################################################ + --- b/drivers/net/ethernet/microsoft/mana/mana_en.c +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c -@@ -777,7 +786,8 @@ INTERDIFF: rejected hunk from patch2, cannot diff context - err = mana_gd_send_request(gc, in_len, in_buf, out_len, - out_buf); - if (err || resp->status) { -- if (req->req.msg_type != MANA_QUERY_PHY_STAT) -+ if (req->req.msg_type != MANA_QUERY_PHY_STAT && -+ mana_need_log(gc, err)) - dev_err(dev, "Failed to send mana message: %d, 0x%x\n", - err, resp->status); - return err ? err : -EPROTO; -@@ -863,7 +872,8 @@ INTERDIFF: rejected hunk from patch1, cannot diff context +@@ -863,7 +872,8 @@ if (err == -EOPNOTSUPP) return err; @@ -867,26 +854,9 @@ diff -u b/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/m dev_err(dev, "Failed to send mana message: %d, 0x%x\n", err, resp->status); return err ? err : -EPROTO; -diff -u b/include/net/mana/gdma.h b/include/net/mana/gdma.h --- b/include/net/mana/gdma.h +++ b/include/net/mana/gdma.h -@@ -57,6 +57,6 @@ -- GDMA_EQE_HWC_INIT_DONE = 131, - GDMA_EQE_HWC_FPGA_RECONFIG = 132, - GDMA_EQE_HWC_SOC_RECONFIG_DATA = 133, -+ GDMA_EQE_HWC_SOC_SERVICE = 134, - GDMA_EQE_RNIC_QP_FATAL = 176, - }; - -@@ -60,6 +60,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context - GDMA_EQE_HWC_INIT_DONE = 131, - GDMA_EQE_HWC_FPGA_RECONFIG = 132, - GDMA_EQE_HWC_SOC_RECONFIG_DATA = 133, -+ GDMA_EQE_HWC_RESET_REQUEST = 135, - GDMA_EQE_RNIC_QP_FATAL = 176, - }; - -@@ -62,6 +62,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context +@@ -62,6 +62,7 @@ GDMA_EQE_HWC_FPGA_RECONFIG = 132, GDMA_EQE_HWC_SOC_RECONFIG_DATA = 133, GDMA_EQE_HWC_SOC_SERVICE = 134, @@ -894,49 +864,53 @@ diff -u b/include/net/mana/gdma.h b/include/net/mana/gdma.h GDMA_EQE_RNIC_QP_FATAL = 176, }; -@@ -571,6 +575,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context - GDMA_DRV_CAP_FLAG_1_HWC_TIMEOUT_RECONFIG | \ +@@ -597,6 +601,7 @@ GDMA_DRV_CAP_FLAG_1_VARIABLE_INDIRECTION_TABLE_SUPPORT | \ GDMA_DRV_CAP_FLAG_1_DEV_LIST_HOLES_SUP | \ + GDMA_DRV_CAP_FLAG_1_DYNAMIC_IRQ_ALLOC_SUPPORT | \ + GDMA_DRV_CAP_FLAG_1_SELF_RESET_ON_EQE | \ GDMA_DRV_CAP_FLAG_1_HANDLE_RECONFIG_EQE) #define GDMA_DRV_CAP_FLAGS2 0 -@@ -579,5 +581,5 @@ --/* Driver can handle holes (zeros) in the device list */ --#define GDMA_DRV_CAP_FLAG_1_DEV_LIST_HOLES_SUP BIT(11) -+/* Driver supports dynamic MSI-X vector allocation */ -+#define GDMA_DRV_CAP_FLAG_1_DYNAMIC_IRQ_ALLOC_SUPPORT BIT(13) - - /* Driver can self reset on EQE notification */ - #define GDMA_DRV_CAP_FLAG_1_SELF_RESET_ON_EQE BIT(14) -@@ -590,9 +590,9 @@ - /* Driver can self reset on FPGA Reconfig EQE notification */ - #define GDMA_DRV_CAP_FLAG_1_HANDLE_RECONFIG_EQE BIT(17) + +================================================================================ +* CONTEXT DIFFERENCES - surrounding code differences between the patches * +================================================================================ + +--- b/drivers/net/ethernet/microsoft/mana/gdma_main.c ++++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c +@@ -5,6 +5,5 @@ +-#include ++#include -- GDMA_DRV_CAP_FLAG_1_HWC_TIMEOUT_RECONFIG | \ - GDMA_DRV_CAP_FLAG_1_VARIABLE_INDIRECTION_TABLE_SUPPORT | \ - GDMA_DRV_CAP_FLAG_1_DEV_LIST_HOLES_SUP | \ -+ GDMA_DRV_CAP_FLAG_1_DYNAMIC_IRQ_ALLOC_SUPPORT | \ - GDMA_DRV_CAP_FLAG_1_HANDLE_RECONFIG_EQE) + #include - #define GDMA_DRV_CAP_FLAGS2 0 -@@ -597,6 +601,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context +-#include + struct dentry *mana_debugfs_root; +--- b/include/net/mana/gdma.h ++++ b/include/net/mana/gdma.h +@@ -58,5 +57,6 @@ + GDMA_EQE_HWC_FPGA_RECONFIG = 132, + GDMA_EQE_HWC_SOC_RECONFIG_DATA = 133, ++ GDMA_EQE_HWC_SOC_SERVICE = 134, + GDMA_EQE_RNIC_QP_FATAL = 176, + }; + +@@ -566,5 +590,6 @@ GDMA_DRV_CAP_FLAG_1_VARIABLE_INDIRECTION_TABLE_SUPPORT | \ GDMA_DRV_CAP_FLAG_1_DEV_LIST_HOLES_SUP | \ - GDMA_DRV_CAP_FLAG_1_DYNAMIC_IRQ_ALLOC_SUPPORT | \ -+ GDMA_DRV_CAP_FLAG_1_SELF_RESET_ON_EQE | \ ++ GDMA_DRV_CAP_FLAG_1_DYNAMIC_IRQ_ALLOC_SUPPORT | \ GDMA_DRV_CAP_FLAG_1_HANDLE_RECONFIG_EQE) #define GDMA_DRV_CAP_FLAGS2 0 -@@ -921,5 +921,5 @@ +@@ -889,4 +915,4 @@ -void mana_register_debugfs(void); -void mana_unregister_debugfs(void); + +int mana_rdma_service_event(struct gdma_context *gc, enum gdma_service_type event); - int mana_gd_suspend(struct pci_dev *pdev, pm_message_t state); - int mana_gd_resume(struct pci_dev *pdev); + #endif /* _GDMA_H */ + EOF ${INTERDIFF} --fuzzy patch1 patch2 2>errors >output diff --git a/tests/fuzzy8/run-test b/tests/fuzzy8/run-test index d6e18e6a..9f7e6850 100755 --- a/tests/fuzzy8/run-test +++ b/tests/fuzzy8/run-test @@ -1521,19 +1521,68 @@ index a48dd5b5d45b1..509011b1ef597 100644 EOF cat << 'EOF' > expected -diff -u b/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c +================================================================================ +* DELTA DIFFERENCES - code changes that differ between the patches * +================================================================================ + --- b/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c -@@ -2395,6 +2395,6 @@ -- struct nft_rule **rules; -+ struct nft_chain *chain; - int err; +@@ -4794,14 +4794,12 @@ + if (alloc_size < size || alloc_size > INT_MAX) + return -ENOMEM; ++ set = kvzalloc(alloc_size, GFP_KERNEL); ++ if (!set) ++ return -ENOMEM; ++ - if (nla[NFTA_CHAIN_HOOK]) { - struct nft_stats __percpu *stats = NULL; -- struct nft_chain_hook hook; -+ struct nft_chain_hook hook = {}; -@@ -5026,8 +5037,10 @@ INTERDIFF: rejected hunk from patch1, cannot diff context + if (!nft_use_inc(&table->use)) + return -EMFILE; + +- set = kvzalloc(alloc_size, GFP_KERNEL); +- if (!set) { +- err = -ENOMEM; +- goto err_alloc; +- } +- + name = nla_strdup(nla[NFTA_SET_NAME], GFP_KERNEL); + if (!name) { +@@ -8045,14 +8043,12 @@ + + nft_ctx_init(&ctx, net, skb, info->nlh, family, table, NULL, nla); + +- if (!nft_use_inc(&table->use)) +- return -EMFILE; +- + flowtable = kzalloc(sizeof(*flowtable), GFP_KERNEL); +- if (!flowtable) { +- err = -ENOMEM; +- goto flowtable_alloc; +- } ++ if (!flowtable) ++ return -ENOMEM; + + flowtable->table = table; + flowtable->handle = nf_tables_alloc_handle(table); ++ if (!nft_use_inc(&table->use)) ++ return -EMFILE; ++ +@@ -9663,7 +9659,7 @@ + break; + case NFT_MSG_DELCHAIN: + case NFT_MSG_DESTROYCHAIN: +- nft_use_inc_restore(&trans->ctx.table->use); ++ trans->ctx.table->use++; + nft_clear(trans->ctx.net, trans->ctx.chain); + nft_trans_destroy(trans); + break; + +################################################################################ +! REJECTED PATCH2 HUNKS - could not be compared; manual review needed ! +################################################################################ + +--- b/net/netfilter/nf_tables_api.c ++++ b/net/netfilter/nf_tables_api.c +@@ -5026,8 +5037,10 @@ if (alloc_size < size || alloc_size > INT_MAX) return -ENOMEM; set = kvzalloc(alloc_size, GFP_KERNEL_ACCOUNT); @@ -1546,7 +1595,7 @@ diff -u b/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c name = nla_strdup(nla[NFTA_SET_NAME], GFP_KERNEL_ACCOUNT); if (!name) { -@@ -8293,8 +8327,10 @@ INTERDIFF: rejected hunk from patch1, cannot diff context +@@ -8293,8 +8327,10 @@ nft_ctx_init(&ctx, net, skb, info->nlh, family, table, NULL, nla); flowtable = kzalloc(sizeof(*flowtable), GFP_KERNEL_ACCOUNT); @@ -1559,91 +1608,7 @@ diff -u b/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c flowtable->table = table; flowtable->handle = nf_tables_alloc_handle(table); -@@ -9672,7 +9710,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context - nft_trans_destroy(trans); - break; - } -- trans->ctx.chain->use--; -+ nft_use_dec_restore(&trans->ctx.chain->use); - list_del_rcu(&nft_trans_rule(trans)->list); - nft_rule_expr_deactivate(&trans->ctx, - nft_trans_rule(trans), -@@ -9682,7 +9720,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context - break; - case NFT_MSG_DELRULE: - case NFT_MSG_DESTROYRULE: -- trans->ctx.chain->use++; -+ nft_use_inc_restore(&trans->ctx.chain->use); - nft_clear(trans->ctx.net, nft_trans_rule(trans)); - nft_rule_expr_activate(&trans->ctx, nft_trans_rule(trans)); - if (trans->ctx.chain->flags & NFT_CHAIN_HW_OFFLOAD) -@@ -9695,7 +9733,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context - nft_trans_destroy(trans); - break; - } -- trans->ctx.table->use--; -+ nft_use_dec_restore(&trans->ctx.table->use); - if (nft_trans_set_bound(trans)) { - nft_trans_destroy(trans); - break; -@@ -9741,16 +9741,3 @@ -- nft_obj_destroy(&trans->ctx, nft_trans_obj_newobj(trans)); -- nft_trans_destroy(trans); -- } else { -- trans->ctx.table->use--; -- nft_obj_del(nft_trans_obj(trans)); -- } -- break; -- case NFT_MSG_DELOBJ: -- case NFT_MSG_DESTROYOBJ: -- trans->ctx.table->use++; -- nft_clear(trans->ctx.net, nft_trans_obj(trans)); -- nft_trans_destroy(trans); -- break; - */ - if (nft_set_is_anonymous(nft_trans_set(trans)) && - !list_empty(&nft_trans_set(trans)->bindings)) -@@ -9748,9 +9786,9 @@ INTERDIFF: rejected hunk from patch2, cannot diff context - nft_obj_destroy(&trans->ctx, nft_trans_obj_newobj(trans)); - nft_trans_destroy(trans); - } else { -- trans->ctx.table->use--; -+ nft_use_dec_restore(&trans->ctx.table->use); - nft_obj_del(nft_trans_obj(trans)); - } - break; - case NFT_MSG_DELOBJ: - case NFT_MSG_DESTROYOBJ: -@@ -9754,7 +9792,7 @@ INTERDIFF: rejected hunk from patch2, cannot diff context - break; - case NFT_MSG_DELOBJ: - case NFT_MSG_DESTROYOBJ: -- trans->ctx.table->use++; -+ nft_use_inc_restore(&trans->ctx.table->use); - nft_clear(trans->ctx.net, nft_trans_obj(trans)); - nft_trans_destroy(trans); - break; -@@ -9991,13 +9981,13 @@ - nft_chain_del(trans->ctx.chain); - nf_tables_unregister_hook(trans->ctx.net, - trans->ctx.table, -- break; -- case NFT_MSG_DELCHAIN: -- case NFT_MSG_DESTROYCHAIN: -- trans->ctx.table->use++; -- nft_clear(trans->ctx.net, trans->ctx.chain); -+ list_splice(&nft_trans_chain_hooks(trans), -+ &nft_trans_basechain(trans)->hook_list); -+ } else { -+ trans->ctx.table->use++; -+ nft_clear(trans->ctx.net, trans->ctx.chain); -+ } - nft_trans_destroy(trans); -- break; - nft_trans_destroy(trans); - break; - } -@@ -10000,7 +10038,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context +@@ -10000,7 +10038,7 @@ nft_trans_destroy(trans); break; } @@ -1652,7 +1617,7 @@ diff -u b/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c list_del_rcu(&nft_trans_rule(trans)->list); nft_rule_expr_deactivate(&trans->ctx, nft_trans_rule(trans), -@@ -10010,7 +10048,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context +@@ -10010,7 +10048,7 @@ break; case NFT_MSG_DELRULE: case NFT_MSG_DESTROYRULE: @@ -1661,7 +1626,7 @@ diff -u b/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c nft_clear(trans->ctx.net, nft_trans_rule(trans)); nft_rule_expr_activate(&trans->ctx, nft_trans_rule(trans)); if (trans->ctx.chain->flags & NFT_CHAIN_HW_OFFLOAD) -@@ -10023,7 +10061,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context +@@ -10023,7 +10061,7 @@ nft_trans_destroy(trans); break; } @@ -1670,7 +1635,7 @@ diff -u b/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c if (nft_trans_set_bound(trans)) { nft_trans_destroy(trans); break; -@@ -10032,7 +10070,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context +@@ -10032,7 +10070,7 @@ break; case NFT_MSG_DELSET: case NFT_MSG_DESTROYSET: @@ -1679,7 +1644,7 @@ diff -u b/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c nft_clear(trans->ctx.net, nft_trans_set(trans)); if (nft_trans_set(trans)->flags & (NFT_SET_MAP | NFT_SET_OBJECT)) nft_map_activate(&trans->ctx, nft_trans_set(trans)); -@@ -10076,9 +10114,9 @@ INTERDIFF: rejected hunk from patch1, cannot diff context +@@ -10076,9 +10114,9 @@ nft_obj_destroy(&trans->ctx, nft_trans_obj_newobj(trans)); nft_trans_destroy(trans); } else { @@ -1690,7 +1655,7 @@ diff -u b/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c break; case NFT_MSG_DELOBJ: case NFT_MSG_DESTROYOBJ: -@@ -10082,7 +10120,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context +@@ -10082,7 +10120,7 @@ break; case NFT_MSG_DELOBJ: case NFT_MSG_DESTROYOBJ: @@ -1699,7 +1664,7 @@ diff -u b/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c nft_clear(trans->ctx.net, nft_trans_obj(trans)); nft_trans_destroy(trans); break; -@@ -10091,7 +10129,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context +@@ -10091,7 +10129,7 @@ nft_unregister_flowtable_net_hooks(net, &nft_trans_flowtable_hooks(trans)); } else { @@ -1708,7 +1673,7 @@ diff -u b/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c list_del_rcu(&nft_trans_flowtable(trans)->list); nft_unregister_flowtable_net_hooks(net, &nft_trans_flowtable(trans)->hook_list); -@@ -10103,7 +10141,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context +@@ -10103,7 +10141,7 @@ list_splice(&nft_trans_flowtable_hooks(trans), &nft_trans_flowtable(trans)->hook_list); } else { @@ -1717,21 +1682,54 @@ diff -u b/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c nft_clear(trans->ctx.net, nft_trans_flowtable(trans)); } nft_trans_destroy(trans); -@@ -10567,4 +10557,4 @@ + +================================================================================ +* CONTEXT DIFFERENCES - surrounding code differences between the patches * +================================================================================ + +--- b/net/netfilter/nf_tables_api.c ++++ b/net/netfilter/nf_tables_api.c +@@ -4785,7 +5005,7 @@ + return -ENOMEM; +- set = kvzalloc(alloc_size, GFP_KERNEL); ++ set = kvzalloc(alloc_size, GFP_KERNEL_ACCOUNT); + if (!set) + return -ENOMEM; - data->verdict.chain = chain; - break; -- default: -+ } -diff -u b/net/netfilter/nft_objref.c b/net/netfilter/nft_objref.c +- name = nla_strdup(nla[NFTA_SET_NAME], GFP_KERNEL); ++ name = nla_strdup(nla[NFTA_SET_NAME], GFP_KERNEL_ACCOUNT); + if (!name) { +@@ -8011,7 +8251,7 @@ + + nft_ctx_init(&ctx, net, skb, info->nlh, family, table, NULL, nla); + +- flowtable = kzalloc(sizeof(*flowtable), GFP_KERNEL); ++ flowtable = kzalloc(sizeof(*flowtable), GFP_KERNEL_ACCOUNT); + if (!flowtable) + return -ENOMEM; + +@@ -9620,6 +9947,7 @@ +- break; +- case NFT_MSG_DELCHAIN: +- case NFT_MSG_DESTROYCHAIN: +- trans->ctx.table->use++; +- nft_clear(trans->ctx.net, trans->ctx.chain); ++ list_splice(&nft_trans_chain_hooks(trans), ++ &nft_trans_basechain(trans)->hook_list); ++ } else { ++ trans->ctx.table->use++; ++ nft_clear(trans->ctx.net, trans->ctx.chain); ++ } + nft_trans_destroy(trans); --- b/net/netfilter/nft_objref.c +++ b/net/netfilter/nft_objref.c -@@ -84,4 +84,4 @@ - nft_use_inc_restore(&obj->use); +@@ -83,4 +83,4 @@ + obj->use++; } -static struct nft_expr_type nft_objref_type; +static const struct nft_expr_ops nft_objref_ops = { + EOF ${INTERDIFF} --fuzzy=3 patch1 patch2 2>errors >output diff --git a/tests/fuzzy9/run-test b/tests/fuzzy9/run-test index b2689f73..c762cd73 100755 --- a/tests/fuzzy9/run-test +++ b/tests/fuzzy9/run-test @@ -172,90 +172,43 @@ index 379f742fd7415..2bde8a3546313 100644 EOF cat << 'EOF' > expected -diff -u b/net/wireless/util.c b/net/wireless/util.c +================================================================================ +* DELTA DIFFERENCES - code changes that differ between the patches * +================================================================================ + --- b/net/wireless/util.c +++ b/net/wireless/util.c -@@ -754,8 +754,7 @@ -+{ -+ unsigned int hlen = ALIGN(extra_headroom, 4); - struct sk_buff *frame = NULL; -- u16 ethertype; -- u8 *payload; - int offset = 0, remaining; -- struct ethhdr eth; -- bool reuse_frag = skb->head_frag && !skb_has_frag_list(skb); -- bool reuse_skb = false; -- bool last = false; -+ struct { -+ struct ethhdr eth; -+ uint8_t flags; -@@ -757,10 +757,10 @@ INTERDIFF: rejected hunk from patch2, cannot diff context +@@ -757,21 +757,17 @@ struct sk_buff *frame = NULL; u16 ethertype; u8 *payload; -- int offset = 0, remaining; -+ int offset = 0; +- int offset = 0; ++ int offset = 0, remaining; struct ethhdr eth; bool reuse_frag = skb->head_frag && !skb_has_frag_list(skb); bool reuse_skb = false; bool last = false; while (!last) { -@@ -763,8 +763,9 @@ INTERDIFF: rejected hunk from patch2, cannot diff context - bool reuse_skb = false; - bool last = false; - - while (!last) { -+ int remaining = skb->len - offset; - unsigned int subframe_len; - int len; - u8 padding; - -@@ -762,15 +761,17 @@ -+ copy_len = sizeof(hdr); - - while (!last) { - unsigned int subframe_len; -- int len; -+ int len, mesh_len = 0; - u8 padding; - -- skb_copy_bits(skb, offset, ð, sizeof(eth)); -- len = ntohs(eth.h_proto); -- subframe_len = sizeof(struct ethhdr) + len; -+ skb_copy_bits(skb, offset, &hdr, copy_len); -+ if (iftype == NL80211_IFTYPE_MESH_POINT) -+ mesh_len = __ieee80211_get_mesh_hdrlen(hdr.flags); - padding = (4 - subframe_len) & 0x3; - - /* the last MSDU has no padding */ -+ remaining = skb->len - offset; - if (subframe_len > remaining) - goto purge; - /* mitigate A-MSDU aggregation injection attacks */ -@@ -767,10 +768,13 @@ INTERDIFF: rejected hunk from patch2, cannot diff context +- int remaining = skb->len - offset; unsigned int subframe_len; int len; u8 padding; -+ if (sizeof(eth) > remaining) -+ goto purge; -+ +- if (sizeof(eth) > remaining) +- goto purge; +- skb_copy_bits(skb, offset, ð, sizeof(eth)); len = ntohs(eth.h_proto); subframe_len = sizeof(struct ethhdr) + len; - padding = (4 - subframe_len) & 0x3; - - /* the last MSDU has no padding */ -@@ -774,7 +778,6 @@ INTERDIFF: rejected hunk from patch2, cannot diff context - padding = (4 - subframe_len) & 0x3; - - /* the last MSDU has no padding */ -- remaining = skb->len - offset; - if (subframe_len > remaining) - goto purge; - /* mitigate A-MSDU aggregation injection attacks */ -@@ -791,7 +791,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context + +################################################################################ +! REJECTED PATCH2 HUNKS - could not be compared; manual review needed ! +################################################################################ + +--- b/net/wireless/util.c ++++ b/net/wireless/util.c +@@ -791,7 +791,7 @@ bool ieee80211_is_valid_amsdu(struct sk_buff *skb, u8 mesh_hdr) { @@ -264,7 +217,7 @@ diff -u b/net/wireless/util.c b/net/wireless/util.c for (offset = 0; offset < skb->len; offset += subframe_len + padding) { struct { -@@ -794,9 +794,10 @@ INTERDIFF: rejected hunk from patch1, cannot diff context +@@ -794,9 +794,10 @@ int offset = 0, remaining, subframe_len, padding; for (offset = 0; offset < skb->len; offset += subframe_len + padding) { @@ -275,7 +228,7 @@ diff -u b/net/wireless/util.c b/net/wireless/util.c } hdr; u16 len; -@@ -800,6 +801,9 @@ INTERDIFF: rejected hunk from patch1, cannot diff context +@@ -800,6 +801,9 @@ } hdr; u16 len; @@ -285,7 +238,7 @@ diff -u b/net/wireless/util.c b/net/wireless/util.c if (skb_copy_bits(skb, offset + 2 * ETH_ALEN, &hdr, sizeof(hdr)) < 0) return false; -@@ -807,7 +811,6 @@ INTERDIFF: rejected hunk from patch1, cannot diff context +@@ -807,7 +811,6 @@ mesh_hdr); subframe_len = sizeof(struct ethhdr) + len; padding = (4 - subframe_len) & 0x3; @@ -293,7 +246,7 @@ diff -u b/net/wireless/util.c b/net/wireless/util.c if (subframe_len > remaining) return false; -@@ -825,7 +828,7 @@ INTERDIFF: rejected hunk from patch1, cannot diff context +@@ -825,7 +828,7 @@ { unsigned int hlen = ALIGN(extra_headroom, 4); struct sk_buff *frame = NULL; @@ -302,7 +255,7 @@ diff -u b/net/wireless/util.c b/net/wireless/util.c struct { struct ethhdr eth; uint8_t flags; -@@ -839,7 +842,8 @@ INTERDIFF: rejected hunk from patch1, cannot diff context +@@ -839,7 +842,8 @@ copy_len = sizeof(hdr); while (!last) { @@ -311,7 +264,7 @@ diff -u b/net/wireless/util.c b/net/wireless/util.c int len, mesh_len = 0; u8 padding; -@@ -843,6 +847,9 @@ INTERDIFF: rejected hunk from patch1, cannot diff context +@@ -843,6 +847,9 @@ int len, mesh_len = 0; u8 padding; @@ -321,6 +274,43 @@ diff -u b/net/wireless/util.c b/net/wireless/util.c skb_copy_bits(skb, offset, &hdr, copy_len); if (iftype == NL80211_IFTYPE_MESH_POINT) mesh_len = __ieee80211_get_mesh_hdrlen(hdr.flags); + +================================================================================ +* CONTEXT DIFFERENCES - surrounding code differences between the patches * +================================================================================ + +--- b/net/wireless/util.c ++++ b/net/wireless/util.c +@@ -754,8 +756,5 @@ + struct sk_buff *frame = NULL; +- u16 ethertype; +- u8 *payload; + int offset = 0, remaining; +- struct ethhdr eth; +- bool reuse_frag = skb->head_frag && !skb_has_frag_list(skb); +- bool reuse_skb = false; +- bool last = false; ++ struct { ++ struct ethhdr eth; ++ uint8_t flags; +@@ -762,12 +762,12 @@ + + while (!last) { + unsigned int subframe_len; +- int len; ++ int len, mesh_len = 0; + u8 padding; + +- skb_copy_bits(skb, offset, ð, sizeof(eth)); +- len = ntohs(eth.h_proto); +- subframe_len = sizeof(struct ethhdr) + len; ++ skb_copy_bits(skb, offset, &hdr, copy_len); ++ if (iftype == NL80211_IFTYPE_MESH_POINT) ++ mesh_len = __ieee80211_get_mesh_hdrlen(hdr.flags); + padding = (4 - subframe_len) & 0x3; + + /* the last MSDU has no padding */ + EOF ${INTERDIFF} --fuzzy patch1 patch2 2>errors >output