153 lines
5.6 KiB
C
153 lines
5.6 KiB
C
#define _POSIX_C_SOURCE 200809L
|
|
#include "xtc/xtc_audio.h"
|
|
#include <assert.h>
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/resource.h>
|
|
#include <sys/wait.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
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;
|
|
}
|