#define _POSIX_C_SOURCE 200809L #include "xtc/xtc_audio.h" #include #include #include #include #include #include #include #include #include #include #include static void reap(pid_t child) { int status; assert(kill(child, SIGKILL) == 0); assert(waitpid(child, &status, 0) == child && WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL); } static size_t read_bytes(const char *path, unsigned char *bytes, size_t size) { FILE *f = fopen(path, "rb"); size_t n; assert(f); n = fread(bytes, 1, size, f); assert(!ferror(f) && fclose(f) == 0); return n; } static int temporaries(const char *directory, int remove_files) { DIR *dir = opendir(directory); struct dirent *entry; int count = 0; assert(dir); while ((entry = readdir(dir)) != NULL) if (strstr(entry->d_name, ".tmp.")) { char path[512]; ++count; if (remove_files) { assert(snprintf(path, sizeof(path), "%s/%s", directory, entry->d_name) > 0); assert(unlink(path) == 0); } } assert(closedir(dir) == 0); return count; } int main(void) { char directory[] = "/tmp/xtc-recovery-XXXXXX", song[256], wave[256]; xtc_project *old = NULL, *next = NULL, *loaded = NULL; uint64_t track, clip; size_t i, n, before_size; unsigned char before[4096], after[4096]; pid_t child; int status, pipefd[2]; char ready; struct timespec delay = {0, 100000}; assert(mkdtemp(directory)); snprintf(song, sizeof(song), "%s/song.xtc", directory); snprintf(wave, sizeof(wave), "%s/take.wav", directory); assert(xtc_project_create(&old) == XTC_OK && xtc_project_create(&next) == XTC_OK && xtc_project_create(&loaded) == XTC_OK); assert(xtc_project_add_track(old, "Old", &track) == XTC_OK && xtc_project_save(old, song) == XTC_OK); before_size = read_bytes(song, before, sizeof(before)); assert(xtc_project_add_track(next, "New", &track) == XTC_OK); for (i = 0; i < XTC_PROJECT_MAX_CLIPS; ++i) assert(xtc_project_add_clip(next, track, "Recovery region", i * 960, 960, &clip) == XTC_OK); // Deterministic per-process file-size failure; no server/system setting change. child = fork(); assert(child >= 0); if (!child) { struct rlimit limit = {128, 128}; signal(SIGXFSZ, SIG_IGN); if (setrlimit(RLIMIT_FSIZE, &limit) != 0) _exit(2); _exit(xtc_project_save(next, song) == XTC_ERR_IO ? 0 : 3); } assert(waitpid(child, &status, 0) == child && WIFEXITED(status) && WEXITSTATUS(status) == 0); assert(read_bytes(song, after, sizeof(after)) == before_size && memcmp(before, after, before_size) == 0 && temporaries(directory, 0) == 0); // Kill a writer while its temporary is visible. Destination must be complete old/new. for (i = 0; i < 8; ++i) { size_t attempts; xtc_track_info info; assert(xtc_project_save(old, song) == XTC_OK); assert(pipe(pipefd) == 0); child = fork(); assert(child >= 0); if (!child) { close(pipefd[0]); if (write(pipefd[1], "R", 1) != 1) _exit(4); close(pipefd[1]); for (;;) if (xtc_project_save(next, song) != XTC_OK) _exit(5); } close(pipefd[1]); assert(read(pipefd[0], &ready, 1) == 1); close(pipefd[0]); for (attempts = 0; attempts < 10000 && !temporaries(directory, 0); ++attempts) nanosleep(&delay, NULL); reap(child); assert(attempts < 10000); assert(xtc_project_load(loaded, song) == XTC_OK && xtc_project_get_track(loaded, 0, &info) == XTC_OK); assert(strcmp(info.name, "Old") == 0 || strcmp(info.name, "New") == 0); temporaries(directory, 1); } // A killed recording leaves the previously finalized destination untouched. { xtc_recorder *recorder = NULL; float samples[128]; for (i = 0; i < 128; ++i) samples[i] = 0.25f; assert(xtc_recorder_create_replace(wave, 48000, 256, 1, &recorder) == XTC_OK); xtc_recorder_push(recorder, samples, 128); assert(xtc_recorder_finish(recorder) == XTC_OK); xtc_recorder_destroy(recorder); before_size = read_bytes(wave, before, sizeof(before)); assert(pipe(pipefd) == 0); child = fork(); assert(child >= 0); if (!child) { close(pipefd[0]); if (xtc_recorder_create_replace(wave, 48000, 256, 1, &recorder) != XTC_OK) _exit(6); xtc_recorder_push(recorder, samples, 128); if (xtc_recorder_drain(recorder) != XTC_OK) _exit(7); if (write(pipefd[1], "R", 1) != 1) _exit(8); for (;;) pause(); } close(pipefd[1]); assert(read(pipefd[0], &ready, 1) == 1); close(pipefd[0]); reap(child); n = read_bytes(wave, after, sizeof(after)); assert(n == before_size && memcmp(before, after, n) == 0); assert(temporaries(directory, 1) > 0); } xtc_project_destroy(old); xtc_project_destroy(next); xtc_project_destroy(loaded); assert(unlink(song) == 0 && unlink(wave) == 0 && rmdir(directory) == 0); puts("Save write failure, 8 killed saves and killed recording preserve complete destinations; " "orphan temporaries remain explicit."); return 0; }