Created
May 9, 2014 16:22
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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