diff options
Diffstat (limited to 'tools/save.c')
-rw-r--r-- | tools/save.c | 462 |
1 files changed, 462 insertions, 0 deletions
diff --git a/tools/save.c b/tools/save.c new file mode 100644 index 0000000..695c1fc --- /dev/null +++ b/tools/save.c @@ -0,0 +1,462 @@ +/* + * Copyright (c) 2013, Red Hat Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * Author: Stef Walter <stefw@redhat.com> + */ + +#include "config.h" + +#include "buffer.h" +#include "debug.h" +#include "dict.h" +#include "library.h" +#include "save.h" + +#include <sys/stat.h> + +#include <assert.h> +#include <dirent.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +struct _p11_save_file { + char *path; + char *temp; + int fd; + int flags; +}; + +struct _p11_save_dir { + p11_dict *cache; + char *path; + int flags; +}; + +bool +p11_save_write_and_finish (p11_save_file *file, + const void *data, + size_t length) +{ + bool ret; + + if (!file) + return false; + + ret = p11_save_write (file, data, length); + if (!p11_save_finish_file (file, ret)) + ret = false; + + return ret; +} + +p11_save_file * +p11_save_open_file (const char *path, + int flags) +{ + struct stat st; + p11_save_file *file; + char *temp; + int fd; + + return_val_if_fail (path != NULL, NULL); + + /* + * This is just an early convenience check. We check again + * later when committing, in a non-racy fashion. + */ + + if (!(flags & P11_SAVE_OVERWRITE)) { + if (stat (path, &st) >= 0) { + p11_message ("file already exists: %s", path); + return NULL; + } + } + + if (asprintf (&temp, "%s.XXXXXX", path) < 0) + return_val_if_reached (NULL); + + fd = mkstemp (temp); + if (fd < 0) { + p11_message ("couldn't create file: %s: %s", + path, strerror (errno)); + free (temp); + return NULL; + } + + file = calloc (1, sizeof (p11_save_file)); + return_val_if_fail (file != NULL, NULL); + file->temp = temp; + file->path = strdup (path); + return_val_if_fail (file->path != NULL, NULL); + file->flags = flags; + file->fd = fd; + + return file; +} + +bool +p11_save_write (p11_save_file *file, + const void *data, + size_t length) +{ + const unsigned char *buf = data; + ssize_t written = 0; + ssize_t res; + + if (!file) + return false; + + while (written < length) { + res = write (file->fd, buf + written, length - written); + if (res <= 0) { + if (errno == EAGAIN && errno == EINTR) + continue; + p11_message ("couldn't write to file: %s: %s", + file->temp, strerror (errno)); + return false; + } else { + written += res; + } + } + + return true; +} + +static void +filo_free (p11_save_file *file) +{ + free (file->temp); + free (file->path); + free (file); +} + +bool +p11_save_finish_file (p11_save_file *file, + bool commit) +{ + bool ret = true; + + if (!file) + return false; + + if (!commit) { + unlink (file->temp); + close (file->fd); + filo_free (file); + return true; + } + + /* Set the mode of the file */ + if (chmod (file->temp, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0) { + p11_message ("couldn't set file permissions: %s: %s", + file->temp, strerror (errno)); + close (file->fd); + ret = false; + + } else if (close (file->fd) < 0) { + p11_message ("couldn't write file: %s: %s", + file->temp, strerror (errno)); + ret = false; + + /* Atomically rename the tempfile over the filename */ + } else if (file->flags & P11_SAVE_OVERWRITE) { + if (rename (file->temp, file->path) < 0) { + p11_message ("couldn't complete writing file: %s: %s", + file->path, strerror (errno)); + ret = false; + } else { + unlink (file->temp); + } + + /* When not overwriting, link will fail if filename exists. */ + } else { + if (link (file->temp, file->path) < 0) { + p11_message ("couldn't complete writing of file: %s: %s", + file->path, strerror (errno)); + ret = false; + } + unlink (file->temp); + } + + filo_free (file); + return ret; +} + +p11_save_dir * +p11_save_open_directory (const char *path, + int flags) +{ + mode_t mode = S_IRWXU | S_IXGRP | S_IRGRP | S_IROTH | S_IXOTH; + p11_save_dir *dir; + + return_val_if_fail (path != NULL, NULL); + + if (mkdir (path, mode) < 0) { + if (!(flags & P11_SAVE_OVERWRITE) || errno != EEXIST) { + p11_message ("directory already exists: %s", path); + return NULL; + } + } + + dir = calloc (1, sizeof (p11_save_dir)); + return_val_if_fail (dir != NULL, NULL); + + dir->path = strdup (path); + return_val_if_fail (dir->path != NULL, NULL); + + dir->cache = p11_dict_new (p11_dict_str_hash, p11_dict_str_equal, free, NULL); + return_val_if_fail (dir->cache != NULL, NULL); + + dir->flags = flags; + return dir; +} + +static char * +make_unique_name (p11_save_dir *dir, + const char *filename, + const char *extension) +{ + char unique[16]; + p11_buffer buf; + int i; + + p11_buffer_init_null (&buf, 0); + + for (i = 0; true; i++) { + + p11_buffer_reset (&buf, 64); + + switch (i) { + + /* + * For the first iteration, just build the filename as + * provided by the caller. + */ + case 0: + p11_buffer_add (&buf, filename, -1); + break; + + /* + * On later iterations we try to add a numeric .N suffix + * before the extension, so the resulting file might look + * like filename.1.ext. + * + * As a special case if the extension is already '.0' then + * just just keep incerementing that. + */ + case 1: + if (extension && strcmp (extension, ".0") == 0) + extension = NULL; + /* fall through */ + + default: + p11_buffer_add (&buf, filename, -1); + snprintf (unique, sizeof (unique), ".%d", i); + p11_buffer_add (&buf, unique, -1); + break; + } + + if (extension) + p11_buffer_add (&buf, extension, -1); + + return_val_if_fail (p11_buffer_ok (&buf), NULL); + + if (!p11_dict_get (dir->cache, buf.data)) + return p11_buffer_steal (&buf, NULL); + } + + assert_not_reached (); +} + +p11_save_file * +p11_save_open_file_in (p11_save_dir *dir, + const char *basename, + const char *extension, + const char **ret_name) +{ + p11_save_file *file = NULL; + char *name; + char *path; + + return_val_if_fail (dir != NULL, NULL); + return_val_if_fail (basename != NULL, NULL); + + name = make_unique_name (dir, basename, extension); + return_val_if_fail (name != NULL, NULL); + + if (asprintf (&path, "%s/%s", dir->path, name) < 0) + return_val_if_reached (NULL); + + file = p11_save_open_file (path, dir->flags); + + if (file) { + if (!p11_dict_set (dir->cache, name, name)) + return_val_if_reached (NULL); + if (ret_name) + *ret_name = name; + name = NULL; + } + + free (name); + free (path); + + return file; +} + +bool +p11_save_symlink_in (p11_save_dir *dir, + const char *linkname, + const char *extension, + const char *destination) +{ + char *name; + char *path; + bool ret; + + return_val_if_fail (dir != NULL, false); + return_val_if_fail (linkname != NULL, false); + return_val_if_fail (destination != NULL, false); + + name = make_unique_name (dir, linkname, extension); + return_val_if_fail (name != NULL, false); + + if (asprintf (&path, "%s/%s", dir->path, name) < 0) + return_val_if_reached (false); + + unlink (path); + + if (symlink (destination, path) < 0) { + p11_message ("couldn't create symlink: %s: %s", + path, strerror (errno)); + ret = false; + } else { + if (!p11_dict_set (dir->cache, name, name)) + return_val_if_reached (false); + name = NULL; + ret = true; + } + + free (path); + free (name); + + return ret; +} + +static bool +cleanup_directory (const char *directory, + p11_dict *cache) +{ + struct dirent *dp; + p11_dict *remove; + p11_dictiter iter; + char *path; + DIR *dir; + int skip; + bool ret; + + /* First we load all the modules */ + dir = opendir (directory); + if (!dir) { + p11_message ("couldn't list directory: %s: %s", + directory, strerror (errno)); + return false; + } + + remove = p11_dict_new (p11_dict_str_hash, p11_dict_str_equal, free, NULL); + + /* We're within a global mutex, so readdir is safe */ + while ((dp = readdir (dir)) != NULL) { + if (p11_dict_get (cache, dp->d_name)) + continue; + + if (asprintf (&path, "%s/%s", directory, dp->d_name) < 0) + return_val_if_reached (false); + +#ifdef HAVE_STRUCT_DIRENT_D_TYPE + if(dp->d_type != DT_UNKNOWN) { + skip = (dp->d_type == DT_DIR); + } else +#endif + { + struct stat st; + + skip = (stat (path, &st) < 0) || S_ISDIR (st.st_mode); + } + + if (!skip) { + if (!p11_dict_set (remove, path, path)) + return_val_if_reached (false); + } else { + free (path); + } + } + + closedir (dir); + + ret = true; + + /* Remove all the files still in the cache */ + p11_dict_iterate (remove, &iter); + while (p11_dict_next (&iter, (void **)&path, NULL)) { + if (unlink (path) < 0 && errno != ENOENT) { + p11_message ("couldn't remove file: %s: %s", + path, strerror (errno)); + ret = false; + break; + } + } + + p11_dict_free (remove); + + return ret; +} + +bool +p11_save_finish_directory (p11_save_dir *dir, + bool commit) +{ + bool ret = true; + + if (!dir) + return false; + + if (commit && (dir->flags & P11_SAVE_OVERWRITE)) + ret = cleanup_directory (dir->path, dir->cache); + + p11_dict_free (dir->cache); + free (dir->path); + free (dir); + + return ret; +} |