/* EXERCISE 08 — REPAIRED.
 *
 * An export feature streams a document to a helper process over a pipe. The
 * helper does the conversion and is expected to outlive the transfer.
 *
 * The repair: turn "your process is terminated" into "your write returns an
 * error you can handle". F_SETNOSIGPIPE suppresses SIGPIPE for ONE descriptor,
 * so write(2) fails with EPIPE instead. The alternative, signal(SIGPIPE,
 * SIG_IGN), has the same effect but process-wide: a library must not make that
 * choice on its host application's behalf, which is why the per-descriptor
 * control exists. (Sockets have the equivalent SO_NOSIGPIPE socket option.)
 *
 * The second half of the repair is not a flag: a peer that can die is part of
 * the API. This version reports the loss, stops, and exits deliberately.
 *
 * Build and run:
 *   clang -O2 -g -Wall -Wextra helperlink.c -o /tmp/helperlink_fixed
 *   /tmp/helperlink_fixed ; echo "exit status: $?"
 *
 * EXPECTED: exit status 0, with helperLost=1 in the output. The transfer still
 * fails — the helper really is gone — but it fails as a reportable outcome
 * rather than as the sudden disappearance of the whole process.
 *
 * Every measurement is printed as key=value so a script can assert on it.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/wait.h>

#define CHUNK        8192
#define CHUNKS       4000       /* ~32 MB of document                      */
#define HELPER_DIES_AFTER 40    /* chunks the helper consumes before dying */
#define WATCHDOG_S   60

static void *watchdog(void *unused) {
    (void)unused;
    sleep(WATCHDOG_S);
    const char m[] = "WATCHDOG: no progress within budget - forcing _exit(75).\n";
    ssize_t ignored = write(2, m, sizeof m - 1); (void)ignored;
    _exit(75);
}

int main(void) {
    pthread_t wd;
    pthread_create(&wd, NULL, watchdog, NULL);
    pthread_detach(wd);
    setvbuf(stdout, NULL, _IOLBF, 0);

    int p[2];
    if (pipe(p) != 0) { perror("pipe"); return 1; }

    pid_t helper = fork();
    if (helper == 0) {
        /* The helper: reads a few chunks, then dies the way a crashing
         * converter does — abruptly, without closing anything politely. */
        close(p[1]);
        char *b = malloc(CHUNK);
        for (int i = 0; i < HELPER_DIES_AFTER; i++) {
            ssize_t r = read(p[0], b, CHUNK);
            if (r <= 0) break;
        }
        _exit(9);               /* helper is gone; its pipe end is closed */
    }
    close(p[0]);

    /* Scope the change to this descriptor. The host application's own SIGPIPE
     * disposition is none of our business. */
    if (fcntl(p[1], F_SETNOSIGPIPE, 1) != 0) { perror("F_SETNOSIGPIPE"); return 1; }

    char *doc = malloc(CHUNK);
    memset(doc, 'D', CHUNK);

    long written = 0;
    int helper_lost = 0;
    printf("phase=streaming\n");
    for (int i = 0; i < CHUNKS; i++) {
        ssize_t w = write(p[1], doc, CHUNK);
        if (w < 0) {
            if (errno == EPIPE) {
                /* The peer is gone. This is an expected outcome of talking to
                 * another process, not an exceptional one. */
                helper_lost = 1;
                printf("phase=peer-gone errno=%d message=%s\n", errno, strerror(errno));
            } else {
                printf("phase=write-error errno=%d message=%s\n", errno, strerror(errno));
            }
            break;
        }
        written += w;
    }

    printf("phase=finished bytesWritten=%ld\n", written);
    printf("helperLost=%d\n", helper_lost);
    printf("reportedCleanly=1\n");
    close(p[1]);
    int st; waitpid(helper, &st, 0);
    free(doc);
    return 0;
}
