Parcourir la source

[LibOS] Add tests for some typical FS use cases

These tests perform common FS operations in various ways:
- open/close
- read/write
- create/delete
- read/change size
- seek/tell
- memory-mapped read/write
- copy directory in different ways

Tests use both direct syscalls and stdio wrappers (FILE).

To run, call `make test` in `LibOS/shim/test/fs`.
Rafał Wojdyła il y a 4 ans
Parent
commit
baab561a75

+ 4 - 0
Jenkinsfiles/Linux

@@ -28,6 +28,10 @@ pipeline {
                             cd LibOS/shim/test/regression
                             make regression
                             '''
+                        sh '''
+                            cd LibOS/shim/test/fs
+                            make test
+                            '''
                         sh '''
                             cd LibOS/shim/test/apps/ltp
                             make

+ 4 - 0
Jenkinsfiles/Linux-18.04

@@ -24,6 +24,10 @@ pipeline {
                             cd LibOS/shim/test/regression
                             make regression
                             '''
+                        sh '''
+                            cd LibOS/shim/test/fs
+                            make test
+                            '''
                         sh '''
                             cd LibOS/shim/test/apps/ltp
                             make

+ 4 - 0
Jenkinsfiles/Linux-Debug

@@ -26,6 +26,10 @@ pipeline {
                             cd LibOS/shim/test/regression
                             make regression
                             '''
+                        sh '''
+                            cd LibOS/shim/test/fs
+                            make test
+                            '''
                         sh '''
                             cd LibOS/shim/test/apps/ltp
                             make

+ 4 - 0
Jenkinsfiles/Linux-Debug-18.04

@@ -21,6 +21,10 @@ pipeline {
                             cd LibOS/shim/test/regression
                             make regression
                             '''
+                        sh '''
+                            cd LibOS/shim/test/fs
+                            make test
+                            '''
                         sh '''
                             cd LibOS/shim/test/apps/ltp
                             make

+ 7 - 0
Jenkinsfiles/Linux-SGX

@@ -57,6 +57,13 @@ pipeline {
                                 make SGX=1 regression
                             '''
                         }
+                        timeout(time: 5, unit: 'MINUTES') {
+                            sh '''
+                                cd LibOS/shim/test/fs
+                                make SGX=1 all sgx-tokens
+                                make SGX=1 test
+                            '''
+                        }
 
                         // LTP is ignored under SGX because of random segfaults
                         sh '''

+ 7 - 0
Jenkinsfiles/Linux-SGX-18.04

@@ -54,6 +54,13 @@ pipeline {
                                 make SGX=1 regression
                             '''
                         }
+                        timeout(time: 5, unit: 'MINUTES') {
+                            sh '''
+                                cd LibOS/shim/test/fs
+                                make SGX=1 all sgx-tokens
+                                make SGX=1 test
+                            '''
+                        }
 
                         // LTP is ignored under SGX because of random segfaults
                         sh '''

+ 1 - 1
LibOS/shim/test/Makefile

@@ -1,5 +1,5 @@
 # SUBDIRS = regression native inline benchmark
-SUBDIRS = native
+SUBDIRS = native fs
 
 SYS ?= $(shell gcc -dumpmachine)
 export SYS

+ 14 - 0
LibOS/shim/test/fs/.gitignore

@@ -0,0 +1,14 @@
+copy_mmap_rev
+copy_mmap_seq
+copy_mmap_whole
+copy_rev
+copy_seq
+copy_whole
+delete
+manifest
+open_close
+read_write
+seek_tell
+stat
+truncate
+*.xml

+ 42 - 0
LibOS/shim/test/fs/Makefile

@@ -0,0 +1,42 @@
+execs = $(filter-out common common_copy,$(patsubst %.c,%,$(wildcard *.c)))
+copy_execs = $(filter copy_%,$(execs))
+manifests = $(patsubst %.manifest.template,%.manifest,$(wildcard *.manifest.template)) manifest
+
+exec_target = $(execs)
+target = $(exec_target) $(manifests)
+
+clean-extra += clean-tmp
+
+.PHONY: default
+default: all
+
+include ../Makefile.Test
+
+ifeq ($(findstring x86_64,$(SYS))$(findstring linux,$(SYS)),x86_64linux)
+
+$(execs): common.o
+
+$(copy_execs): common_copy.o
+
+%.o: %.c
+	$(call cmd,cc_o_c)
+
+else
+.IGNORE: $(execs)
+$(execs)
+endif
+
+export PAL_LOADER = $(RUNTIME)/pal-$(PAL_HOST)
+export PYTHONPATH = ../../../../Scripts
+
+.PHONY: test
+test: $(target)
+	$(RM) fs-test.xml
+	$(MAKE) fs-test.xml
+
+fs-test.xml:
+	python3 -m pytest --junit-xml $@ -v test_fs.py
+
+.PHONY: clean-tmp
+clean-tmp:
+	rm -rf *.tmp *.cached *.manifest.sgx *~ *.sig *.token *.o __pycache__ .pytest_cache .cache *.xml

+ 17 - 0
LibOS/shim/test/fs/README.md

@@ -0,0 +1,17 @@
+Test purpose
+------------
+
+These tests perform common FS operations in various ways to exercise the Graphene FS subsystem:
+
+- open/close
+- read/write
+- create/delete
+- read/change size
+- seek/tell
+- memory-mapped read/write
+- copy directory in different ways
+
+How to execute
+--------------
+
+Run `make test`.

+ 153 - 0
LibOS/shim/test/fs/common.c

@@ -0,0 +1,153 @@
+#include "common.h"
+
+noreturn void fatal_error(const char* fmt, ...) {
+    va_list args;
+    va_start(args, fmt);
+    fprintf(stderr, "ERROR: ");
+    vfprintf(stderr, fmt, args);
+    va_end(args);
+    exit(-1);
+}
+
+void setup() {
+    // set output to line-buffered for easier debugging
+    setvbuf(stdout, NULL, _IOLBF, 0);
+    setvbuf(stderr, NULL, _IOLBF, 0);
+
+    unsigned int seed = time(NULL);
+    printf("Random seed: %u\n", seed);
+    srand(seed);
+}
+
+int open_input_fd(const char* path) {
+    int fd = open(path, O_RDONLY);
+    if (fd < 0)
+        fatal_error("Failed to open input file %s: %s\n", path, strerror(errno));
+    return fd;
+}
+
+void read_fd(const char* path, int fd, void* buffer, size_t size) {
+    off_t offset = 0;
+    while (size > 0) {
+        ssize_t ret = read(fd, buffer + offset, size);
+        if (ret == -EINTR)
+            continue;
+        if (ret <= 0)
+            fatal_error("Failed to read file %s: %s\n", path, strerror(errno));
+        size -= ret;
+        offset += ret;
+    }
+}
+
+void seek_fd(const char* path, int fd, ssize_t offset, int mode) {
+    off_t ret = lseek(fd, offset, mode);
+    if (ret == -1)
+        fatal_error("Failed to lseek(%zd, %d) file %s: %s\n", offset, mode, path, strerror(errno));
+}
+
+off_t tell_fd(const char* path, int fd) {
+    off_t pos = lseek(fd, 0, SEEK_CUR);
+    if (pos == -1)
+        fatal_error("Failed to lseek(0, SEEK_CUR) file %s: %s\n", path, strerror(errno));
+    return pos;
+}
+
+int open_output_fd(const char* path, bool rdwr) {
+    int fd = open(path, rdwr ? O_RDWR|O_CREAT : O_WRONLY|O_CREAT, 0664);
+    if (fd < 0)
+        fatal_error("Failed to open output file %s: %s\n", path, strerror(errno));
+    return fd;
+}
+
+void write_fd(const char* path, int fd, const void* buffer, size_t size) {
+    off_t offset = 0;
+    while (size > 0) {
+        ssize_t ret = write(fd, buffer + offset, size);
+        if (ret == -EINTR)
+            continue;
+        if (ret <= 0)
+            fatal_error("Failed to write file %s: %s\n", path, strerror(errno));
+        size -= ret;
+        offset += ret;
+    }
+}
+
+void close_fd(const char* path, int fd) {
+    if (fd >= 0 && close(fd) != 0)
+        fatal_error("Failed to close file %s: %s\n", path, strerror(errno));
+}
+
+void* mmap_fd(const char* path, int fd, int protection, size_t offset, size_t size) {
+    void* address = mmap(NULL, size, protection, MAP_SHARED, fd, offset);
+
+    if (address == MAP_FAILED)
+        fatal_error("Failed to mmap file %s: %s\n", path, strerror(errno));
+    return address;
+}
+
+void munmap_fd(const char* path, void* address, size_t size) {
+    if (munmap(address, size) < 0)
+        fatal_error("Failed to munmap file %s: %s\n", path, strerror(errno));
+}
+
+FILE* open_input_stdio(const char* path) {
+    FILE* f = fopen(path, "r");
+    if (!f)
+        fatal_error("Failed to fopen input file %s: %s\n", path, strerror(errno));
+    return f;
+}
+
+void read_stdio(const char* path, FILE* f, void* buffer, size_t size) {
+    if (size > 0) {
+        size_t ret = fread(buffer, size, 1, f);
+        if (ret != 1)
+            fatal_error("Failed to fread file %s: %s\n", path, strerror(errno));
+    }
+}
+
+void seek_stdio(const char* path, FILE* f, off_t offset, int mode) {
+    if (offset > LONG_MAX)
+        fatal_error("Failed to fseek file %s(%zd): offset too big\n", path, offset);
+    int ret = fseek(f, (long)offset, mode);
+    if (ret < 0)
+        fatal_error("Failed to fseek file %s(%zd): %s\n", path, offset, strerror(errno));
+}
+
+off_t tell_stdio(const char* path, FILE* f) {
+    long pos = ftell(f);
+    if (pos < 0)
+        fatal_error("Failed to ftell file %s: %s\n", path, strerror(errno));
+    return pos;
+}
+
+void close_stdio(const char* path, FILE* f) {
+    if (f && fclose(f) != 0)
+        fatal_error("Failed to fclose file %s: %s\n", path, strerror(errno));
+}
+
+FILE* open_output_stdio(const char* path, bool rdwr) {
+    FILE* f = fopen(path, rdwr ? "r+" : "w");
+    if (!f)
+        fatal_error("Failed to fopen output file %s: %s\n", path, strerror(errno));
+    return f;
+}
+
+void write_stdio(const char* path, FILE* f, const void* buffer, size_t size) {
+    if (size > 0) {
+        size_t ret = fwrite(buffer, size, 1, f);
+        if (ret != 1)
+            fatal_error("Failed to fwrite file %s: %s\n", path, strerror(errno));
+    }
+}
+
+void* alloc_buffer(size_t size) {
+    void* buffer = malloc(size);
+    if (!buffer)
+        fatal_error("No memory\n");
+    return buffer;
+}
+
+void fill_random(void* buffer, size_t size) {
+    for (size_t i = 0; i < size; i++)
+        ((uint8_t*)buffer)[i] = rand() % 256;
+}

+ 46 - 0
LibOS/shim/test/fs/common.h

@@ -0,0 +1,46 @@
+#ifndef COMMON_H
+#define COMMON_H
+
+#define _GNU_SOURCE
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdnoreturn.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+noreturn void fatal_error(const char* fmt, ...);
+void setup();
+int open_input_fd(const char* path);
+void read_fd(const char* path, int fd, void* buffer, size_t size);
+void seek_fd(const char* path, int fd, off_t offset, int mode);
+off_t tell_fd(const char* path, int fd);
+int open_output_fd(const char* path, bool rdwr);
+void write_fd(const char* path, int fd, const void* buffer, size_t size);
+void close_fd(const char* path, int fd);
+void* mmap_fd(const char* path, int fd, int protection, size_t offset, size_t size);
+void munmap_fd(const char* path, void* address, size_t size);
+FILE* open_input_stdio(const char* path);
+void read_stdio(const char* path, FILE* f, void* buffer, size_t size);
+void seek_stdio(const char* path, FILE* f, off_t offset, int mode);
+off_t tell_stdio(const char* path, FILE* f);
+void close_stdio(const char* path, FILE* f);
+FILE* open_output_stdio(const char* path, bool rdwr);
+void write_stdio(const char* path, FILE* f, const void* buffer, size_t size);
+void* alloc_buffer(size_t size);
+void fill_random(void* buffer, size_t size);
+
+void copy_data(int fi, int fo, const char* input_path, const char* output_path, size_t size);
+
+#endif

+ 76 - 0
LibOS/shim/test/fs/common_copy.c

@@ -0,0 +1,76 @@
+#include "common.h"
+
+void copy_file(const char* input_path, const char* output_path, size_t size) {
+    int fi = open_input_fd(input_path);
+    printf("open(%zu) input OK\n", size);
+
+    struct stat st;
+    if (fstat(fi, &st) < 0)
+        fatal_error("Failed to stat file %s: %s\n", input_path, strerror(errno));
+    if (st.st_size != size)
+        fatal_error("Size mismatch: expected %zu, got %zu\n", size, st.st_size);
+    printf("fstat(%zu) input OK\n", size);
+
+    int fo = open_output_fd(output_path, /*rdwr=*/false);
+    printf("open(%zu) output OK\n", size);
+
+    if (fstat(fo, &st) < 0)
+        fatal_error("Failed to stat file %s: %s\n", output_path, strerror(errno));
+    if (st.st_size != 0)
+        fatal_error("Size mismatch: expected 0, got %zu\n", st.st_size);
+    printf("fstat(%zu) output 1 OK\n", size);
+
+    copy_data(fi, fo, input_path, output_path, size);
+
+    if (fstat(fo, &st) < 0)
+        fatal_error("Failed to stat file %s: %s\n", output_path, strerror(errno));
+    if (st.st_size != size)
+        fatal_error("Size mismatch: expected %zu, got %zu\n", size, st.st_size);
+    printf("fstat(%zu) output 2 OK\n", size);
+
+    close_fd(input_path, fi);
+    printf("close(%zu) input OK\n", size);
+    close_fd(output_path, fo);
+    printf("close(%zu) output OK\n", size);
+}
+
+int main(int argc, char* argv[]) {
+    if (argc < 3)
+        fatal_error("Usage: %s <input_dir> <output_dir>\n", argv[0]);
+
+    setup();
+
+    char* input_dir = argv[1];
+    char* output_dir = argv[2];
+
+    // Process input directory
+    DIR* dfd = opendir(input_dir);
+    if (!dfd)
+        fatal_error("Failed to open input directory %s: %s\n", input_dir, strerror(errno));
+    printf("opendir(%s) OK\n", input_dir);
+
+    struct dirent* de = NULL;
+    while ((de = readdir(dfd)) != NULL) {
+        printf("readdir(%s) OK\n", de->d_name);
+        if (!strcmp(de->d_name, "."))
+            continue;
+        if (!strcmp(de->d_name, ".."))
+            continue;
+
+        // assume files have names that are their sizes as string
+        size_t input_path_size = strlen(input_dir) + 1 + strlen(de->d_name) + 1;
+        size_t output_path_size = strlen(output_dir) + 1 + strlen(de->d_name) + 1;
+        char* input_path = alloc_buffer(input_path_size);
+        char* output_path = alloc_buffer(output_path_size);
+        size_t size = (size_t)strtoumax(de->d_name, NULL, 10);
+        snprintf(input_path, input_path_size, "%s/%s", input_dir, de->d_name);
+        snprintf(output_path, output_path_size, "%s/%s", output_dir, de->d_name);
+
+        copy_file(input_path, output_path, size);
+
+        free(input_path);
+        free(output_path);
+    }
+
+    return 0;
+}

+ 38 - 0
LibOS/shim/test/fs/copy_mmap_rev.c

@@ -0,0 +1,38 @@
+#include "common.h"
+
+void copy_data(int fi, int fo, const char* input_path, const char* output_path, size_t size) {
+    if (size > 0) {
+        size_t max_step = 16;
+        if (size > 65536)
+            max_step = 256;
+
+        // map whole input/output file
+        void* in = mmap_fd(input_path, fi, PROT_READ, 0, size);
+        printf("mmap_fd(%zu) input OK\n", size);
+        void* out = mmap_fd(output_path, fo, PROT_WRITE, 0, size);
+        printf("mmap_fd(%zu) output OK\n", size);
+
+        // copy data
+        if (ftruncate(fo, size) != 0)
+            fatal_error("ftruncate(%s, %zu) failed: %s\n", output_path, size, strerror(errno));
+        printf("ftruncate(%zu) output OK\n", size);
+
+        ssize_t offset = size;
+        ssize_t step;
+        while (offset > 0) {
+            if (offset > max_step)
+                step = rand() % max_step + 1;
+            else
+                step = offset;
+            offset -= step;
+
+            memcpy(out + offset, in + offset, step);
+        }
+
+        // unmap
+        munmap_fd(input_path, in, size);
+        printf("munmap_fd(%zu) input OK\n", size);
+        munmap_fd(output_path, out, size);
+        printf("munmap_fd(%zu) output OK\n", size);
+    }
+}

+ 37 - 0
LibOS/shim/test/fs/copy_mmap_seq.c

@@ -0,0 +1,37 @@
+#include "common.h"
+
+void copy_data(int fi, int fo, const char* input_path, const char* output_path, size_t size) {
+    if (size > 0) {
+        size_t max_step = 16;
+        if (size > 65536)
+            max_step = 256;
+
+        // map whole input/output file
+        void* in = mmap_fd(input_path, fi, PROT_READ, 0, size);
+        printf("mmap_fd(%zu) input OK\n", size);
+        void* out = mmap_fd(output_path, fo, PROT_WRITE, 0, size);
+        printf("mmap_fd(%zu) output OK\n", size);
+
+        // copy data
+        ssize_t offset = 0;
+        ssize_t step;
+        while (offset < size) {
+            if (offset + max_step <= size)
+                step = rand() % max_step + 1;
+            else
+                step = size - offset;
+
+            if (ftruncate(fo, offset + step) != 0)
+                fatal_error("ftruncate(%s, %zu) failed: %s\n", output_path, offset + step, strerror(errno));
+
+            memcpy(out + offset, in + offset, step);
+            offset += step;
+        }
+
+        // unmap
+        munmap_fd(input_path, in, size);
+        printf("munmap_fd(%zu) input OK\n", size);
+        munmap_fd(output_path, out, size);
+        printf("munmap_fd(%zu) output OK\n", size);
+    }
+}

+ 21 - 0
LibOS/shim/test/fs/copy_mmap_whole.c

@@ -0,0 +1,21 @@
+#include "common.h"
+
+void copy_data(int fi, int fo, const char* input_path, const char* output_path, size_t size) {
+    if (size > 0) {
+        // map whole input/output file
+        void* in = mmap_fd(input_path, fi, PROT_READ, 0, size);
+        printf("mmap_fd(%zu) input OK\n", size);
+        void* out = mmap_fd(output_path, fo, PROT_WRITE, 0, size);
+        printf("mmap_fd(%zu) output OK\n", size);
+        // copy data
+        if (ftruncate(fo, size) != 0)
+            fatal_error("ftruncate(%s, %zu) failed: %s\n", output_path, size, strerror(errno));
+        printf("ftruncate(%zu) output OK\n", size);
+        memcpy(out, in, size);
+        // unmap
+        munmap_fd(input_path, in, size);
+        printf("munmap_fd(%zu) input OK\n", size);
+        munmap_fd(output_path, out, size);
+        printf("munmap_fd(%zu) output OK\n", size);
+    }
+}

+ 23 - 0
LibOS/shim/test/fs/copy_rev.c

@@ -0,0 +1,23 @@
+#include "common.h"
+
+void copy_data(int fi, int fo, const char* input_path, const char* output_path, size_t size) {
+    size_t max_step = 16;
+    if (size > 65536)
+        max_step = 256;
+
+    void* data = alloc_buffer(max_step);
+    ssize_t offset = size;
+    ssize_t step;
+    while (offset > 0) {
+        if (offset > max_step)
+            step = rand() % max_step + 1;
+        else
+            step = offset;
+        offset -= step;
+        seek_fd(input_path, fi, offset, SEEK_SET);
+        seek_fd(output_path, fo, offset, SEEK_SET);
+        read_fd(input_path, fi, data, step);
+        write_fd(output_path, fo, data, step);
+    }
+    free(data);
+}

+ 21 - 0
LibOS/shim/test/fs/copy_seq.c

@@ -0,0 +1,21 @@
+#include "common.h"
+
+void copy_data(int fi, int fo, const char* input_path, const char* output_path, size_t size) {
+    size_t max_step = 16;
+    if (size > 65536)
+        max_step = 256;
+
+    void* data = alloc_buffer(max_step);
+    ssize_t offset = 0;
+    ssize_t step;
+    while (offset < size) {
+        if (offset + max_step <= size)
+            step = rand() % max_step + 1;
+        else
+            step = size - offset;
+        read_fd(input_path, fi, data, step);
+        write_fd(output_path, fo, data, step);
+        offset += step;
+    }
+    free(data);
+}

+ 10 - 0
LibOS/shim/test/fs/copy_whole.c

@@ -0,0 +1,10 @@
+#include "common.h"
+
+void copy_data(int fi, int fo, const char* input_path, const char* output_path, size_t size) {
+    void* data = alloc_buffer(size);
+    read_fd(input_path, fi, data, size);
+    printf("read_fd(%zu) input OK\n", size);
+    write_fd(output_path, fo, data, size);
+    printf("write_fd(%zu) output OK\n", size);
+    free(data);
+}

+ 41 - 0
LibOS/shim/test/fs/delete.c

@@ -0,0 +1,41 @@
+#include "common.h"
+
+void file_delete(const char* file_path_1, const char* file_path_2, bool writable) {
+    const char* type = writable ? "output" : "input";
+
+    int fd = writable ? open_output_fd(file_path_1, /*rdwr=*/false) : open_input_fd(file_path_1);
+    printf("open(%s) %s 1 OK\n", file_path_1, type);
+
+    close_fd(file_path_1, fd);
+    printf("close(%s) %s 1 OK\n", file_path_1, type);
+
+    if (unlink(file_path_1) != 0)
+        fatal_error("Failed to unlink file %s: %s\n", file_path_1, strerror(errno));
+    printf("unlink(%s) %s 1 OK\n", file_path_1, type);
+
+    fd = writable ? open_output_fd(file_path_2, /*rdwr=*/false) : open_input_fd(file_path_2);
+    printf("open(%s) %s 2 OK\n", file_path_2, type);
+
+    if (unlink(file_path_2) != 0)
+        fatal_error("Failed to unlink file %s: %s\n", file_path_2, strerror(errno));
+    printf("unlink(%s) %s 2 OK\n", file_path_2, type);
+
+    close_fd(file_path_2, fd);
+    printf("close(%s) %s 2 OK\n", file_path_2, type);
+}
+
+int main(int argc, char* argv[])
+{
+    if (argc < 6)
+        fatal_error("Usage: %s <path1> <path2> <path3> <path4> <path5>\n", argv[0]);
+
+    setup();
+    if (unlink(argv[1]) != 0)
+        fatal_error("Failed to unlink file %s: %s\n", argv[1], strerror(errno));
+    printf("unlink(%s) OK\n", argv[1]);
+
+    file_delete(argv[2], argv[3], /*writable=*/false);
+    file_delete(argv[4], argv[5], /*writable=*/true);
+
+    return 0;
+}

+ 33 - 0
LibOS/shim/test/fs/manifest.template

@@ -0,0 +1,33 @@
+loader.preload = file:../../src/libsysdb.so
+loader.env.LD_LIBRARY_PATH = /lib:/lib/x86_64-linux-gnu:/usr/lib/x86_64-linux-gnu
+loader.debug_type = none
+loader.syscall_symbol = syscalldb
+
+fs.mount.graphene_lib.type = chroot
+fs.mount.graphene_lib.path = /lib
+fs.mount.graphene_lib.uri = file:../../../../Runtime
+
+fs.mount.host_lib.type = chroot
+fs.mount.host_lib.path = /lib/x86_64-linux-gnu
+fs.mount.host_lib.uri = file:/lib/x86_64-linux-gnu
+
+fs.mount.host_usr_lib.type = chroot
+fs.mount.host_usr_lib.path = /usr/lib/x86_64-linux-gnu
+fs.mount.host_usr_lib.uri = file:/usr/lib/x86_64-linux-gnu
+
+fs.mount.bin.type = chroot
+fs.mount.bin.path = /bin
+fs.mount.bin.uri = file:/bin
+
+fs.mount.output.type = chroot
+fs.mount.output.path = /mounted
+fs.mount.output.uri = file:tmp
+
+sgx.trusted_files.ld = file:../../../../Runtime/ld-linux-x86-64.so.2
+sgx.trusted_files.libc = file:../../../../Runtime/libc.so.6
+sgx.trusted_files.libdl = file:../../../../Runtime/libdl.so.2
+sgx.trusted_files.libm = file:../../../../Runtime/libm.so.6
+sgx.trusted_files.libpthread = file:../../../../Runtime/libpthread.so.0
+sgx.trusted_files.libgcc_s = file:/lib/x86_64-linux-gnu/libgcc_s.so.1
+
+sgx.allowed_files.tmp_dir = file:tmp/

+ 78 - 0
LibOS/shim/test/fs/open_close.c

@@ -0,0 +1,78 @@
+#include "common.h"
+
+void open_close_input_fd(const char* input_path) {
+    int fi = open_input_fd(input_path);
+    printf("open(%s) input OK\n", input_path);
+    close_fd(input_path, fi);
+    printf("close(%s) input OK\n", input_path);
+
+    int f1 = open_input_fd(input_path);
+    printf("open(%s) input 1 OK\n", input_path);
+    int f2 = open_input_fd(input_path);
+    printf("open(%s) input 2 OK\n", input_path);
+    close_fd(input_path, f1);
+    printf("close(%s) input 1 OK\n", input_path);
+    close_fd(input_path, f2);
+    printf("close(%s) input 2 OK\n", input_path);
+}
+
+void open_close_input_stdio(const char* input_path) {
+    FILE* fi = open_input_stdio(input_path);
+    printf("fopen(%s) input OK\n", input_path);
+    close_stdio(input_path, fi);
+    printf("fclose(%s) input OK\n", input_path);
+
+    FILE* f1 = open_input_stdio(input_path);
+    printf("fopen(%s) input 1 OK\n", input_path);
+    FILE* f2 = open_input_stdio(input_path);
+    printf("fopen(%s) input 2 OK\n", input_path);
+    close_stdio(input_path, f1);
+    printf("fclose(%s) input 1 OK\n", input_path);
+    close_stdio(input_path, f2);
+    printf("fclose(%s) input 2 OK\n", input_path);
+}
+
+void open_close_output_fd(const char* output_path) {
+    int fo = open_output_fd(output_path, /*rdwr=*/false);
+    printf("open(%s) output OK\n", output_path);
+    close_fd(output_path, fo);
+    printf("close(%s) output OK\n", output_path);
+
+    int f1 = open_output_fd(output_path, /*rdwr=*/false);
+    printf("open(%s) output 1 OK\n", output_path);
+    int f2 = open_output_fd(output_path, /*rdwr=*/false);
+    printf("open(%s) output 2 OK\n", output_path);
+    close_fd(output_path, f1);
+    printf("close(%s) output 1 OK\n", output_path);
+    close_fd(output_path, f2);
+    printf("close(%s) output 2 OK\n", output_path);
+}
+
+void open_close_output_stdio(const char* output_path) {
+    FILE* fo = open_output_stdio(output_path, /*rdwr=*/false);
+    printf("fopen(%s) output OK\n", output_path);
+    close_stdio(output_path, fo);
+    printf("fclose(%s) output OK\n", output_path);
+
+    FILE* f1 = open_output_stdio(output_path, /*rdwr=*/false);
+    printf("fopen(%s) output 1 OK\n", output_path);
+    FILE* f2 = open_output_stdio(output_path, /*rdwr=*/false);
+    printf("fopen(%s) output 2 OK\n", output_path);
+    close_stdio(output_path, f1);
+    printf("fclose(%s) output 1 OK\n", output_path);
+    close_stdio(output_path, f2);
+    printf("fclose(%s) output 2 OK\n", output_path);
+}
+
+int main(int argc, char* argv[]) {
+    if (argc < 3)
+        fatal_error("Usage: %s <input_path> <output_path>\n", argv[0]);
+
+    setup();
+    open_close_input_fd(argv[1]);
+    open_close_input_stdio(argv[1]);
+    open_close_output_fd(argv[2]);
+    open_close_output_stdio(argv[2]);
+
+    return 0;
+}

+ 46 - 0
LibOS/shim/test/fs/read_write.c

@@ -0,0 +1,46 @@
+#include "common.h"
+
+void read_write(const char* file_path) {
+    const size_t size = 1024 * 1024;
+    int fd = open_output_fd(file_path, /*rdwr=*/true);
+    printf("open(%s) RW OK\n", file_path);
+
+    void* buf1 = alloc_buffer(size);
+    void* buf2 = alloc_buffer(size);
+    fill_random(buf1, size);
+    write_fd(file_path, fd, buf1, size);
+    printf("write(%s) RW OK\n", file_path);
+    seek_fd(file_path, fd, 0, SEEK_SET);
+    printf("seek(%s) RW OK\n", file_path);
+    read_fd(file_path, fd, buf2, size);
+    printf("read(%s) RW OK\n", file_path);
+    if (memcmp(buf1, buf2, size) != 0)
+        fatal_error("Read data is different from what was written\n");
+    printf("compare(%s) RW OK\n", file_path);
+
+    for (size_t i = 0; i < 1024; i++) {
+        size_t offset = rand() % (size - 1024);
+        size_t chunk_size = rand() % 1024;
+        fill_random(buf1, chunk_size);
+        seek_fd(file_path, fd, offset, SEEK_SET);
+        write_fd(file_path, fd, buf1, chunk_size);
+        seek_fd(file_path, fd, offset, SEEK_SET);
+        read_fd(file_path, fd, buf2, chunk_size);
+        if (memcmp(buf1, buf2, chunk_size) != 0)
+            fatal_error("Chunk data is different from what was written (offset %zu, size %zu)\n", offset, chunk_size);
+    }
+
+    close_fd(file_path, fd);
+    printf("close(%s) RW OK\n", file_path);
+    free(buf1);
+    free(buf2);
+}
+
+int main(int argc, char* argv[]) {
+    if (argc < 2)
+        fatal_error("Usage: %s <file_path>\n", argv[0]);
+
+    setup();
+    read_write(argv[1]);
+    return 0;
+}

+ 112 - 0
LibOS/shim/test/fs/seek_tell.c

@@ -0,0 +1,112 @@
+#include "common.h"
+
+#define EXTEND_SIZE 4097
+
+void seek_input_fd(const char* path) {
+    int f = open_input_fd(path);
+    printf("open(%s) input OK\n", path);
+    seek_fd(path, f, 0, SEEK_SET);
+    printf("seek(%s) input start OK\n", path);
+    seek_fd(path, f, 0, SEEK_END);
+    printf("seek(%s) input end OK\n", path);
+    off_t pos = tell_fd(path, f);
+    printf("tell(%s) input end OK: %zd\n", path, pos);
+    seek_fd(path, f, -pos, SEEK_END); // rewind
+    printf("seek(%s) input rewind OK\n", path);
+    pos = tell_fd(path, f);
+    printf("tell(%s) input start OK: %zd\n", path, pos);
+    close_fd(path, f);
+    printf("close(%s) input OK\n", path);
+}
+
+void seek_input_stdio(const char* path) {
+    FILE* f = open_input_stdio(path);
+    printf("fopen(%s) input OK\n", path);
+    seek_stdio(path, f, 0, SEEK_SET);
+    printf("fseek(%s) input start OK\n", path);
+    seek_stdio(path, f, 0, SEEK_END);
+    printf("fseek(%s) input end OK\n", path);
+    off_t pos = tell_stdio(path, f);
+    printf("ftell(%s) input end OK: %zd\n", path, pos);
+    seek_stdio(path, f, -pos, SEEK_END); // rewind
+    printf("fseek(%s) input rewind OK\n", path);
+    pos = tell_stdio(path, f);
+    printf("ftell(%s) input start OK: %zd\n", path, pos);
+    close_stdio(path, f);
+    printf("fclose(%s) input OK\n", path);
+}
+
+void seek_output_fd(const char* path) {
+    uint8_t buf[EXTEND_SIZE + 1] = {1};
+    int f = open_output_fd(path, /*rdwr=*/true);
+    printf("open(%s) output OK\n", path);
+    seek_fd(path, f, 0, SEEK_SET);
+    printf("seek(%s) output start OK\n", path);
+    seek_fd(path, f, 0, SEEK_END);
+    printf("seek(%s) output end OK\n", path);
+    off_t pos = tell_fd(path, f);
+    printf("tell(%s) output end OK: %zd\n", path, pos);
+    seek_fd(path, f, EXTEND_SIZE, SEEK_CUR); // extend
+    printf("seek(%s) output end 2 OK\n", path);
+    write_fd(path, f, buf, 1);
+    seek_fd(path, f, -EXTEND_SIZE - 1, SEEK_CUR); // rewind to former end
+    printf("seek(%s) output end 3 OK\n", path);
+    read_fd(path, f, buf, EXTEND_SIZE + 1);
+    for (size_t i=0; i<EXTEND_SIZE + 1; i++) {
+        if (i == EXTEND_SIZE) {
+            if (buf[i] != 1)
+                fatal_error("invalid last byte\n");
+        } else {
+            if (buf[i] != 0)
+                fatal_error("extended buffer not zeroed\n");
+        }
+    }
+    pos = tell_fd(path, f);
+    printf("tell(%s) output end 2 OK: %zd\n", path, pos);
+    close_fd(path, f);
+    printf("close(%s) output OK\n", path);
+}
+
+void seek_output_stdio(const char* path) {
+    uint8_t buf[EXTEND_SIZE + 1] = {1};
+    FILE* f = open_output_stdio(path, /*rdwr=*/true);
+    printf("fopen(%s) output OK\n", path);
+    seek_stdio(path, f, 0, SEEK_SET);
+    printf("fseek(%s) output start OK\n", path);
+    seek_stdio(path, f, 0, SEEK_END);
+    printf("fseek(%s) output end OK\n", path);
+    off_t pos = tell_stdio(path, f);
+    printf("ftell(%s) output end OK: %zd\n", path, pos);
+    seek_stdio(path, f, EXTEND_SIZE, SEEK_CUR); // extend
+    printf("fseek(%s) output end 2 OK\n", path);
+    write_stdio(path, f, buf, 1);
+    seek_stdio(path, f, -EXTEND_SIZE - 1, SEEK_CUR); // rewind to former end
+    printf("fseek(%s) output end 3 OK\n", path);
+    read_stdio(path, f, buf, EXTEND_SIZE + 1);
+    for (size_t i=0; i<EXTEND_SIZE + 1; i++) {
+        if (i == EXTEND_SIZE) {
+            if (buf[i] != 1)
+                fatal_error("invalid last byte\n");
+        } else {
+            if (buf[i] != 0)
+                fatal_error("extended buffer not zeroed\n");
+        }
+    }
+    pos = tell_stdio(path, f);
+    printf("ftell(%s) output end 2 OK: %zd\n", path, pos);
+    close_stdio(path, f);
+    printf("fclose(%s) output OK\n", path);
+}
+
+int main(int argc, char* argv[]) {
+    if (argc < 4)
+        fatal_error("Usage: %s <input_path> <output_path_1> <output_path_2>\n", argv[0]);
+
+    setup();
+    seek_input_fd(argv[1]);
+    seek_input_stdio(argv[1]);
+    seek_output_fd(argv[2]);
+    seek_output_stdio(argv[3]);
+
+    return 0;
+}

+ 35 - 0
LibOS/shim/test/fs/stat.c

@@ -0,0 +1,35 @@
+#include "common.h"
+
+void file_stat(const char* file_path, bool writable) {
+    struct stat st;
+    const char* type = writable ? "output" : "input";
+
+    if (stat(file_path, &st) != 0)
+        fatal_error("Failed to stat file %s: %s\n", file_path, strerror(errno));
+    printf("stat(%s) %s 1 OK: %zu\n", file_path, type, st.st_size);
+
+    int fd = writable ? open_output_fd(file_path, /*rdwr=*/false) : open_input_fd(file_path);
+    printf("open(%s) %s 2 OK\n", file_path, type);
+
+    if (stat(file_path, &st) != 0)
+        fatal_error("Failed to stat file %s: %s\n", file_path, strerror(errno));
+    printf("stat(%s) %s 2 OK: %zu\n", file_path, type, st.st_size);
+
+    if (fstat(fd, &st) != 0)
+        fatal_error("Failed to fstat file %s: %s\n", file_path, strerror(errno));
+    printf("fstat(%s) %s 2 OK: %zu\n", file_path, type, st.st_size);
+
+    close_fd(file_path, fd);
+    printf("close(%s) %s 2 OK\n", file_path, type);
+}
+
+int main(int argc, char* argv[]) {
+    if (argc < 3)
+        fatal_error("Usage: %s <input_path> <output_path>\n", argv[0]);
+
+    setup();
+    file_stat(argv[1], false);
+    file_stat(argv[2], true);
+
+    return 0;
+}

+ 292 - 0
LibOS/shim/test/fs/test_fs.py

@@ -0,0 +1,292 @@
+#!/usr/bin/env python3
+
+import filecmp
+import os
+import shutil
+import sys
+import unittest
+
+from regression import (
+    HAS_SGX,
+    RegressionTestCase,
+    expectedFailureIf,
+)
+
+# Generic FS tests that mimic probable usage patterns in applications.
+class TC_00_FileSystem(RegressionTestCase):
+    @classmethod
+    def setUpClass(c):
+        c.FILE_SIZES   = [0, 1, 2, 15, 16, 17, 255, 256, 257, 1023, 1024, 1025, 65535, 65536, 65537, 1048575, 1048576, 1048577]
+        c.TEST_DIR     = 'tmp'
+        c.INDEXES      = range(len(c.FILE_SIZES))
+        c.INPUT_DIR    = os.path.join(c.TEST_DIR, 'input')
+        c.INPUT_FILES  = [os.path.join(c.INPUT_DIR,  str(x)) for x in c.FILE_SIZES]
+        c.OUTPUT_DIR   = os.path.join(c.TEST_DIR, 'output')
+        c.OUTPUT_FILES = [os.path.join(c.OUTPUT_DIR, str(x)) for x in c.FILE_SIZES]
+
+        # create directory structure and test files
+        os.mkdir(c.TEST_DIR)
+        os.mkdir(c.INPUT_DIR)
+        for i in c.INDEXES:
+            with open(c.INPUT_FILES[i], 'wb') as file:
+                file.write(os.urandom(c.FILE_SIZES[i]))
+
+    @classmethod
+    def tearDownClass(c):
+        shutil.rmtree(c.TEST_DIR)
+
+    def setUp(self):
+        # clean output for each test
+        shutil.rmtree(self.OUTPUT_DIR, ignore_errors=True)
+        os.mkdir(self.OUTPUT_DIR)
+
+    # copy input file to output dir (for tests that alter the file so input remains untouched)
+    def copy_input(self, input, output):
+        shutil.copy(input, output)
+
+    def verify_open_close(self, stdout, stderr, input_path, output_path):
+        self.assertNotIn('ERROR: ', stderr)
+        self.assertIn('open(' + input_path + ') input OK', stdout)
+        self.assertIn('close(' + input_path + ') input OK', stdout)
+        self.assertIn('open(' + input_path + ') input 1 OK', stdout)
+        self.assertIn('open(' + input_path + ') input 2 OK', stdout)
+        self.assertIn('close(' + input_path + ') input 1 OK', stdout)
+        self.assertIn('close(' + input_path + ') input 2 OK', stdout)
+        self.assertIn('fopen(' + input_path + ') input OK', stdout)
+        self.assertIn('fclose(' + input_path + ') input OK', stdout)
+        self.assertIn('fopen(' + input_path + ') input 1 OK', stdout)
+        self.assertIn('fopen(' + input_path + ') input 2 OK', stdout)
+        self.assertIn('fclose(' + input_path + ') input 1 OK', stdout)
+        self.assertIn('fclose(' + input_path + ') input 2 OK', stdout)
+
+        self.assertIn('open(' + output_path + ') output OK', stdout)
+        self.assertIn('close(' + output_path + ') output OK', stdout)
+        self.assertTrue(os.path.isfile(output_path))
+        self.assertIn('open(' + output_path + ') output 1 OK', stdout)
+        self.assertIn('open(' + output_path + ') output 2 OK', stdout)
+        self.assertIn('close(' + output_path + ') output 1 OK', stdout)
+        self.assertIn('close(' + output_path + ') output 2 OK', stdout)
+        self.assertIn('fopen(' + output_path + ') output OK', stdout)
+        self.assertIn('fclose(' + output_path + ') output OK', stdout)
+        self.assertIn('fopen(' + output_path + ') output 1 OK', stdout)
+        self.assertIn('fopen(' + output_path + ') output 2 OK', stdout)
+        self.assertIn('fclose(' + output_path + ') output 1 OK', stdout)
+        self.assertIn('fclose(' + output_path + ') output 2 OK', stdout)
+
+    def test_100_open_close(self):
+        input_path = self.INPUT_FILES[-1] # existing file
+        output_path = os.path.join(self.OUTPUT_DIR, 'test_100') # new file to be created
+        stdout, stderr = self.run_binary(['open_close', input_path, output_path])
+        self.verify_open_close(stdout, stderr, input_path, output_path)
+
+    def test_110_read_write(self):
+        file_path = os.path.join(self.OUTPUT_DIR, 'test_110') # new file to be created
+        stdout, stderr = self.run_binary(['read_write', file_path])
+        self.assertNotIn('ERROR: ', stderr)
+        self.assertTrue(os.path.isfile(file_path))
+        self.assertIn('open(' + file_path + ') RW OK', stdout)
+        self.assertIn('write(' + file_path + ') RW OK', stdout)
+        self.assertIn('seek(' + file_path + ') RW OK', stdout)
+        self.assertIn('read(' + file_path + ') RW OK', stdout)
+        self.assertIn('compare(' + file_path + ') RW OK', stdout)
+        self.assertIn('close(' + file_path + ') RW OK', stdout)
+
+    def verify_seek_tell(self, stdout, stderr, input_path, output_path_1, output_path_2, size):
+        self.assertNotIn('ERROR: ', stderr)
+        self.assertIn('open(' + input_path + ') input OK', stdout)
+        self.assertIn('seek(' + input_path + ') input start OK', stdout)
+        self.assertIn('seek(' + input_path + ') input end OK', stdout)
+        self.assertIn('tell(' + input_path + ') input end OK: ' + str(size), stdout)
+        self.assertIn('seek(' + input_path + ') input rewind OK', stdout)
+        self.assertIn('tell(' + input_path + ') input start OK: 0', stdout)
+        self.assertIn('close(' + input_path + ') input OK', stdout)
+        self.assertIn('fopen(' + input_path + ') input OK', stdout)
+        self.assertIn('fseek(' + input_path + ') input start OK', stdout)
+        self.assertIn('fseek(' + input_path + ') input end OK', stdout)
+        self.assertIn('ftell(' + input_path + ') input end OK: ' + str(size), stdout)
+        self.assertIn('fseek(' + input_path + ') input rewind OK', stdout)
+        self.assertIn('ftell(' + input_path + ') input start OK: 0', stdout)
+        self.assertIn('fclose(' + input_path + ') input OK', stdout)
+
+        self.assertIn('open(' + output_path_1 + ') output OK', stdout)
+        self.assertIn('seek(' + output_path_1 + ') output start OK', stdout)
+        self.assertIn('seek(' + output_path_1 + ') output end OK', stdout)
+        self.assertIn('tell(' + output_path_1 + ') output end OK: ' + str(size), stdout)
+        self.assertIn('seek(' + output_path_1 + ') output end 2 OK', stdout)
+        self.assertIn('seek(' + output_path_1 + ') output end 3 OK', stdout)
+        self.assertIn('tell(' + output_path_1 + ') output end 2 OK: ' + str(size + 4098), stdout)
+        self.assertIn('close(' + output_path_1 + ') output OK', stdout)
+        self.assertIn('fopen(' + output_path_2 + ') output OK', stdout)
+        self.assertIn('fseek(' + output_path_2 + ') output start OK', stdout)
+        self.assertIn('fseek(' + output_path_2 + ') output end OK', stdout)
+        self.assertIn('ftell(' + output_path_2 + ') output end OK: ' + str(size), stdout)
+        self.assertIn('fseek(' + output_path_2 + ') output end 2 OK', stdout)
+        self.assertIn('fseek(' + output_path_2 + ') output end 3 OK', stdout)
+        self.assertIn('ftell(' + output_path_2 + ') output end 2 OK: ' + str(size + 4098), stdout)
+        self.assertIn('fclose(' + output_path_2 + ') output OK', stdout)
+
+    def test_115_seek_tell(self):
+        input_path = self.INPUT_FILES[-1] # existing file
+        output_path_1 = os.path.join(self.OUTPUT_DIR, 'test_115a') # writable files
+        output_path_2 = os.path.join(self.OUTPUT_DIR, 'test_115b')
+        self.copy_input(input_path, output_path_1)
+        self.copy_input(input_path, output_path_2)
+        stdout, stderr = self.run_binary(['seek_tell', input_path, output_path_1, output_path_2])
+        self.verify_seek_tell(stdout, stderr, input_path, output_path_1, output_path_2, self.FILE_SIZES[-1])
+
+    def test_120_file_delete(self):
+        file_path = 'test_120'
+        file_in = self.INPUT_FILES[-1] # existing file to be copied
+        file_out_1 = os.path.join(self.OUTPUT_DIR, file_path + 'a')
+        file_out_2 = os.path.join(self.OUTPUT_DIR, file_path + 'b')
+        file_out_3 = os.path.join(self.OUTPUT_DIR, file_path + 'c')
+        file_out_4 = os.path.join(self.OUTPUT_DIR, file_path + 'd')
+        file_out_5 = os.path.join(self.OUTPUT_DIR, file_path + 'e')
+        # 3 existing files, 2 new files
+        self.copy_input(file_in, file_out_1)
+        self.copy_input(file_in, file_out_2)
+        self.copy_input(file_in, file_out_3)
+        stdout, stderr = self.run_binary(['delete', file_out_1, file_out_2, file_out_3, file_out_4, file_out_5])
+        # verify
+        self.assertNotIn('ERROR: ', stderr)
+        self.assertFalse(os.path.isfile(file_out_1))
+        self.assertFalse(os.path.isfile(file_out_2))
+        self.assertFalse(os.path.isfile(file_out_3))
+        self.assertFalse(os.path.isfile(file_out_4))
+        self.assertFalse(os.path.isfile(file_out_5))
+        self.assertIn('unlink(' + file_out_1 + ') OK', stdout)
+        self.assertIn('open(' + file_out_2 + ') input 1 OK', stdout)
+        self.assertIn('close(' + file_out_2 + ') input 1 OK', stdout)
+        self.assertIn('unlink(' + file_out_2 + ') input 1 OK', stdout)
+        self.assertIn('open(' + file_out_3 + ') input 2 OK', stdout)
+        self.assertIn('unlink(' + file_out_3 + ') input 2 OK', stdout)
+        self.assertIn('close(' + file_out_3 + ') input 2 OK', stdout)
+        self.assertIn('open(' + file_out_4 + ') output 1 OK', stdout)
+        self.assertIn('close(' + file_out_4 + ') output 1 OK', stdout)
+        self.assertIn('unlink(' + file_out_4 + ') output 1 OK', stdout)
+        self.assertIn('open(' + file_out_5 + ') output 2 OK', stdout)
+        self.assertIn('unlink(' + file_out_5 + ') output 2 OK', stdout)
+        self.assertIn('close(' + file_out_5 + ') output 2 OK', stdout)
+
+    def verify_stat(self, stdout, stderr, input_path, output_path, size):
+        self.assertNotIn('ERROR: ', stderr)
+        self.assertIn('stat(' + input_path + ') input 1 OK', stdout)
+        self.assertIn('open(' + input_path + ') input 2 OK', stdout)
+        self.assertIn('stat(' + input_path + ') input 2 OK: ' + size, stdout)
+        self.assertIn('fstat(' + input_path + ') input 2 OK: ' + size, stdout)
+        self.assertIn('close(' + input_path + ') input 2 OK', stdout)
+
+        self.assertIn('stat(' + output_path + ') output 1 OK', stdout)
+        self.assertIn('open(' + output_path + ') output 2 OK', stdout)
+        self.assertIn('stat(' + output_path + ') output 2 OK: ' + size, stdout)
+        self.assertIn('fstat(' + output_path + ') output 2 OK: ' + size, stdout)
+        self.assertIn('close(' + output_path + ') output 2 OK', stdout)
+
+    def test_130_file_stat(self):
+        # running for every file separately so the process doesn't need to enumerate directory
+        # (different code path, enumeration also performs stat)
+        for i in self.INDEXES:
+            input_path = self.INPUT_FILES[i] # existing file
+            output_path = self.OUTPUT_FILES[i] # file that will be opened in write mode
+            size = str(self.FILE_SIZES[i])
+            self.copy_input(input_path, output_path)
+            stdout, stderr = self.run_binary(['stat', input_path, output_path])
+            self.verify_stat(stdout, stderr, input_path, output_path, size)
+
+    def verify_size(self, file, size):
+        self.assertEqual(os.stat(file).st_size, size)
+
+    def do_truncate_test(self, size_in, size_out):
+        # prepare paths/files
+        i = self.FILE_SIZES.index(size_in)
+        input = self.INPUT_FILES[i] # source file to be truncated
+        out_1 = self.OUTPUT_FILES[i] + 'a'
+        out_2 = self.OUTPUT_FILES[i] + 'b'
+        self.copy_input(input, out_1)
+        self.copy_input(input, out_2)
+        # run test
+        stdout, stderr = self.run_binary(['truncate', out_1, out_2, str(size_out)])
+        self.assertNotIn('ERROR: ', stderr)
+        self.assertIn('truncate(' + out_1 + ') to ' + str(size_out) + ' OK', stdout)
+        self.assertIn('open(' + out_2 + ') output OK', stdout)
+        self.assertIn('ftruncate(' + out_2 + ') to ' + str(size_out) + ' OK', stdout)
+        self.assertIn('close(' + out_2 + ') output OK', stdout)
+        self.verify_size(out_1, size_out)
+        self.verify_size(out_2, size_out)
+
+    def test_140_file_truncate(self):
+        self.do_truncate_test(0, 1)
+        self.do_truncate_test(0, 16)
+        self.do_truncate_test(0, 65537)
+        self.do_truncate_test(1, 0)
+        self.do_truncate_test(1, 17)
+        self.do_truncate_test(16, 0)
+        self.do_truncate_test(16, 1048576)
+        self.do_truncate_test(255, 15)
+        self.do_truncate_test(255, 256)
+        self.do_truncate_test(65537, 65535)
+        self.do_truncate_test(65537, 65536)
+
+    def verify_copy_content(self, input, output):
+        self.assertTrue(filecmp.cmp(input, output, shallow=False))
+
+    def verify_copy(self, stdout, stderr, input_dir, exec):
+        self.assertNotIn('ERROR: ', stderr)
+        self.assertIn('opendir(' + input_dir + ') OK', stdout)
+        self.assertIn('readdir(.) OK', stdout)
+        if input_dir[0] != '/':
+            self.assertIn('readdir(..) OK', stdout)
+        for i in self.INDEXES:
+            size = str(self.FILE_SIZES[i])
+            self.assertIn('readdir(' + size + ') OK', stdout)
+            self.assertIn('open(' + size + ') input OK', stdout)
+            self.assertIn('fstat(' + size + ') input OK', stdout)
+            self.assertIn('open(' + size + ') output OK', stdout)
+            self.assertIn('fstat(' + size + ') output 1 OK', stdout)
+            if exec == 'copy_whole':
+                self.assertIn('read_fd(' + size + ') input OK', stdout)
+                self.assertIn('write_fd(' + size + ') output OK', stdout)
+            if size != '0':
+                if 'copy_mmap' in exec:
+                    self.assertIn('mmap_fd(' + size + ') input OK', stdout)
+                    self.assertIn('mmap_fd(' + size + ') output OK', stdout)
+                    self.assertIn('munmap_fd(' + size + ') input OK', stdout)
+                    self.assertIn('munmap_fd(' + size + ') output OK', stdout)
+                if exec == 'copy_mmap_rev':
+                    self.assertIn('ftruncate(' + size + ') output OK', stdout)
+            self.assertIn('fstat(' + size + ') output 2 OK', stdout)
+            self.assertIn('close(' + size + ') input OK', stdout)
+            self.assertIn('close(' + size + ') output OK', stdout)
+        # compare
+        for i in self.INDEXES:
+            self.verify_copy_content(self.INPUT_FILES[i], self.OUTPUT_FILES[i])
+
+    def do_copy_test(self, exec, timeout):
+        stdout, stderr = self.run_binary([exec, self.INPUT_DIR, self.OUTPUT_DIR], timeout=timeout)
+        self.verify_copy(stdout, stderr, self.INPUT_DIR, exec)
+
+    def test_200_copy_dir_whole(self):
+        self.do_copy_test('copy_whole', 30)
+
+    def test_201_copy_dir_seq(self):
+        self.do_copy_test('copy_seq', 60)
+
+    def test_202_copy_dir_rev(self):
+        self.do_copy_test('copy_rev', 60)
+
+    @expectedFailureIf(HAS_SGX)
+    def test_203_copy_dir_mmap_whole(self):
+        self.do_copy_test('copy_mmap_whole', 30)
+
+    @expectedFailureIf(HAS_SGX)
+    def test_204_copy_dir_mmap_seq(self):
+        self.do_copy_test('copy_mmap_seq', 60)
+
+    @expectedFailureIf(HAS_SGX)
+    def test_205_copy_dir_mmap_rev(self):
+        self.do_copy_test('copy_mmap_rev', 60)
+
+    def test_210_copy_dir_mounted(self):
+        exec = 'copy_whole'
+        stdout, stderr = self.run_binary([exec, '/mounted/input', '/mounted/output'], timeout=30)
+        self.verify_copy(stdout, stderr, '/mounted/input', exec)

+ 28 - 0
LibOS/shim/test/fs/truncate.c

@@ -0,0 +1,28 @@
+#include "common.h"
+
+void file_truncate(const char* file_path_1, const char* file_path_2, size_t size) {
+    if (truncate(file_path_1, size) != 0)
+        fatal_error("Failed to truncate file %s to %zu: %s\n", file_path_1, size, strerror(errno));
+    printf("truncate(%s) to %zu OK\n", file_path_1, size);
+
+    int fd = open_output_fd(file_path_2, /*rdwr=*/false);
+    printf("open(%s) output OK\n", file_path_2);
+
+    if (ftruncate(fd, size) != 0)
+        fatal_error("Failed to ftruncate file %s to %zu: %s\n", file_path_2, size, strerror(errno));
+    printf("ftruncate(%s) to %zu OK\n", file_path_2, size);
+
+    close_fd(file_path_2, fd);
+    printf("close(%s) output OK\n", file_path_2);
+}
+
+int main(int argc, char* argv[]) {
+    if (argc < 4)
+        fatal_error("Usage: %s <file_path_1> <file_path_2> <size>\n", argv[0]);
+
+    setup();
+    size_t size = strtoul(argv[3], NULL, 10);
+    file_truncate(argv[1], argv[2], size);
+
+    return 0;
+}