/* DF-1662: i915_gem_context destroy ioctl double-close race.
 * Lookup OUTSIDE struct_mutex, then mutex_lock, then __destroy_hw_context.
 * Two threads sharing fd race both past lookup (both get a ctx ref),
 * both enter __destroy_hw_context, second hits GEM_BUG_ON(is_closed)
 * (panic on debug) or runs full close + put (refcount underflow on prod).
 *
 * Harness: simulate the race; patched version takes mutex FIRST.
 */
#include <stdio.h>
#include <stdint.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>

static int fixed = 0;
static int mutex_held;
static int is_closed;
static int refcount;
static int close_count;
static int bug_count;

static void mutex_lock(void)   { while (__sync_lock_test_and_set(&mutex_held, 1)) { /* spin */ } }
static void mutex_unlock(void) { __sync_lock_release(&mutex_held); }

static void context_close(void) {
    close_count++;
    if (is_closed) {
        /* GEM_BUG_ON(is_closed) - panics on debug builds */
        bug_count++;
    }
    is_closed = 1;
}

/* simulates i915_gem_context_destroy_ioctl body */
static void *destroy_thread(void *arg) {
    /* both threads do ctx = lookup(); refcount++ */
    __sync_fetch_and_add(&refcount, 1);

    if (fixed) {
        mutex_lock();
        if (is_closed) { mutex_unlock(); return NULL; }
        context_close();
        mutex_unlock();
    } else {
        /* buggy: lookup outside mutex */
        mutex_lock();
        context_close();   /* re-entered by both threads */
        mutex_unlock();
    }
    return NULL;
}

int main(int argc, char **argv) {
    if (argc > 1 && !strcmp(argv[1], "--fixed")) fixed = 1;

    refcount = 1; is_closed = 0; close_count = 0; bug_count = 0;
    pthread_t t1, t2;
    pthread_create(&t1, NULL, destroy_thread, NULL);
    pthread_create(&t2, NULL, destroy_thread, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    if (bug_count > 0)
        printf("RESULT: BUGGY - GEM_BUG_ON(is_closed) hit %d time(s)\n", bug_count);
    else
        printf("RESULT: PATCHED - close ran once, no race\n");
    return 0;
}
