From ce948adbe6c19c7df4b908ec12c02fd187d086fc Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Fri, 17 Oct 2025 14:46:14 -0700 Subject: [PATCH 1/6] 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 2/6] 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 3/6] 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 4/6] 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 a32d159b7ebafc2426f2f8e36c2a9f602a910d4c Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Thu, 20 Nov 2025 00:04:35 -0800 Subject: [PATCH 5/6] 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 | 1156 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 1101 insertions(+), 55 deletions(-) diff --git a/src/interdiff.c b/src/interdiff.c index 2a15eef0..4f8aa9a7 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,41 +2285,62 @@ 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 (;;) { - ssize_t got = getline (&line, &linelen, in); - if (got < 0) - break; - fwrite (line, (size_t) got, 1, tmpdiff); - if (*line != ' ' && !strcmp (line + 1, file.unline)) { - /* Uh-oh. We're trying to output a - * line that made up (we never saw the - * original). As long as this is at - * the end of a hunk we can safely - * drop it (done in trim_context - * later). */ - got = getline (&line, &linelen, in); + + if (!diff_is_empty) { + char *line = NULL; + size_t linelen; + for (;;) { + ssize_t got = getline (&line, &linelen, in); if (got < 0) - continue; - else if (strncmp (line, "@@ ", 3)) { - /* An interdiff isn't possible. - * Evasive action: just revert the - * original and copy the new - * version. */ - fclose (tmpdiff); - free (line); - goto evasive_action; - } + 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 + * line that made up (we never saw the + * original). As long as this is at + * the end of a hunk we can safely + * drop it (done in trim_context + * later). */ + got = getline (&line, &linelen, in); + if (got < 0) + continue; + else if (strncmp (line, "@@ ", 3)) { + /* An interdiff isn't possible. + * Evasive action: just revert + * the original and copy the new + * version. */ + fclose (tmpdiff); + free (line); + goto evasive_action; + } + fwrite (line, (size_t) got, 1, tmpdiff); + } } + free (line); } - free (line); + + /* Output any remaining reject hunks */ + output_rej_hunks (NULL, &rej1_ptr, &rej2_ptr, tmpdiff); /* First character */ if (human_readable) { @@ -1347,13 +2368,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 +2392,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 +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)) + if (apply_patch (p1, tmpp1, 1, NULL)) error (EXIT_FAILURE, 0, "Error reconstructing original file"); @@ -1828,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)) + if (apply_patch (p2, tmpp3, 0, NULL)) error (EXIT_FAILURE, 0, "Error reconstructing final file"); @@ -2230,7 +3260,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 +3327,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 +3415,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 60a60b3909d0e29c0ff286f6a73de4168977b097 Mon Sep 17 00:00:00 2001 From: Sultan Alsawaf Date: Thu, 20 Nov 2025 00:04:41 -0800 Subject: [PATCH 6/6] 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