Replacing an existing file
[Posted September 4, 2011 by corbet]
/*
* Copyright 2011, Red Hat, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <libgen.h>
#include <limits.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include "sync-samples.h"
#define TEMPLATE "mynewfileXXXXXX"
char *template;
int template_len;
const char *message1 = "Version 1 of my data.\n";
const char *message2 = "Version 2 of my data.\n";
int
main(int argc, char **argv)
{
int ret;
size_t message_len;
int fd, new_fd, dir_fd;
mode_t old_mode;
char *path, *containing_dir;
if (argc < 2) {
fprintf(stderr, "Usage: %s <filename>\n", basename(argv[0]));
exit(USER_ERR);
}
/*
* basename and dirname may modify the string passed in
*/
path = strdup(argv[1]);
if (!path) {
perror("strdup");
exit(LIB_ERR);
}
containing_dir = dirname(path);
/*
* Note that this will truncate the file.
*/
old_mode = umask((mode_t)0);
fd = open(argv[1], O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
if (fd < 0) {
perror("open");
exit(SYS_ERR);
}
umask(old_mode);
/*
* You can't write directly to a directory. fsync, however
* is allowed on the directory, even when opened read-only.
*/
dir_fd = open(containing_dir, O_RDONLY);
if (dir_fd < 0) {
perror("open");
exit(SYS_ERR);
}
message_len = strlen(message1);
ret = full_write(fd, message1, message_len);
if (ret != (int)message_len) {
if (ret < 0) {
perror("write");
exit(SYS_ERR);
}
/*
* Short write. This can happen if the file system is
* full, for example. In our case, we can't use the
* partial data, so just unlink the file. If the
* unlink fails, report this to the user.
*/
if (unlink(argv[1]) < 0)
perror("unlink");
exit(SYS_ERR);
}
/*
* Now the data is in the kernel's page cache. The next step
* flushes the page cache for this file to disk.
*/
if (fsync(fd) < 0) {
perror("fsync");
exit(SYS_ERR);
}
/*
* Because we just created this file, we also need to ensure that
* the new directory entry gets flushed to disk.
*/
if (fsync(dir_fd) < 0) {
perror("fsync2");
exit(SYS_ERR);
}
if (close(fd) < 0) {
perror("close");
exit(SYS_ERR);
}
/*
* Now we have version 1 of our data safely on disk. Let's start
* working on version 2 by creating a new file to hold the updates.
* Note that we are creating the temp file in the same directory
* as the target file. The reason for this is to keep the example
* as simple as possible.
*/
template_len = strlen(containing_dir) + strlen(TEMPLATE) + 2;
template = malloc(template_len);
if (!template) {
perror("malloc");
exit(SYS_ERR);
}
ret = snprintf(template, template_len, "%s/%s", containing_dir, TEMPLATE);
if (ret >= template_len) {
/*
* Coding error, there should have been enough room in
* the template.
*/
fprintf(stderr, "Internal Error\n");
exit(INTERNAL_ERR);
}
new_fd = mkstemp(template);
if (new_fd == -1) {
perror("mkstemp");
exit(SYS_ERR);
}
message_len = strlen(message2);
ret = full_write(new_fd, message2, message_len);
if (ret != (int)message_len) {
if (ret < 0) {
perror("write");
exit(SYS_ERR);
}
/*
* Short write. This can happen if the file system is
* full, for example. In our case, we can't use the
* partial data, so just unlink the file. If unlink
* fails, notify the user.
*/
if (unlink(template) < 0)
perror("unlink");
exit(SYS_ERR);
}
/* ok, now sync the new file out to disk. */
if (fsync(new_fd) < 0) {
perror("fsync");
exit(SYS_ERR);
}
if (close(new_fd) < 0) {
perror("close");
exit(SYS_ERR);
}
/*
* It wasn't necessary to sync out the directory at this
* point, since we're not relying on this new file for any
* user data (at least not this file as it is--we will rely
* on it after the rename).
*/
/* now rename the new file to replace the old one */
if (rename(template, argv[1]) < 0) {
perror("rename");
exit(SYS_ERR);
}
free(template);
/* and sync out the directory fd */
if (fsync(dir_fd) < 0) {
perror("fsync dir_fd");
exit(SYS_ERR);
}
if (close(dir_fd) < 0) {
perror("close dir_fd");
exit(SYS_ERR);
}
free(path);
/* and that's it! */
exit(0);
}
(
Log in to post comments)