Skip to content

Instantly share code, notes, and snippets.

@datenwolf
Created May 9, 2014 16:22
Show Gist options
  • Save datenwolf/a8f5d194b268659e3d37 to your computer and use it in GitHub Desktop.
Save datenwolf/a8f5d194b268659e3d37 to your computer and use it in GitHub Desktop.
Safely writing to files atomically (write to temporary and rename) with crash safe cleanup handling
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <signal.h>
char const HelloWorld[] = "Hello World!\n";
/* Global so that the writer process SIGHUP handler can access it */
char tmpfile_path[255];
/* Called if the parent/controlling process dies.
* Should this happen we want to unlink the filesystem entry.
* Since the writer process is the last process to hold the fd
* (the other process holding that fd _was_ the parent process
* that just died) the file gets deleted as soon as the writer
* process terminates which happens by calling _exit. */
void handler_SIGHUP(int sig)
{
unlink(tmpfile_path);
_exit(1);
}
/* Calling write on invalid memory will not crash the program but
* generate a EFAULT error condition. This little helper allows
* to crash the programm intentionally. */
ssize_t buffered_write(int fd, const void *buf, size_t count)
{
char stage[512];
ssize_t p = 0;
while(p < count) {
size_t const len = (p + sizeof(stage) < count) ?
sizeof(stage)
: (count-p);
memcpy(stage, (char*)buf + p, len);
ssize_t wrr;
for(;;) {
wrr = write(fd, stage, len);
if( -1 == wrr ) {
if(EAGAIN == errno) {
continue;
}
return -1;
}
break;
};
p += wrr;
}
return p;
}
void write_stuff_to_file(int fd)
{
write(fd, HelloWorld, sizeof(HelloWorld));
close(fd);
}
int main(int argc, char *argv[])
{
char const * tmp = getenv("TEMP");
if(!tmp) {
tmp = "/tmp";
}
char dstfile_path[255];
snprintf(tmpfile_path, sizeof(tmpfile_path), "%s/safewritesXXXXXX", tmp);
snprintf(dstfile_path, sizeof(dstfile_path), "%s/wrotesafe", tmp);
int fd = mkostemp(tmpfile_path, O_CREAT | O_EXCL | O_WRONLY);
if( -1 != fd ) {
pid_t pid = fork();
if( !pid ) {
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = handler_SIGHUP;
int const rv_sa = sigaction(SIGHUP, &sa, NULL);
write_stuff_to_file(fd);
_exit(0);
}
if( -1 == pid ) {
perror("fork");
}
close(fd);
if( 0 < pid ) {
int status;
waitpid(pid, &status, WUNTRACED | WCONTINUED);
if( WIFSIGNALED(status) ) {
fprintf(stderr,
"writing process killed by signal %d\n",
(int)WTERMSIG(status) );
goto fail;
}
if( WIFSTOPPED(status) ) {
fprintf(stderr,
"writing process halted by signal %d, killing it\n",
(int)WSTOPSIG(status) );
kill(pid, SIGKILL);
goto fail;
}
}
else {
goto fail;
}
rename(tmpfile_path, dstfile_path);
}
else {
perror("mkostemp");
return 1;
}
return 0;
fail:
unlink(tmpfile_path);
return 1;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment