/* * 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 */ #include "config.h" #include "buffer.h" #include "debug.h" #include "dict.h" #include "message.h" #include "save.h" #include #include #include #include #include #include #include #include struct _p11_save_file { char *bare; char *extension; char *temp; int fd; int flags; }; struct _p11_save_dir { p11_dict *cache; char *path; int flags; }; static char * make_unique_name (const char *bare, const char *extension, int (*check) (void *, char *), void *data); bool p11_save_write_and_finish (p11_save_file *file, const void *data, ssize_t length) { bool ret; if (!file) return false; ret = p11_save_write (file, data, length); if (!p11_save_finish_file (file, NULL, ret)) ret = false; return ret; } p11_save_file * p11_save_open_file (const char *path, const char *extension, int flags) { p11_save_file *file; char *temp; int fd; return_val_if_fail (path != NULL, NULL); if (extension == NULL) extension = ""; if (asprintf (&temp, "%s%s.XXXXXX", path, extension) < 0) return_val_if_reached (NULL); fd = mkstemp (temp); if (fd < 0) { p11_message_err (errno, "couldn't create file: %s%s", path, extension); free (temp); return NULL; } file = calloc (1, sizeof (p11_save_file)); return_val_if_fail (file != NULL, NULL); file->temp = temp; file->bare = strdup (path); return_val_if_fail (file->bare != NULL, NULL); file->extension = strdup (extension); return_val_if_fail (file->extension != NULL, NULL); file->flags = flags; file->fd = fd; return file; } bool p11_save_write (p11_save_file *file, const void *data, ssize_t length) { const unsigned char *buf = data; ssize_t written = 0; ssize_t res; if (!file) return false; /* Automatically calculate length */ if (length < 0) { if (!data) return true; length = strlen (data); } while (written < length) { res = write (file->fd, buf + written, length - written); if (res <= 0) { if (errno == EAGAIN || errno == EINTR) continue; p11_message_err (errno, "couldn't write to file: %s", file->temp); return false; } else { written += res; } } return true; } static void filo_free (p11_save_file *file) { free (file->temp); free (file->bare); free (file->extension); free (file); } #ifdef OS_UNIX static int on_unique_try_link (void *data, char *path) { p11_save_file *file = data; if (link (file->temp, path) < 0) { if (errno == EEXIST) return 0; /* Continue trying other names */ p11_message_err (errno, "couldn't complete writing of file: %s", path); return -1; } return 1; /* All done */ } #else /* OS_WIN32 */ static int on_unique_try_rename (void *data, char *path) { p11_save_file *file = data; if (rename (file->temp, path) < 0) { if (errno == EEXIST) return 0; /* Continue trying other names */ p11_message ("couldn't complete writing of file: %s", path); return -1; } return 1; /* All done */ } #endif /* OS_WIN32 */ bool p11_save_finish_file (p11_save_file *file, char **path_out, bool commit) { bool ret = true; char *path; if (!file) return false; if (!commit) { close (file->fd); unlink (file->temp); filo_free (file); return true; } if (asprintf (&path, "%s%s", file->bare, file->extension) < 0) return_val_if_reached (false); if (close (file->fd) < 0) { p11_message_err (errno, "couldn't write file: %s", file->temp); ret = false; #ifdef OS_UNIX /* Set the mode of the file, readable by everyone, but not writable */ } else if (chmod (file->temp, S_IRUSR | S_IRGRP | S_IROTH) < 0) { p11_message_err (errno, "couldn't set file permissions: %s", file->temp); ret = false; /* Atomically rename the tempfile over the filename */ } else if (file->flags & P11_SAVE_OVERWRITE) { if (rename (file->temp, path) < 0) { p11_message_err (errno, "couldn't complete writing file: %s", path); ret = false; } else { unlink (file->temp); } /* Create a unique name if requested unique file name */ } else if (file->flags & P11_SAVE_UNIQUE) { free (path); path = make_unique_name (file->bare, file->extension, on_unique_try_link, file); if (!path) ret = false; unlink (file->temp); /* When not overwriting, link will fail if filename exists. */ } else { if (link (file->temp, path) < 0) { p11_message_err (errno, "couldn't complete writing of file: %s", path); ret = false; } unlink (file->temp); #else /* OS_WIN32 */ /* Windows does not do atomic renames, so delete original file first */ } else { /* Create a unique name if requested unique file name */ if (file->flags & P11_SAVE_UNIQUE) { free (path); path = make_unique_name (file->bare, file->extension, on_unique_try_rename, file); if (!path) ret = false; } else if ((file->flags & P11_SAVE_OVERWRITE) && unlink (path) < 0 && errno != ENOENT) { p11_message_err (errno, "couldn't remove original file: %s", path); ret = false; } if (ret == true && rename (file->temp, path) < 0) { p11_message_err (errno, "couldn't complete writing file: %s", path); ret = false; } unlink (file->temp); #endif /* OS_WIN32 */ } if (ret && path_out) { *path_out = path; path = NULL; } free (path); filo_free (file); return ret; } p11_save_dir * p11_save_open_directory (const char *path, int flags) { #ifdef OS_UNIX struct stat sb; #endif p11_save_dir *dir; return_val_if_fail (path != NULL, NULL); #ifdef OS_UNIX /* We update the permissions when we finish writing */ if (mkdir (path, S_IRWXU) < 0) { #else /* OS_WIN32 */ if (mkdir (path) < 0) { #endif /* Some random error, report it */ if (errno != EEXIST) { p11_message_err (errno, "couldn't create directory: %s", path); /* The directory exists and we're not overwriting */ } else if (!(flags & P11_SAVE_OVERWRITE)) { p11_message ("directory already exists: %s", path); return NULL; } #ifdef OS_UNIX /* * If the directory exists on unix, we may have restricted * the directory permissions to read-only. We have to change * them back to writable in order for things to work. */ if (stat (path, &sb) >= 0) { if ((sb.st_mode & S_IRWXU) != S_IRWXU && chmod (path, S_IRWXU | sb.st_mode) < 0) { p11_message_err (errno, "couldn't make directory writable: %s", path); return NULL; } } #endif /* OS_UNIX */ } 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 (const char *bare, const char *extension, int (*check) (void *, char *), void *data) { char unique[16]; p11_buffer buf; int ret; int i; assert (bare != NULL); assert (check != NULL); 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, bare, -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, bare, -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); ret = check (data, buf.data); if (ret < 0) return NULL; else if (ret > 0) return p11_buffer_steal (&buf, NULL); } assert_not_reached (); } static int on_unique_check_dir (void *data, char *name) { p11_save_dir *dir = data; if (!p11_dict_get (dir->cache, name)) return 1; return 0; /* Keep looking */ } p11_save_file * p11_save_open_file_in (p11_save_dir *dir, const char *basename, const char *extension) { 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 (basename, extension, on_unique_check_dir, dir); 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, NULL, dir->flags); if (file) { if (!p11_dict_set (dir->cache, name, name)) return_val_if_reached (NULL); name = NULL; } free (name); free (path); return file; } #ifdef OS_UNIX 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 (linkname, extension, on_unique_check_dir, dir); 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_err (errno, "couldn't create symlink: %s", path); 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; } #endif /* OS_UNIX */ static bool cleanup_directory (const char *directory, p11_dict *cache) { struct dirent *dp; struct stat st; p11_dict *remove; p11_dictiter iter; char *path; DIR *dir; bool ret; /* First we load all the modules */ dir = opendir (directory); if (!dir) { p11_message_err (errno, "couldn't list directory: %s", directory); return false; } remove = p11_dict_new (p11_dict_str_hash, p11_dict_str_equal, free, NULL); 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); if (stat (path, &st) >= 0 && !S_ISDIR (st.st_mode)) { 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_err (errno, "couldn't remove file: %s", path); 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) { if (dir->flags & P11_SAVE_OVERWRITE) ret = cleanup_directory (dir->path, dir->cache); #ifdef OS_UNIX /* Try to set the mode of the directory to readable */ if (ret && chmod (dir->path, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0) { p11_message_err (errno, "couldn't set directory permissions: %s", dir->path); ret = false; } #endif /* OS_UNIX */ } p11_dict_free (dir->cache); free (dir->path); free (dir); return ret; }