diff --git a/Documentation/devel/features.md b/Documentation/devel/features.md
index 265cd9b427..af9e77c1db 100644
--- a/Documentation/devel/features.md
+++ b/Documentation/devel/features.md
@@ -389,10 +389,10 @@ The below list is generated from the [syscall table of Linux
- ☑ `fdatasync()`
[9a](#file-system-operations)
-- ▣ `truncate()`
+- ☑ `truncate()`
[9a](#file-system-operations)
-- ▣ `ftruncate()`
+- ☑ `ftruncate()`
[9a](#file-system-operations)
- ☑ `getdents()`
@@ -2087,9 +2087,7 @@ Gramine supports file flushes (via `fsync()` and `fdatasync()`). However, flushi
metadata (`sync()` and `syncfs()`) is not supported. Similarly, `sync_file_range()` system call is
currently not supported.
-Gramine supports file truncation (via `truncate()` and `ftruncate()`). There is one exception
-currently: shrinking encrypted files to arbitrary size is not supported (only shrink-to-zero is
-supported).
+Gramine supports file truncation (via `truncate()` and `ftruncate()`).
Gramine has very limited support of `fallocate()` system call. Only mode 0 is supported ("allocating
disk space"). The emulation of this mode simply extends the file size if applicable, otherwise does
@@ -2164,8 +2162,8 @@ Gramine currently does *not* support changing file access/modification times, vi
- ▣ `ppoll()`: dummy
- ☑ `fsync()`
- ☑ `fdatasync()`
-- ▣ `truncate()`: see note above
-- ▣ `ftruncate()`: see note above
+- ☑ `truncate()`
+- ☑ `ftruncate()`
- ▣ `fallocate()`: dummy
- ▣ `fadvise64()`: dummy
diff --git a/common/src/protected_files/README.rst b/common/src/protected_files/README.rst
index 483fe96d07..64537a0fe1 100644
--- a/common/src/protected_files/README.rst
+++ b/common/src/protected_files/README.rst
@@ -66,7 +66,6 @@ Some tests in ``libos/test/regression`` also work with encrypted files.
TODO
====
-- Shrinking protected files via truncate(2) to arbitrary size is not yet implemented.
- The recovery file feature is disabled, this needs to be discussed if it's
needed in Gramine.
- Tests for invalid/malformed/corrupted files need to be ported to the new
diff --git a/common/src/protected_files/protected_files.c b/common/src/protected_files/protected_files.c
index 96f4e0f9a9..40ad501c75 100644
--- a/common/src/protected_files/protected_files.c
+++ b/common/src/protected_files/protected_files.c
@@ -1123,8 +1123,17 @@ static size_t ipf_read(pf_context_t* pf, void* ptr, uint64_t offset, size_t size
return data_attempted_to_read - data_left_to_read;
}
-static bool ipf_close(pf_context_t* pf) {
+static void ipf_delete_cache(pf_context_t* pf) {
void* data;
+ while ((data = lruc_get_last(pf->cache)) != NULL) {
+ file_node_t* file_node = (file_node_t*)data;
+ erase_memory(&file_node->decrypted, sizeof(file_node->decrypted));
+ free(file_node);
+ lruc_remove_last(pf->cache);
+ }
+}
+
+static bool ipf_close(pf_context_t* pf) {
bool retval = true;
if (pf->file_status != PF_STATUS_SUCCESS) {
@@ -1140,12 +1149,7 @@ static bool ipf_close(pf_context_t* pf) {
// omeg: fs close is done by Gramine handler
pf->file_status = PF_STATUS_UNINITIALIZED;
- while ((data = lruc_get_last(pf->cache)) != NULL) {
- file_node_t* file_node = (file_node_t*)data;
- erase_memory(&file_node->decrypted, sizeof(file_node->decrypted));
- free(file_node);
- lruc_remove_last(pf->cache);
- }
+ ipf_delete_cache(pf);
// scrub first MD_USER_DATA_SIZE of file data and the gmac_key
erase_memory(&pf->encrypted_part_plain, sizeof(pf->encrypted_part_plain));
@@ -1211,7 +1215,6 @@ pf_status_t pf_get_size(pf_context_t* pf, uint64_t* size) {
return PF_STATUS_SUCCESS;
}
-// TODO: File truncation to arbitrary size.
pf_status_t pf_set_size(pf_context_t* pf, uint64_t size) {
if (!g_initialized)
return PF_STATUS_UNINITIALIZED;
@@ -1232,40 +1235,36 @@ pf_status_t pf_set_size(pf_context_t* pf, uint64_t size) {
return PF_STATUS_SUCCESS;
}
- if (size == 0) {
- // Shrink the file to zero.
- void* data;
- char path[PATH_MAX_SIZE];
- size_t path_len;
- pf_status_t status = g_cb_truncate(pf->file, 0);
- if (PF_FAILURE(status))
- return status;
-
- path_len = strlen(pf->encrypted_part_plain.path);
- memcpy(path, pf->encrypted_part_plain.path, path_len);
- erase_memory(&pf->encrypted_part_plain, sizeof(pf->encrypted_part_plain));
- memcpy(pf->encrypted_part_plain.path, path, path_len);
-
- memset(&pf->file_metadata, 0, sizeof(pf->file_metadata));
- pf->file_metadata.plain_part.file_id = PF_FILE_ID;
- pf->file_metadata.plain_part.major_version = PF_MAJOR_VERSION;
- pf->file_metadata.plain_part.minor_version = PF_MINOR_VERSION;
-
- ipf_init_root_mht(&pf->root_mht);
+ // Truncation.
- pf->need_writing = true;
+ // The structure of the protected file is such that we can simply truncate
+ // the file after the last data block belonging to still-used data.
+ // Some MHT entries will be left with dangling data describing the truncated
+ // nodes, but this is not a problem since they will be unused, and will be
+ // overwritten with new data when the relevant nodes get allocated again.
- while ((data = lruc_get_last(pf->cache)) != NULL) {
- file_node_t* file_node = (file_node_t*)data;
- erase_memory(&file_node->decrypted, sizeof(file_node->decrypted));
- free(file_node);
- lruc_remove_last(pf->cache);
- }
-
- return PF_STATUS_SUCCESS;
- }
+ // First, ensure any nodes that will be truncated are not in cache. We do it
+ // by simply flushing and then emptying the entire cache.
+ if (!ipf_internal_flush(pf))
+ return pf->last_error;
+ ipf_delete_cache(pf);
- return PF_STATUS_NOT_IMPLEMENTED;
+ // Calculate new file size.
+ uint64_t new_file_size;
+ if (size <= MD_USER_DATA_SIZE) {
+ new_file_size = PF_NODE_SIZE;
+ } else {
+ uint64_t physical_node_number;
+ get_node_numbers(size - 1, NULL, NULL, NULL, &physical_node_number);
+ new_file_size = (physical_node_number + 1) * PF_NODE_SIZE;
+ }
+ pf_status_t status = g_cb_truncate(pf->file, new_file_size);
+ if (PF_FAILURE(status))
+ return status;
+ // If successfully truncated, update our bookkeeping.
+ pf->encrypted_part_plain.size = size;
+ pf->need_writing = true;
+ return PF_STATUS_SUCCESS;
}
pf_status_t pf_rename(pf_context_t* pf, const char* new_path) {
diff --git a/common/src/protected_files/protected_files.h b/common/src/protected_files/protected_files.h
index 793a4864a5..4354f3e6fe 100644
--- a/common/src/protected_files/protected_files.h
+++ b/common/src/protected_files/protected_files.h
@@ -281,8 +281,7 @@ pf_status_t pf_get_size(pf_context_t* pf, uint64_t* size);
*
* \returns PF status.
*
- * If the file is extended, added bytes are zero. Shrinking to arbitrary size is not implemented
- * yet (TODO).
+ * If the file is extended, added bytes are zero.
*/
pf_status_t pf_set_size(pf_context_t* pf, uint64_t size);
diff --git a/libos/src/fs/chroot/encrypted.c b/libos/src/fs/chroot/encrypted.c
index 94a03be134..37af2f9edc 100644
--- a/libos/src/fs/chroot/encrypted.c
+++ b/libos/src/fs/chroot/encrypted.c
@@ -31,8 +31,6 @@
*
* TODO:
*
- * - truncate - The truncate functionality does not support shrinking to arbitrary size.
- * It has to be added to the `protected_files` module.
* - flush all files on process exit
*/
diff --git a/libos/test/fs/seek_tell_truncate.c b/libos/test/fs/seek_tell_truncate.c
index 7b653fd7cf..6b8262218b 100644
--- a/libos/test/fs/seek_tell_truncate.c
+++ b/libos/test/fs/seek_tell_truncate.c
@@ -2,18 +2,19 @@
#define CHUNK_SIZE 512
-static void setup_file(const char* path, size_t size) {
+static void setup_file(const char* path, size_t size, void* check_buf, size_t check_size) {
int fd = open_output_fd(path, /*rdwr=*/false);
void* buf = alloc_buffer(size);
fill_random(buf, size);
write_fd(path, fd, buf, size);
+ memcpy(check_buf, buf, size < check_size ? size : check_size);
free(buf);
close_fd(path, fd);
}
-static void seek_truncate(const char* path, size_t file_pos, size_t file_truncate) {
+static void seek_truncate(const char* path, size_t file_pos, size_t file_truncate, void* check_buf) {
int fd = open_output_fd(path, /*rdwr=*/false);
seek_fd(path, fd, file_pos, SEEK_SET);
@@ -37,6 +38,7 @@ static void seek_truncate(const char* path, size_t file_pos, size_t file_truncat
void* buf = alloc_buffer(CHUNK_SIZE);
fill_random(buf, CHUNK_SIZE);
write_fd(path, fd, buf, CHUNK_SIZE);
+ memcpy(check_buf + pos, buf, CHUNK_SIZE);
free(buf);
size_t next_file_pos = file_pos + CHUNK_SIZE;
@@ -49,6 +51,19 @@ static void seek_truncate(const char* path, size_t file_pos, size_t file_truncat
close_fd(path, fd);
}
+static void check_contents(const char* path, void* data, size_t size) {
+ int fd = open_input_fd(path);
+
+ void* buf = alloc_buffer(size);
+ read_fd(path, fd, buf, size);
+ if (memcmp(buf, data, size)) {
+ fatal_error("truncated data mismatch");
+ }
+ free(buf);
+
+ close_fd(path, fd);
+}
+
int main(int argc, char* argv[]) {
if (argc < 5)
fatal_error("Usage: %s