diff --git a/src/common/storagedir.c b/src/common/storagedir.c
index e28a66f693..7e0be6754b 100644
--- a/src/common/storagedir.c
+++ b/src/common/storagedir.c
@@ -3,6 +3,8 @@
#include "container.h"
#include "compat.h"
+#include "confline.h"
+#include "memarea.h"
#include "sandbox.h"
#include "storagedir.h"
#include "torlog.h"
@@ -237,6 +239,40 @@ find_unused_fname(storage_dir_t *d)
return NULL;
}
+/** Helper: As storage_dir_save_bytes_to_file, but store a smartlist of
+ * sized_chunk_t rather than a single byte array. */
+static int
+storage_dir_save_chunks_to_file(storage_dir_t *d,
+ const smartlist_t *chunks,
+ int binary,
+ char **fname_out)
+{
+ uint64_t total_length = 0;
+ char *fname = find_unused_fname(d);
+ if (!fname)
+ return -1;
+
+ SMARTLIST_FOREACH(chunks, const sized_chunk_t *, ch,
+ total_length += ch->len);
+
+ char *path = NULL;
+ tor_asprintf(&path, "%s/%s", d->directory, fname);
+
+ int r = write_chunks_to_file(path, chunks, binary, 0);
+ if (r == 0) {
+ if (d->usage_known)
+ d->usage += total_length;
+ if (fname_out) {
+ *fname_out = tor_strdup(fname);
+ }
+ if (d->contents)
+ smartlist_add(d->contents, tor_strdup(fname));
+ }
+ tor_free(fname);
+ tor_free(path);
+ return r;
+}
+
/** Try to write the length bytes at data into a new file
* in d. On success, return 0 and set *fname_out to a
* newly allocated string containing the filename. On failure, return
@@ -248,25 +284,11 @@ storage_dir_save_bytes_to_file(storage_dir_t *d,
int binary,
char **fname_out)
{
- char *fname = find_unused_fname(d);
- if (!fname)
- return -1;
-
- char *path = NULL;
- tor_asprintf(&path, "%s/%s", d->directory, fname);
-
- int r = write_bytes_to_file(path, (const char *)data, length, binary);
- if (r == 0) {
- if (d->usage_known)
- d->usage += length;
- if (fname_out) {
- *fname_out = tor_strdup(fname);
- }
- if (d->contents)
- smartlist_add(d->contents, tor_strdup(fname));
- }
- tor_free(fname);
- tor_free(path);
+ smartlist_t *chunks = smartlist_new();
+ sized_chunk_t chunk = { (const char *)data, length };
+ smartlist_add(chunks, &chunk);
+ int r = storage_dir_save_chunks_to_file(d, chunks, binary, fname_out);
+ smartlist_free(chunks);
return r;
}
@@ -284,6 +306,106 @@ storage_dir_save_string_to_file(storage_dir_t *d,
(const uint8_t*)str, strlen(str), binary, fname_out);
}
+/**
+ * As storage_dir_save_bytes_to_file, but associates the data with the
+ * key-value pairs in labels. Files
+ * stored in this format can be recovered with storage_dir_map_labeled
+ * or storage_dir_read_labeled().
+ */
+int
+storage_dir_save_labeled_to_file(storage_dir_t *d,
+ const config_line_t *labels,
+ const uint8_t *data,
+ size_t length,
+ char **fname_out)
+{
+ /*
+ * The storage format is to prefix the data with the key-value pairs in
+ * labels, and a single NUL separator. But code outside this module
+ * MUST NOT rely on that format.
+ */
+
+ smartlist_t *chunks = smartlist_new();
+ memarea_t *area = memarea_new();
+ const config_line_t *line;
+ for (line = labels; line; line = line->next) {
+ sized_chunk_t *sz = memarea_alloc(area, sizeof(sized_chunk_t));
+ sz->len = strlen(line->key) + 1 + strlen(line->value) + 1;
+ const size_t allocated = sz->len + 1;
+ char *bytes = memarea_alloc(area, allocated);
+ tor_snprintf(bytes, allocated, "%s %s\n", line->key, line->value);
+ sz->bytes = bytes;
+ smartlist_add(chunks, sz);
+ }
+
+ sized_chunk_t *nul = memarea_alloc(area, sizeof(sized_chunk_t));
+ nul->len = 1;
+ nul->bytes = "\0";
+ smartlist_add(chunks, nul);
+
+ sized_chunk_t *datachunk = memarea_alloc(area, sizeof(sized_chunk_t));
+ datachunk->bytes = (const char *)data;
+ datachunk->len = length;
+ smartlist_add(chunks, datachunk);
+
+ int r = storage_dir_save_chunks_to_file(d, chunks, 1, fname_out);
+ smartlist_free(chunks);
+ memarea_drop_all(area);
+ return r;
+}
+
+/**
+ * Map a file that was created with storage_dir_save_labeled(). On failure,
+ * return NULL. On success, write a set of newly allocated labels into to
+ * *labels_out, a pointer to the into *data_out, and the data's
+ * into *sz_out. On success, also return a tor_mmap_t object whose
+ * contents should not be used -- it needs to be kept around, though, for as
+ * long as data_out is going to be valid.
+ */
+tor_mmap_t *
+storage_dir_map_labeled(storage_dir_t *dir,
+ const char *fname,
+ config_line_t **labels_out,
+ const uint8_t **data_out,
+ size_t *sz_out)
+{
+ tor_mmap_t *m = storage_dir_map(dir, fname);
+ if (! m)
+ goto err;
+ const char *nulp = memchr(m->data, '\0', m->size);
+ if (! nulp)
+ goto err;
+ if (labels_out && config_get_lines(m->data, labels_out, 0) < 0)
+ goto err;
+ size_t offset = nulp - m->data + 1;
+ tor_assert(offset <= m->size);
+ *data_out = (const uint8_t *)(m->data + offset);
+ *sz_out = m->size - offset;
+
+ return m;
+ err:
+ tor_munmap_file(m);
+ return NULL;
+}
+
+/** As storage_dir_map_labeled, but return a new byte array containing the
+ * data. */
+uint8_t *
+storage_dir_read_labeled(storage_dir_t *dir,
+ const char *fname,
+ config_line_t **labels_out,
+ size_t *sz_out)
+{
+ const uint8_t *data = NULL;
+ tor_mmap_t *m = storage_dir_map_labeled(dir, fname, labels_out,
+ &data, sz_out);
+ if (m == NULL)
+ return NULL;
+ uint8_t *result = tor_memdup(data, *sz_out);
+ tor_munmap_file(m);
+ return result;
+}
+
/**
* Remove the file called fname from d.
*/
diff --git a/src/common/storagedir.h b/src/common/storagedir.h
index 29be941b14..781194407f 100644
--- a/src/common/storagedir.h
+++ b/src/common/storagedir.h
@@ -5,7 +5,7 @@
#define TOR_STORAGEDIR_H
typedef struct storage_dir_t storage_dir_t;
-
+struct config_line_t;
struct sandbox_cfg_elem;
storage_dir_t * storage_dir_new(const char *dirname, int n_files);
@@ -26,6 +26,19 @@ int storage_dir_save_string_to_file(storage_dir_t *d,
const char *data,
int binary,
char **fname_out);
+int storage_dir_save_labeled_to_file(storage_dir_t *d,
+ const struct config_line_t *labels,
+ const uint8_t *data,
+ size_t length,
+ char **fname_out);
+tor_mmap_t *storage_dir_map_labeled(storage_dir_t *dir,
+ const char *fname,
+ struct config_line_t **labels_out,
+ const uint8_t **data_out,
+ size_t *size_out);
+uint8_t *storage_dir_read_labeled(storage_dir_t *d, const char *fname,
+ struct config_line_t **labels_out,
+ size_t *sz_out);
void storage_dir_remove_file(storage_dir_t *d,
const char *fname);
int storage_dir_shrink(storage_dir_t *d,