]> granicus.if.org Git - p11-kit/commitdiff
trust: Add support for saving files with unique file names
authorStef Walter <stef@thewalter.net>
Wed, 3 Jul 2013 08:45:50 +0000 (10:45 +0200)
committerStef Walter <stef@thewalter.net>
Wed, 3 Jul 2013 10:13:02 +0000 (12:13 +0200)
trust/extract-cer.c
trust/extract-jks.c
trust/extract-openssl.c
trust/extract-pem.c
trust/save.c
trust/save.h
trust/tests/test-openssl.c
trust/tests/test-save.c

index 4a0d9c0a29dfa71de4271d4e595da5533bca375e..81a5bf69cf0f515b71c0c0cce216d88d89e38038 100644 (file)
@@ -56,7 +56,7 @@ p11_extract_x509_file (P11KitIter *iter,
                        break;
                }
 
-               file = p11_save_open_file (ex->destination, ex->flags);
+               file = p11_save_open_file (ex->destination, NULL, ex->flags);
                if (!p11_save_write_and_finish (file, ex->cert_der, ex->cert_len))
                        return false;
 
@@ -95,7 +95,7 @@ p11_extract_x509_directory (P11KitIter *iter,
                filename = p11_extract_info_filename (ex);
                return_val_if_fail (filename != NULL, -1);
 
-               file = p11_save_open_file_in (dir, filename, ".cer", NULL);
+               file = p11_save_open_file_in (dir, filename, ".cer");
                free (filename);
 
                if (!p11_save_write_and_finish (file, ex->cert_der, ex->cert_len)) {
index 2c78a510848b826ac26c6af3bac251b6d223b117..987ea261874523d27a9d4e8274196bd105d4706c 100644 (file)
@@ -322,7 +322,7 @@ p11_extract_jks_cacerts (P11KitIter *iter,
        p11_buffer_init (&buffer, 1024 * 10);
        ret = prepare_jks_buffer (iter, ex, &buffer);
        if (ret) {
-               file = p11_save_open_file (ex->destination, ex->flags);
+               file = p11_save_open_file (ex->destination, NULL, ex->flags);
                ret = p11_save_write_and_finish (file, buffer.data, buffer.len);
        }
 
index 91a9965ebf5f3b171393c7265907d245ee937f13..312a779ee987f140d7d8cd860b213780d2081baa 100644 (file)
@@ -44,6 +44,7 @@
 #include "hash.h"
 #include "message.h"
 #include "oid.h"
+#include "path.h"
 #include "pem.h"
 #include "pkcs11.h"
 #include "pkcs11x.h"
@@ -320,7 +321,7 @@ p11_extract_openssl_bundle (P11KitIter *iter,
        bool first;
        CK_RV rv;
 
-       file = p11_save_open_file (ex->destination, ex->flags);
+       file = p11_save_open_file (ex->destination, NULL, ex->flags);
        if (!file)
                return false;
 
@@ -362,7 +363,8 @@ p11_extract_openssl_bundle (P11KitIter *iter,
         * certificates were found.
         */
 
-       p11_save_finish_file (file, ret);
+       if (!p11_save_finish_file (file, NULL, ret))
+               ret = false;
        return ret;
 }
 
@@ -584,12 +586,13 @@ bool
 p11_extract_openssl_directory (P11KitIter *iter,
                                p11_extract_info *ex)
 {
-       const char *filename;
+       char *filename;
        p11_save_file *file;
        p11_save_dir *dir;
        p11_buffer output;
        p11_buffer buf;
        bool ret = true;
+       char *path;
        char *name;
        CK_RV rv;
 
@@ -617,7 +620,18 @@ p11_extract_openssl_directory (P11KitIter *iter,
                        name = p11_extract_info_filename (ex);
                        return_val_if_fail (name != NULL, false);
 
-                       file = p11_save_open_file_in (dir, name, ".pem", &filename);
+                       filename = NULL;
+                       path = NULL;
+                       ret = false;
+
+                       file = p11_save_open_file_in (dir, name, ".pem");
+                       if (file != NULL) {
+                               ret = p11_save_write (file, output.data, output.len);
+                               if (!p11_save_finish_file (file, &path, ret))
+                                       ret = false;
+                               if (ret)
+                                       filename = p11_path_base (path);
+                       }
 
                        /*
                         * The OpenSSL style c_rehash stuff
@@ -633,28 +647,26 @@ p11_extract_openssl_directory (P11KitIter *iter,
                         * On Windows no symlinks.
                         */
 
-                       ret = true;
-
 #ifdef OS_UNIX
-                       linkname = symlink_for_subject_hash (ex);
-                       if (file && linkname) {
-                               ret = p11_save_symlink_in (dir, linkname, ".0", filename);
-                               free (linkname);
+                       if (ret) {
+                               linkname = symlink_for_subject_hash (ex);
+                               if (linkname) {
+                                       ret = p11_save_symlink_in (dir, linkname, ".0", filename);
+                                       free (linkname);
+                               }
                        }
 
-                       linkname = symlink_for_subject_old_hash (ex);
-                       if (ret && file && linkname) {
-                               ret = p11_save_symlink_in (dir, linkname, ".0", filename);
-                               free (linkname);
+                       if (ret) {
+                               linkname = symlink_for_subject_old_hash (ex);
+                               if (linkname) {
+                                       ret = p11_save_symlink_in (dir, linkname, ".0", filename);
+                                       free (linkname);
+                               }
                        }
 #endif /* OS_UNIX */
 
-                       if (ret)
-                               ret = p11_save_write_and_finish (file, output.data, output.len);
-                       else
-                               p11_save_finish_file (file, false);
-
-                       free (name);
+                       free (filename);
+                       free (path);
                }
 
                if (!ret)
index 0bae3cb72f847e8ad8f2b483a195f18b44563679..718cd991126de056a6ecb6a31d71a23f6caffbca 100644 (file)
@@ -56,7 +56,7 @@ p11_extract_pem_bundle (P11KitIter *iter,
        bool first = true;
        CK_RV rv;
 
-       file = p11_save_open_file (ex->destination, ex->flags);
+       file = p11_save_open_file (ex->destination, NULL, ex->flags);
        if (!file)
                return false;
 
@@ -92,7 +92,9 @@ p11_extract_pem_bundle (P11KitIter *iter,
         * certificates were found.
         */
 
-       p11_save_finish_file (file, ret);
+       if (!p11_save_finish_file (file, NULL, ret))
+               ret = false;
+
        return ret;
 }
 
@@ -122,7 +124,7 @@ p11_extract_pem_directory (P11KitIter *iter,
                filename = p11_extract_info_filename (ex);
                return_val_if_fail (filename != NULL, false);
 
-               file = p11_save_open_file_in (dir, filename, ".pem", NULL);
+               file = p11_save_open_file_in (dir, filename, ".pem");
                free (filename);
 
                ret = p11_save_write_and_finish (file, buf.data, buf.len);
index f1605a32a9b0ffd2a285437ef6fc63dec9280446..1533c089518e60ab2f762bf6f4edbd5ee938583b 100644 (file)
@@ -51,7 +51,8 @@
 #include <unistd.h>
 
 struct _p11_save_file {
-       char *path;
+       char *bare;
+       char *extension;
        char *temp;
        int fd;
        int flags;
@@ -63,6 +64,11 @@ struct _p11_save_dir {
        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,
@@ -74,7 +80,7 @@ p11_save_write_and_finish (p11_save_file *file,
                return false;
 
        ret = p11_save_write (file, data, length);
-       if (!p11_save_finish_file (file, ret))
+       if (!p11_save_finish_file (file, NULL, ret))
                ret = false;
 
        return ret;
@@ -82,34 +88,25 @@ p11_save_write_and_finish (p11_save_file *file,
 
 p11_save_file *
 p11_save_open_file (const char *path,
+                    const char *extension,
                     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 (extension == NULL)
+               extension = "";
 
-       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)
+       if (asprintf (&temp, "%s%s.XXXXXX", path, extension) < 0)
                return_val_if_reached (NULL);
 
        fd = mkstemp (temp);
        if (fd < 0) {
-               p11_message ("couldn't create file: %s: %s",
-                            path, strerror (errno));
+               p11_message ("couldn't create file: %s%s: %s",
+                            path, extension, strerror (errno));
                free (temp);
                return NULL;
        }
@@ -117,8 +114,10 @@ p11_save_open_file (const char *path,
        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->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;
 
@@ -164,15 +163,58 @@ static void
 filo_free (p11_save_file *file)
 {
        free (file->temp);
-       free (file->path);
+       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 ("couldn't complete writing of file: %s: %s",
+                            path, strerror (errno));
+               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: %s",
+                            path, strerror (errno));
+               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;
@@ -184,6 +226,9 @@ p11_save_finish_file (p11_save_file *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 ("couldn't write file: %s: %s",
                             file->temp, strerror (errno));
@@ -199,19 +244,28 @@ p11_save_finish_file (p11_save_file *file,
 
        /* Atomically rename the tempfile over the filename */
        } else if (file->flags & P11_SAVE_OVERWRITE) {
-               if (rename (file->temp, file->path) < 0) {
+               if (rename (file->temp, path) < 0) {
                        p11_message ("couldn't complete writing file: %s: %s",
-                                    file->path, strerror (errno));
+                                    path, strerror (errno));
                        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, file->path) < 0) {
+               if (link (file->temp, path) < 0) {
                        p11_message ("couldn't complete writing of file: %s: %s",
-                                    file->path, strerror (errno));
+                                    path, strerror (errno));
                        ret = false;
                }
                unlink (file->temp);
@@ -220,16 +274,24 @@ p11_save_finish_file (p11_save_file *file,
 
        /* Windows does not do atomic renames, so delete original file first */
        } else {
-               if (file->flags & P11_SAVE_OVERWRITE) {
-                       if (unlink (file->path) < 0 && errno != ENOENT) {
+               /* 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 ("couldn't remove original file: %s: %s",
-                                            file->path, strerror (errno));
+                                            path, strerror (errno));
                                ret = false;
                        }
-               }
 
-               if (ret == true) {
-                       if (rename (file->temp, file->path) < 0) {
+                       if (ret == true &&
+                           rename (file->temp, file->path) < 0) {
                                p11_message ("couldn't complete writing file: %s: %s",
                                             file->path, strerror (errno));
                                ret = false;
@@ -241,6 +303,12 @@ p11_save_finish_file (p11_save_file *file,
 #endif /* OS_WIN32 */
        }
 
+       if (ret && path_out) {
+               *path_out = path;
+               path = NULL;
+       }
+
+       free (path);
        filo_free (file);
        return ret;
 }
@@ -302,14 +370,19 @@ p11_save_open_directory (const char *path,
 }
 
 static char *
-make_unique_name (p11_save_dir *dir,
-                  const char *filename,
-                  const char *extension)
+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++) {
@@ -323,7 +396,7 @@ make_unique_name (p11_save_dir *dir,
                 * provided by the caller.
                 */
                case 0:
-                       p11_buffer_add (&buf, filename, -1);
+                       p11_buffer_add (&buf, bare, -1);
                        break;
 
                /*
@@ -340,7 +413,7 @@ make_unique_name (p11_save_dir *dir,
                        /* fall through */
 
                default:
-                       p11_buffer_add (&buf, filename, -1);
+                       p11_buffer_add (&buf, bare, -1);
                        snprintf (unique, sizeof (unique), ".%d", i);
                        p11_buffer_add (&buf, unique, -1);
                        break;
@@ -351,18 +424,32 @@ make_unique_name (p11_save_dir *dir,
 
                return_val_if_fail (p11_buffer_ok (&buf), NULL);
 
-               if (!p11_dict_get (dir->cache, buf.data))
+               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,
-                       const char **ret_name)
+                       const char *extension)
 {
        p11_save_file *file = NULL;
        char *name;
@@ -371,19 +458,17 @@ p11_save_open_file_in (p11_save_dir *dir,
        return_val_if_fail (dir != NULL, NULL);
        return_val_if_fail (basename != NULL, NULL);
 
-       name = make_unique_name (dir, basename, extension);
+       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, dir->flags);
+       file = p11_save_open_file (path, NULL, 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;
        }
 
@@ -409,7 +494,7 @@ p11_save_symlink_in (p11_save_dir *dir,
        return_val_if_fail (linkname != NULL, false);
        return_val_if_fail (destination != NULL, false);
 
-       name = make_unique_name (dir, linkname, extension);
+       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)
index f68d0547dabbe8ae99cc0b4cb7914f2faebb90e9..81f1044af2aea32a051ed1d381d3d00dec073d63 100644 (file)
 
 enum {
        P11_SAVE_OVERWRITE = 1 << 0,
+       P11_SAVE_UNIQUE = 1 << 1,
 };
 
 typedef struct _p11_save_file p11_save_file;
 typedef struct _p11_save_dir p11_save_dir;
 
 p11_save_file *  p11_save_open_file         (const char *path,
+                                             const char *extension,
                                              int flags);
 
 bool             p11_save_write             (p11_save_file *file,
@@ -56,6 +58,7 @@ bool             p11_save_write_and_finish  (p11_save_file *file,
                                              ssize_t length);
 
 bool             p11_save_finish_file       (p11_save_file *file,
+                                             char **path,
                                              bool commit);
 
 const char *     p11_save_file_name         (p11_save_file *file);
@@ -65,8 +68,7 @@ p11_save_dir *   p11_save_open_directory    (const char *path,
 
 p11_save_file *  p11_save_open_file_in      (p11_save_dir *directory,
                                              const char *basename,
-                                             const char *extension,
-                                             const char **filename);
+                                             const char *extension);
 
 #ifdef OS_UNIX
 
index 93f8692051bb364516d21127d3a604a3f2e72507..2d41b9275b92fb797903f381e9fe428b889ad445 100644 (file)
@@ -52,7 +52,6 @@
 #include "oid.h"
 #include "test.h"
 
-#include <assert.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
index be072f51c6f5eed2d96478be9c9d12564429b34a..eba5632668d1ac23a792aabc2296de0289eddbe6 100644 (file)
@@ -48,7 +48,6 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 
-#include <assert.h>
 #include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
@@ -106,7 +105,7 @@ test_file_write (void)
        if (asprintf (&filename, "%s/%s", test.directory, "extract-file") < 0)
                assert_not_reached ();
 
-       file = p11_save_open_file (filename, 0);
+       file = p11_save_open_file (filename, NULL, 0);
        assert_ptr_not_null (file);
 
        ret = p11_save_write_and_finish (file, test_cacert3_ca_der, sizeof (test_cacert3_ca_der));
@@ -129,8 +128,11 @@ test_file_exists (void)
 
        p11_message_quiet ();
 
-       file = p11_save_open_file (filename, 0);
-       assert (file == NULL);
+       file = p11_save_open_file (filename, NULL, 0);
+       assert (file != NULL);
+
+       if (p11_save_finish_file (file, NULL, true))
+               assert_not_reached ();
 
        p11_message_loud ();
 
@@ -149,7 +151,7 @@ test_file_bad_directory (void)
 
        p11_message_quiet ();
 
-       file = p11_save_open_file (filename, 0);
+       file = p11_save_open_file (filename, NULL, 0);
        assert (file == NULL);
 
        p11_message_loud ();
@@ -169,7 +171,7 @@ test_file_overwrite (void)
 
        write_zero_file (test.directory, "extract-file");
 
-       file = p11_save_open_file (filename, P11_SAVE_OVERWRITE);
+       file = p11_save_open_file (filename, NULL, P11_SAVE_OVERWRITE);
        assert_ptr_not_null (file);
 
        ret = p11_save_write_and_finish (file, test_cacert3_ca_der, sizeof (test_cacert3_ca_der));
@@ -179,6 +181,29 @@ test_file_overwrite (void)
        test_check_file (test.directory, "extract-file", SRCDIR "/files/cacert3.der");
 }
 
+static void
+test_file_unique (void)
+{
+       p11_save_file *file;
+       char *filename;
+       bool ret;
+
+       if (asprintf (&filename, "%s/%s", test.directory, "extract-file") < 0)
+               assert_not_reached ();
+
+       write_zero_file (test.directory, "extract-file");
+
+       file = p11_save_open_file (filename, NULL, P11_SAVE_UNIQUE);
+       assert_ptr_not_null (file);
+
+       ret = p11_save_write_and_finish (file, test_cacert3_ca_der, sizeof (test_cacert3_ca_der));
+       assert_num_eq (true, ret);
+       free (filename);
+
+       test_check_file (test.directory, "extract-file", SRCDIR "/files/empty-file");
+       test_check_file (test.directory, "extract-file.1", SRCDIR "/files/cacert3.der");
+}
+
 static void
 test_file_auto_empty (void)
 {
@@ -189,7 +214,7 @@ test_file_auto_empty (void)
        if (asprintf (&filename, "%s/%s", test.directory, "extract-file") < 0)
                assert_not_reached ();
 
-       file = p11_save_open_file (filename, 0);
+       file = p11_save_open_file (filename, NULL, 0);
        assert_ptr_not_null (file);
 
        ret = p11_save_write_and_finish (file, NULL, -1);
@@ -209,7 +234,7 @@ test_file_auto_length (void)
        if (asprintf (&filename, "%s/%s", test.directory, "extract-file") < 0)
                assert_not_reached ();
 
-       file = p11_save_open_file (filename, 0);
+       file = p11_save_open_file (filename, NULL, 0);
        assert_ptr_not_null (file);
 
        ret = p11_save_write_and_finish (file, "The simple string is hairy", -1);
@@ -243,16 +268,19 @@ test_file_abort (void)
        struct stat st;
        p11_save_file *file;
        char *filename;
+       char *path;
        bool ret;
 
        if (asprintf (&filename, "%s/%s", test.directory, "extract-file") < 0)
                assert_not_reached ();
 
-       file = p11_save_open_file (filename, 0);
+       file = p11_save_open_file (filename, NULL, 0);
        assert_ptr_not_null (file);
 
-       ret = p11_save_finish_file (file, false);
+       path = NULL;
+       ret = p11_save_finish_file (file, &path, false);
        assert_num_eq (true, ret);
+       assert (path == NULL);
 
        if (stat (filename, &st) >= 0 || errno != ENOENT)
                assert_fail ("file should not exist", filename);
@@ -286,7 +314,9 @@ test_directory_empty (void)
 static void
 test_directory_files (void)
 {
-       const char *filename;
+       char *path;
+       char *check;
+       p11_save_file *file;
        p11_save_dir *dir;
        char *subdir;
        bool ret;
@@ -297,15 +327,29 @@ test_directory_files (void)
        dir = p11_save_open_directory (subdir, 0);
        assert_ptr_not_null (dir);
 
-       ret = p11_save_write_and_finish (p11_save_open_file_in (dir, "blah", ".cer", &filename),
-                                        test_cacert3_ca_der, sizeof (test_cacert3_ca_der));
+       file = p11_save_open_file_in (dir, "blah", ".cer");
+       assert_ptr_not_null (file);
+       ret = p11_save_write (file, test_cacert3_ca_der, sizeof (test_cacert3_ca_der));
+       assert_num_eq (true, ret);
+       ret = p11_save_finish_file (file, &path, true);
        assert_num_eq (true, ret);
-       assert_str_eq ("blah.cer", filename);
+       if (asprintf (&check, "%s/%s", subdir, "blah.cer") < 0)
+               assert_not_reached ();
+       assert_str_eq (check, path);
+       free (check);
+       free (path);
 
-       ret = p11_save_write_and_finish (p11_save_open_file_in (dir, "file", ".txt", &filename),
-                                        test_text, strlen (test_text));
+       file = p11_save_open_file_in (dir, "file", ".txt");
+       assert_ptr_not_null (file);
+       ret = p11_save_write (file, test_text, strlen (test_text));
+       assert_num_eq (true, ret);
+       ret = p11_save_finish_file (file, &path, true);
        assert_num_eq (true, ret);
-       assert_str_eq ("file.txt", filename);
+       if (asprintf (&check, "%s/%s", subdir, "file.txt") < 0)
+               assert_not_reached ();
+       assert_str_eq (check, path);
+       free (check);
+       free (path);
 
 #ifdef OS_UNIX
        ret = p11_save_symlink_in (dir, "link", ".ext", "/the/destination");
@@ -333,7 +377,9 @@ test_directory_files (void)
 static void
 test_directory_dups (void)
 {
-       const char *filename;
+       char *path;
+       char *check;
+       p11_save_file *file;
        p11_save_dir *dir;
        char *subdir;
        bool ret;
@@ -344,33 +390,47 @@ test_directory_dups (void)
        dir = p11_save_open_directory (subdir, 0);
        assert_ptr_not_null (dir);
 
-       ret = p11_save_write_and_finish (p11_save_open_file_in (dir, "file", ".txt", &filename),
-                                        test_text, 5);
+       file = p11_save_open_file_in (dir, "file", ".txt");
+       assert_ptr_not_null (file);
+       ret = p11_save_write (file, test_text, 5);
+       assert_num_eq (true, ret);
+       ret = p11_save_finish_file (file, &path, true);
        assert_num_eq (true, ret);
-       assert_str_eq ("file.txt", filename);
+       if (asprintf (&check, "%s/%s", subdir, "file.txt") < 0)
+               assert_not_reached ();
+       assert_str_eq (check, path);
+       free (check);
+       free (path);
 
-       ret = p11_save_write_and_finish (p11_save_open_file_in (dir, "file", ".txt", &filename),
-                                        test_text, 10);
+       file = p11_save_open_file_in (dir, "file", ".txt");
+       assert_ptr_not_null (file);
+       ret = p11_save_write (file, test_text, 10);
+       assert_num_eq (true, ret);
+       ret = p11_save_finish_file (file, &path, true);
        assert_num_eq (true, ret);
-       assert_str_eq ("file.1.txt", filename);
+       if (asprintf (&check, "%s/%s", subdir, "file.1.txt") < 0)
+               assert_not_reached ();
+       assert_str_eq (check, path);
+       free (check);
+       free (path);
 
-       ret = p11_save_write_and_finish (p11_save_open_file_in (dir, "file", ".txt", NULL),
+       ret = p11_save_write_and_finish (p11_save_open_file_in (dir, "file", ".txt"),
                                         test_text, 15);
        assert_num_eq (true, ret);
 
-       ret = p11_save_write_and_finish (p11_save_open_file_in (dir, "no-ext", NULL, NULL),
+       ret = p11_save_write_and_finish (p11_save_open_file_in (dir, "no-ext", NULL),
                                         test_text, 8);
        assert_num_eq (true, ret);
 
-       ret = p11_save_write_and_finish (p11_save_open_file_in (dir, "no-ext", NULL, NULL),
+       ret = p11_save_write_and_finish (p11_save_open_file_in (dir, "no-ext", NULL),
                                         test_text, 16);
        assert_num_eq (true, ret);
 
-       ret = p11_save_write_and_finish (p11_save_open_file_in (dir, "with-num", ".0", NULL),
+       ret = p11_save_write_and_finish (p11_save_open_file_in (dir, "with-num", ".0"),
                                         test_text, 14);
        assert_num_eq (true, ret);
 
-       ret = p11_save_write_and_finish (p11_save_open_file_in (dir, "with-num", ".0", NULL),
+       ret = p11_save_write_and_finish (p11_save_open_file_in (dir, "with-num", ".0"),
                                         test_text, 15);
        assert_num_eq (true, ret);
 
@@ -438,7 +498,9 @@ test_directory_exists (void)
 static void
 test_directory_overwrite (void)
 {
-       const char *filename;
+       char *path;
+       char *check;
+       p11_save_file *file;
        p11_save_dir *dir;
        char *subdir;
        bool ret;
@@ -448,9 +510,9 @@ test_directory_overwrite (void)
 
        /* Some initial files into this directory, which get overwritten */
        dir = p11_save_open_directory (subdir, 0);
-       ret = p11_save_write_and_finish (p11_save_open_file_in (dir, "file", ".txt", NULL), "", 0) &&
-             p11_save_write_and_finish (p11_save_open_file_in (dir, "another-file", NULL, NULL), "", 0) &&
-             p11_save_write_and_finish (p11_save_open_file_in (dir, "third-file", NULL, NULL), "", 0) &&
+       ret = p11_save_write_and_finish (p11_save_open_file_in (dir, "file", ".txt"), "", 0) &&
+             p11_save_write_and_finish (p11_save_open_file_in (dir, "another-file", NULL), "", 0) &&
+             p11_save_write_and_finish (p11_save_open_file_in (dir, "third-file", NULL), "", 0) &&
              p11_save_finish_directory (dir, true);
        assert (ret && dir);
 
@@ -458,20 +520,41 @@ test_directory_overwrite (void)
        dir = p11_save_open_directory (subdir, P11_SAVE_OVERWRITE);
        assert_ptr_not_null (dir);
 
-       ret = p11_save_write_and_finish (p11_save_open_file_in (dir, "blah", ".cer", &filename),
-                                        test_cacert3_ca_der, sizeof (test_cacert3_ca_der));
+       file = p11_save_open_file_in (dir, "blah", ".cer");
+       assert_ptr_not_null (file);
+       ret = p11_save_write (file, test_cacert3_ca_der, sizeof (test_cacert3_ca_der));
        assert_num_eq (true, ret);
-       assert_str_eq ("blah.cer", filename);
+       ret = p11_save_finish_file (file, &path, true);
+       assert_num_eq (true, ret);
+       if (asprintf (&check, "%s/%s", subdir, "blah.cer") < 0)
+               assert_not_reached ();
+       assert_str_eq (check, path);
+       free (check);
+       free (path);
 
-       ret = p11_save_write_and_finish (p11_save_open_file_in (dir, "file", ".txt", &filename),
-                                        test_text, strlen (test_text));
+       file = p11_save_open_file_in (dir, "file", ".txt");
+       assert_ptr_not_null (file);
+       ret = p11_save_write (file, test_text, strlen (test_text));
        assert_num_eq (true, ret);
-       assert_str_eq ("file.txt", filename);
+       ret = p11_save_finish_file (file, &path, true);
+       assert_num_eq (true, ret);
+       if (asprintf (&check, "%s/%s", subdir, "file.txt") < 0)
+               assert_not_reached ();
+       assert_str_eq (check, path);
+       free (check);
+       free (path);
 
-       ret = p11_save_write_and_finish (p11_save_open_file_in (dir, "file", ".txt", &filename),
-                                        test_text, 10);
+       file = p11_save_open_file_in (dir, "file", ".txt");
+       assert_ptr_not_null (file);
+       ret = p11_save_write (file, test_text, 10);
        assert_num_eq (true, ret);
-       assert_str_eq ("file.1.txt", filename);
+       ret = p11_save_finish_file (file, &path, true);
+       assert_num_eq (true, ret);
+       if (asprintf (&check, "%s/%s", subdir, "file.1.txt") < 0)
+               assert_not_reached ();
+       assert_str_eq (check, path);
+       free (check);
+       free (path);
 
        ret = p11_save_finish_directory (dir, true);
        assert_num_eq (true, ret);
@@ -494,6 +577,7 @@ main (int argc,
        p11_test (test_file_exists, "/save/test_file_exists");
        p11_test (test_file_bad_directory, "/save/test_file_bad_directory");
        p11_test (test_file_overwrite, "/save/test_file_overwrite");
+       p11_test (test_file_unique, "/save/file-unique");
        p11_test (test_file_auto_empty, "/save/test_file_auto_empty");
        p11_test (test_file_auto_length, "/save/test_file_auto_length");