// prioinv.c — inversión de prioridad real, con pthreads y SCHED_FIFO.
//
// Uso: ./prioinv none      mutex normal: M retrasa a H
//      ./prioinv inherit   PTHREAD_PRIO_INHERIT: L hereda la prioridad de H
// Todos los hilos corren en la CPU 0 con SCHED_FIFO: L (10), M (20), H (30);
// main, con prioridad 40, solo los lanza. L toma el lock y lo usa 50 ms de
// CPU; H llega y pide el lock; M llega y usa 200 ms de CPU sin tocar el lock.
// SCHED_FIFO requiere privilegios (CAP_SYS_NICE): sin ellos, el programa lo
// avisa y termina. env/run.sh lanza el contenedor del curso con ese permiso:
//   env/run.sh sh -c 'cd code/week-11 && make prioinv && ./prioinv none && ./prioinv inherit'
// En un Linux propio: sudo ./prioinv none
#define _GNU_SOURCE
#include <errno.h>
#include <sched.h>
#include <string.h>
#include "trace.h"

static pthread_mutex_t m;
static long h_asked;

// Gasta `ms` milisegundos de CPU de este hilo (no de reloj: si lo desalojan,
// el tiempo que no corre no cuenta).
static void burn(int ms) {
    struct timespec a, b;
    clock_gettime(CLOCK_THREAD_CPUTIME_ID, &a);
    do
        clock_gettime(CLOCK_THREAD_CPUTIME_ID, &b);
    while ((b.tv_sec - a.tv_sec) * 1000 + (b.tv_nsec - a.tv_nsec) / 1000000 < ms);
}

static void *low(void *arg) {
    (void)arg;
    pthread_mutex_lock(&m);
    TRACE("L (10): toma el lock");
    burn(50);                           // sección crítica larga
    TRACE("L (10): suelta el lock");
    pthread_mutex_unlock(&m);
    return NULL;
}

static void *medium(void *arg) {
    (void)arg;
    TRACE("M (20): empieza (no usa el lock)");
    burn(200);
    TRACE("M (20): termina");
    return NULL;
}

static void *high(void *arg) {
    (void)arg;
    h_asked = now_ms();
    TRACE("H (30): pide el lock");
    pthread_mutex_lock(&m);
    TRACE("H (30): obtiene el lock, esperó %ld ms", now_ms() - h_asked);
    burn(5);
    pthread_mutex_unlock(&m);
    return NULL;
}

// Crea un hilo SCHED_FIFO con la prioridad dada.
static pthread_t spawn(void *(*fn)(void *), int prio) {
    pthread_attr_t a;
    struct sched_param sp = {.sched_priority = prio};
    pthread_attr_init(&a);
    pthread_attr_setinheritsched(&a, PTHREAD_EXPLICIT_SCHED);
    pthread_attr_setschedpolicy(&a, SCHED_FIFO);
    pthread_attr_setschedparam(&a, &sp);
    pthread_t t;
    int e = pthread_create(&t, &a, fn, NULL);
    if (e) {
        fprintf(stderr, "pthread_create: %s\n", strerror(e));
        exit(1);
    }
    pthread_attr_destroy(&a);
    return t;
}

int main(int argc, char *argv[]) {
    const char *mode = argc > 1 ? argv[1] : "";
    int proto;
    if (!strcmp(mode, "none"))
        proto = PTHREAD_PRIO_NONE;
    else if (!strcmp(mode, "inherit"))
        proto = PTHREAD_PRIO_INHERIT;
    else {
        fprintf(stderr, "uso: %s none|inherit\n", argv[0]);
        return 1;
    }

    // Una sola CPU: así M de verdad le quita la CPU a L.
    cpu_set_t one;
    CPU_ZERO(&one);
    CPU_SET(0, &one);
    if (sched_setaffinity(0, sizeof one, &one) != 0) {
        perror("sched_setaffinity");
        return 1;
    }
    struct sched_param sp = {.sched_priority = 40};
    if (sched_setscheduler(0, SCHED_FIFO, &sp) != 0) {
        printf("%s: SCHED_FIFO no permitido (%s).\n", mode, strerror(errno));
        printf("  Hace falta CAP_SYS_NICE: docker run --cap-add=SYS_NICE ... o sudo.\n");
        printf("  Sin privilegios, ./priosim %s muestra lo mismo simulado.\n", mode);
        return 0;
    }

    pthread_mutexattr_t ma;
    pthread_mutexattr_init(&ma);
    pthread_mutexattr_setprotocol(&ma, proto);   // NONE o INHERIT
    pthread_mutex_init(&m, &ma);

    printf("protocolo: %s\n", mode);
    trace_start();
    pthread_t l = spawn(low, 10);
    msleep(10);                         // L ya tiene el lock
    pthread_t h = spawn(high, 30);
    msleep(1);                          // H ya se bloqueó
    pthread_t mm = spawn(medium, 20);
    pthread_join(h, NULL);
    pthread_join(mm, NULL);
    pthread_join(l, NULL);
    return 0;
}
